2018-09-27 00:33:08 +01:00
# include "project.h"
2018-12-26 18:20:51 +00:00
# include "config.h"
2018-09-27 00:33:08 +01:00
# include "history.h"
2018-12-20 23:30:35 +00:00
# include "log.h"
2018-09-27 00:33:08 +01:00
# include "parseutil.h"
2019-04-07 01:58:38 +01:00
# include "paletteutil.h"
2018-09-27 00:33:08 +01:00
# include "tile.h"
# include "tileset.h"
2020-03-13 06:23:47 +00:00
# include "map.h"
2024-10-09 17:35:12 +01:00
# include "filedialog.h"
2018-09-27 00:33:08 +01:00
2020-03-06 03:46:25 +00:00
# include "orderedjson.h"
2018-09-27 00:33:08 +01:00
# include <QDir>
2019-02-01 17:43:25 +00:00
# include <QJsonArray>
# include <QJsonDocument>
# include <QJsonObject>
# include <QJsonValue>
2018-09-27 00:33:08 +01:00
# include <QFile>
# include <QTextStream>
# include <QStandardItem>
# include <QMessageBox>
# include <QRegularExpression>
2019-10-01 00:54:27 +01:00
# include <algorithm>
2018-09-27 00:33:08 +01:00
2020-03-06 03:46:25 +00:00
using OrderedJson = poryjson : : Json ;
using OrderedJsonDoc = poryjson : : JsonDoc ;
2018-09-27 00:33:08 +01:00
int Project : : num_tiles_primary = 512 ;
int Project : : num_tiles_total = 1024 ;
int Project : : num_metatiles_primary = 512 ;
int Project : : num_pals_primary = 6 ;
int Project : : num_pals_total = 13 ;
2020-05-16 21:57:03 +01:00
int Project : : max_map_data_size = 10240 ; // 0x2800
2024-11-12 18:44:04 +00:00
int Project : : default_map_dimension = 20 ;
2020-07-10 21:34:42 +01:00
int Project : : max_object_events = 64 ;
2018-09-27 00:33:08 +01:00
2023-02-08 16:48:42 +00:00
Project : : Project ( QObject * parent ) :
2024-01-13 00:22:54 +00:00
QObject ( parent )
2018-09-27 00:33:08 +01:00
{
2024-10-30 00:09:01 +00:00
QObject : : connect ( & this - > fileWatcher , & QFileSystemWatcher : : fileChanged , this , & Project : : fileChanged ) ;
2020-04-07 19:20:31 +01:00
}
Project : : ~ Project ( )
{
2020-04-08 01:25:09 +01:00
clearMapCache ( ) ;
clearTilesetCache ( ) ;
2024-09-11 18:09:03 +01:00
clearMapLayouts ( ) ;
clearEventGraphics ( ) ;
2018-09-27 00:33:08 +01:00
}
2019-05-06 17:42:09 +01:00
void Project : : set_root ( QString dir ) {
this - > root = dir ;
2024-10-09 17:35:12 +01:00
FileDialog : : setDirectory ( dir ) ;
2019-05-06 17:42:09 +01:00
this - > parser . set_root ( dir ) ;
}
2024-07-16 19:19:47 +01:00
// Before attempting the initial project load we should check for a few notable files.
// If all are missing then we can warn the user, they may have accidentally selected the wrong folder.
bool Project : : sanityCheck ( ) {
// The goal with the file selection is to pick files that are important enough that any reasonable project would have
// at least 1 in the expected location, but unique enough that they're unlikely to overlap with a completely unrelated
// directory (e.g. checking for 'data/maps/' is a bad choice because it's too generic, pokeyellow would pass for instance)
static const QSet < ProjectFilePath > pathsToCheck = {
ProjectFilePath : : json_map_groups ,
ProjectFilePath : : json_layouts ,
ProjectFilePath : : tilesets_headers ,
ProjectFilePath : : global_fieldmap ,
} ;
for ( auto pathId : pathsToCheck ) {
const QString path = QString ( " %1/%2 " ) . arg ( this - > root ) . arg ( projectConfig . getFilePath ( pathId ) ) ;
QFileInfo fileInfo ( path ) ;
if ( fileInfo . exists ( ) & & fileInfo . isFile ( ) )
return true ;
}
return false ;
}
bool Project : : load ( ) {
2024-08-29 17:39:25 +01:00
this - > disabledSettingsNames . clear ( ) ;
2024-07-16 19:19:47 +01:00
bool success = readMapLayouts ( )
& & readRegionMapSections ( )
& & readItemNames ( )
& & readFlagNames ( )
& & readVarNames ( )
& & readMovementTypes ( )
& & readInitialFacingDirections ( )
& & readMapTypes ( )
& & readMapBattleScenes ( )
& & readWeatherNames ( )
& & readCoordEventWeatherNames ( )
& & readSecretBaseIds ( )
& & readBgEventFacingDirections ( )
& & readTrainerTypes ( )
& & readMetatileBehaviors ( )
& & readFieldmapProperties ( )
& & readFieldmapMasks ( )
& & readTilesetLabels ( )
& & readTilesetMetatileLabels ( )
& & readHealLocations ( )
& & readMiscellaneousConstants ( )
& & readSpeciesIconPaths ( )
& & readWildMonData ( )
& & readEventScriptLabels ( )
& & readObjEventGfxConstants ( )
& & readEventGraphics ( )
& & readSongNames ( )
& & readMapGroups ( ) ;
applyParsedLimits ( ) ;
return success ;
}
2018-09-27 00:33:08 +01:00
QString Project : : getProjectTitle ( ) {
if ( ! root . isNull ( ) ) {
return root . section ( ' / ' , - 1 ) ;
} else {
return QString ( ) ;
}
}
2020-04-08 01:25:09 +01:00
void Project : : clearMapCache ( ) {
2021-02-18 22:41:36 +00:00
for ( auto * map : mapCache . values ( ) ) {
if ( map )
delete map ;
2020-04-08 01:25:09 +01:00
}
2021-02-18 22:41:36 +00:00
mapCache . clear ( ) ;
2020-04-08 01:25:09 +01:00
}
void Project : : clearTilesetCache ( ) {
2021-02-18 22:41:36 +00:00
for ( auto * tileset : tilesetCache . values ( ) ) {
if ( tileset )
delete tileset ;
2020-04-08 01:25:09 +01:00
}
2021-02-18 22:41:36 +00:00
tilesetCache . clear ( ) ;
2020-04-08 01:25:09 +01:00
}
2024-11-12 18:13:09 +00:00
Map * Project : : loadMap ( QString mapName ) {
if ( mapName = = DYNAMIC_MAP_NAME )
2024-07-12 19:05:37 +01:00
return nullptr ;
2018-09-27 00:33:08 +01:00
Map * map ;
2024-11-12 18:13:09 +00:00
if ( mapCache . contains ( mapName ) ) {
map = mapCache . value ( mapName ) ;
2018-09-27 00:33:08 +01:00
// TODO: uncomment when undo/redo history is fully implemented for all actions.
if ( true /*map->hasUnsavedChanges()*/ ) {
return map ;
}
} else {
map = new Map ;
2024-11-12 18:13:09 +00:00
map - > setName ( mapName ) ;
map - > setConstantName ( this - > mapNamesToMapConstants . value ( mapName ) ) ; // TODO: How should we handle if !mapNamesToMapConstants.contains(mapName) here
2018-09-27 00:33:08 +01:00
}
2024-08-05 00:11:29 +01:00
if ( ! ( loadMapData ( map ) & & loadMapLayout ( map ) ) ) {
delete map ;
2018-12-20 23:30:35 +00:00
return nullptr ;
2024-08-05 00:11:29 +01:00
}
2018-12-20 23:30:35 +00:00
2024-11-12 18:13:09 +00:00
mapCache . insert ( mapName , map ) ;
2024-08-05 00:11:29 +01:00
emit mapLoaded ( map ) ;
2018-09-27 00:33:08 +01:00
return map ;
}
2022-10-14 23:55:18 +01:00
const QSet < QString > defaultTopLevelMapFields = {
" id " ,
" name " ,
" layout " ,
" music " ,
" region_map_section " ,
" requires_flash " ,
" weather " ,
" map_type " ,
" show_map_name " ,
" battle_scene " ,
" connections " ,
" object_events " ,
" warp_events " ,
" coord_events " ,
" bg_events " ,
" shared_events_map " ,
" shared_scripts_map " ,
2020-05-22 22:51:56 +01:00
} ;
2022-10-14 23:55:18 +01:00
QSet < QString > Project : : getTopLevelMapFields ( ) {
QSet < QString > topLevelMapFields = defaultTopLevelMapFields ;
2024-07-15 21:14:38 +01:00
if ( projectConfig . mapAllowFlagsEnabled ) {
2022-10-14 23:55:18 +01:00
topLevelMapFields . insert ( " allow_cycling " ) ;
topLevelMapFields . insert ( " allow_escaping " ) ;
topLevelMapFields . insert ( " allow_running " ) ;
2019-02-03 16:38:45 +00:00
}
2021-02-16 12:15:47 +00:00
2024-07-15 21:14:38 +01:00
if ( projectConfig . floorNumberEnabled ) {
2022-10-14 23:55:18 +01:00
topLevelMapFields . insert ( " floor_number " ) ;
2020-05-22 22:51:56 +01:00
}
return topLevelMapFields ;
2019-02-03 16:38:45 +00:00
}
2024-11-12 18:13:09 +00:00
bool Project : : readMapJson ( const QString & mapName , QJsonDocument * out ) {
const QString mapFilepath = QString ( " %1%2/map.json " ) . arg ( projectConfig . getFilePath ( ProjectFilePath : : data_map_folders ) ) . arg ( mapName ) ;
if ( ! parser . tryParseJsonFile ( out , QString ( " %1/%2 " ) . arg ( this - > root ) . arg ( mapFilepath ) ) ) {
logError ( QString ( " Failed to read map data from %1 " ) . arg ( mapFilepath ) ) ;
return false ;
}
return true ;
}
2019-02-01 17:43:25 +00:00
bool Project : : loadMapData ( Map * map ) {
2024-11-12 03:28:53 +00:00
if ( ! map - > isPersistedToFile ( ) ) {
2018-12-20 23:30:35 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2019-04-20 15:06:59 +01:00
QJsonDocument mapDoc ;
2024-11-12 18:13:09 +00:00
if ( ! readMapJson ( map - > name ( ) , & mapDoc ) )
2018-12-20 23:30:35 +00:00
return false ;
2019-02-01 17:43:25 +00:00
QJsonObject mapObj = mapDoc . object ( ) ;
2024-11-12 03:28:53 +00:00
map - > setSong ( ParseUtil : : jsonToQString ( mapObj [ " music " ] ) ) ;
map - > setLayoutId ( ParseUtil : : jsonToQString ( mapObj [ " layout " ] ) ) ;
map - > setLocation ( ParseUtil : : jsonToQString ( mapObj [ " region_map_section " ] ) ) ;
map - > setRequiresFlash ( ParseUtil : : jsonToBool ( mapObj [ " requires_flash " ] ) ) ;
map - > setWeather ( ParseUtil : : jsonToQString ( mapObj [ " weather " ] ) ) ;
map - > setType ( ParseUtil : : jsonToQString ( mapObj [ " map_type " ] ) ) ;
2024-11-12 19:27:35 +00:00
map - > setShowsLocationName ( ParseUtil : : jsonToBool ( mapObj [ " show_map_name " ] ) ) ;
2024-11-12 03:28:53 +00:00
map - > setBattleScene ( ParseUtil : : jsonToQString ( mapObj [ " battle_scene " ] ) ) ;
2022-10-14 23:55:18 +01:00
2024-07-15 21:14:38 +01:00
if ( projectConfig . mapAllowFlagsEnabled ) {
2024-11-12 03:28:53 +00:00
map - > setAllowsBiking ( ParseUtil : : jsonToBool ( mapObj [ " allow_cycling " ] ) ) ;
map - > setAllowsEscaping ( ParseUtil : : jsonToBool ( mapObj [ " allow_escaping " ] ) ) ;
map - > setAllowsRunning ( ParseUtil : : jsonToBool ( mapObj [ " allow_running " ] ) ) ;
2020-05-22 22:51:56 +01:00
}
2024-07-15 21:14:38 +01:00
if ( projectConfig . floorNumberEnabled ) {
2024-11-12 03:28:53 +00:00
map - > setFloorNumber ( ParseUtil : : jsonToInt ( mapObj [ " floor_number " ] ) ) ;
2019-02-01 17:43:25 +00:00
}
2024-11-12 03:28:53 +00:00
map - > setSharedEventsMap ( ParseUtil : : jsonToQString ( mapObj [ " shared_events_map " ] ) ) ;
map - > setSharedScriptsMap ( ParseUtil : : jsonToQString ( mapObj [ " shared_scripts_map " ] ) ) ;
2019-02-01 17:43:25 +00:00
// Events
2024-11-12 03:28:53 +00:00
map - > resetEvents ( ) ;
2019-02-01 17:43:25 +00:00
QJsonArray objectEventsArr = mapObj [ " object_events " ] . toArray ( ) ;
for ( int i = 0 ; i < objectEventsArr . size ( ) ; i + + ) {
QJsonObject event = objectEventsArr [ i ] . toObject ( ) ;
2022-02-06 02:31:54 +00:00
// If clone objects are not enabled then no type field is present
2024-07-15 21:14:38 +01:00
QString type = projectConfig . eventCloneObjectEnabled ? ParseUtil : : jsonToQString ( event [ " type " ] ) : " object " ;
2022-06-25 04:24:15 +01:00
if ( type . isEmpty ( ) | | type = = " object " ) {
2022-07-19 22:56:12 +01:00
ObjectEvent * object = new ObjectEvent ( ) ;
object - > loadFromJson ( event , this ) ;
map - > addEvent ( object ) ;
2022-02-06 02:31:54 +00:00
} else if ( type = = " clone " ) {
2022-07-19 22:56:12 +01:00
CloneObjectEvent * clone = new CloneObjectEvent ( ) ;
if ( clone - > loadFromJson ( event , this ) ) {
map - > addEvent ( clone ) ;
}
else {
delete clone ;
2022-02-06 02:31:54 +00:00
}
} else {
2024-11-12 03:28:53 +00:00
logError ( QString ( " Map %1 object_event %2 has invalid type '%3'. Must be 'object' or 'clone'. " ) . arg ( map - > name ( ) ) . arg ( i ) . arg ( type ) ) ;
2022-02-06 02:31:54 +00:00
}
2019-02-01 17:43:25 +00:00
}
QJsonArray warpEventsArr = mapObj [ " warp_events " ] . toArray ( ) ;
for ( int i = 0 ; i < warpEventsArr . size ( ) ; i + + ) {
QJsonObject event = warpEventsArr [ i ] . toObject ( ) ;
2022-07-19 22:56:12 +01:00
WarpEvent * warp = new WarpEvent ( ) ;
if ( warp - > loadFromJson ( event , this ) ) {
map - > addEvent ( warp ) ;
2018-12-26 18:20:51 +00:00
}
2022-07-19 22:56:12 +01:00
else {
delete warp ;
2018-12-26 18:20:51 +00:00
}
2019-02-01 17:43:25 +00:00
}
QJsonArray coordEventsArr = mapObj [ " coord_events " ] . toArray ( ) ;
for ( int i = 0 ; i < coordEventsArr . size ( ) ; i + + ) {
QJsonObject event = coordEventsArr [ i ] . toObject ( ) ;
2022-10-18 03:37:06 +01:00
QString type = ParseUtil : : jsonToQString ( event [ " type " ] ) ;
2019-02-01 17:43:25 +00:00
if ( type = = " trigger " ) {
2022-07-19 22:56:12 +01:00
TriggerEvent * coord = new TriggerEvent ( ) ;
coord - > loadFromJson ( event , this ) ;
map - > addEvent ( coord ) ;
2019-02-01 17:43:25 +00:00
} else if ( type = = " weather " ) {
2022-07-19 22:56:12 +01:00
WeatherTriggerEvent * coord = new WeatherTriggerEvent ( ) ;
coord - > loadFromJson ( event , this ) ;
map - > addEvent ( coord ) ;
2020-02-12 16:43:17 +00:00
} else {
2024-11-12 03:28:53 +00:00
logError ( QString ( " Map %1 coord_event %2 has invalid type '%3'. Must be 'trigger' or 'weather'. " ) . arg ( map - > name ( ) ) . arg ( i ) . arg ( type ) ) ;
2018-12-26 18:20:51 +00:00
}
2019-02-01 17:43:25 +00:00
}
2018-12-26 18:20:51 +00:00
2019-02-01 17:43:25 +00:00
QJsonArray bgEventsArr = mapObj [ " bg_events " ] . toArray ( ) ;
for ( int i = 0 ; i < bgEventsArr . size ( ) ; i + + ) {
QJsonObject event = bgEventsArr [ i ] . toObject ( ) ;
2022-10-18 03:37:06 +01:00
QString type = ParseUtil : : jsonToQString ( event [ " type " ] ) ;
2019-02-01 17:43:25 +00:00
if ( type = = " sign " ) {
2022-07-19 22:56:12 +01:00
SignEvent * bg = new SignEvent ( ) ;
bg - > loadFromJson ( event , this ) ;
map - > addEvent ( bg ) ;
2019-02-01 17:43:25 +00:00
} else if ( type = = " hidden_item " ) {
2022-07-19 22:56:12 +01:00
HiddenItemEvent * bg = new HiddenItemEvent ( ) ;
bg - > loadFromJson ( event , this ) ;
map - > addEvent ( bg ) ;
2019-02-01 17:43:25 +00:00
} else if ( type = = " secret_base " ) {
2022-07-19 22:56:12 +01:00
SecretBaseEvent * bg = new SecretBaseEvent ( ) ;
bg - > loadFromJson ( event , this ) ;
map - > addEvent ( bg ) ;
2020-02-12 16:43:17 +00:00
} else {
2024-11-12 03:28:53 +00:00
logError ( QString ( " Map %1 bg_event %2 has invalid type '%3'. Must be 'sign', 'hidden_item', or 'secret_base'. " ) . arg ( map - > name ( ) ) . arg ( i ) . arg ( type ) ) ;
2018-12-26 18:20:51 +00:00
}
2019-02-01 17:43:25 +00:00
}
2018-12-26 18:20:51 +00:00
2024-11-12 03:28:53 +00:00
/* TODO: Re-enable
2023-12-18 07:19:12 +00:00
const QString mapPrefix = projectConfig . getIdentifier ( ProjectIdentifier : : define_map_prefix ) ;
2022-07-19 22:56:12 +01:00
for ( auto it = healLocations . begin ( ) ; it ! = healLocations . end ( ) ; it + + ) {
HealLocation loc = * it ;
//if TRUE map is flyable / has healing location
2023-12-18 07:19:12 +00:00
if ( loc . mapName = = Map : : mapConstantFromName ( map - > name , false ) ) {
2022-07-19 22:56:12 +01:00
HealLocationEvent * heal = new HealLocationEvent ( ) ;
heal - > setMap ( map ) ;
heal - > setX ( loc . x ) ;
heal - > setY ( loc . y ) ;
2024-07-15 21:14:38 +01:00
heal - > setElevation ( projectConfig . defaultElevation ) ;
2022-07-19 22:56:12 +01:00
heal - > setLocationName ( loc . mapName ) ;
heal - > setIdName ( loc . idName ) ;
heal - > setIndex ( loc . index ) ;
2024-07-15 21:14:38 +01:00
if ( projectConfig . healLocationRespawnDataEnabled ) {
2023-12-18 07:19:12 +00:00
heal - > setRespawnMap ( mapConstantsToMapNames . value ( QString ( mapPrefix + loc . respawnMap ) ) ) ;
2022-07-19 22:56:12 +01:00
heal - > setRespawnNPC ( loc . respawnNPC ) ;
}
map - > events [ Event : : Group : : Heal ] . append ( heal ) ;
2024-09-11 18:09:03 +01:00
map - > ownedEvents . append ( heal ) ;
2022-07-19 22:56:12 +01:00
}
}
2024-11-12 03:28:53 +00:00
*/
2022-07-19 22:56:12 +01:00
2024-08-09 06:29:28 +01:00
map - > deleteConnections ( ) ;
2019-02-01 17:43:25 +00:00
QJsonArray connectionsArr = mapObj [ " connections " ] . toArray ( ) ;
if ( ! connectionsArr . isEmpty ( ) ) {
for ( int i = 0 ; i < connectionsArr . size ( ) ; i + + ) {
QJsonObject connectionObj = connectionsArr [ i ] . toObject ( ) ;
2024-08-04 21:08:16 +01:00
const QString direction = ParseUtil : : jsonToQString ( connectionObj [ " direction " ] ) ;
2024-11-12 18:13:09 +00:00
const int offset = ParseUtil : : jsonToInt ( connectionObj [ " offset " ] ) ;
2024-08-04 21:08:16 +01:00
const QString mapConstant = ParseUtil : : jsonToQString ( connectionObj [ " map " ] ) ;
2024-11-12 18:13:09 +00:00
if ( this - > mapConstantsToMapNames . contains ( mapConstant ) ) {
2024-08-04 21:08:16 +01:00
// Successully read map connection
2024-11-12 18:13:09 +00:00
map - > loadConnection ( new MapConnection ( this - > mapConstantsToMapNames . value ( mapConstant ) , direction , offset ) ) ;
2019-02-01 17:43:25 +00:00
} else {
logError ( QString ( " Failed to find connected map for map constant '%1' " ) . arg ( mapConstant ) ) ;
}
}
2018-12-26 18:20:51 +00:00
}
2019-02-03 16:38:45 +00:00
// Check for custom fields
2024-11-12 03:28:53 +00:00
/* // TODO: Re-enable
2022-10-14 23:55:18 +01:00
QSet < QString > baseFields = this - > getTopLevelMapFields ( ) ;
2019-02-03 16:38:45 +00:00
for ( QString key : mapObj . keys ( ) ) {
if ( ! baseFields . contains ( key ) ) {
2022-10-15 08:22:51 +01:00
map - > customHeaders . insert ( key , mapObj [ key ] ) ;
2019-02-03 16:38:45 +00:00
}
}
2024-11-12 03:28:53 +00:00
*/
2019-02-03 16:38:45 +00:00
2018-12-20 23:30:35 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2024-02-16 03:19:49 +00:00
Layout * Project : : createNewLayout ( Layout : : SimpleSettings & layoutSettings ) {
QString basePath = projectConfig . getFilePath ( ProjectFilePath : : data_layouts_folders ) ;
Layout * layout ;
// Handle the case where we are copying from an existing layout first.
if ( ! layoutSettings . from_id . isEmpty ( ) ) {
// load from layout
loadLayout ( mapLayouts [ layoutSettings . from_id ] ) ;
layout = mapLayouts [ layoutSettings . from_id ] - > copy ( ) ;
layout - > name = layoutSettings . name ;
layout - > id = layoutSettings . id ;
layout - > border_path = QString ( " %1%2/border.bin " ) . arg ( basePath , layoutSettings . name ) ;
layout - > blockdata_path = QString ( " %1%2/map.bin " ) . arg ( basePath , layoutSettings . name ) ;
}
else {
layout = new Layout ;
layout - > name = layoutSettings . name ;
layout - > id = layoutSettings . id ;
layout - > width = layoutSettings . width ;
layout - > height = layoutSettings . height ;
layout - > border_width = DEFAULT_BORDER_WIDTH ;
layout - > border_height = DEFAULT_BORDER_HEIGHT ;
layout - > tileset_primary_label = layoutSettings . tileset_primary_label ;
layout - > tileset_secondary_label = layoutSettings . tileset_secondary_label ;
layout - > border_path = QString ( " %1%2/border.bin " ) . arg ( basePath , layoutSettings . name ) ;
layout - > blockdata_path = QString ( " %1%2/map.bin " ) . arg ( basePath , layoutSettings . name ) ;
setNewLayoutBlockdata ( layout ) ;
setNewLayoutBorder ( layout ) ;
}
// Create a new directory for the layout
QString newLayoutDir = QString ( root + " /%1%2 " ) . arg ( projectConfig . getFilePath ( ProjectFilePath : : data_layouts_folders ) , layout - > name ) ;
if ( ! QDir : : root ( ) . mkdir ( newLayoutDir ) ) {
logError ( QString ( " Error: failed to create directory for new layout: '%1' " ) . arg ( newLayoutDir ) ) ;
delete layout ;
return nullptr ;
}
mapLayouts . insert ( layout - > id , layout ) ;
mapLayoutsMaster . insert ( layout - > id , layout - > copy ( ) ) ;
mapLayoutsTable . append ( layout - > id ) ;
mapLayoutsTableMaster . append ( layout - > id ) ;
layoutIdsToNames . insert ( layout - > id , layout - > name ) ;
saveLayout ( layout ) ;
this - > loadLayout ( layout ) ;
return layout ;
}
2023-02-08 14:31:39 +00:00
bool Project : : loadLayout ( Layout * layout ) {
2023-02-07 18:04:52 +00:00
if ( ! layout - > loaded ) {
// Force these to run even if one fails
bool loadedTilesets = loadLayoutTilesets ( layout ) ;
bool loadedBlockdata = loadBlockdata ( layout ) ;
bool loadedBorder = loadLayoutBorder ( layout ) ;
if ( loadedTilesets & & loadedBlockdata & & loadedBorder ) {
layout - > loaded = true ;
return true ;
} else {
return false ;
}
}
return true ;
2021-07-20 03:02:31 +01:00
}
2023-02-02 01:28:54 +00:00
Layout * Project : : loadLayout ( QString layoutId ) {
if ( mapLayouts . contains ( layoutId ) ) {
Layout * layout = mapLayouts [ layoutId ] ;
if ( loadLayout ( layout ) ) {
return layout ;
}
}
logError ( QString ( " Error: Failed to load layout '%1' " ) . arg ( layoutId ) ) ;
return nullptr ;
}
2020-02-12 16:22:40 +00:00
bool Project : : loadMapLayout ( Map * map ) {
2024-11-12 03:28:53 +00:00
if ( ! map - > isPersistedToFile ( ) ) {
2020-02-12 16:22:40 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2024-11-12 03:28:53 +00:00
if ( mapLayouts . contains ( map - > layoutId ( ) ) ) {
map - > setLayout ( mapLayouts [ map - > layoutId ( ) ] ) ;
2020-02-12 16:22:40 +00:00
} else {
2024-11-12 03:28:53 +00:00
logError ( QString ( " Error: Map '%1' has an unknown layout '%2' " ) . arg ( map - > name ( ) ) . arg ( map - > layoutId ( ) ) ) ;
2020-02-12 16:22:40 +00:00
return false ;
2018-09-27 00:33:08 +01:00
}
2024-10-30 01:51:05 +00:00
if ( map - > hasUnsavedChanges ( ) ) {
2021-07-20 03:02:31 +01:00
return true ;
} else {
2024-11-12 03:28:53 +00:00
return loadLayout ( map - > layout ( ) ) ;
2021-07-20 03:02:31 +01:00
}
2018-09-27 00:33:08 +01:00
}
2024-09-11 18:09:03 +01:00
void Project : : clearMapLayouts ( ) {
qDeleteAll ( mapLayouts ) ;
2018-09-27 00:33:08 +01:00
mapLayouts . clear ( ) ;
2024-10-17 19:01:27 +01:00
qDeleteAll ( mapLayoutsMaster ) ;
mapLayoutsMaster . clear ( ) ;
2019-02-01 17:43:25 +00:00
mapLayoutsTable . clear ( ) ;
2023-02-01 15:09:50 +00:00
layoutIdsToNames . clear ( ) ;
2024-09-11 18:09:03 +01:00
}
bool Project : : readMapLayouts ( ) {
clearMapLayouts ( ) ;
2018-09-27 00:33:08 +01:00
2023-01-26 04:18:06 +00:00
QString layoutsFilepath = projectConfig . getFilePath ( ProjectFilePath : : json_layouts ) ;
QString fullFilepath = QString ( " %1/%2 " ) . arg ( root ) . arg ( layoutsFilepath ) ;
fileWatcher . addPath ( fullFilepath ) ;
2019-04-20 15:06:59 +01:00
QJsonDocument layoutsDoc ;
2023-01-26 04:18:06 +00:00
if ( ! parser . tryParseJsonFile ( & layoutsDoc , fullFilepath ) ) {
logError ( QString ( " Failed to read map layouts from %1 " ) . arg ( fullFilepath ) ) ;
2020-02-12 00:34:08 +00:00
return false ;
2019-02-01 17:43:25 +00:00
}
QJsonObject layoutsObj = layoutsDoc . object ( ) ;
2020-02-12 00:34:08 +00:00
QJsonArray layouts = layoutsObj [ " layouts " ] . toArray ( ) ;
if ( layouts . size ( ) = = 0 ) {
logError ( QString ( " 'layouts' array is missing from %1. " ) . arg ( layoutsFilepath ) ) ;
return false ;
}
2022-10-14 23:55:18 +01:00
layoutsLabel = ParseUtil : : jsonToQString ( layoutsObj [ " layouts_table_label " ] ) ;
2020-02-12 00:34:08 +00:00
if ( layoutsLabel . isNull ( ) ) {
layoutsLabel = " gMapLayouts " ;
logWarn ( QString ( " 'layouts_table_label' value is missing from %1. Defaulting to %2 " )
. arg ( layoutsFilepath )
. arg ( layoutsLabel ) ) ;
}
2018-09-27 00:33:08 +01:00
2024-09-11 18:09:03 +01:00
static const QList < QString > requiredFields = QList < QString > {
2020-02-12 00:34:08 +00:00
" id " ,
" name " ,
" width " ,
" height " ,
" primary_tileset " ,
" secondary_tileset " ,
" border_filepath " ,
" blockdata_filepath " ,
} ;
2019-02-01 17:43:25 +00:00
for ( int i = 0 ; i < layouts . size ( ) ; i + + ) {
QJsonObject layoutObj = layouts [ i ] . toObject ( ) ;
2020-03-11 05:52:00 +00:00
if ( layoutObj . isEmpty ( ) )
continue ;
2020-02-12 00:34:08 +00:00
if ( ! parser . ensureFieldsExist ( layoutObj , requiredFields ) ) {
logError ( QString ( " Layout %1 is missing field(s) in %2. " ) . arg ( i ) . arg ( layoutsFilepath ) ) ;
return false ;
}
2023-02-08 14:31:39 +00:00
Layout * layout = new Layout ( ) ;
2022-10-14 23:55:18 +01:00
layout - > id = ParseUtil : : jsonToQString ( layoutObj [ " id " ] ) ;
2020-02-12 00:34:08 +00:00
if ( layout - > id . isEmpty ( ) ) {
logError ( QString ( " Missing 'id' value on layout %1 in %2 " ) . arg ( i ) . arg ( layoutsFilepath ) ) ;
2023-01-26 04:39:10 +00:00
delete layout ;
2020-02-12 00:34:08 +00:00
return false ;
}
2023-01-26 04:18:06 +00:00
if ( mapLayouts . contains ( layout - > id ) ) {
logWarn ( QString ( " Duplicate layout entry for %1 in %2 will be ignored. " ) . arg ( layout - > id ) . arg ( layoutsFilepath ) ) ;
delete layout ;
continue ;
}
2022-10-14 23:55:18 +01:00
layout - > name = ParseUtil : : jsonToQString ( layoutObj [ " name " ] ) ;
2020-02-12 00:34:08 +00:00
if ( layout - > name . isEmpty ( ) ) {
2023-01-26 04:18:06 +00:00
logError ( QString ( " Missing 'name' value for %1 in %2 " ) . arg ( layout - > id ) . arg ( layoutsFilepath ) ) ;
2023-01-26 04:39:10 +00:00
delete layout ;
2020-02-12 00:34:08 +00:00
return false ;
}
2022-10-14 23:55:18 +01:00
int lwidth = ParseUtil : : jsonToInt ( layoutObj [ " width " ] ) ;
2020-02-12 00:34:08 +00:00
if ( lwidth < = 0 ) {
2023-01-26 04:18:06 +00:00
logError ( QString ( " Invalid 'width' value '%1' for %2 in %3. Must be greater than 0. " ) . arg ( lwidth ) . arg ( layout - > id ) . arg ( layoutsFilepath ) ) ;
2023-01-26 04:39:10 +00:00
delete layout ;
2020-02-12 00:34:08 +00:00
return false ;
}
2022-10-16 07:49:42 +01:00
layout - > width = lwidth ;
2022-10-14 23:55:18 +01:00
int lheight = ParseUtil : : jsonToInt ( layoutObj [ " height " ] ) ;
2020-02-12 00:34:08 +00:00
if ( lheight < = 0 ) {
2023-01-26 04:18:06 +00:00
logError ( QString ( " Invalid 'height' value '%1' for %2 in %3. Must be greater than 0. " ) . arg ( lheight ) . arg ( layout - > id ) . arg ( layoutsFilepath ) ) ;
2023-01-26 04:39:10 +00:00
delete layout ;
2020-02-12 00:34:08 +00:00
return false ;
}
2022-10-16 07:49:42 +01:00
layout - > height = lheight ;
2024-07-15 21:14:38 +01:00
if ( projectConfig . useCustomBorderSize ) {
2022-10-14 23:55:18 +01:00
int bwidth = ParseUtil : : jsonToInt ( layoutObj [ " border_width " ] ) ;
2020-03-13 06:23:47 +00:00
if ( bwidth < = 0 ) { // 0 is an expected border width/height that should be handled, GF used it for the RS layouts in FRLG
2023-01-26 04:18:06 +00:00
logWarn ( QString ( " Invalid 'border_width' value '%1' for %2 in %3. Must be greater than 0. Using default (%4) instead. " )
. arg ( bwidth ) . arg ( layout - > id ) . arg ( layoutsFilepath ) . arg ( DEFAULT_BORDER_WIDTH ) ) ;
2020-03-13 06:23:47 +00:00
bwidth = DEFAULT_BORDER_WIDTH ;
}
2022-10-16 07:49:42 +01:00
layout - > border_width = bwidth ;
2022-10-14 23:55:18 +01:00
int bheight = ParseUtil : : jsonToInt ( layoutObj [ " border_height " ] ) ;
2020-03-13 06:23:47 +00:00
if ( bheight < = 0 ) {
2023-01-26 04:18:06 +00:00
logWarn ( QString ( " Invalid 'border_height' value '%1' for %2 in %3. Must be greater than 0. Using default (%4) instead. " )
. arg ( bheight ) . arg ( layout - > id ) . arg ( layoutsFilepath ) . arg ( DEFAULT_BORDER_HEIGHT ) ) ;
2020-03-13 06:23:47 +00:00
bheight = DEFAULT_BORDER_HEIGHT ;
}
2022-10-16 07:49:42 +01:00
layout - > border_height = bheight ;
2020-03-13 06:23:47 +00:00
} else {
2022-10-16 07:49:42 +01:00
layout - > border_width = DEFAULT_BORDER_WIDTH ;
layout - > border_height = DEFAULT_BORDER_HEIGHT ;
2020-03-13 06:23:47 +00:00
}
2022-10-14 23:55:18 +01:00
layout - > tileset_primary_label = ParseUtil : : jsonToQString ( layoutObj [ " primary_tileset " ] ) ;
2020-02-12 00:34:08 +00:00
if ( layout - > tileset_primary_label . isEmpty ( ) ) {
2023-01-26 04:18:06 +00:00
logError ( QString ( " Missing 'primary_tileset' value for %1 in %2 " ) . arg ( layout - > id ) . arg ( layoutsFilepath ) ) ;
2023-01-26 04:39:10 +00:00
delete layout ;
2020-02-12 00:34:08 +00:00
return false ;
}
2022-10-14 23:55:18 +01:00
layout - > tileset_secondary_label = ParseUtil : : jsonToQString ( layoutObj [ " secondary_tileset " ] ) ;
2020-02-12 00:34:08 +00:00
if ( layout - > tileset_secondary_label . isEmpty ( ) ) {
2023-01-26 04:18:06 +00:00
logError ( QString ( " Missing 'secondary_tileset' value for %1 in %2 " ) . arg ( layout - > id ) . arg ( layoutsFilepath ) ) ;
2023-01-26 04:39:10 +00:00
delete layout ;
2020-02-12 00:34:08 +00:00
return false ;
}
2022-10-14 23:55:18 +01:00
layout - > border_path = ParseUtil : : jsonToQString ( layoutObj [ " border_filepath " ] ) ;
2020-02-12 00:34:08 +00:00
if ( layout - > border_path . isEmpty ( ) ) {
2023-01-26 04:18:06 +00:00
logError ( QString ( " Missing 'border_filepath' value for %1 in %2 " ) . arg ( layout - > id ) . arg ( layoutsFilepath ) ) ;
2023-01-26 04:39:10 +00:00
delete layout ;
2020-02-12 00:34:08 +00:00
return false ;
}
2022-10-14 23:55:18 +01:00
layout - > blockdata_path = ParseUtil : : jsonToQString ( layoutObj [ " blockdata_filepath " ] ) ;
2020-03-13 06:23:47 +00:00
if ( layout - > blockdata_path . isEmpty ( ) ) {
2023-01-26 04:18:06 +00:00
logError ( QString ( " Missing 'blockdata_filepath' value for %1 in %2 " ) . arg ( layout - > id ) . arg ( layoutsFilepath ) ) ;
2023-01-26 04:39:10 +00:00
delete layout ;
2020-02-12 00:34:08 +00:00
return false ;
}
2019-02-01 17:43:25 +00:00
mapLayouts . insert ( layout - > id , layout ) ;
2023-02-14 17:09:22 +00:00
mapLayoutsMaster . insert ( layout - > id , layout - > copy ( ) ) ;
2019-02-01 17:43:25 +00:00
mapLayoutsTable . append ( layout - > id ) ;
2023-02-14 17:09:22 +00:00
mapLayoutsTableMaster . append ( layout - > id ) ;
2023-02-01 15:09:50 +00:00
layoutIdsToNames . insert ( layout - > id , layout - > name ) ;
2018-09-27 00:33:08 +01:00
}
2020-02-12 00:34:08 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2019-02-01 17:43:25 +00:00
void Project : : saveMapLayouts ( ) {
2022-09-27 23:06:43 +01:00
QString layoutsFilepath = root + " / " + projectConfig . getFilePath ( ProjectFilePath : : json_layouts ) ;
2019-02-01 17:43:25 +00:00
QFile layoutsFile ( layoutsFilepath ) ;
if ( ! layoutsFile . open ( QIODevice : : WriteOnly ) ) {
logError ( QString ( " Error: Could not open %1 for writing " ) . arg ( layoutsFilepath ) ) ;
return ;
2018-09-27 00:33:08 +01:00
}
2020-03-06 03:46:25 +00:00
OrderedJson : : object layoutsObj ;
2019-02-01 17:43:25 +00:00
layoutsObj [ " layouts_table_label " ] = layoutsLabel ;
2020-03-06 03:46:25 +00:00
OrderedJson : : array layoutsArr ;
2019-02-01 17:43:25 +00:00
for ( QString layoutId : mapLayoutsTableMaster ) {
2023-02-14 17:09:22 +00:00
Layout * layout = mapLayoutsMaster . value ( layoutId ) ;
2020-03-06 03:46:25 +00:00
OrderedJson : : object layoutObj ;
2019-02-01 17:43:25 +00:00
layoutObj [ " id " ] = layout - > id ;
layoutObj [ " name " ] = layout - > name ;
2022-10-16 07:49:42 +01:00
layoutObj [ " width " ] = layout - > width ;
layoutObj [ " height " ] = layout - > height ;
2024-07-15 21:14:38 +01:00
if ( projectConfig . useCustomBorderSize ) {
2022-10-16 07:49:42 +01:00
layoutObj [ " border_width " ] = layout - > border_width ;
layoutObj [ " border_height " ] = layout - > border_height ;
2020-03-13 06:23:47 +00:00
}
2019-02-01 17:43:25 +00:00
layoutObj [ " primary_tileset " ] = layout - > tileset_primary_label ;
layoutObj [ " secondary_tileset " ] = layout - > tileset_secondary_label ;
layoutObj [ " border_filepath " ] = layout - > border_path ;
layoutObj [ " blockdata_filepath " ] = layout - > blockdata_path ;
2020-03-06 03:46:25 +00:00
layoutsArr . push_back ( layoutObj ) ;
2019-02-01 17:43:25 +00:00
}
2020-04-25 22:11:14 +01:00
ignoreWatchedFileTemporarily ( layoutsFilepath ) ;
2019-02-01 17:43:25 +00:00
layoutsObj [ " layouts " ] = layoutsArr ;
2020-03-06 03:46:25 +00:00
OrderedJson layoutJson ( layoutsObj ) ;
OrderedJsonDoc jsonDoc ( & layoutJson ) ;
jsonDoc . dump ( & layoutsFile ) ;
layoutsFile . close ( ) ;
2020-04-25 22:11:14 +01:00
}
void Project : : ignoreWatchedFileTemporarily ( QString filepath ) {
// Ignore any file-change events for this filepath for the next 5 seconds.
modifiedFileTimestamps . insert ( filepath , QDateTime : : currentMSecsSinceEpoch ( ) + 5000 ) ;
2018-09-27 00:33:08 +01:00
}
2019-02-01 17:43:25 +00:00
void Project : : saveMapGroups ( ) {
2022-09-01 17:14:47 +01:00
QString mapGroupsFilepath = QString ( " %1/%2 " ) . arg ( root ) . arg ( projectConfig . getFilePath ( ProjectFilePath : : json_map_groups ) ) ;
2019-02-01 17:43:25 +00:00
QFile mapGroupsFile ( mapGroupsFilepath ) ;
if ( ! mapGroupsFile . open ( QIODevice : : WriteOnly ) ) {
logError ( QString ( " Error: Could not open %1 for writing " ) . arg ( mapGroupsFilepath ) ) ;
return ;
}
2020-03-06 03:46:25 +00:00
OrderedJson : : object mapGroupsObj ;
2019-02-01 17:43:25 +00:00
2020-03-06 03:46:25 +00:00
OrderedJson : : array groupNamesArr ;
2021-02-15 09:21:41 +00:00
for ( QString groupName : this - > groupNames ) {
2020-03-06 03:46:25 +00:00
groupNamesArr . push_back ( groupName ) ;
2019-02-01 17:43:25 +00:00
}
mapGroupsObj [ " group_order " ] = groupNamesArr ;
2018-09-27 00:33:08 +01:00
int groupNum = 0 ;
for ( QStringList mapNames : groupedMapNames ) {
2020-03-06 03:46:25 +00:00
OrderedJson : : array groupArr ;
2018-09-27 00:33:08 +01:00
for ( QString mapName : mapNames ) {
2020-03-06 03:46:25 +00:00
groupArr . push_back ( mapName ) ;
2018-09-27 00:33:08 +01:00
}
2021-02-15 09:21:41 +00:00
mapGroupsObj [ this - > groupNames . at ( groupNum ) ] = groupArr ;
2018-09-27 00:33:08 +01:00
groupNum + + ;
}
2020-04-25 22:11:14 +01:00
ignoreWatchedFileTemporarily ( mapGroupsFilepath ) ;
2020-03-06 03:46:25 +00:00
OrderedJson mapGroupJson ( mapGroupsObj ) ;
OrderedJsonDoc jsonDoc ( & mapGroupJson ) ;
jsonDoc . dump ( & mapGroupsFile ) ;
mapGroupsFile . close ( ) ;
2018-09-27 00:33:08 +01:00
}
2024-11-05 02:49:49 +00:00
void Project : : saveRegionMapSections ( ) {
const QString filepath = QString ( " %1/%2 " ) . arg ( this - > root ) . arg ( projectConfig . getFilePath ( ProjectFilePath : : json_region_map_entries ) ) ;
QFile file ( filepath ) ;
if ( ! file . open ( QIODevice : : WriteOnly ) ) {
logError ( QString ( " Could not open '%1' for writing " ) . arg ( filepath ) ) ;
return ;
}
2024-02-17 00:17:56 +00:00
2024-11-05 02:49:49 +00:00
const QString emptyMapsecName = getEmptyMapsecName ( ) ;
OrderedJson : : array mapSectionArray ;
for ( const auto & idName : this - > mapSectionIdNames ) {
// The 'empty' map section (MAPSEC_NONE) isn't normally present in the region map sections data file.
// We append this name to mapSectionIdNames ourselves if it isn't present, in which case we don't want to output data for it here.
if ( ! this - > saveEmptyMapsec & & idName = = emptyMapsecName )
continue ;
2024-02-17 00:17:56 +00:00
2024-11-05 02:49:49 +00:00
OrderedJson : : object mapSectionObj ;
mapSectionObj [ " id " ] = idName ;
2024-02-17 00:17:56 +00:00
2024-11-05 02:49:49 +00:00
if ( this - > regionMapEntries . contains ( idName ) ) {
MapSectionEntry entry = this - > regionMapEntries . value ( idName ) ;
mapSectionObj [ " name " ] = entry . name ;
mapSectionObj [ " x " ] = entry . x ;
mapSectionObj [ " y " ] = entry . y ;
mapSectionObj [ " width " ] = entry . width ;
mapSectionObj [ " height " ] = entry . height ;
}
2024-02-18 03:47:48 +00:00
2024-11-05 02:49:49 +00:00
mapSectionArray . append ( mapSectionObj ) ;
2024-02-17 00:17:56 +00:00
}
2024-11-05 02:49:49 +00:00
OrderedJson : : object object ;
object [ " map_sections " ] = mapSectionArray ;
2024-02-17 00:17:56 +00:00
ignoreWatchedFileTemporarily ( filepath ) ;
2024-11-05 02:49:49 +00:00
OrderedJson json ( object ) ;
OrderedJsonDoc jsonDoc ( & json ) ;
jsonDoc . dump ( & file ) ;
file . close ( ) ;
2024-02-17 00:17:56 +00:00
}
2019-06-15 23:49:30 +01:00
void Project : : saveWildMonData ( ) {
2024-01-18 17:00:18 +00:00
if ( ! this - > wildEncountersLoaded ) return ;
2019-07-03 21:21:48 +01:00
2022-09-01 17:14:47 +01:00
QString wildEncountersJsonFilepath = QString ( " %1/%2 " ) . arg ( root ) . arg ( projectConfig . getFilePath ( ProjectFilePath : : json_wild_encounters ) ) ;
2019-06-15 23:49:30 +01:00
QFile wildEncountersFile ( wildEncountersJsonFilepath ) ;
if ( ! wildEncountersFile . open ( QIODevice : : WriteOnly ) ) {
logError ( QString ( " Error: Could not open %1 for writing " ) . arg ( wildEncountersJsonFilepath ) ) ;
return ;
}
2020-03-06 03:46:25 +00:00
OrderedJson : : object wildEncountersObject ;
OrderedJson : : array wildEncounterGroups ;
2019-06-15 23:49:30 +01:00
2020-03-06 03:46:25 +00:00
OrderedJson : : object monHeadersObject ;
2023-12-18 07:19:12 +00:00
monHeadersObject [ " label " ] = projectConfig . getIdentifier ( ProjectIdentifier : : symbol_wild_encounters ) ;
2019-06-15 23:49:30 +01:00
monHeadersObject [ " for_maps " ] = true ;
2019-06-20 17:40:36 +01:00
2020-03-06 03:46:25 +00:00
OrderedJson : : array fieldsInfoArray ;
2019-09-30 00:07:34 +01:00
for ( EncounterField fieldInfo : wildMonFields ) {
2020-03-06 03:46:25 +00:00
OrderedJson : : object fieldObject ;
OrderedJson : : array rateArray ;
2019-06-20 17:40:36 +01:00
2019-09-30 00:07:34 +01:00
for ( int rate : fieldInfo . encounterRates ) {
2020-03-06 03:46:25 +00:00
rateArray . push_back ( rate ) ;
2019-09-30 00:07:34 +01:00
}
2019-06-20 17:40:36 +01:00
2019-09-30 00:07:34 +01:00
fieldObject [ " type " ] = fieldInfo . name ;
2019-06-20 17:40:36 +01:00
fieldObject [ " encounter_rates " ] = rateArray ;
2020-03-06 03:46:25 +00:00
OrderedJson : : object groupsObject ;
2021-08-13 00:19:21 +01:00
for ( auto groupNamePair : fieldInfo . groups ) {
QString groupName = groupNamePair . first ;
2020-03-06 03:46:25 +00:00
OrderedJson : : array subGroupIndices ;
2019-10-01 00:54:27 +01:00
std : : sort ( fieldInfo . groups [ groupName ] . begin ( ) , fieldInfo . groups [ groupName ] . end ( ) ) ;
2019-09-30 00:07:34 +01:00
for ( int slotIndex : fieldInfo . groups [ groupName ] ) {
2020-03-06 03:46:25 +00:00
subGroupIndices . push_back ( slotIndex ) ;
2019-09-30 00:07:34 +01:00
}
groupsObject [ groupName ] = subGroupIndices ;
}
2020-03-06 03:46:25 +00:00
if ( ! groupsObject . empty ( ) ) fieldObject [ " groups " ] = groupsObject ;
2019-09-30 00:07:34 +01:00
2019-06-20 17:40:36 +01:00
fieldsInfoArray . append ( fieldObject ) ;
}
monHeadersObject [ " fields " ] = fieldsInfoArray ;
2020-03-06 03:46:25 +00:00
OrderedJson : : array encountersArray ;
2020-04-20 15:18:03 +01:00
for ( auto keyPair : wildMonData ) {
QString key = keyPair . first ;
for ( auto grouplLabelPair : wildMonData [ key ] ) {
QString groupLabel = grouplLabelPair . first ;
2020-03-06 03:46:25 +00:00
OrderedJson : : object encounterObject ;
2019-06-20 17:40:36 +01:00
encounterObject [ " map " ] = key ;
2019-07-03 02:44:19 +01:00
encounterObject [ " base_label " ] = groupLabel ;
2019-06-20 17:40:36 +01:00
2020-04-20 15:18:03 +01:00
WildPokemonHeader encounterHeader = wildMonData [ key ] [ groupLabel ] ;
2021-08-13 00:19:21 +01:00
for ( auto fieldNamePair : encounterHeader . wildMons ) {
QString fieldName = fieldNamePair . first ;
2020-03-06 03:46:25 +00:00
OrderedJson : : object fieldObject ;
2021-08-13 00:19:21 +01:00
WildMonInfo monInfo = encounterHeader . wildMons [ fieldName ] ;
2019-06-20 17:40:36 +01:00
fieldObject [ " encounter_rate " ] = monInfo . encounterRate ;
2020-03-06 03:46:25 +00:00
OrderedJson : : array monArray ;
2019-06-20 17:40:36 +01:00
for ( WildPokemon wildMon : monInfo . wildPokemon ) {
2020-03-06 03:46:25 +00:00
OrderedJson : : object monEntry ;
2019-06-20 17:40:36 +01:00
monEntry [ " min_level " ] = wildMon . minLevel ;
monEntry [ " max_level " ] = wildMon . maxLevel ;
monEntry [ " species " ] = wildMon . species ;
2020-03-06 03:46:25 +00:00
monArray . push_back ( monEntry ) ;
2019-06-20 17:40:36 +01:00
}
2019-07-03 02:44:19 +01:00
fieldObject [ " mons " ] = monArray ;
2019-06-20 17:40:36 +01:00
encounterObject [ fieldName ] = fieldObject ;
}
2020-03-06 03:46:25 +00:00
encountersArray . push_back ( encounterObject ) ;
2019-06-20 17:40:36 +01:00
}
2019-06-15 23:49:30 +01:00
}
monHeadersObject [ " encounters " ] = encountersArray ;
2020-03-06 03:46:25 +00:00
wildEncounterGroups . push_back ( monHeadersObject ) ;
2019-06-15 23:49:30 +01:00
// add extra Json objects that are not associated with maps to the file
2020-03-06 03:46:25 +00:00
for ( auto extraObject : extraEncounterGroups ) {
wildEncounterGroups . push_back ( extraObject ) ;
2019-06-15 23:49:30 +01:00
}
wildEncountersObject [ " wild_encounter_groups " ] = wildEncounterGroups ;
2020-04-25 22:11:14 +01:00
ignoreWatchedFileTemporarily ( wildEncountersJsonFilepath ) ;
2020-03-06 03:46:25 +00:00
OrderedJson encounterJson ( wildEncountersObject ) ;
OrderedJsonDoc jsonDoc ( & encounterJson ) ;
jsonDoc . dump ( & wildEncountersFile ) ;
2019-06-15 23:49:30 +01:00
wildEncountersFile . close ( ) ;
}
2022-09-10 03:44:42 +01:00
void Project : : saveHealLocations ( Map * map ) {
this - > saveHealLocationsData ( map ) ;
this - > saveHealLocationsConstants ( ) ;
}
2018-09-27 00:33:08 +01:00
2022-09-10 03:44:42 +01:00
// Saves heal location maps/coords/respawn data in root + /src/data/heal_locations.h
2024-11-12 18:44:04 +00:00
void Project : : saveHealLocationsData ( Map * ) {
/* TODO: Will be re-implemented as part of changes to reading heal locations from map.json
2022-09-10 03:44:42 +01:00
// Update heal locations from map
2022-07-19 22:56:12 +01:00
if ( map - > events [ Event : : Group : : Heal ] . length ( ) > 0 ) {
for ( Event * healEvent : map - > events [ Event : : Group : : Heal ] ) {
2018-09-27 00:33:08 +01:00
HealLocation hl = HealLocation : : fromEvent ( healEvent ) ;
2022-09-09 22:41:50 +01:00
this - > healLocations [ hl . index - 1 ] = hl ;
2018-09-27 00:33:08 +01:00
}
}
2024-11-12 18:44:04 +00:00
2018-09-27 00:33:08 +01:00
2022-09-10 03:44:42 +01:00
// Find any duplicate constant names
QMap < QString , int > healLocationsDupes ;
QSet < QString > healLocationsUnique ;
for ( auto hl : this - > healLocations ) {
QString idName = hl . idName ;
if ( healLocationsUnique . contains ( idName ) )
healLocationsDupes [ idName ] = 1 ;
else
healLocationsUnique . insert ( idName ) ;
}
// Create the definition text for each data table
2024-07-15 21:14:38 +01:00
bool respawnEnabled = projectConfig . healLocationRespawnDataEnabled ;
2022-09-10 03:44:42 +01:00
const QString qualifiers = QString ( healLocationDataQualifiers . isStatic ? " static " : " " )
+ QString ( healLocationDataQualifiers . isConst ? " const " : " " ) ;
2024-01-04 17:22:06 +00:00
QString locationTableText = QString ( " %1%2 %3[] = \n { \n " ) . arg ( qualifiers )
. arg ( projectConfig . getIdentifier ( ProjectIdentifier : : symbol_heal_locations_type ) )
. arg ( this - > healLocationsTableName ) ;
2022-09-10 03:44:42 +01:00
QString respawnMapTableText , respawnNPCTableText ;
if ( respawnEnabled ) {
2024-01-04 17:22:06 +00:00
respawnMapTableText = QString ( " \n %1%2[][2] = \n { \n " ) . arg ( qualifiers ) . arg ( projectConfig . getIdentifier ( ProjectIdentifier : : symbol_spawn_maps ) ) ;
respawnNPCTableText = QString ( " \n %1%2[] = \n { \n " ) . arg ( qualifiers ) . arg ( projectConfig . getIdentifier ( ProjectIdentifier : : symbol_spawn_npcs ) ) ;
2022-09-10 03:44:42 +01:00
}
// Populate the data tables with the heal location data
int i = 0 ;
2023-12-18 07:19:12 +00:00
const QString emptyMapName = projectConfig . getIdentifier ( ProjectIdentifier : : define_map_empty ) ;
2022-09-10 03:44:42 +01:00
for ( auto hl : this - > healLocations ) {
// Add numbered suffix for duplicate constants
if ( healLocationsDupes . keys ( ) . contains ( hl . idName ) ) {
QString duplicateName = hl . idName ;
hl . idName + = QString ( " _%1 " ) . arg ( healLocationsDupes [ duplicateName ] ) ;
2020-04-29 17:34:49 +01:00
healLocationsDupes [ duplicateName ] + + ;
2022-09-10 03:44:42 +01:00
this - > healLocations [ i ] . idName = hl . idName ; // Update the name for writing constants later
2020-03-20 07:09:48 +00:00
}
2022-09-10 03:44:42 +01:00
// Add entry to map/coords table
QString mapName = ! hl . mapName . isEmpty ( ) ? hl . mapName : emptyMapName ;
locationTableText + = QString ( " [%1 - 1] = {MAP_GROUP(%2), MAP_NUM(%2), %3, %4}, \n " )
. arg ( hl . idName )
. arg ( mapName )
. arg ( hl . x )
. arg ( hl . y ) ;
2018-09-27 00:33:08 +01:00
2022-09-10 03:44:42 +01:00
// Add entry to respawn map and npc tables
if ( respawnEnabled ) {
mapName = ! hl . respawnMap . isEmpty ( ) ? hl . respawnMap : emptyMapName ;
respawnMapTableText + = QString ( " [%1 - 1] = {MAP_GROUP(%2), MAP_NUM(%2)}, \n " )
. arg ( hl . idName )
. arg ( mapName ) ;
respawnNPCTableText + = QString ( " [%1 - 1] = %2, \n " )
. arg ( hl . idName )
. arg ( hl . respawnNPC ) ;
2018-09-27 00:33:08 +01:00
}
i + + ;
}
2022-09-10 03:44:42 +01:00
const QString tableEnd = QString ( " }; \n " ) ;
QString text = locationTableText + tableEnd ;
if ( respawnEnabled )
text + = respawnMapTableText + tableEnd + respawnNPCTableText + tableEnd ;
2020-03-20 07:09:48 +00:00
2022-10-18 06:48:08 +01:00
QString filepath = root + " / " + projectConfig . getFilePath ( ProjectFilePath : : data_heal_locations ) ;
2022-09-10 03:44:42 +01:00
ignoreWatchedFileTemporarily ( filepath ) ;
saveTextFile ( filepath , text ) ;
2024-11-12 18:44:04 +00:00
*/
2022-09-10 03:44:42 +01:00
}
// Saves heal location defines in root + /include/constants/heal_locations.h
void Project : : saveHealLocationsConstants ( ) {
// Get existing defines, and create an inverted map so they'll be in sorted order for printing
int nextDefineValue = 1 ;
QMap < int , QString > valuesToNames = QMap < int , QString > ( ) ;
QStringList defineNames = this - > healLocationNameToValue . keys ( ) ;
QList < int > defineValues = this - > healLocationNameToValue . values ( ) ;
for ( auto name : defineNames ) {
int value = this - > healLocationNameToValue . value ( name ) ;
if ( valuesToNames . contains ( value ) ) {
do { // Redefine duplicate as first available value
value = nextDefineValue + + ;
} while ( defineValues . contains ( value ) ) ;
2020-03-20 07:09:48 +00:00
}
2022-09-10 03:44:42 +01:00
valuesToNames . insert ( value , name ) ;
}
// Check for new id names in the heal locations list
for ( auto hl : this - > healLocations ) {
if ( this - > healLocationNameToValue . contains ( hl . idName ) )
continue ;
int value ;
do { // Give new heal location first available value
value = nextDefineValue + + ;
} while ( valuesToNames . contains ( value ) ) ;
valuesToNames . insert ( value , hl . idName ) ;
2020-03-20 07:09:48 +00:00
}
2018-09-27 00:33:08 +01:00
2022-09-10 03:44:42 +01:00
// Include guards
const QString guardName = " GUARD_CONSTANTS_HEAL_LOCATIONS_H " ;
QString constantsText = QString ( " #ifndef %1 \n #define %1 \n \n " ) . arg ( guardName ) ;
// List defines in ascending order
QMap < int , QString > : : const_iterator i ;
for ( i = valuesToNames . constBegin ( ) ; i ! = valuesToNames . constEnd ( ) ; i + + )
constantsText + = QString ( " #define %1 %2 \n " ) . arg ( i . value ( ) ) . arg ( i . key ( ) ) ;
2018-09-27 00:33:08 +01:00
2022-09-10 03:44:42 +01:00
constantsText + = QString ( " \n #endif // %1 \n " ) . arg ( guardName ) ;
2020-04-12 01:39:50 +01:00
2022-09-10 03:44:42 +01:00
QString filepath = root + " / " + projectConfig . getFilePath ( ProjectFilePath : : constants_heal_locations ) ;
ignoreWatchedFileTemporarily ( filepath ) ;
saveTextFile ( filepath , constantsText ) ;
2018-09-27 00:33:08 +01:00
}
2018-10-03 01:01:09 +01:00
void Project : : saveTilesets ( Tileset * primaryTileset , Tileset * secondaryTileset ) {
2019-04-04 06:44:31 +01:00
saveTilesetMetatileLabels ( primaryTileset , secondaryTileset ) ;
2018-10-03 01:01:09 +01:00
saveTilesetMetatileAttributes ( primaryTileset ) ;
saveTilesetMetatileAttributes ( secondaryTileset ) ;
saveTilesetMetatiles ( primaryTileset ) ;
saveTilesetMetatiles ( secondaryTileset ) ;
2018-10-03 01:01:24 +01:00
saveTilesetTilesImage ( primaryTileset ) ;
saveTilesetTilesImage ( secondaryTileset ) ;
2020-05-03 16:00:56 +01:00
saveTilesetPalettes ( primaryTileset ) ;
saveTilesetPalettes ( secondaryTileset ) ;
2018-10-03 01:01:09 +01:00
}
2023-05-19 02:29:27 +01:00
void Project : : updateTilesetMetatileLabels ( Tileset * tileset ) {
// Erase old labels, then repopulate with new labels
const QString prefix = tileset - > getMetatileLabelPrefix ( ) ;
metatileLabelsMap [ tileset - > name ] . clear ( ) ;
for ( int metatileId : tileset - > metatileLabels . keys ( ) ) {
2023-05-19 07:21:41 +01:00
if ( tileset - > metatileLabels [ metatileId ] . isEmpty ( ) )
continue ;
2023-05-19 02:29:27 +01:00
QString label = prefix + tileset - > metatileLabels [ metatileId ] ;
metatileLabelsMap [ tileset - > name ] [ label ] = metatileId ;
}
}
2019-09-09 23:24:30 +01:00
2023-05-19 02:29:27 +01:00
// Given a map of define names to define values, returns a formatted list of #defines
2023-12-19 18:42:07 +00:00
QString Project : : buildMetatileLabelsText ( const QMap < QString , uint16_t > defines ) {
2023-05-19 02:29:27 +01:00
QStringList labels = defines . keys ( ) ;
2019-09-09 23:24:30 +01:00
2023-05-19 02:29:27 +01:00
// Setup for pretty formatting.
int longestLength = 0 ;
for ( QString label : labels ) {
if ( label . size ( ) > longestLength )
longestLength = label . size ( ) ;
2019-09-09 23:24:30 +01:00
}
2019-04-04 06:44:31 +01:00
2023-05-19 02:29:27 +01:00
// Generate defines text
QString output = QString ( ) ;
for ( QString label : labels ) {
2023-09-25 15:48:35 +01:00
QString line = QString ( " #define %1 %2 \n " )
2023-05-19 02:29:27 +01:00
. arg ( label , - 1 * longestLength )
2023-09-25 15:48:35 +01:00
. arg ( Metatile : : getMetatileIdString ( defines [ label ] ) ) ;
2023-05-19 02:29:27 +01:00
output + = line ;
2019-04-04 06:44:31 +01:00
}
2023-05-19 02:29:27 +01:00
return output ;
}
2019-04-04 06:44:31 +01:00
2023-05-19 02:29:27 +01:00
void Project : : saveTilesetMetatileLabels ( Tileset * primaryTileset , Tileset * secondaryTileset ) {
// Skip writing the file if there are no labels in both the new and old sets
if ( metatileLabelsMap [ primaryTileset - > name ] . size ( ) = = 0 & & primaryTileset - > metatileLabels . size ( ) = = 0
& & metatileLabelsMap [ secondaryTileset - > name ] . size ( ) = = 0 & & secondaryTileset - > metatileLabels . size ( ) = = 0 )
2019-04-04 06:44:31 +01:00
return ;
2023-05-19 02:29:27 +01:00
updateTilesetMetatileLabels ( primaryTileset ) ;
updateTilesetMetatileLabels ( secondaryTileset ) ;
2019-07-18 21:35:00 +01:00
2023-05-19 02:29:27 +01:00
// Recreate metatile labels file
const QString guardName = " GUARD_METATILE_LABELS_H " ;
QString outputText = QString ( " #ifndef %1 \n #define %1 \n " ) . arg ( guardName ) ;
2019-07-18 21:35:00 +01:00
2023-05-19 02:29:27 +01:00
for ( QString tilesetName : metatileLabelsMap . keys ( ) ) {
if ( metatileLabelsMap [ tilesetName ] . size ( ) = = 0 )
continue ;
outputText + = QString ( " \n // %1 \n " ) . arg ( tilesetName ) ;
outputText + = buildMetatileLabelsText ( metatileLabelsMap [ tilesetName ] ) ;
}
2019-07-18 21:35:00 +01:00
2023-05-19 02:29:27 +01:00
if ( unusedMetatileLabels . size ( ) ! = 0 ) {
// Append any defines originally read from the file that aren't associated with any tileset.
outputText + = QString ( " \n // Other \n " ) ;
outputText + = buildMetatileLabelsText ( unusedMetatileLabels ) ;
2019-04-04 06:44:31 +01:00
}
2023-05-19 02:29:27 +01:00
outputText + = QString ( " \n #endif // %1 \n " ) . arg ( guardName ) ;
2020-04-12 01:39:50 +01:00
2023-05-19 02:29:27 +01:00
QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_metatile_labels ) ;
ignoreWatchedFileTemporarily ( root + " / " + filename ) ;
saveTextFile ( root + " / " + filename , outputText ) ;
2019-04-04 06:44:31 +01:00
}
2018-10-03 01:01:09 +01:00
void Project : : saveTilesetMetatileAttributes ( Tileset * tileset ) {
QFile attrs_file ( tileset - > metatile_attrs_path ) ;
if ( attrs_file . open ( QIODevice : : WriteOnly | QIODevice : : Truncate ) ) {
QByteArray data ;
2024-10-16 16:04:25 +01:00
for ( const auto & metatile : tileset - > metatiles ( ) ) {
2022-10-25 22:51:32 +01:00
uint32_t attributes = metatile - > getAttributes ( ) ;
2024-07-15 21:14:38 +01:00
for ( int i = 0 ; i < projectConfig . metatileAttributesSize ; i + + )
2022-02-03 23:10:50 +00:00
data . append ( static_cast < char > ( attributes > > ( 8 * i ) ) ) ;
2018-10-03 01:01:09 +01:00
}
attrs_file . write ( data ) ;
} else {
2018-12-20 23:30:35 +00:00
logError ( QString ( " Could not save tileset metatile attributes file '%1' " ) . arg ( tileset - > metatile_attrs_path ) ) ;
2018-10-03 01:01:09 +01:00
}
}
void Project : : saveTilesetMetatiles ( Tileset * tileset ) {
QFile metatiles_file ( tileset - > metatiles_path ) ;
if ( metatiles_file . open ( QIODevice : : WriteOnly | QIODevice : : Truncate ) ) {
QByteArray data ;
2022-10-04 02:28:16 +01:00
int numTiles = projectConfig . getNumTilesInMetatile ( ) ;
2024-10-16 16:04:25 +01:00
for ( const auto & metatile : tileset - > metatiles ( ) ) {
2020-06-25 06:32:42 +01:00
for ( int i = 0 ; i < numTiles ; i + + ) {
2022-02-04 02:32:27 +00:00
uint16_t tile = metatile - > tiles . at ( i ) . rawValue ( ) ;
data . append ( static_cast < char > ( tile ) ) ;
data . append ( static_cast < char > ( tile > > 8 ) ) ;
2018-10-03 01:01:09 +01:00
}
}
metatiles_file . write ( data ) ;
} else {
2024-10-16 16:04:25 +01:00
tileset - > clearMetatiles ( ) ;
2018-12-20 23:30:35 +00:00
logError ( QString ( " Could not open tileset metatiles file '%1' " ) . arg ( tileset - > metatiles_path ) ) ;
2018-10-03 01:01:09 +01:00
}
}
2018-10-03 01:01:24 +01:00
void Project : : saveTilesetTilesImage ( Tileset * tileset ) {
2023-06-16 12:39:32 +01:00
// Only write the tiles image if it was changed.
// Porymap will only ever change an existing tiles image by importing a new one.
if ( tileset - > hasUnsavedTilesImage ) {
if ( ! tileset - > tilesImage . save ( tileset - > tilesImagePath , " PNG " ) ) {
logError ( QString ( " Failed to save tiles image '%1' " ) . arg ( tileset - > tilesImagePath ) ) ;
return ;
}
tileset - > hasUnsavedTilesImage = false ;
}
2018-10-03 01:01:24 +01:00
}
2020-05-03 16:00:56 +01:00
void Project : : saveTilesetPalettes ( Tileset * tileset ) {
2019-03-22 00:56:00 +00:00
for ( int i = 0 ; i < Project : : getNumPalettesTotal ( ) ; i + + ) {
2018-10-03 01:01:41 +01:00
QString filepath = tileset - > palettePaths . at ( i ) ;
2021-02-18 08:25:26 +00:00
PaletteUtil : : writeJASC ( filepath , tileset - > palettes . at ( i ) . toVector ( ) , 0 , 16 ) ;
2018-10-03 01:01:41 +01:00
}
}
2023-02-08 14:31:39 +00:00
bool Project : : loadLayoutTilesets ( Layout * layout ) {
2021-07-20 03:02:31 +01:00
layout - > tileset_primary = getTileset ( layout - > tileset_primary_label ) ;
if ( ! layout - > tileset_primary ) {
2022-10-24 00:28:55 +01:00
QString defaultTileset = this - > getDefaultPrimaryTilesetLabel ( ) ;
2021-07-20 03:02:31 +01:00
logWarn ( QString ( " Map layout %1 has invalid primary tileset '%2'. Using default '%3' " ) . arg ( layout - > id ) . arg ( layout - > tileset_primary_label ) . arg ( defaultTileset ) ) ;
layout - > tileset_primary_label = defaultTileset ;
layout - > tileset_primary = getTileset ( layout - > tileset_primary_label ) ;
if ( ! layout - > tileset_primary ) {
2020-04-04 19:14:16 +01:00
logError ( QString ( " Failed to set default primary tileset. " ) ) ;
return false ;
}
2020-02-12 16:22:40 +00:00
}
2021-07-20 03:02:31 +01:00
layout - > tileset_secondary = getTileset ( layout - > tileset_secondary_label ) ;
if ( ! layout - > tileset_secondary ) {
2022-10-24 00:28:55 +01:00
QString defaultTileset = this - > getDefaultSecondaryTilesetLabel ( ) ;
2021-07-20 03:02:31 +01:00
logWarn ( QString ( " Map layout %1 has invalid secondary tileset '%2'. Using default '%3' " ) . arg ( layout - > id ) . arg ( layout - > tileset_secondary_label ) . arg ( defaultTileset ) ) ;
layout - > tileset_secondary_label = defaultTileset ;
layout - > tileset_secondary = getTileset ( layout - > tileset_secondary_label ) ;
if ( ! layout - > tileset_secondary ) {
2020-04-04 19:14:16 +01:00
logError ( QString ( " Failed to set default secondary tileset. " ) ) ;
return false ;
}
2020-02-12 16:22:40 +00:00
}
return true ;
2018-09-27 00:33:08 +01:00
}
2018-10-03 01:01:15 +01:00
Tileset * Project : : loadTileset ( QString label , Tileset * tileset ) {
2022-10-08 18:51:48 +01:00
auto memberMap = Tileset : : getHeaderMemberMap ( this - > usingAsmTilesets ) ;
2022-09-28 01:17:55 +01:00
if ( this - > usingAsmTilesets ) {
// Read asm tileset header. Backwards compatibility
const QStringList values = parser . getLabelValues ( parser . parseAsm ( projectConfig . getFilePath ( ProjectFilePath : : tilesets_headers_asm ) ) , label ) ;
if ( values . isEmpty ( ) ) {
return nullptr ;
}
if ( tileset = = nullptr ) {
tileset = new Tileset ;
}
tileset - > name = label ;
2022-10-24 04:38:27 +01:00
tileset - > is_secondary = ParseUtil : : gameStringToBool ( values . value ( memberMap . key ( " isSecondary " ) ) ) ;
2022-10-08 18:51:48 +01:00
tileset - > tiles_label = values . value ( memberMap . key ( " tiles " ) ) ;
tileset - > palettes_label = values . value ( memberMap . key ( " palettes " ) ) ;
tileset - > metatiles_label = values . value ( memberMap . key ( " metatiles " ) ) ;
tileset - > metatile_attrs_label = values . value ( memberMap . key ( " metatileAttributes " ) ) ;
2020-04-19 23:11:45 +01:00
} else {
2022-09-28 01:17:55 +01:00
// Read C tileset header
2022-10-08 18:51:48 +01:00
const auto structs = parser . readCStructs ( projectConfig . getFilePath ( ProjectFilePath : : tilesets_headers ) , label , memberMap ) ;
2022-09-28 01:17:55 +01:00
if ( ! structs . contains ( label ) ) {
return nullptr ;
}
if ( tileset = = nullptr ) {
tileset = new Tileset ;
}
2022-10-08 18:51:48 +01:00
const auto tilesetAttributes = structs [ label ] ;
2022-09-28 01:17:55 +01:00
tileset - > name = label ;
2022-10-24 04:38:27 +01:00
tileset - > is_secondary = ParseUtil : : gameStringToBool ( tilesetAttributes . value ( " isSecondary " ) ) ;
2022-09-28 01:17:55 +01:00
tileset - > tiles_label = tilesetAttributes . value ( " tiles " ) ;
tileset - > palettes_label = tilesetAttributes . value ( " palettes " ) ;
tileset - > metatiles_label = tilesetAttributes . value ( " metatiles " ) ;
tileset - > metatile_attrs_label = tilesetAttributes . value ( " metatileAttributes " ) ;
2020-04-19 23:11:45 +01:00
}
2018-09-27 00:33:08 +01:00
loadTilesetAssets ( tileset ) ;
2021-02-15 16:33:30 +00:00
tilesetCache . insert ( label , tileset ) ;
2018-09-27 00:33:08 +01:00
return tileset ;
}
2023-02-08 14:31:39 +00:00
bool Project : : loadBlockdata ( Layout * layout ) {
2021-07-20 03:02:31 +01:00
QString path = QString ( " %1/%2 " ) . arg ( root ) . arg ( layout - > blockdata_path ) ;
layout - > blockdata = readBlockdata ( path ) ;
2022-08-29 19:48:16 +01:00
layout - > lastCommitBlocks . blocks = layout - > blockdata ;
2023-04-08 02:50:46 +01:00
layout - > lastCommitBlocks . layoutDimensions = QSize ( layout - > getWidth ( ) , layout - > getHeight ( ) ) ;
2019-01-07 18:06:25 +00:00
2021-07-20 03:02:31 +01:00
if ( layout - > blockdata . count ( ) ! = layout - > getWidth ( ) * layout - > getHeight ( ) ) {
2019-01-07 18:06:25 +00:00
logWarn ( QString ( " Layout blockdata length %1 does not match dimensions %2x%3 (should be %4). Resizing blockdata. " )
2021-07-20 03:02:31 +01:00
. arg ( layout - > blockdata . count ( ) )
. arg ( layout - > getWidth ( ) )
. arg ( layout - > getHeight ( ) )
. arg ( layout - > getWidth ( ) * layout - > getHeight ( ) ) ) ;
layout - > blockdata . resize ( layout - > getWidth ( ) * layout - > getHeight ( ) ) ;
2019-01-07 18:06:25 +00:00
}
2020-02-12 16:22:40 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2024-02-16 03:19:49 +00:00
void Project : : setNewLayoutBlockdata ( Layout * layout ) {
layout - > blockdata . clear ( ) ;
int width = layout - > getWidth ( ) ;
int height = layout - > getHeight ( ) ;
2024-07-15 21:14:38 +01:00
Block block ( projectConfig . defaultMetatileId , projectConfig . defaultCollision , projectConfig . defaultElevation ) ;
2022-09-11 04:30:28 +01:00
for ( int i = 0 ; i < width * height ; i + + ) {
2024-02-16 03:19:49 +00:00
layout - > blockdata . append ( block ) ;
2018-09-27 00:33:08 +01:00
}
2024-02-16 03:19:49 +00:00
layout - > lastCommitBlocks . blocks = layout - > blockdata ;
layout - > lastCommitBlocks . layoutDimensions = QSize ( width , height ) ;
2018-09-27 00:33:08 +01:00
}
2023-02-08 14:31:39 +00:00
bool Project : : loadLayoutBorder ( Layout * layout ) {
2021-07-20 03:02:31 +01:00
QString path = QString ( " %1/%2 " ) . arg ( root ) . arg ( layout - > border_path ) ;
layout - > border = readBlockdata ( path ) ;
2022-08-29 19:48:16 +01:00
layout - > lastCommitBlocks . border = layout - > border ;
layout - > lastCommitBlocks . borderDimensions = QSize ( layout - > getBorderWidth ( ) , layout - > getBorderHeight ( ) ) ;
2021-07-20 03:02:31 +01:00
int borderLength = layout - > getBorderWidth ( ) * layout - > getBorderHeight ( ) ;
if ( layout - > border . count ( ) ! = borderLength ) {
2020-02-12 16:22:40 +00:00
logWarn ( QString ( " Layout border blockdata length %1 must be %2. Resizing border blockdata. " )
2021-07-20 03:02:31 +01:00
. arg ( layout - > border . count ( ) )
2020-02-12 16:22:40 +00:00
. arg ( borderLength ) ) ;
2021-07-20 03:02:31 +01:00
layout - > border . resize ( borderLength ) ;
2020-02-12 16:22:40 +00:00
}
return true ;
2018-09-27 00:33:08 +01:00
}
2024-02-16 03:19:49 +00:00
void Project : : setNewLayoutBorder ( Layout * layout ) {
layout - > border . clear ( ) ;
int width = layout - > getBorderWidth ( ) ;
int height = layout - > getBorderHeight ( ) ;
2023-08-24 02:07:13 +01:00
2024-07-15 21:14:38 +01:00
if ( projectConfig . newMapBorderMetatileIds . length ( ) ! = width * height ) {
2023-08-24 02:07:13 +01:00
// Border size doesn't match the number of default border metatiles.
// Fill the border with empty metatiles.
2022-09-11 04:30:28 +01:00
for ( int i = 0 ; i < width * height ; i + + ) {
2024-02-16 03:19:49 +00:00
layout - > border . append ( 0 ) ;
2020-03-14 07:44:55 +00:00
}
2020-03-13 06:23:47 +00:00
} else {
2023-08-24 02:07:13 +01:00
// Fill the border with the default metatiles from the config.
for ( int i = 0 ; i < width * height ; i + + ) {
2024-09-24 16:31:06 +01:00
layout - > border . append ( projectConfig . newMapBorderMetatileIds . at ( i ) ) ;
2022-09-11 04:30:28 +01:00
}
2020-03-13 06:23:47 +00:00
}
2023-08-24 02:07:13 +01:00
2024-02-16 03:19:49 +00:00
layout - > lastCommitBlocks . border = layout - > border ;
layout - > lastCommitBlocks . borderDimensions = QSize ( width , height ) ;
2018-09-27 00:33:08 +01:00
}
2023-02-14 17:09:22 +00:00
void Project : : saveLayoutBorder ( Layout * layout ) {
QString path = QString ( " %1/%2 " ) . arg ( root ) . arg ( layout - > border_path ) ;
writeBlockdata ( path , layout - > border ) ;
2018-09-27 00:33:08 +01:00
}
2023-02-14 17:09:22 +00:00
void Project : : saveLayoutBlockdata ( Layout * layout ) {
QString path = QString ( " %1/%2 " ) . arg ( root ) . arg ( layout - > blockdata_path ) ;
writeBlockdata ( path , layout - > blockdata ) ;
2018-09-27 00:33:08 +01:00
}
2021-02-14 21:34:17 +00:00
void Project : : writeBlockdata ( QString path , const Blockdata & blockdata ) {
2018-09-27 00:33:08 +01:00
QFile file ( path ) ;
if ( file . open ( QIODevice : : WriteOnly ) ) {
2021-02-14 21:34:17 +00:00
QByteArray data = blockdata . serialize ( ) ;
2018-09-27 00:33:08 +01:00
file . write ( data ) ;
} else {
2018-12-20 23:30:35 +00:00
logError ( QString ( " Failed to open blockdata file for writing: '%1' " ) . arg ( path ) ) ;
2018-09-27 00:33:08 +01:00
}
}
void Project : : saveAllMaps ( ) {
2021-02-18 22:41:36 +00:00
for ( auto * map : mapCache . values ( ) )
2018-09-27 00:33:08 +01:00
saveMap ( map ) ;
}
void Project : : saveMap ( Map * map ) {
// Create/Modify a few collateral files for brand new maps.
2022-09-27 23:06:43 +01:00
QString basePath = projectConfig . getFilePath ( ProjectFilePath : : data_map_folders ) ;
2024-11-12 03:28:53 +00:00
QString mapDataDir = root + " / " + basePath + map - > name ( ) ;
if ( ! map - > isPersistedToFile ( ) ) {
2019-02-01 17:43:25 +00:00
if ( ! QDir : : root ( ) . mkdir ( mapDataDir ) ) {
logError ( QString ( " Error: failed to create directory for new map: '%1' " ) . arg ( mapDataDir ) ) ;
2018-09-27 00:33:08 +01:00
}
// Create file data/maps/<map_name>/scripts.inc
2024-11-12 03:28:53 +00:00
QString text = this - > getScriptDefaultString ( projectConfig . usePoryScript , map - > name ( ) ) ;
2024-07-15 21:14:38 +01:00
saveTextFile ( mapDataDir + " /scripts " + this - > getScriptFileExtension ( projectConfig . usePoryScript ) , text ) ;
2018-09-27 00:33:08 +01:00
2024-07-15 21:14:38 +01:00
if ( projectConfig . createMapTextFileEnabled ) {
2019-01-08 14:59:43 +00:00
// Create file data/maps/<map_name>/text.inc
2024-07-15 21:14:38 +01:00
saveTextFile ( mapDataDir + " /text " + this - > getScriptFileExtension ( projectConfig . usePoryScript ) , " \n " ) ;
2019-01-08 14:59:43 +00:00
}
2018-09-27 00:33:08 +01:00
// Simply append to data/event_scripts.s.
2024-11-12 03:28:53 +00:00
text = QString ( " \n \t .include \" %1%2/scripts.inc \" \n " ) . arg ( basePath , map - > name ( ) ) ;
2024-07-15 21:14:38 +01:00
if ( projectConfig . createMapTextFileEnabled ) {
2024-11-12 03:28:53 +00:00
text + = QString ( " \t .include \" %1%2/text.inc \" \n " ) . arg ( basePath , map - > name ( ) ) ;
2019-01-08 14:59:43 +00:00
}
2022-09-27 23:06:43 +01:00
appendTextFile ( root + " / " + projectConfig . getFilePath ( ProjectFilePath : : data_event_scripts ) , text ) ;
2018-09-27 00:33:08 +01:00
2024-11-12 03:28:53 +00:00
if ( map - > needsLayoutDir ( ) ) {
QString newLayoutDir = QString ( root + " /%1%2 " ) . arg ( projectConfig . getFilePath ( ProjectFilePath : : data_layouts_folders ) , map - > name ( ) ) ;
2019-02-01 17:43:25 +00:00
if ( ! QDir : : root ( ) . mkdir ( newLayoutDir ) ) {
logError ( QString ( " Error: failed to create directory for new layout: '%1' " ) . arg ( newLayoutDir ) ) ;
}
}
}
2018-09-27 00:33:08 +01:00
2019-02-01 17:43:25 +00:00
// Create map.json for map data.
QString mapFilepath = QString ( " %1/map.json " ) . arg ( mapDataDir ) ;
QFile mapFile ( mapFilepath ) ;
if ( ! mapFile . open ( QIODevice : : WriteOnly ) ) {
logError ( QString ( " Error: Could not open %1 for writing " ) . arg ( mapFilepath ) ) ;
return ;
2018-09-27 00:33:08 +01:00
}
2020-03-06 03:46:25 +00:00
OrderedJson : : object mapObj ;
2019-02-01 17:43:25 +00:00
// Header values.
2024-11-12 03:28:53 +00:00
mapObj [ " id " ] = map - > constantName ( ) ;
mapObj [ " name " ] = map - > name ( ) ;
mapObj [ " layout " ] = map - > layout ( ) - > id ;
mapObj [ " music " ] = map - > song ( ) ;
mapObj [ " region_map_section " ] = map - > location ( ) ;
mapObj [ " requires_flash " ] = map - > requiresFlash ( ) ;
mapObj [ " weather " ] = map - > weather ( ) ;
mapObj [ " map_type " ] = map - > type ( ) ;
2024-07-15 21:14:38 +01:00
if ( projectConfig . mapAllowFlagsEnabled ) {
2024-11-12 03:28:53 +00:00
mapObj [ " allow_cycling " ] = map - > allowsBiking ( ) ;
mapObj [ " allow_escaping " ] = map - > allowsEscaping ( ) ;
mapObj [ " allow_running " ] = map - > allowsRunning ( ) ;
2020-10-25 21:46:04 +00:00
}
2024-11-12 19:27:35 +00:00
mapObj [ " show_map_name " ] = map - > showsLocationName ( ) ;
2024-07-15 21:14:38 +01:00
if ( projectConfig . floorNumberEnabled ) {
2024-11-12 03:28:53 +00:00
mapObj [ " floor_number " ] = map - > floorNumber ( ) ;
2020-03-18 07:12:43 +00:00
}
2024-11-12 03:28:53 +00:00
mapObj [ " battle_scene " ] = map - > battleScene ( ) ;
2019-02-01 17:43:25 +00:00
// Connections
2024-08-09 06:29:28 +01:00
auto connections = map - > getConnections ( ) ;
if ( connections . length ( ) > 0 ) {
2020-03-06 03:46:25 +00:00
OrderedJson : : array connectionsArr ;
2024-08-09 06:29:28 +01:00
for ( auto connection : connections ) {
2024-11-12 18:13:09 +00:00
if ( this - > mapNamesToMapConstants . contains ( connection - > targetMapName ( ) ) ) {
2020-03-06 03:46:25 +00:00
OrderedJson : : object connectionObj ;
2024-08-04 21:08:16 +01:00
connectionObj [ " map " ] = this - > mapNamesToMapConstants . value ( connection - > targetMapName ( ) ) ;
connectionObj [ " offset " ] = connection - > offset ( ) ;
connectionObj [ " direction " ] = connection - > direction ( ) ;
2019-02-01 17:43:25 +00:00
connectionsArr . append ( connectionObj ) ;
} else {
2024-08-04 21:08:16 +01:00
logError ( QString ( " Failed to write map connection. '%1' is not a valid map name " ) . arg ( connection - > targetMapName ( ) ) ) ;
2019-02-01 17:43:25 +00:00
}
}
mapObj [ " connections " ] = connectionsArr ;
} else {
mapObj [ " connections " ] = QJsonValue : : Null ;
}
2024-11-12 03:28:53 +00:00
if ( map - > sharedEventsMap ( ) . isEmpty ( ) ) {
2019-02-02 20:56:05 +00:00
// Object events
2020-03-06 03:46:25 +00:00
OrderedJson : : array objectEventsArr ;
2024-11-12 03:28:53 +00:00
for ( const auto & event : map - > getEvents ( Event : : Group : : Object ) ) {
objectEventsArr . push_back ( event - > buildEventJson ( this ) ) ;
2019-02-01 17:43:25 +00:00
}
2019-02-02 20:56:05 +00:00
mapObj [ " object_events " ] = objectEventsArr ;
2022-07-19 22:56:12 +01:00
2019-02-02 20:56:05 +00:00
// Warp events
2020-03-06 03:46:25 +00:00
OrderedJson : : array warpEventsArr ;
2024-11-12 03:28:53 +00:00
for ( const auto & event : map - > getEvents ( Event : : Group : : Warp ) ) {
warpEventsArr . push_back ( event - > buildEventJson ( this ) ) ;
2019-02-02 20:56:05 +00:00
}
mapObj [ " warp_events " ] = warpEventsArr ;
// Coord events
2020-03-06 03:46:25 +00:00
OrderedJson : : array coordEventsArr ;
2024-11-12 03:28:53 +00:00
for ( const auto & event : map - > getEvents ( Event : : Group : : Coord ) ) {
coordEventsArr . push_back ( event - > buildEventJson ( this ) ) ;
2019-02-02 20:56:05 +00:00
}
mapObj [ " coord_events " ] = coordEventsArr ;
// Bg Events
2020-03-06 03:46:25 +00:00
OrderedJson : : array bgEventsArr ;
2024-11-12 03:28:53 +00:00
for ( const auto & event : map - > getEvents ( Event : : Group : : Bg ) ) {
bgEventsArr . push_back ( event - > buildEventJson ( this ) ) ;
2019-02-01 17:43:25 +00:00
}
2019-02-02 20:56:05 +00:00
mapObj [ " bg_events " ] = bgEventsArr ;
} else {
2024-11-12 03:28:53 +00:00
mapObj [ " shared_events_map " ] = map - > sharedEventsMap ( ) ;
2019-02-02 20:56:05 +00:00
}
2024-11-12 03:28:53 +00:00
if ( ! map - > sharedScriptsMap ( ) . isEmpty ( ) ) {
mapObj [ " shared_scripts_map " ] = map - > sharedScriptsMap ( ) ;
2019-02-01 17:43:25 +00:00
}
2019-02-03 16:38:45 +00:00
// Custom header fields.
2024-11-12 03:28:53 +00:00
/* // TODO: Re-enable
2019-02-03 16:38:45 +00:00
for ( QString key : map - > customHeaders . keys ( ) ) {
2022-10-16 07:30:13 +01:00
mapObj [ key ] = OrderedJson : : fromQJsonValue ( map - > customHeaders [ key ] ) ;
2019-02-03 16:38:45 +00:00
}
2024-11-12 03:28:53 +00:00
*/
2019-02-03 16:38:45 +00:00
2020-03-06 03:46:25 +00:00
OrderedJson mapJson ( mapObj ) ;
OrderedJsonDoc jsonDoc ( & mapJson ) ;
jsonDoc . dump ( & mapFile ) ;
2019-04-20 15:06:59 +01:00
mapFile . close ( ) ;
2019-02-01 17:43:25 +00:00
2024-11-12 03:28:53 +00:00
saveLayout ( map - > layout ( ) ) ;
2022-09-10 03:44:42 +01:00
saveHealLocations ( map ) ;
2018-09-27 00:33:08 +01:00
2024-11-12 03:28:53 +00:00
map - > setClean ( ) ;
2018-09-27 00:33:08 +01:00
}
2023-02-14 17:09:22 +00:00
void Project : : saveLayout ( Layout * layout ) {
//
saveLayoutBorder ( layout ) ;
saveLayoutBlockdata ( layout ) ;
// Update global data structures with current map data.
updateLayout ( layout ) ;
layout - > editHistory . setClean ( ) ;
}
void Project : : updateLayout ( Layout * layout ) {
if ( ! mapLayoutsTableMaster . contains ( layout - > id ) ) {
mapLayoutsTableMaster . append ( layout - > id ) ;
2018-09-27 00:33:08 +01:00
}
2023-02-14 17:09:22 +00:00
if ( mapLayoutsMaster . contains ( layout - > id ) ) {
mapLayoutsMaster [ layout - > id ] - > copyFrom ( layout ) ;
}
else {
mapLayoutsMaster . insert ( layout - > id , layout - > copy ( ) ) ;
}
2018-09-27 00:33:08 +01:00
}
void Project : : saveAllDataStructures ( ) {
2019-02-01 17:43:25 +00:00
saveMapLayouts ( ) ;
saveMapGroups ( ) ;
2024-11-05 02:49:49 +00:00
saveRegionMapSections ( ) ;
2019-06-15 23:49:30 +01:00
saveWildMonData ( ) ;
2024-07-15 21:14:38 +01:00
saveConfig ( ) ;
2024-10-30 01:51:05 +00:00
this - > hasUnsavedDataChanges = false ;
2024-07-15 21:14:38 +01:00
}
void Project : : saveConfig ( ) {
projectConfig . save ( ) ;
userConfig . save ( ) ;
2018-09-27 00:33:08 +01:00
}
void Project : : loadTilesetAssets ( Tileset * tileset ) {
if ( tileset - > name . isNull ( ) ) {
return ;
}
2022-09-28 01:17:55 +01:00
this - > readTilesetPaths ( tileset ) ;
2020-02-12 16:22:40 +00:00
QImage image ;
if ( QFile : : exists ( tileset - > tilesImagePath ) ) {
2022-11-04 17:06:50 +00:00
image = QImage ( tileset - > tilesImagePath ) . convertToFormat ( QImage : : Format_Indexed8 , Qt : : ThresholdDither ) ;
2023-06-16 12:39:32 +01:00
flattenTo4bppImage ( & image ) ;
2020-02-12 16:22:40 +00:00
} else {
image = QImage ( 8 , 8 , QImage : : Format_Indexed8 ) ;
}
2018-10-03 01:01:24 +01:00
this - > loadTilesetTiles ( tileset , image ) ;
this - > loadTilesetMetatiles ( tileset ) ;
2019-04-04 06:44:31 +01:00
this - > loadTilesetMetatileLabels ( tileset ) ;
2022-09-28 01:17:55 +01:00
this - > loadTilesetPalettes ( tileset ) ;
}
void Project : : readTilesetPaths ( Tileset * tileset ) {
// Parse the tileset data files to try and get explicit file paths for this tileset's assets
2022-10-07 19:29:51 +01:00
const QString rootDir = this - > root + " / " ;
2022-09-28 01:17:55 +01:00
if ( this - > usingAsmTilesets ) {
// Read asm tileset data files. Backwards compatibility
const QList < QStringList > graphics = parser . parseAsm ( projectConfig . getFilePath ( ProjectFilePath : : tilesets_graphics_asm ) ) ;
const QList < QStringList > metatiles_macros = parser . parseAsm ( projectConfig . getFilePath ( ProjectFilePath : : tilesets_metatiles_asm ) ) ;
const QStringList tiles_values = parser . getLabelValues ( graphics , tileset - > tiles_label ) ;
const QStringList palettes_values = parser . getLabelValues ( graphics , tileset - > palettes_label ) ;
const QStringList metatiles_values = parser . getLabelValues ( metatiles_macros , tileset - > metatiles_label ) ;
const QStringList metatile_attrs_values = parser . getLabelValues ( metatiles_macros , tileset - > metatile_attrs_label ) ;
if ( ! tiles_values . isEmpty ( ) )
2022-10-07 19:29:51 +01:00
tileset - > tilesImagePath = this - > fixGraphicPath ( rootDir + tiles_values . value ( 0 ) . section ( ' " ' , 1 , 1 ) ) ;
2022-09-28 01:17:55 +01:00
if ( ! metatiles_values . isEmpty ( ) )
2022-10-07 19:29:51 +01:00
tileset - > metatiles_path = rootDir + metatiles_values . value ( 0 ) . section ( ' " ' , 1 , 1 ) ;
2022-09-28 01:17:55 +01:00
if ( ! metatile_attrs_values . isEmpty ( ) )
2022-10-07 19:29:51 +01:00
tileset - > metatile_attrs_path = rootDir + metatile_attrs_values . value ( 0 ) . section ( ' " ' , 1 , 1 ) ;
2022-10-07 16:47:05 +01:00
for ( const auto & value : palettes_values )
2022-10-07 19:29:51 +01:00
tileset - > palettePaths . append ( this - > fixPalettePath ( rootDir + value . section ( ' " ' , 1 , 1 ) ) ) ;
2022-09-28 01:17:55 +01:00
} else {
// Read C tileset data files
2022-10-07 16:47:05 +01:00
const QString graphicsFile = projectConfig . getFilePath ( ProjectFilePath : : tilesets_graphics ) ;
const QString metatilesFile = projectConfig . getFilePath ( ProjectFilePath : : tilesets_metatiles ) ;
const QString tilesImagePath = parser . readCIncbin ( graphicsFile , tileset - > tiles_label ) ;
const QStringList palettePaths = parser . readCIncbinArray ( graphicsFile , tileset - > palettes_label ) ;
const QString metatilesPath = parser . readCIncbin ( metatilesFile , tileset - > metatiles_label ) ;
const QString metatileAttrsPath = parser . readCIncbin ( metatilesFile , tileset - > metatile_attrs_label ) ;
if ( ! tilesImagePath . isEmpty ( ) )
2022-10-07 19:29:51 +01:00
tileset - > tilesImagePath = this - > fixGraphicPath ( rootDir + tilesImagePath ) ;
2022-10-07 16:47:05 +01:00
if ( ! metatilesPath . isEmpty ( ) )
2022-10-07 19:29:51 +01:00
tileset - > metatiles_path = rootDir + metatilesPath ;
2022-10-07 16:47:05 +01:00
if ( ! metatileAttrsPath . isEmpty ( ) )
2022-10-07 19:29:51 +01:00
tileset - > metatile_attrs_path = rootDir + metatileAttrsPath ;
2022-10-07 16:47:05 +01:00
for ( const auto & path : palettePaths )
2022-10-07 19:29:51 +01:00
tileset - > palettePaths . append ( this - > fixPalettePath ( rootDir + path ) ) ;
2022-09-28 01:17:55 +01:00
}
2022-10-07 19:29:51 +01:00
// Try to set default paths, if any weren't found by reading the files above
QString defaultPath = rootDir + tileset - > getExpectedDir ( ) ;
2022-09-28 01:17:55 +01:00
if ( tileset - > tilesImagePath . isEmpty ( ) )
tileset - > tilesImagePath = defaultPath + " /tiles.png " ;
if ( tileset - > metatiles_path . isEmpty ( ) )
tileset - > metatiles_path = defaultPath + " /metatiles.bin " ;
if ( tileset - > metatile_attrs_path . isEmpty ( ) )
tileset - > metatile_attrs_path = defaultPath + " /metatile_attributes.bin " ;
if ( tileset - > palettePaths . isEmpty ( ) ) {
QString palettes_dir_path = defaultPath + " /palettes/ " ;
for ( int i = 0 ; i < 16 ; i + + ) {
tileset - > palettePaths . append ( palettes_dir_path + QString ( " %1 " ) . arg ( i , 2 , 10 , QLatin1Char ( ' 0 ' ) ) + " .pal " ) ;
}
}
}
2018-10-03 01:01:24 +01:00
2022-09-28 01:17:55 +01:00
void Project : : loadTilesetPalettes ( Tileset * tileset ) {
2021-02-17 02:45:54 +00:00
QList < QList < QRgb > > palettes ;
QList < QList < QRgb > > palettePreviews ;
2018-10-03 01:01:41 +01:00
for ( int i = 0 ; i < tileset - > palettePaths . length ( ) ; i + + ) {
QString path = tileset - > palettePaths . value ( i ) ;
2023-01-17 00:44:09 +00:00
bool error = false ;
QList < QRgb > palette = PaletteUtil : : parse ( path , & error ) ;
if ( error ) {
2018-10-03 01:01:24 +01:00
for ( int j = 0 ; j < 16 ; j + + ) {
palette . append ( qRgb ( j * 16 , j * 16 , j * 16 ) ) ;
}
}
2021-02-17 02:45:54 +00:00
palettes . append ( palette ) ;
palettePreviews . append ( palette ) ;
2018-10-03 01:01:24 +01:00
}
tileset - > palettes = palettes ;
2020-05-03 16:31:44 +01:00
tileset - > palettePreviews = palettePreviews ;
2018-10-03 01:01:24 +01:00
}
2018-09-27 00:33:08 +01:00
2018-10-03 01:01:24 +01:00
void Project : : loadTilesetTiles ( Tileset * tileset , QImage image ) {
2021-02-17 02:45:54 +00:00
QList < QImage > tiles ;
2018-09-27 00:33:08 +01:00
int w = 8 ;
int h = 8 ;
2018-10-03 01:01:24 +01:00
for ( int y = 0 ; y < image . height ( ) ; y + = h )
for ( int x = 0 ; x < image . width ( ) ; x + = w ) {
QImage tile = image . copy ( x , y , w , h ) ;
2021-02-17 02:45:54 +00:00
tiles . append ( tile ) ;
2018-09-27 00:33:08 +01:00
}
2018-10-03 01:01:24 +01:00
tileset - > tilesImage = image ;
2018-09-27 00:33:08 +01:00
tileset - > tiles = tiles ;
2018-10-03 01:01:24 +01:00
}
2018-09-27 00:33:08 +01:00
2018-10-03 01:01:24 +01:00
void Project : : loadTilesetMetatiles ( Tileset * tileset ) {
2018-10-03 01:01:09 +01:00
QFile metatiles_file ( tileset - > metatiles_path ) ;
2018-09-27 00:33:08 +01:00
if ( metatiles_file . open ( QIODevice : : ReadOnly ) ) {
QByteArray data = metatiles_file . readAll ( ) ;
2022-10-04 02:28:16 +01:00
int tilesPerMetatile = projectConfig . getNumTilesInMetatile ( ) ;
2022-08-20 17:14:11 +01:00
int bytesPerMetatile = 2 * tilesPerMetatile ;
int num_metatiles = data . length ( ) / bytesPerMetatile ;
2021-02-17 02:45:54 +00:00
QList < Metatile * > metatiles ;
2018-09-27 00:33:08 +01:00
for ( int i = 0 ; i < num_metatiles ; i + + ) {
Metatile * metatile = new Metatile ;
2022-08-20 17:14:11 +01:00
int index = i * bytesPerMetatile ;
for ( int j = 0 ; j < tilesPerMetatile ; j + + ) {
2022-02-04 23:59:57 +00:00
uint16_t tileRaw = static_cast < unsigned char > ( data [ index + + ] ) ;
tileRaw | = static_cast < unsigned char > ( data [ index + + ] ) < < 8 ;
metatile - > tiles . append ( Tile ( tileRaw ) ) ;
2018-09-27 00:33:08 +01:00
}
2021-02-17 02:45:54 +00:00
metatiles . append ( metatile ) ;
2018-09-27 00:33:08 +01:00
}
2024-10-16 16:04:25 +01:00
tileset - > setMetatiles ( metatiles ) ;
2018-09-27 00:33:08 +01:00
} else {
2024-10-16 16:04:25 +01:00
tileset - > clearMetatiles ( ) ;
2018-12-20 23:30:35 +00:00
logError ( QString ( " Could not open tileset metatiles file '%1' " ) . arg ( tileset - > metatiles_path ) ) ;
2018-09-27 00:33:08 +01:00
}
2018-10-03 01:01:09 +01:00
QFile attrs_file ( tileset - > metatile_attrs_path ) ;
2018-09-27 00:33:08 +01:00
if ( attrs_file . open ( QIODevice : : ReadOnly ) ) {
QByteArray data = attrs_file . readAll ( ) ;
2024-10-16 16:04:25 +01:00
int num_metatiles = tileset - > numMetatiles ( ) ;
2024-07-15 21:14:38 +01:00
int attrSize = projectConfig . metatileAttributesSize ;
2022-02-03 23:10:50 +00:00
int num_metatileAttrs = data . length ( ) / attrSize ;
if ( num_metatiles ! = num_metatileAttrs ) {
logWarn ( QString ( " Metatile count %1 does not match metatile attribute count %2 in %3 " ) . arg ( num_metatiles ) . arg ( num_metatileAttrs ) . arg ( tileset - > name ) ) ;
if ( num_metatileAttrs > num_metatiles )
num_metatileAttrs = num_metatiles ;
}
for ( int i = 0 ; i < num_metatileAttrs ; i + + ) {
uint32_t attributes = 0 ;
for ( int j = 0 ; j < attrSize ; j + + )
attributes | = static_cast < unsigned char > ( data . at ( i * attrSize + j ) ) < < ( 8 * j ) ;
2024-10-16 16:04:25 +01:00
tileset - > metatileAt ( i ) - > setAttributes ( attributes ) ;
2018-10-03 01:01:09 +01:00
}
2018-09-27 00:33:08 +01:00
} else {
2018-12-20 23:30:35 +00:00
logError ( QString ( " Could not open tileset metatile attributes file '%1' " ) . arg ( tileset - > metatile_attrs_path ) ) ;
2018-09-27 00:33:08 +01:00
}
}
2023-05-19 02:29:27 +01:00
QString Project : : findMetatileLabelsTileset ( QString label ) {
for ( QString tilesetName : this - > tilesetLabelsOrdered ) {
QString metatileLabelPrefix = Tileset : : getMetatileLabelPrefix ( tilesetName ) ;
if ( label . startsWith ( metatileLabelPrefix ) )
return tilesetName ;
}
return QString ( ) ;
}
2023-01-07 04:15:41 +00:00
bool Project : : readTilesetMetatileLabels ( ) {
metatileLabelsMap . clear ( ) ;
2023-05-19 02:29:27 +01:00
unusedMetatileLabels . clear ( ) ;
2023-01-07 04:15:41 +00:00
2022-09-01 17:14:47 +01:00
QString metatileLabelsFilename = projectConfig . getFilePath ( ProjectFilePath : : constants_metatile_labels ) ;
2020-05-22 20:52:34 +01:00
fileWatcher . addPath ( root + " / " + metatileLabelsFilename ) ;
2019-09-09 23:24:30 +01:00
2024-08-28 21:06:28 +01:00
const QStringList regexList = { QString ( " \\ b%1 " ) . arg ( projectConfig . getIdentifier ( ProjectIdentifier : : define_metatile_label_prefix ) ) } ;
QMap < QString , int > defines = parser . readCDefinesByRegex ( metatileLabelsFilename , regexList ) ;
2023-01-07 04:15:41 +00:00
2023-05-19 02:29:27 +01:00
for ( QString label : defines . keys ( ) ) {
2023-12-19 17:29:16 +00:00
uint32_t metatileId = static_cast < uint32_t > ( defines [ label ] ) ;
if ( metatileId > Block : : maxValue ) {
metatileId & = Block : : maxValue ;
logWarn ( QString ( " Value of metatile label '%1' truncated to %2 " ) . arg ( label ) . arg ( Metatile : : getMetatileIdString ( metatileId ) ) ) ;
}
2023-05-19 02:29:27 +01:00
QString tilesetName = findMetatileLabelsTileset ( label ) ;
if ( ! tilesetName . isEmpty ( ) ) {
2023-12-19 17:29:16 +00:00
metatileLabelsMap [ tilesetName ] [ label ] = metatileId ;
2023-05-19 02:29:27 +01:00
} else {
// This #define name does not match any existing tileset.
// Save it separately to be outputted later.
2023-12-19 17:29:16 +00:00
unusedMetatileLabels [ label ] = metatileId ;
2023-01-07 04:15:41 +00:00
}
}
return true ;
}
void Project : : loadTilesetMetatileLabels ( Tileset * tileset ) {
2023-02-14 20:28:18 +00:00
QString metatileLabelPrefix = tileset - > getMetatileLabelPrefix ( ) ;
2023-01-07 04:15:41 +00:00
2023-02-14 19:10:05 +00:00
// Reverse map for faster lookup by metatile id
2023-01-07 04:15:41 +00:00
for ( QString labelName : metatileLabelsMap [ tileset - > name ] . keys ( ) ) {
2023-12-19 17:29:16 +00:00
auto metatileId = metatileLabelsMap [ tileset - > name ] [ labelName ] ;
2023-02-14 20:28:18 +00:00
tileset - > metatileLabels [ metatileId ] = labelName . replace ( metatileLabelPrefix , " " ) ;
2019-04-04 06:44:31 +01:00
}
}
2021-02-14 21:34:17 +00:00
Blockdata Project : : readBlockdata ( QString path ) {
Blockdata blockdata ;
2018-09-27 00:33:08 +01:00
QFile file ( path ) ;
if ( file . open ( QIODevice : : ReadOnly ) ) {
QByteArray data = file . readAll ( ) ;
for ( int i = 0 ; ( i + 1 ) < data . length ( ) ; i + = 2 ) {
uint16_t word = static_cast < uint16_t > ( ( data [ i ] & 0xff ) + ( ( data [ i + 1 ] & 0xff ) < < 8 ) ) ;
2021-02-14 21:34:17 +00:00
blockdata . append ( word ) ;
2018-09-27 00:33:08 +01:00
}
} else {
2018-12-20 23:30:35 +00:00
logError ( QString ( " Failed to open blockdata path '%1' " ) . arg ( path ) ) ;
2018-09-27 00:33:08 +01:00
}
return blockdata ;
}
Map * Project : : getMap ( QString map_name ) {
2021-02-15 16:33:30 +00:00
if ( mapCache . contains ( map_name ) ) {
return mapCache . value ( map_name ) ;
2018-09-27 00:33:08 +01:00
} else {
Map * map = loadMap ( map_name ) ;
return map ;
}
}
2018-10-03 01:01:15 +01:00
Tileset * Project : : getTileset ( QString label , bool forceLoad ) {
Tileset * existingTileset = nullptr ;
2021-02-15 16:33:30 +00:00
if ( tilesetCache . contains ( label ) ) {
existingTileset = tilesetCache . value ( label ) ;
2018-10-03 01:01:15 +01:00
}
if ( existingTileset & & ! forceLoad ) {
2021-02-17 02:45:54 +00:00
return existingTileset ;
2018-09-27 00:33:08 +01:00
} else {
2018-10-03 01:01:15 +01:00
Tileset * tileset = loadTileset ( label , existingTileset ) ;
2018-09-27 00:33:08 +01:00
return tileset ;
}
}
void Project : : saveTextFile ( QString path , QString text ) {
QFile file ( path ) ;
if ( file . open ( QIODevice : : WriteOnly ) ) {
file . write ( text . toUtf8 ( ) ) ;
} else {
2018-12-20 23:30:35 +00:00
logError ( QString ( " Could not open '%1' for writing: " ) . arg ( path ) + file . errorString ( ) ) ;
2018-09-27 00:33:08 +01:00
}
}
void Project : : appendTextFile ( QString path , QString text ) {
QFile file ( path ) ;
if ( file . open ( QIODevice : : Append ) ) {
file . write ( text . toUtf8 ( ) ) ;
} else {
2018-12-20 23:30:35 +00:00
logError ( QString ( " Could not open '%1' for appending: " ) . arg ( path ) + file . errorString ( ) ) ;
2018-09-27 00:33:08 +01:00
}
}
void Project : : deleteFile ( QString path ) {
QFile file ( path ) ;
if ( file . exists ( ) & & ! file . remove ( ) ) {
2018-12-20 23:30:35 +00:00
logError ( QString ( " Could not delete file '%1': " ) . arg ( path ) + file . errorString ( ) ) ;
2018-09-27 00:33:08 +01:00
}
}
2020-02-12 15:13:58 +00:00
bool Project : : readWildMonData ( ) {
2024-08-27 21:14:02 +01:00
this - > extraEncounterGroups . clear ( ) ;
this - > wildMonFields . clear ( ) ;
this - > wildMonData . clear ( ) ;
this - > encounterGroupLabels . clear ( ) ;
this - > pokemonMinLevel = 0 ;
this - > pokemonMaxLevel = 100 ;
this - > maxEncounterRate = 2880 / 16 ;
2024-01-18 17:00:18 +00:00
this - > wildEncountersLoaded = false ;
2024-07-15 21:14:38 +01:00
if ( ! userConfig . useEncounterJson ) {
2020-02-12 15:13:58 +00:00
return true ;
}
2019-07-03 21:21:48 +01:00
2024-08-27 21:14:02 +01:00
// Read max encounter rate. The games multiply the encounter rate value in the map data by 16, so our input limit is the max/16.
const QString encounterRateFile = projectConfig . getFilePath ( ProjectFilePath : : wild_encounter ) ;
const QString maxEncounterRateName = projectConfig . getIdentifier ( ProjectIdentifier : : define_max_encounter_rate ) ;
fileWatcher . addPath ( QString ( " %1/%2 " ) . arg ( root ) . arg ( encounterRateFile ) ) ;
auto defines = parser . readCDefinesByName ( encounterRateFile , { maxEncounterRateName } ) ;
if ( defines . contains ( maxEncounterRateName ) )
this - > maxEncounterRate = defines . value ( maxEncounterRateName ) / 16 ;
// Read min/max level
const QString levelRangeFile = projectConfig . getFilePath ( ProjectFilePath : : constants_pokemon ) ;
const QString minLevelName = projectConfig . getIdentifier ( ProjectIdentifier : : define_min_level ) ;
const QString maxLevelName = projectConfig . getIdentifier ( ProjectIdentifier : : define_max_level ) ;
fileWatcher . addPath ( QString ( " %1/%2 " ) . arg ( root ) . arg ( levelRangeFile ) ) ;
defines = parser . readCDefinesByName ( levelRangeFile , { minLevelName , maxLevelName } ) ;
if ( defines . contains ( minLevelName ) )
this - > pokemonMinLevel = defines . value ( minLevelName ) ;
if ( defines . contains ( maxLevelName ) )
this - > pokemonMaxLevel = defines . value ( maxLevelName ) ;
this - > pokemonMinLevel = qMin ( this - > pokemonMinLevel , this - > pokemonMaxLevel ) ;
this - > pokemonMaxLevel = qMax ( this - > pokemonMinLevel , this - > pokemonMaxLevel ) ;
// Read encounter data
2022-09-01 17:14:47 +01:00
QString wildMonJsonFilepath = QString ( " %1/%2 " ) . arg ( root ) . arg ( projectConfig . getFilePath ( ProjectFilePath : : json_wild_encounters ) ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( wildMonJsonFilepath ) ;
2021-08-13 00:19:21 +01:00
OrderedJson : : object wildMonObj ;
if ( ! parser . tryParseOrderedJsonFile ( & wildMonObj , wildMonJsonFilepath ) ) {
2024-01-18 17:00:18 +00:00
// Failing to read wild encounters data is not a critical error, the encounter editor will just be disabled
2022-03-23 03:49:49 +00:00
logWarn ( QString ( " Failed to read wild encounters from %1 " ) . arg ( wildMonJsonFilepath ) ) ;
return true ;
2019-06-13 03:20:28 +01:00
}
2023-12-29 05:15:51 +00:00
// For each encounter type, count the number of times each encounter rate value occurs.
// The most common value will be used as the default for new groups.
QMap < QString , QMap < int , int > > encounterRateFrequencyMaps ;
2021-08-13 00:19:21 +01:00
for ( OrderedJson subObjectRef : wildMonObj [ " wild_encounter_groups " ] . array_items ( ) ) {
OrderedJson : : object subObject = subObjectRef . object_items ( ) ;
if ( ! subObject [ " for_maps " ] . bool_value ( ) ) {
2022-02-06 22:18:28 +00:00
extraEncounterGroups . push_back ( subObject ) ;
2019-06-15 23:49:30 +01:00
continue ;
}
2019-06-13 03:20:28 +01:00
2021-08-13 00:19:21 +01:00
for ( OrderedJson field : subObject [ " fields " ] . array_items ( ) ) {
2019-09-30 00:07:34 +01:00
EncounterField encounterField ;
2021-08-13 00:19:21 +01:00
OrderedJson : : object fieldObj = field . object_items ( ) ;
encounterField . name = fieldObj [ " type " ] . string_value ( ) ;
for ( auto val : fieldObj [ " encounter_rates " ] . array_items ( ) ) {
encounterField . encounterRates . append ( val . int_value ( ) ) ;
2019-09-30 00:07:34 +01:00
}
2021-08-13 00:19:21 +01:00
QList < QString > subGroups ;
for ( auto groupPair : fieldObj [ " groups " ] . object_items ( ) ) {
subGroups . append ( groupPair . first ) ;
}
for ( QString group : subGroups ) {
OrderedJson : : object groupsObj = fieldObj [ " groups " ] . object_items ( ) ;
for ( auto slotNum : groupsObj [ group ] . array_items ( ) ) {
encounterField . groups [ group ] . append ( slotNum . int_value ( ) ) ;
2019-09-30 00:07:34 +01:00
}
}
2023-12-29 05:15:51 +00:00
encounterRateFrequencyMaps . insert ( encounterField . name , QMap < int , int > ( ) ) ;
2019-06-15 23:49:30 +01:00
wildMonFields . append ( encounterField ) ;
2019-06-13 17:14:49 +01:00
}
2021-08-13 00:19:21 +01:00
auto encounters = subObject [ " encounters " ] . array_items ( ) ;
for ( auto encounter : encounters ) {
OrderedJson : : object encounterObj = encounter . object_items ( ) ;
QString mapConstant = encounterObj [ " map " ] . string_value ( ) ;
2019-06-15 23:49:30 +01:00
2019-06-13 03:20:28 +01:00
WildPokemonHeader header ;
2019-09-30 00:07:34 +01:00
for ( EncounterField monField : wildMonFields ) {
QString field = monField . name ;
2021-08-13 00:19:21 +01:00
if ( ! encounterObj [ field ] . is_null ( ) ) {
OrderedJson : : object encounterFieldObj = encounterObj [ field ] . object_items ( ) ;
2019-06-13 17:14:49 +01:00
header . wildMons [ field ] . active = true ;
2021-08-13 00:19:21 +01:00
header . wildMons [ field ] . encounterRate = encounterFieldObj [ " encounter_rate " ] . int_value ( ) ;
2023-12-29 05:15:51 +00:00
encounterRateFrequencyMaps [ field ] [ header . wildMons [ field ] . encounterRate ] + + ;
2021-08-13 00:19:21 +01:00
for ( auto mon : encounterFieldObj [ " mons " ] . array_items ( ) ) {
2019-09-22 01:48:53 +01:00
WildPokemon newMon ;
2021-08-13 00:19:21 +01:00
OrderedJson : : object monObj = mon . object_items ( ) ;
newMon . minLevel = monObj [ " min_level " ] . int_value ( ) ;
newMon . maxLevel = monObj [ " max_level " ] . int_value ( ) ;
newMon . species = monObj [ " species " ] . string_value ( ) ;
2019-09-22 01:48:53 +01:00
header . wildMons [ field ] . wildPokemon . append ( newMon ) ;
2019-06-13 17:14:49 +01:00
}
2023-11-07 18:03:32 +00:00
// If the user supplied too few pokémon for this group then we fill in the rest.
for ( int i = header . wildMons [ field ] . wildPokemon . length ( ) ; i < monField . encounterRates . length ( ) ; i + + ) {
WildPokemon newMon ; // Keep default values
header . wildMons [ field ] . wildPokemon . append ( newMon ) ;
}
2019-06-13 17:14:49 +01:00
}
}
2021-08-13 00:19:21 +01:00
wildMonData [ mapConstant ] . insert ( { encounterObj [ " base_label " ] . string_value ( ) , header } ) ;
encounterGroupLabels . append ( encounterObj [ " base_label " ] . string_value ( ) ) ;
2019-06-13 03:20:28 +01:00
}
}
2021-08-13 00:19:21 +01:00
2023-12-29 05:15:51 +00:00
// For each encounter type, set default encounter rate to most common value.
// Iterate over map of encounter type names to frequency maps...
for ( auto i = encounterRateFrequencyMaps . cbegin ( ) , i_end = encounterRateFrequencyMaps . cend ( ) ; i ! = i_end ; i + + ) {
int frequency = 0 ;
int rate = 1 ;
const QMap < int , int > frequencyMap = i . value ( ) ;
// Iterate over frequency map (encounter rate to number of occurrences)...
for ( auto j = frequencyMap . cbegin ( ) , j_end = frequencyMap . cend ( ) ; j ! = j_end ; j + + ) {
if ( j . value ( ) > frequency ) {
frequency = j . value ( ) ;
rate = j . key ( ) ;
}
}
setDefaultEncounterRate ( i . key ( ) , rate ) ;
}
2024-01-18 17:00:18 +00:00
this - > wildEncountersLoaded = true ;
2020-02-12 15:13:58 +00:00
return true ;
2019-06-13 03:20:28 +01:00
}
2020-02-12 16:22:40 +00:00
bool Project : : readMapGroups ( ) {
2024-01-19 16:59:04 +00:00
this - > mapConstantsToMapNames . clear ( ) ;
this - > mapNamesToMapConstants . clear ( ) ;
this - > mapGroups . clear ( ) ;
this - > groupNames . clear ( ) ;
this - > groupedMapNames . clear ( ) ;
this - > mapNames . clear ( ) ;
const QString filepath = root + " / " + projectConfig . getFilePath ( ProjectFilePath : : json_map_groups ) ;
fileWatcher . addPath ( filepath ) ;
2019-04-20 15:06:59 +01:00
QJsonDocument mapGroupsDoc ;
2024-01-19 16:59:04 +00:00
if ( ! parser . tryParseJsonFile ( & mapGroupsDoc , filepath ) ) {
logError ( QString ( " Failed to read map groups from %1 " ) . arg ( filepath ) ) ;
2020-02-12 16:22:40 +00:00
return false ;
2018-09-27 00:33:08 +01:00
}
2019-02-01 17:43:25 +00:00
QJsonObject mapGroupsObj = mapGroupsDoc . object ( ) ;
QJsonArray mapGroupOrder = mapGroupsObj [ " group_order " ] . toArray ( ) ;
for ( int groupIndex = 0 ; groupIndex < mapGroupOrder . size ( ) ; groupIndex + + ) {
2024-11-12 18:13:09 +00:00
const QString groupName = ParseUtil : : jsonToQString ( mapGroupOrder . at ( groupIndex ) ) ;
const QJsonArray mapNamesJson = mapGroupsObj . value ( groupName ) . toArray ( ) ;
2024-01-19 16:59:04 +00:00
this - > groupedMapNames . append ( QStringList ( ) ) ;
this - > groupNames . append ( groupName ) ;
for ( int j = 0 ; j < mapNamesJson . size ( ) ; j + + ) {
2024-11-12 18:13:09 +00:00
const QString mapName = ParseUtil : : jsonToQString ( mapNamesJson . at ( j ) ) ;
2024-01-19 16:59:04 +00:00
if ( mapName = = DYNAMIC_MAP_NAME ) {
logWarn ( QString ( " Ignoring map with reserved name '%1'. " ) . arg ( mapName ) ) ;
continue ;
}
2024-11-12 18:13:09 +00:00
if ( this - > mapNames . contains ( mapName ) ) {
logWarn ( QString ( " Ignoring repeated map name '%1'. " ) . arg ( mapName ) ) ;
continue ;
}
2018-09-27 00:33:08 +01:00
2024-11-12 18:13:09 +00:00
// Load the map's json file so we can get its ID constant (and two other constants we use for the map list).
QJsonDocument mapDoc ;
if ( ! readMapJson ( mapName , & mapDoc ) )
continue ; // Error message has already been logged
// Read and validate the map's ID from its JSON data.
const QJsonObject mapObj = mapDoc . object ( ) ;
const QString mapConstant = ParseUtil : : jsonToQString ( mapObj [ " id " ] ) ;
if ( mapConstant . isEmpty ( ) ) {
logWarn ( QString ( " Map '%1' is missing an \" id \" value and will be ignored. " ) . arg ( mapName ) ) ;
continue ;
}
const QString expectedPrefix = projectConfig . getIdentifier ( ProjectIdentifier : : define_map_prefix ) ;
if ( ! mapConstant . startsWith ( expectedPrefix ) ) {
logWarn ( QString ( " Map '%1' has invalid \" id \" value '%2' and will be ignored. Value must begin with '%3'. " ) . arg ( mapName ) . arg ( mapConstant ) . arg ( expectedPrefix ) ) ;
continue ;
}
auto it = this - > mapConstantsToMapNames . constFind ( mapConstant ) ;
if ( it ! = this - > mapConstantsToMapNames . constEnd ( ) ) {
logWarn ( QString ( " Map '%1' has the same \" id \" value '%2' as map '%3' and will be ignored. " ) . arg ( mapName ) . arg ( it . key ( ) ) . arg ( it . value ( ) ) ) ;
continue ;
}
// Success, save the constants to the project
this - > mapNames . append ( mapName ) ;
this - > groupedMapNames [ groupIndex ] . append ( mapName ) ;
this - > mapGroups . insert ( mapName , groupIndex ) ;
2024-01-19 16:59:04 +00:00
this - > mapConstantsToMapNames . insert ( mapConstant , mapName ) ;
this - > mapNamesToMapConstants . insert ( mapName , mapConstant ) ;
2024-11-12 18:44:04 +00:00
// TODO: Keep these updated
2024-11-12 18:13:09 +00:00
this - > mapNameToLayoutId . insert ( mapName , ParseUtil : : jsonToQString ( mapObj [ " layout " ] ) ) ;
this - > mapNameToMapSectionName . insert ( mapName , ParseUtil : : jsonToQString ( mapObj [ " region_map_section " ] ) ) ;
2018-09-27 00:33:08 +01:00
}
}
2024-01-19 16:59:04 +00:00
if ( this - > groupNames . isEmpty ( ) ) {
logError ( QString ( " Failed to find any map groups in %1 " ) . arg ( filepath ) ) ;
return false ;
}
if ( this - > mapNames . isEmpty ( ) ) {
logError ( QString ( " Failed to find any map names in %1 " ) . arg ( filepath ) ) ;
return false ;
}
2023-12-18 07:19:12 +00:00
const QString defineName = this - > getDynamicMapDefineName ( ) ;
2024-01-19 16:59:04 +00:00
this - > mapConstantsToMapNames . insert ( defineName , DYNAMIC_MAP_NAME ) ;
this - > mapNamesToMapConstants . insert ( DYNAMIC_MAP_NAME , defineName ) ;
this - > mapNames . append ( DYNAMIC_MAP_NAME ) ;
2018-09-27 00:33:08 +01:00
2020-02-12 16:22:40 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2024-11-12 18:44:04 +00:00
Map * Project : : addNewMapToGroup ( Map * newMap , int groupNum , bool existingLayout , bool importedMap ) {
2024-07-24 18:52:46 +01:00
int mapNamePos = 0 ;
for ( int i = 0 ; i < = groupNum ; i + + )
mapNamePos + = this - > groupedMapNames . value ( i ) . length ( ) ;
2024-11-12 18:44:04 +00:00
this - > mapNames . insert ( mapNamePos , newMap - > name ( ) ) ;
this - > mapGroups . insert ( newMap - > name ( ) , groupNum ) ;
this - > groupedMapNames [ groupNum ] . append ( newMap - > name ( ) ) ;
this - > mapConstantsToMapNames . insert ( newMap - > constantName ( ) , newMap - > name ( ) ) ;
this - > mapNamesToMapConstants . insert ( newMap - > name ( ) , newMap - > constantName ( ) ) ;
2019-01-07 23:14:44 +00:00
2024-11-12 03:28:53 +00:00
newMap - > setIsPersistedToFile ( false ) ;
2019-01-07 23:14:44 +00:00
2019-02-01 17:43:25 +00:00
if ( ! existingLayout ) {
2024-11-12 03:28:53 +00:00
this - > mapLayouts . insert ( newMap - > layoutId ( ) , newMap - > layout ( ) ) ;
this - > mapLayoutsTable . append ( newMap - > layoutId ( ) ) ;
this - > layoutIdsToNames . insert ( newMap - > layout ( ) - > id , newMap - > layout ( ) - > name ) ;
2021-07-24 00:20:41 +01:00
if ( ! importedMap ) {
2024-11-12 03:28:53 +00:00
setNewLayoutBlockdata ( newMap - > layout ( ) ) ;
2020-09-19 20:05:27 +01:00
}
2024-11-12 03:28:53 +00:00
if ( newMap - > layout ( ) - > border . isEmpty ( ) ) {
setNewLayoutBorder ( newMap - > layout ( ) ) ;
2020-09-21 22:26:58 +01:00
}
2019-01-07 23:14:44 +00:00
}
2024-11-12 03:28:53 +00:00
loadLayoutTilesets ( newMap - > layout ( ) ) ;
2019-01-07 23:14:44 +00:00
2021-02-18 22:41:36 +00:00
return newMap ;
2019-01-07 23:14:44 +00:00
}
2018-09-27 00:33:08 +01:00
QString Project : : getNewMapName ( ) {
// Ensure default name doesn't already exist.
int i = 0 ;
QString newMapName ;
do {
newMapName = QString ( " NewMap%1 " ) . arg ( + + i ) ;
2021-02-15 09:21:41 +00:00
} while ( mapNames . contains ( newMapName ) ) ;
2018-09-27 00:33:08 +01:00
return newMapName ;
}
2019-04-29 01:20:48 +01:00
Project : : DataQualifiers Project : : getDataQualifiers ( QString text , QString label ) {
Project : : DataQualifiers qualifiers ;
QRegularExpression regex ( QString ( " \\ s*(?<static>static \\ s*) ? ( ? < const > const \ \ s * ) ? [ A - Za - z0 - 9 _ \ \ s ] * \ \ b % 1 \ \ b " ).arg(label)) ;
QRegularExpressionMatch match = regex . match ( text ) ;
qualifiers . isStatic = match . captured ( " static " ) . isNull ( ) ? false : true ;
qualifiers . isConst = match . captured ( " const " ) . isNull ( ) ? false : true ;
return qualifiers ;
}
2022-10-24 00:28:55 +01:00
QString Project : : getDefaultPrimaryTilesetLabel ( ) {
2024-07-15 21:14:38 +01:00
QString defaultLabel = projectConfig . defaultPrimaryTileset ;
2022-10-24 00:28:55 +01:00
if ( ! this - > primaryTilesetLabels . contains ( defaultLabel ) ) {
QString firstLabel = this - > primaryTilesetLabels . first ( ) ;
logWarn ( QString ( " Unable to find default primary tileset '%1', using '%2' instead. " ) . arg ( defaultLabel ) . arg ( firstLabel ) ) ;
defaultLabel = firstLabel ;
}
return defaultLabel ;
}
QString Project : : getDefaultSecondaryTilesetLabel ( ) {
2024-07-15 21:14:38 +01:00
QString defaultLabel = projectConfig . defaultSecondaryTileset ;
2022-10-24 00:28:55 +01:00
if ( ! this - > secondaryTilesetLabels . contains ( defaultLabel ) ) {
QString firstLabel = this - > secondaryTilesetLabels . first ( ) ;
logWarn ( QString ( " Unable to find default secondary tileset '%1', using '%2' instead. " ) . arg ( defaultLabel ) . arg ( firstLabel ) ) ;
defaultLabel = firstLabel ;
}
return defaultLabel ;
}
2022-10-24 14:33:51 +01:00
void Project : : appendTilesetLabel ( QString label , QString isSecondaryStr ) {
2022-10-08 23:05:11 +01:00
bool ok ;
bool isSecondary = ParseUtil : : gameStringToBool ( isSecondaryStr , & ok ) ;
if ( ! ok ) {
logError ( QString ( " Unable to convert value '%1' of isSecondary to bool for tileset %2. " ) . arg ( isSecondaryStr ) . arg ( label ) ) ;
return ;
}
2022-10-24 14:33:51 +01:00
QStringList * list = isSecondary ? & this - > secondaryTilesetLabels : & this - > primaryTilesetLabels ;
list - > append ( label ) ;
this - > tilesetLabelsOrdered . append ( label ) ;
2022-10-08 23:05:11 +01:00
}
2022-09-28 01:17:55 +01:00
bool Project : : readTilesetLabels ( ) {
2022-10-23 23:59:59 +01:00
this - > primaryTilesetLabels . clear ( ) ;
this - > secondaryTilesetLabels . clear ( ) ;
2022-09-28 01:17:55 +01:00
this - > tilesetLabelsOrdered . clear ( ) ;
2022-10-07 16:47:05 +01:00
QString filename = projectConfig . getFilePath ( ProjectFilePath : : tilesets_headers ) ;
QFileInfo fileInfo ( this - > root + " / " + filename ) ;
2022-09-28 01:17:55 +01:00
if ( ! fileInfo . exists ( ) | | ! fileInfo . isFile ( ) ) {
// If the tileset headers file is missing, the user may still have the old assembly format.
this - > usingAsmTilesets = true ;
2022-10-07 16:47:05 +01:00
QString asm_filename = projectConfig . getFilePath ( ProjectFilePath : : tilesets_headers_asm ) ;
QString text = parser . readTextFile ( this - > root + " / " + asm_filename ) ;
2022-09-28 01:17:55 +01:00
if ( text . isEmpty ( ) ) {
logError ( QString ( " Failed to read tileset labels from '%1' or '%2'. " ) . arg ( filename ) . arg ( asm_filename ) ) ;
return false ;
}
2022-11-23 03:57:26 +00:00
static const QRegularExpression re ( " (?<label>[A-Za-z0-9_]*) : { 1 , 2 } [ A - Za - z0 - 9 _ @ ] * \ \ s + . + \ \ s + \ \ . byte \ \ s + ( ? < isSecondary > [ A - Za - z0 - 9 _ ] + ) " );
2022-09-28 01:17:55 +01:00
QRegularExpressionMatchIterator iter = re . globalMatch ( text ) ;
while ( iter . hasNext ( ) ) {
QRegularExpressionMatch match = iter . next ( ) ;
2022-10-24 14:33:51 +01:00
appendTilesetLabel ( match . captured ( " label " ) , match . captured ( " isSecondary " ) ) ;
2022-09-28 01:17:55 +01:00
}
2022-10-25 00:55:06 +01:00
this - > primaryTilesetLabels . sort ( ) ;
this - > secondaryTilesetLabels . sort ( ) ;
this - > tilesetLabelsOrdered . sort ( ) ;
2022-09-28 01:17:55 +01:00
filename = asm_filename ; // For error reporting further down
} else {
this - > usingAsmTilesets = false ;
2022-10-08 18:51:48 +01:00
const auto structs = parser . readCStructs ( filename , " " , Tileset : : getHeaderMemberMap ( this - > usingAsmTilesets ) ) ;
2024-09-11 05:38:27 +01:00
const QStringList labels = structs . keys ( ) ;
2022-10-08 23:05:11 +01:00
// TODO: This is alphabetical, AdvanceMap import wants the vanilla order in tilesetLabelsOrdered
2024-09-11 05:38:27 +01:00
for ( const auto & tilesetLabel : labels ) {
2022-10-24 14:33:51 +01:00
appendTilesetLabel ( tilesetLabel , structs [ tilesetLabel ] . value ( " isSecondary " ) ) ;
2018-09-27 00:33:08 +01:00
}
2022-09-28 01:17:55 +01:00
}
2018-09-27 00:33:08 +01:00
2022-09-28 01:17:55 +01:00
bool success = true ;
2022-10-23 23:59:59 +01:00
if ( this - > secondaryTilesetLabels . isEmpty ( ) ) {
2022-09-28 01:17:55 +01:00
logError ( QString ( " Failed to find any secondary tilesets in %1 " ) . arg ( filename ) ) ;
success = false ;
}
2022-10-23 23:59:59 +01:00
if ( this - > primaryTilesetLabels . isEmpty ( ) ) {
2022-09-28 01:17:55 +01:00
logError ( QString ( " Failed to find any primary tilesets in %1 " ) . arg ( filename ) ) ;
success = false ;
2018-09-27 00:33:08 +01:00
}
2022-09-28 01:17:55 +01:00
return success ;
2018-09-27 00:33:08 +01:00
}
2023-12-19 20:46:10 +00:00
bool Project : : readFieldmapProperties ( ) {
2023-12-18 07:19:12 +00:00
const QString numTilesPrimaryName = projectConfig . getIdentifier ( ProjectIdentifier : : define_tiles_primary ) ;
const QString numTilesTotalName = projectConfig . getIdentifier ( ProjectIdentifier : : define_tiles_total ) ;
const QString numMetatilesPrimaryName = projectConfig . getIdentifier ( ProjectIdentifier : : define_metatiles_primary ) ;
const QString numPalsPrimaryName = projectConfig . getIdentifier ( ProjectIdentifier : : define_pals_primary ) ;
const QString numPalsTotalName = projectConfig . getIdentifier ( ProjectIdentifier : : define_pals_total ) ;
const QString maxMapSizeName = projectConfig . getIdentifier ( ProjectIdentifier : : define_map_size ) ;
2024-08-29 17:39:25 +01:00
const QString numTilesPerMetatileName = projectConfig . getIdentifier ( ProjectIdentifier : : define_tiles_per_metatile ) ;
2023-12-18 07:19:12 +00:00
const QStringList names = {
numTilesPrimaryName ,
numTilesTotalName ,
numMetatilesPrimaryName ,
numPalsPrimaryName ,
numPalsTotalName ,
maxMapSizeName ,
2024-08-29 17:39:25 +01:00
numTilesPerMetatileName ,
2023-12-18 00:03:58 +00:00
} ;
2023-12-19 20:46:10 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_fieldmap ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2023-12-19 20:46:10 +00:00
const QMap < QString , int > defines = parser . readCDefinesByName ( filename , names ) ;
2020-05-16 23:52:46 +01:00
2023-12-19 20:46:10 +00:00
auto loadDefine = [ defines ] ( const QString name , int * dest ) {
auto it = defines . find ( name ) ;
if ( it ! = defines . end ( ) ) {
* dest = it . value ( ) ;
} else {
logWarn ( QString ( " Value for tileset property '%1' not found. Using default (%2) instead. " ) . arg ( name ) . arg ( * dest ) ) ;
}
} ;
loadDefine ( numTilesPrimaryName , & Project : : num_tiles_primary ) ;
loadDefine ( numTilesTotalName , & Project : : num_tiles_total ) ;
loadDefine ( numMetatilesPrimaryName , & Project : : num_metatiles_primary ) ;
loadDefine ( numPalsPrimaryName , & Project : : num_pals_primary ) ;
loadDefine ( numPalsTotalName , & Project : : num_pals_total ) ;
2020-05-16 23:52:46 +01:00
2023-12-19 20:46:10 +00:00
auto it = defines . find ( maxMapSizeName ) ;
2020-05-16 21:57:03 +01:00
if ( it ! = defines . end ( ) ) {
int min = getMapDataSize ( 1 , 1 ) ;
if ( it . value ( ) > = min ) {
Project : : max_map_data_size = it . value ( ) ;
calculateDefaultMapSize ( ) ;
} else {
// must be large enough to support a 1x1 map
2023-12-18 07:19:12 +00:00
logWarn ( QString ( " Value for map property '%1' is %2, must be at least %3. Using default (%4) instead. " )
. arg ( maxMapSizeName )
2020-05-16 21:57:03 +01:00
. arg ( it . value ( ) )
. arg ( min )
. arg ( Project : : max_map_data_size ) ) ;
}
}
else {
2023-12-18 07:19:12 +00:00
logWarn ( QString ( " Value for map property '%1' not found. Using default (%2) instead. " )
. arg ( maxMapSizeName )
2020-05-16 21:57:03 +01:00
. arg ( Project : : max_map_data_size ) ) ;
}
2023-12-18 00:03:58 +00:00
2024-08-29 17:39:25 +01:00
it = defines . find ( numTilesPerMetatileName ) ;
if ( it ! = defines . end ( ) ) {
// We can determine whether triple-layer metatiles are in-use by reading this constant.
// If the constant is missing (or is using a value other than 8 or 12) the user must tell
// us whether they're using triple-layer metatiles under Project Settings.
static const int numTilesPerLayer = 4 ;
int numTilesPerMetatile = it . value ( ) ;
if ( numTilesPerMetatile = = 2 * numTilesPerLayer ) {
projectConfig . tripleLayerMetatilesEnabled = false ;
this - > disabledSettingsNames . insert ( numTilesPerMetatileName ) ;
} else if ( numTilesPerMetatile = = 3 * numTilesPerLayer ) {
projectConfig . tripleLayerMetatilesEnabled = true ;
this - > disabledSettingsNames . insert ( numTilesPerMetatileName ) ;
}
}
2020-05-16 23:52:46 +01:00
return true ;
}
2023-12-13 07:30:22 +00:00
// Read data masks for Blocks and metatile attributes.
bool Project : : readFieldmapMasks ( ) {
2023-12-18 07:19:12 +00:00
const QString metatileIdMaskName = projectConfig . getIdentifier ( ProjectIdentifier : : define_mask_metatile ) ;
const QString collisionMaskName = projectConfig . getIdentifier ( ProjectIdentifier : : define_mask_collision ) ;
const QString elevationMaskName = projectConfig . getIdentifier ( ProjectIdentifier : : define_mask_elevation ) ;
const QString behaviorMaskName = projectConfig . getIdentifier ( ProjectIdentifier : : define_mask_behavior ) ;
const QString layerTypeMaskName = projectConfig . getIdentifier ( ProjectIdentifier : : define_mask_layer ) ;
const QStringList searchNames = {
metatileIdMaskName ,
collisionMaskName ,
elevationMaskName ,
behaviorMaskName ,
layerTypeMaskName ,
2023-12-18 00:03:58 +00:00
} ;
2023-12-15 19:33:36 +00:00
QString globalFieldmap = projectConfig . getFilePath ( ProjectFilePath : : global_fieldmap ) ;
fileWatcher . addPath ( root + " / " + globalFieldmap ) ;
2023-12-18 00:03:58 +00:00
QMap < QString , int > defines = parser . readCDefinesByName ( globalFieldmap , searchNames ) ;
2023-12-15 19:33:36 +00:00
// These mask values are accessible via the settings editor for users who don't have these defines.
// If users do have the defines we disable them in the settings editor and direct them to their project files.
// Record the names we read so we know later which settings to disable.
const QStringList defineNames = defines . keys ( ) ;
2024-08-29 17:39:25 +01:00
for ( auto name : defineNames )
this - > disabledSettingsNames . insert ( name ) ;
2023-12-15 19:33:36 +00:00
// Read Block masks
2024-01-03 19:24:14 +00:00
auto readBlockMask = [ defines ] ( const QString name , uint16_t * value ) {
auto it = defines . find ( name ) ;
if ( it = = defines . end ( ) )
return false ;
* value = static_cast < uint16_t > ( it . value ( ) ) ;
if ( * value ! = it . value ( ) ) {
logWarn ( QString ( " Value for %1 truncated from '0x%2' to '0x%3' " )
. arg ( name )
. arg ( QString : : number ( it . value ( ) , 16 ) . toUpper ( ) )
. arg ( QString : : number ( * value , 16 ) . toUpper ( ) ) ) ;
}
return true ;
} ;
2024-07-15 21:14:38 +01:00
2024-01-03 19:24:14 +00:00
uint16_t blockMask ;
if ( readBlockMask ( metatileIdMaskName , & blockMask ) )
2024-07-15 21:14:38 +01:00
projectConfig . blockMetatileIdMask = blockMask ;
2024-01-03 19:24:14 +00:00
if ( readBlockMask ( collisionMaskName , & blockMask ) )
2024-07-15 21:14:38 +01:00
projectConfig . blockCollisionMask = blockMask ;
2024-01-03 19:24:14 +00:00
if ( readBlockMask ( elevationMaskName , & blockMask ) )
2024-07-15 21:14:38 +01:00
projectConfig . blockElevationMask = blockMask ;
2023-12-13 07:30:22 +00:00
2023-12-15 19:33:36 +00:00
// Read RSE metatile attribute masks
2024-01-03 19:24:14 +00:00
auto it = defines . find ( behaviorMaskName ) ;
2023-12-15 19:33:36 +00:00
if ( it ! = defines . end ( ) )
2024-07-15 21:14:38 +01:00
projectConfig . metatileBehaviorMask = static_cast < uint32_t > ( it . value ( ) ) ;
2023-12-18 07:19:12 +00:00
it = defines . find ( layerTypeMaskName ) ;
2023-12-15 19:33:36 +00:00
if ( it ! = defines . end ( ) )
2024-07-15 21:14:38 +01:00
projectConfig . metatileLayerTypeMask = static_cast < uint32_t > ( it . value ( ) ) ;
2023-12-13 07:30:22 +00:00
2023-12-15 19:33:36 +00:00
// pokefirered keeps its attribute masks in a separate table, parse this too.
2023-12-18 07:19:12 +00:00
const QString attrTableName = projectConfig . getIdentifier ( ProjectIdentifier : : symbol_attribute_table ) ;
const QString srcFieldmap = projectConfig . getFilePath ( ProjectFilePath : : fieldmap ) ;
const QMap < QString , QString > attrTable = parser . readNamedIndexCArray ( srcFieldmap , attrTableName ) ;
2023-12-15 19:33:36 +00:00
if ( ! attrTable . isEmpty ( ) ) {
2023-12-19 05:23:32 +00:00
const QString behaviorTableName = projectConfig . getIdentifier ( ProjectIdentifier : : define_attribute_behavior ) ;
const QString layerTypeTableName = projectConfig . getIdentifier ( ProjectIdentifier : : define_attribute_layer ) ;
const QString encounterTypeTableName = projectConfig . getIdentifier ( ProjectIdentifier : : define_attribute_encounter ) ;
const QString terrainTypeTableName = projectConfig . getIdentifier ( ProjectIdentifier : : define_attribute_terrain ) ;
2023-12-15 19:33:36 +00:00
fileWatcher . addPath ( root + " / " + srcFieldmap ) ;
2023-12-18 07:19:12 +00:00
2023-12-15 19:33:36 +00:00
bool ok ;
// Read terrain type mask
2023-12-18 07:19:12 +00:00
uint32_t mask = attrTable . value ( terrainTypeTableName ) . toUInt ( & ok , 0 ) ;
2023-12-15 19:33:36 +00:00
if ( ok ) {
2024-07-15 21:14:38 +01:00
projectConfig . metatileTerrainTypeMask = mask ;
2023-12-18 07:19:12 +00:00
this - > disabledSettingsNames . insert ( terrainTypeTableName ) ;
2023-12-15 19:33:36 +00:00
}
// Read encounter type mask
2023-12-18 07:19:12 +00:00
mask = attrTable . value ( encounterTypeTableName ) . toUInt ( & ok , 0 ) ;
2023-12-15 19:33:36 +00:00
if ( ok ) {
2024-07-15 21:14:38 +01:00
projectConfig . metatileEncounterTypeMask = mask ;
2023-12-18 07:19:12 +00:00
this - > disabledSettingsNames . insert ( encounterTypeTableName ) ;
2023-12-15 19:33:36 +00:00
}
// If we haven't already parsed behavior and layer type then try those too
2023-12-18 07:19:12 +00:00
if ( ! this - > disabledSettingsNames . contains ( behaviorMaskName ) ) {
2023-12-15 19:33:36 +00:00
// Read behavior mask
2023-12-18 07:19:12 +00:00
mask = attrTable . value ( behaviorTableName ) . toUInt ( & ok , 0 ) ;
2023-12-15 19:33:36 +00:00
if ( ok ) {
2024-07-15 21:14:38 +01:00
projectConfig . metatileBehaviorMask = mask ;
2023-12-18 07:19:12 +00:00
this - > disabledSettingsNames . insert ( behaviorTableName ) ;
2023-12-15 19:33:36 +00:00
}
}
2023-12-18 07:19:12 +00:00
if ( ! this - > disabledSettingsNames . contains ( layerTypeMaskName ) ) {
2023-12-15 19:33:36 +00:00
// Read layer type mask
2023-12-18 07:19:12 +00:00
mask = attrTable . value ( layerTypeTableName ) . toUInt ( & ok , 0 ) ;
2023-12-15 19:33:36 +00:00
if ( ok ) {
2024-07-15 21:14:38 +01:00
projectConfig . metatileLayerTypeMask = mask ;
2023-12-18 07:19:12 +00:00
this - > disabledSettingsNames . insert ( layerTypeTableName ) ;
2023-12-15 19:33:36 +00:00
}
}
}
2020-02-12 15:13:58 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2020-02-12 14:12:12 +00:00
bool Project : : readRegionMapSections ( ) {
2024-11-05 02:49:49 +00:00
this - > mapSectionIdNames . clear ( ) ;
this - > regionMapEntries . clear ( ) ;
this - > saveEmptyMapsec = false ;
const QString defaultName = getEmptyMapsecName ( ) ;
const QString requiredPrefix = projectConfig . getIdentifier ( ProjectIdentifier : : define_map_section_prefix ) ;
QJsonDocument doc ;
const QString filepath = QString ( " %1/%2 " ) . arg ( this - > root ) . arg ( projectConfig . getFilePath ( ProjectFilePath : : json_region_map_entries ) ) ;
if ( ! parser . tryParseJsonFile ( & doc , filepath ) ) {
logError ( QString ( " Failed to read region map sections from '%1' " ) . arg ( filepath ) ) ;
2020-02-12 14:12:12 +00:00
return false ;
}
2024-11-05 02:49:49 +00:00
fileWatcher . addPath ( filepath ) ;
2020-02-12 14:12:12 +00:00
2024-11-05 02:49:49 +00:00
QJsonArray mapSections = doc . object ( ) [ " map_sections " ] . toArray ( ) ;
2024-11-08 19:59:41 +00:00
for ( int i = 0 ; i < mapSections . size ( ) ; i + + ) {
QJsonObject mapSectionObj = mapSections . at ( i ) . toObject ( ) ;
// For each map section, "id" is the only required field. This is the field we use to display the location names in various drop-downs.
const QString idField = " id " ;
if ( ! mapSectionObj . contains ( idField ) ) {
logWarn ( QString ( " Ignoring data for map section %1. Missing required field \" %2 \" " ) . arg ( i ) . arg ( idField ) ) ;
continue ;
}
const QString idName = ParseUtil : : jsonToQString ( mapSectionObj [ idField ] ) ;
2024-11-05 02:49:49 +00:00
if ( ! idName . startsWith ( requiredPrefix ) ) {
logWarn ( QString ( " Ignoring data for map section '%1'. IDs must start with the prefix '%2' " ) . arg ( idName ) . arg ( requiredPrefix ) ) ;
2024-02-17 00:17:56 +00:00
continue ;
}
2024-11-05 02:49:49 +00:00
this - > mapSectionIdNames . append ( idName ) ;
if ( idName = = defaultName ) {
// If the user has data for the 'empty' MAPSEC we need to know to output it later,
// because we will otherwise add a dummy entry for this value.
this - > saveEmptyMapsec = true ;
2024-02-17 00:17:56 +00:00
}
2024-11-05 02:49:49 +00:00
// Map sections may have additional data indicating their position on the region map.
// If they have this data, we can add them to the region map entry list.
bool hasRegionMapData = true ;
static const QSet < QString > regionMapFieldNames = { " name " , " x " , " y " , " width " , " height " } ;
for ( auto fieldName : regionMapFieldNames ) {
if ( ! mapSectionObj . contains ( fieldName ) ) {
hasRegionMapData = false ;
break ;
}
2024-02-17 00:17:56 +00:00
}
2024-11-05 02:49:49 +00:00
if ( ! hasRegionMapData )
continue ;
MapSectionEntry entry ;
entry . name = ParseUtil : : jsonToQString ( mapSectionObj [ " name " ] ) ;
entry . x = ParseUtil : : jsonToInt ( mapSectionObj [ " x " ] ) ;
entry . y = ParseUtil : : jsonToInt ( mapSectionObj [ " y " ] ) ;
entry . width = ParseUtil : : jsonToInt ( mapSectionObj [ " width " ] ) ;
entry . height = ParseUtil : : jsonToInt ( mapSectionObj [ " height " ] ) ;
entry . valid = true ;
this - > regionMapEntries [ idName ] = entry ;
2024-02-17 00:17:56 +00:00
}
2024-11-05 02:49:49 +00:00
// Make sure the default name is present in the list.
if ( ! this - > mapSectionIdNames . contains ( defaultName ) ) {
this - > mapSectionIdNames . append ( defaultName ) ;
}
2020-02-12 14:12:12 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2024-10-17 19:01:27 +01:00
QString Project : : getEmptyMapsecName ( ) {
return projectConfig . getIdentifier ( ProjectIdentifier : : define_map_section_prefix ) + projectConfig . getIdentifier ( ProjectIdentifier : : define_map_section_empty ) ;
}
2024-11-05 02:49:49 +00:00
// This function assumes a valid and unique name
2024-11-08 18:55:50 +00:00
void Project : : addNewMapsec ( const QString & name ) {
2024-11-05 02:49:49 +00:00
if ( ! this - > mapSectionIdNames . isEmpty ( ) & & this - > mapSectionIdNames . last ( ) = = getEmptyMapsecName ( ) ) {
// If the default map section name (MAPSEC_NONE) is last in the list we'll keep it last in the list.
this - > mapSectionIdNames . insert ( this - > mapSectionIdNames . length ( ) - 1 , name ) ;
} else {
this - > mapSectionIdNames . append ( name ) ;
2024-10-17 19:01:27 +01:00
}
2024-10-30 01:51:05 +00:00
this - > hasUnsavedDataChanges = true ;
2024-11-08 18:55:50 +00:00
emit mapSectionIdNamesChanged ( ) ;
}
void Project : : removeMapsec ( const QString & name ) {
if ( ! this - > mapSectionIdNames . contains ( name ) | | name = = getEmptyMapsecName ( ) )
return ;
this - > mapSectionIdNames . removeOne ( name ) ;
this - > hasUnsavedDataChanges = true ;
emit mapSectionIdNamesChanged ( ) ;
2024-02-17 00:17:56 +00:00
}
2022-09-09 22:41:50 +01:00
// Read the constants to preserve any "unused" heal locations when writing the file later
bool Project : : readHealLocationConstants ( ) {
this - > healLocationNameToValue . clear ( ) ;
2024-08-28 21:06:28 +01:00
const QStringList regexList = {
2023-12-18 07:19:12 +00:00
QString ( " \\ b%1 " ) . arg ( projectConfig . getIdentifier ( ProjectIdentifier : : define_heal_locations_prefix ) ) ,
QString ( " \\ b%1 " ) . arg ( projectConfig . getIdentifier ( ProjectIdentifier : : define_spawn_prefix ) )
} ;
2022-10-18 06:48:08 +01:00
QString constantsFilename = projectConfig . getFilePath ( ProjectFilePath : : constants_heal_locations ) ;
2022-09-09 22:41:50 +01:00
fileWatcher . addPath ( root + " / " + constantsFilename ) ;
2024-08-28 21:06:28 +01:00
this - > healLocationNameToValue = parser . readCDefinesByRegex ( constantsFilename , regexList ) ;
2022-09-09 22:41:50 +01:00
// No need to check if empty, not finding any heal location constants is ok
return true ;
}
2023-12-18 07:19:12 +00:00
// TODO: Simplify using the new C struct parsing functions (and indexed array parsing functions)
2020-02-12 15:13:58 +00:00
bool Project : : readHealLocations ( ) {
2022-09-09 22:41:50 +01:00
this - > healLocations . clear ( ) ;
if ( ! this - > readHealLocationConstants ( ) )
return false ;
2022-09-01 17:14:47 +01:00
QString filename = projectConfig . getFilePath ( ProjectFilePath : : data_heal_locations ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2020-02-12 15:13:58 +00:00
QString text = parser . readTextFile ( root + " / " + filename ) ;
2022-09-09 22:41:50 +01:00
// Strip comments
2022-11-23 03:57:26 +00:00
static const QRegularExpression re_comments ( " //.*?( \r \n ?| \n ) | / \ \ * . * ? \ \ */ " , QRegularExpression::DotMatchesEverythingOption) ;
text . replace ( re_comments , " " ) ;
2019-04-28 14:05:37 +01:00
2024-07-15 21:14:38 +01:00
bool respawnEnabled = projectConfig . healLocationRespawnDataEnabled ;
2022-09-09 22:41:50 +01:00
2024-01-04 17:22:06 +00:00
// Search for the name of the main Heal Locations table
const QRegularExpression tableNameExpr ( QString ( " %1 \\ s+(?<name>[A-Za-z0-9_]+) \ \ [ " ).arg(projectConfig.getIdentifier(ProjectIdentifier::symbol_heal_locations_type))) ;
const QRegularExpressionMatch tableNameMatch = tableNameExpr . match ( text ) ;
if ( tableNameMatch . hasMatch ( ) ) {
// Found table name, record it and its qualifiers for output when saving.
this - > healLocationsTableName = tableNameMatch . captured ( " name " ) ;
this - > healLocationDataQualifiers = this - > getDataQualifiers ( text , this - > healLocationsTableName ) ;
} else {
// No table name found, initialize default name for output when saving.
this - > healLocationsTableName = respawnEnabled ? projectConfig . getIdentifier ( ProjectIdentifier : : symbol_spawn_points )
: projectConfig . getIdentifier ( ProjectIdentifier : : symbol_heal_locations ) ;
this - > healLocationDataQualifiers = { . isStatic = true , . isConst = true } ;
}
2022-09-09 22:41:50 +01:00
2022-09-10 03:44:42 +01:00
// Create regex pattern for the constants (ex: "SPAWN_PALLET_TOWN" or "HEAL_LOCATION_PETALBURG_CITY")
2023-12-18 07:19:12 +00:00
const QString spawnPrefix = projectConfig . getIdentifier ( ProjectIdentifier : : define_spawn_prefix ) ;
const QString healLocPrefix = projectConfig . getIdentifier ( ProjectIdentifier : : define_heal_locations_prefix ) ;
const QRegularExpression constantsExpr ( QString ( " \\ b(%1|%2) [ A - Za - z0 - 9 _ ] + " ).arg(spawnPrefix).arg(healLocPrefix)) ;
2022-09-09 22:41:50 +01:00
// Find all the unique heal location constants used in the data tables.
// Porymap doesn't care whether or not a constant appeared in the heal locations constants file.
// Any data entry without a designated initializer using one of these constants will be silently discarded.
2022-09-10 03:44:42 +01:00
// Any data entry that repeats a designated initializer will also be discarded.
2022-09-09 22:41:50 +01:00
QStringList constants = QStringList ( ) ;
QRegularExpressionMatchIterator constantsMatch = constantsExpr . globalMatch ( text ) ;
while ( constantsMatch . hasNext ( ) )
constants < < constantsMatch . next ( ) . captured ( ) ;
constants . removeDuplicates ( ) ;
2022-09-10 03:44:42 +01:00
// Pattern for a map value pair (ex: "MAP_GROUP(PALLET_TOWN), MAP_NUM(PALLET_TOWN)")
2022-09-09 22:41:50 +01:00
const QString mapPattern = " MAP_GROUP[ \\ ( \\ s]+(?<map>[A-Za-z0-9_]+)[ \\ s \\ )]+, \\ s*MAP_NUM[ \\ ( \\ s]+( \\ 1)[ \\ s \\ )]+ " ;
// Pattern for an x, y number pair
const QString coordPattern = " \\ s*(?<x>[0-9A-Fa-fx]+), \\ s*(?<y>[0-9A-Fa-fx]+) " ;
2024-09-11 05:38:27 +01:00
for ( const auto & idName : constants ) {
2022-09-09 22:41:50 +01:00
// Create regex pattern for e.g. "SPAWN_PALLET_TOWN - 1] = "
2023-09-30 23:39:14 +01:00
const QString initializerPattern = QString ( " %1 \\ s*- \\ s*1 \\ s* \\ ] \\ s*= \\ s* " ) . arg ( idName ) ;
2022-09-09 22:41:50 +01:00
// Expression for location data, e.g. "SPAWN_PALLET_TOWN - 1] = {MAP_GROUP(PALLET_TOWN), MAP_NUM(PALLET_TOWN), x, y}"
QRegularExpression locationRegex ( QString ( " %1 \\ {%2,%3} " ) . arg ( initializerPattern ) . arg ( mapPattern ) . arg ( coordPattern ) ) ;
QRegularExpressionMatch match = locationRegex . match ( text ) ;
// Read location data
HealLocation healLocation ;
if ( match . hasMatch ( ) ) {
QString mapName = match . captured ( " map " ) ;
2022-10-28 17:40:36 +01:00
int x = match . captured ( " x " ) . toInt ( nullptr , 0 ) ;
int y = match . captured ( " y " ) . toInt ( nullptr , 0 ) ;
2022-09-09 22:41:50 +01:00
healLocation = HealLocation ( idName , mapName , this - > healLocations . size ( ) + 1 , x , y ) ;
} else {
2022-09-10 03:44:42 +01:00
// This heal location has data, but is missing from the location table and won't be displayed by Porymap.
// Add a dummy entry, and preserve the rest of its data for the user anyway
healLocation = HealLocation ( idName , " " , this - > healLocations . size ( ) + 1 , 0 , 0 ) ;
2020-03-20 07:09:48 +00:00
}
2022-09-09 22:41:50 +01:00
// Read respawn data
if ( respawnEnabled ) {
// Expression for respawn map data, e.g. "SPAWN_PALLET_TOWN - 1] = {MAP_GROUP(PALLET_TOWN_PLAYERS_HOUSE_1F), MAP_NUM(PALLET_TOWN_PLAYERS_HOUSE_1F)}"
QRegularExpression respawnMapRegex ( QString ( " %1 \\ {%2} " ) . arg ( initializerPattern ) . arg ( mapPattern ) ) ;
match = respawnMapRegex . match ( text ) ;
if ( match . hasMatch ( ) )
healLocation . respawnMap = match . captured ( " map " ) ;
// Expression for respawn npc data, e.g. "SPAWN_PALLET_TOWN - 1] = 1"
QRegularExpression respawnNPCRegex ( QString ( " %1(?<npc>[0-9]+) " ).arg(initializerPattern)) ;
match = respawnNPCRegex . match ( text ) ;
if ( match . hasMatch ( ) )
2022-10-28 17:40:36 +01:00
healLocation . respawnNPC = match . captured ( " npc " ) . toInt ( nullptr , 0 ) ;
2020-03-20 07:09:48 +00:00
}
2022-09-09 22:41:50 +01:00
this - > healLocations . append ( healLocation ) ;
2019-04-28 14:05:37 +01:00
}
2022-09-09 22:41:50 +01:00
// No need to check if empty, not finding any heal locations is ok
2020-02-12 15:13:58 +00:00
return true ;
2019-04-28 14:05:37 +01:00
}
2020-02-12 15:13:58 +00:00
bool Project : : readItemNames ( ) {
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_items ) } ;
2023-12-18 07:19:12 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_items ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
itemNames = parser . readCDefineNames ( filename , regexList ) ;
2024-01-19 16:59:04 +00:00
if ( itemNames . isEmpty ( ) )
logWarn ( QString ( " Failed to read item constants from %1 " ) . arg ( filename ) ) ;
2020-02-12 15:13:58 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2020-02-12 15:13:58 +00:00
bool Project : : readFlagNames ( ) {
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_flags ) } ;
2023-12-18 07:19:12 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_flags ) ;
2023-12-11 21:40:40 +00:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
flagNames = parser . readCDefineNames ( filename , regexList ) ;
2024-01-19 16:59:04 +00:00
if ( flagNames . isEmpty ( ) )
logWarn ( QString ( " Failed to read flag constants from %1 " ) . arg ( filename ) ) ;
2020-02-12 15:13:58 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2020-02-12 15:13:58 +00:00
bool Project : : readVarNames ( ) {
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_vars ) } ;
2023-12-18 07:19:12 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_vars ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
varNames = parser . readCDefineNames ( filename , regexList ) ;
2024-01-19 16:59:04 +00:00
if ( varNames . isEmpty ( ) )
logWarn ( QString ( " Failed to read var constants from %1 " ) . arg ( filename ) ) ;
2020-02-12 15:13:58 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2020-02-12 15:13:58 +00:00
bool Project : : readMovementTypes ( ) {
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_movement_types ) } ;
2023-12-18 07:19:12 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_obj_event_movement ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
movementTypes = parser . readCDefineNames ( filename , regexList ) ;
2024-01-19 16:59:04 +00:00
if ( movementTypes . isEmpty ( ) )
logWarn ( QString ( " Failed to read movement type constants from %1 " ) . arg ( filename ) ) ;
2020-02-12 15:13:58 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2020-02-12 15:13:58 +00:00
bool Project : : readInitialFacingDirections ( ) {
2022-10-24 13:03:51 +01:00
QString filename = projectConfig . getFilePath ( ProjectFilePath : : initial_facing_table ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2023-12-18 07:19:12 +00:00
facingDirections = parser . readNamedIndexCArray ( filename , projectConfig . getIdentifier ( ProjectIdentifier : : symbol_facing_directions ) ) ;
2024-01-19 16:59:04 +00:00
if ( facingDirections . isEmpty ( ) )
logWarn ( QString ( " Failed to read initial movement type facing directions from %1 " ) . arg ( filename ) ) ;
2020-02-12 15:13:58 +00:00
return true ;
2019-04-03 00:51:33 +01:00
}
2020-02-12 15:13:58 +00:00
bool Project : : readMapTypes ( ) {
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_map_types ) } ;
2023-12-18 07:19:12 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_map_types ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
mapTypes = parser . readCDefineNames ( filename , regexList ) ;
2024-01-19 16:59:04 +00:00
if ( mapTypes . isEmpty ( ) )
logWarn ( QString ( " Failed to read map type constants from %1 " ) . arg ( filename ) ) ;
2020-02-12 15:13:58 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2020-02-12 15:13:58 +00:00
bool Project : : readMapBattleScenes ( ) {
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_battle_scenes ) } ;
2023-12-18 07:19:12 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_map_types ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
mapBattleScenes = parser . readCDefineNames ( filename , regexList ) ;
2024-01-19 16:59:04 +00:00
if ( mapBattleScenes . isEmpty ( ) )
logWarn ( QString ( " Failed to read map battle scene constants from %1 " ) . arg ( filename ) ) ;
2020-02-12 15:13:58 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2020-02-12 15:13:58 +00:00
bool Project : : readWeatherNames ( ) {
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_weather ) } ;
2023-12-18 07:19:12 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_weather ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
weatherNames = parser . readCDefineNames ( filename , regexList ) ;
2024-01-19 16:59:04 +00:00
if ( weatherNames . isEmpty ( ) )
logWarn ( QString ( " Failed to read weather constants from %1 " ) . arg ( filename ) ) ;
2020-02-12 15:13:58 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2020-02-12 15:13:58 +00:00
bool Project : : readCoordEventWeatherNames ( ) {
2024-07-15 21:14:38 +01:00
if ( ! projectConfig . eventWeatherTriggerEnabled )
2021-02-16 12:15:47 +00:00
return true ;
2020-05-22 22:51:56 +01:00
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_coord_event_weather ) } ;
2023-12-18 07:19:12 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_weather ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
coordEventWeatherNames = parser . readCDefineNames ( filename , regexList ) ;
2024-01-19 16:59:04 +00:00
if ( coordEventWeatherNames . isEmpty ( ) )
logWarn ( QString ( " Failed to read coord event weather constants from %1 " ) . arg ( filename ) ) ;
2020-02-12 15:13:58 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2020-02-12 15:13:58 +00:00
bool Project : : readSecretBaseIds ( ) {
2024-07-15 21:14:38 +01:00
if ( ! projectConfig . eventSecretBaseEnabled )
2021-02-16 12:15:47 +00:00
return true ;
2020-05-22 22:51:56 +01:00
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_secret_bases ) } ;
2023-12-18 07:19:12 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_secret_bases ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
secretBaseIds = parser . readCDefineNames ( filename , regexList ) ;
2024-01-19 16:59:04 +00:00
if ( secretBaseIds . isEmpty ( ) )
logWarn ( QString ( " Failed to read secret base id constants from '%1' " ) . arg ( filename ) ) ;
2020-02-12 15:13:58 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2020-02-12 15:13:58 +00:00
bool Project : : readBgEventFacingDirections ( ) {
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_sign_facing_directions ) } ;
2023-12-18 07:19:12 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_event_bg ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
bgEventFacingDirections = parser . readCDefineNames ( filename , regexList ) ;
2024-01-19 16:59:04 +00:00
if ( bgEventFacingDirections . isEmpty ( ) )
logWarn ( QString ( " Failed to read bg event facing direction constants from %1 " ) . arg ( filename ) ) ;
2020-02-12 15:13:58 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2020-03-27 14:51:57 +00:00
bool Project : : readTrainerTypes ( ) {
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_trainer_types ) } ;
2023-12-18 07:19:12 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_trainer_types ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
trainerTypes = parser . readCDefineNames ( filename , regexList ) ;
2024-01-19 16:59:04 +00:00
if ( trainerTypes . isEmpty ( ) )
logWarn ( QString ( " Failed to read trainer type constants from %1 " ) . arg ( filename ) ) ;
2020-03-27 14:51:57 +00:00
return true ;
}
2020-02-12 15:13:58 +00:00
bool Project : : readMetatileBehaviors ( ) {
2018-10-03 01:01:09 +01:00
this - > metatileBehaviorMap . clear ( ) ;
this - > metatileBehaviorMapInverse . clear ( ) ;
2019-05-05 21:11:00 +01:00
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_behaviors ) } ;
2022-09-01 17:14:47 +01:00
QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_metatile_behaviors ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
QMap < QString , int > defines = parser . readCDefinesByRegex ( filename , regexList ) ;
2023-12-19 18:42:07 +00:00
if ( defines . isEmpty ( ) ) {
2023-12-19 20:46:10 +00:00
// Not having any metatile behavior names is ok (their values will be displayed instead).
// If the user's metatiles can have nonzero values then warn them, as they likely want names.
2024-07-15 21:14:38 +01:00
if ( projectConfig . metatileBehaviorMask )
2023-12-19 20:46:10 +00:00
logWarn ( QString ( " Failed to read metatile behaviors from %1. " ) . arg ( filename ) ) ;
return true ;
2020-02-12 15:13:58 +00:00
}
2023-12-19 18:42:07 +00:00
for ( auto i = defines . cbegin ( ) , end = defines . cend ( ) ; i ! = end ; i + + ) {
uint32_t value = static_cast < uint32_t > ( i . value ( ) ) ;
this - > metatileBehaviorMap . insert ( i . key ( ) , value ) ;
this - > metatileBehaviorMapInverse . insert ( value , i . key ( ) ) ;
2018-10-03 01:01:09 +01:00
}
2023-12-17 01:35:54 +00:00
2020-02-12 15:13:58 +00:00
return true ;
2018-10-03 01:01:09 +01:00
}
2022-01-21 20:22:20 +00:00
bool Project : : readSongNames ( ) {
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_music ) } ;
2023-12-18 07:19:12 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_songs ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
this - > songNames = parser . readCDefineNames ( filename , regexList ) ;
2024-01-19 16:59:04 +00:00
if ( this - > songNames . isEmpty ( ) )
logWarn ( QString ( " Failed to read song names from %1. " ) . arg ( filename ) ) ;
2023-12-11 21:40:40 +00:00
// Song names don't have a very useful order (esp. if we include SE_* values), so sort them alphabetically.
2024-01-19 20:18:14 +00:00
// The default song should be the first in the list, not the first alphabetically, so save that before sorting.
this - > defaultSong = this - > songNames . value ( 0 , " 0 " ) ;
2023-12-11 21:40:40 +00:00
this - > songNames . sort ( ) ;
2022-01-21 20:22:20 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2022-01-21 20:22:20 +00:00
bool Project : : readObjEventGfxConstants ( ) {
2024-08-28 21:06:28 +01:00
const QStringList regexList = { projectConfig . getIdentifier ( ProjectIdentifier : : regex_obj_event_gfx ) } ;
2022-09-01 17:14:47 +01:00
QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_obj_events ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2024-08-28 21:06:28 +01:00
this - > gfxDefines = parser . readCDefinesByRegex ( filename , regexList ) ;
2024-01-19 16:59:04 +00:00
if ( this - > gfxDefines . isEmpty ( ) )
logWarn ( QString ( " Failed to read object event graphics constants from %1. " ) . arg ( filename ) ) ;
2022-01-21 20:22:20 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2020-02-12 15:13:58 +00:00
bool Project : : readMiscellaneousConstants ( ) {
2023-12-18 07:19:12 +00:00
const QString filename = projectConfig . getFilePath ( ProjectFilePath : : constants_global ) ;
const QString maxObjectEventsName = projectConfig . getIdentifier ( ProjectIdentifier : : define_obj_event_count ) ;
2020-07-10 21:34:42 +01:00
fileWatcher . addPath ( root + " / " + filename ) ;
2023-12-18 07:19:12 +00:00
QMap < QString , int > defines = parser . readCDefinesByName ( filename , { maxObjectEventsName } ) ;
2020-07-10 21:34:42 +01:00
2023-12-18 07:19:12 +00:00
auto it = defines . find ( maxObjectEventsName ) ;
2020-07-10 21:34:42 +01:00
if ( it ! = defines . end ( ) ) {
if ( it . value ( ) > 0 ) {
Project : : max_object_events = it . value ( ) ;
} else {
2023-12-18 07:19:12 +00:00
logWarn ( QString ( " Value for '%1' is %2, must be greater than 0. Using default (%3) instead. " )
. arg ( maxObjectEventsName )
2020-07-10 21:34:42 +01:00
. arg ( it . value ( ) )
. arg ( Project : : max_object_events ) ) ;
}
}
else {
2023-12-18 07:19:12 +00:00
logWarn ( QString ( " Value for '%1' not found. Using default (%2) instead. " )
. arg ( maxObjectEventsName )
2020-07-10 21:34:42 +01:00
. arg ( Project : : max_object_events ) ) ;
}
2020-02-12 15:13:58 +00:00
return true ;
2019-07-03 03:08:58 +01:00
}
2021-01-30 03:05:40 +00:00
bool Project : : readEventScriptLabels ( ) {
2024-01-05 17:45:16 +00:00
globalScriptLabels . clear ( ) ;
2021-01-30 03:05:40 +00:00
for ( const auto & filePath : getEventScriptsFilePaths ( ) )
2021-04-16 14:04:38 +01:00
globalScriptLabels < < ParseUtil : : getGlobalScriptLabels ( filePath ) ;
2021-01-30 03:05:40 +00:00
2024-01-05 17:45:16 +00:00
globalScriptLabels . sort ( Qt : : CaseInsensitive ) ;
globalScriptLabels . removeDuplicates ( ) ;
2021-01-30 03:05:40 +00:00
return true ;
}
2018-10-03 01:01:41 +01:00
QString Project : : fixPalettePath ( QString path ) {
2022-11-23 03:57:26 +00:00
static const QRegularExpression re_gbapal ( " \\ .gbapal$ " ) ;
path = path . replace ( re_gbapal , " .pal " ) ;
2018-10-03 01:01:41 +01:00
return path ;
}
2018-09-27 00:33:08 +01:00
QString Project : : fixGraphicPath ( QString path ) {
2022-11-23 03:57:26 +00:00
static const QRegularExpression re_lz ( " \\ .lz$ " ) ;
path = path . replace ( re_lz , " " ) ;
static const QRegularExpression re_bpp ( " \\ .[1248]bpp$ " ) ;
path = path . replace ( re_bpp , " .png " ) ;
2018-09-27 00:33:08 +01:00
return path ;
}
2023-12-30 02:54:37 +00:00
QString Project : : getScriptFileExtension ( bool usePoryScript ) {
2019-10-22 13:48:41 +01:00
if ( usePoryScript ) {
return " .pory " ;
} else {
return " .inc " ;
}
}
2020-11-21 22:33:16 +00:00
QString Project : : getScriptDefaultString ( bool usePoryScript , QString mapName ) const {
2019-10-22 13:48:41 +01:00
if ( usePoryScript )
2021-03-07 15:16:54 +00:00
return QString ( " mapscripts %1_MapScripts {} \n " ) . arg ( mapName ) ;
2019-10-22 13:48:41 +01:00
else
return QString ( " %1_MapScripts:: \n \t .byte 0 \n " ) . arg ( mapName ) ;
}
2020-12-04 14:29:38 +00:00
QStringList Project : : getEventScriptsFilePaths ( ) const {
2022-09-01 17:14:47 +01:00
QStringList filePaths ( QDir : : cleanPath ( root + " / " + projectConfig . getFilePath ( ProjectFilePath : : data_event_scripts ) ) ) ;
const QString scriptsDir = QDir : : cleanPath ( root + " / " + projectConfig . getFilePath ( ProjectFilePath : : data_scripts_folders ) ) ;
const QString mapsDir = QDir : : cleanPath ( root + " / " + projectConfig . getFilePath ( ProjectFilePath : : data_map_folders ) ) ;
2020-12-04 14:29:38 +00:00
2024-07-15 21:14:38 +01:00
if ( projectConfig . usePoryScript ) {
2020-12-04 14:29:38 +00:00
QDirIterator it_pory_shared ( scriptsDir , { " *.pory " } , QDir : : Files ) ;
while ( it_pory_shared . hasNext ( ) )
filePaths < < it_pory_shared . next ( ) ;
QDirIterator it_pory_maps ( mapsDir , { " scripts.pory " } , QDir : : Files , QDirIterator : : Subdirectories ) ;
while ( it_pory_maps . hasNext ( ) )
filePaths < < it_pory_maps . next ( ) ;
}
QDirIterator it_inc_shared ( scriptsDir , { " *.inc " } , QDir : : Files ) ;
while ( it_inc_shared . hasNext ( ) )
filePaths < < it_inc_shared . next ( ) ;
QDirIterator it_inc_maps ( mapsDir , { " scripts.inc " } , QDir : : Files , QDirIterator : : Subdirectories ) ;
while ( it_inc_maps . hasNext ( ) )
filePaths < < it_inc_maps . next ( ) ;
return filePaths ;
}
2022-07-19 22:56:12 +01:00
void Project : : setEventPixmap ( Event * event , bool forceLoad ) {
if ( event & & ( event - > getPixmap ( ) . isNull ( ) | | forceLoad ) )
event - > loadPixmap ( this ) ;
2022-01-24 22:17:31 +00:00
}
2018-09-27 00:33:08 +01:00
2024-09-11 18:09:03 +01:00
void Project : : clearEventGraphics ( ) {
qDeleteAll ( eventGraphicsMap ) ;
eventGraphicsMap . clear ( ) ;
}
2022-01-24 22:17:31 +00:00
bool Project : : readEventGraphics ( ) {
2024-09-11 18:09:03 +01:00
clearEventGraphics ( ) ;
2022-09-01 17:14:47 +01:00
fileWatcher . addPaths ( QStringList ( ) < < root + " / " + projectConfig . getFilePath ( ProjectFilePath : : data_obj_event_gfx_pointers )
< < root + " / " + projectConfig . getFilePath ( ProjectFilePath : : data_obj_event_gfx_info )
< < root + " / " + projectConfig . getFilePath ( ProjectFilePath : : data_obj_event_pic_tables )
< < root + " / " + projectConfig . getFilePath ( ProjectFilePath : : data_obj_event_gfx ) ) ;
2020-04-08 05:42:38 +01:00
2023-12-18 07:19:12 +00:00
const QString pointersFilepath = projectConfig . getFilePath ( ProjectFilePath : : data_obj_event_gfx_pointers ) ;
const QString pointersName = projectConfig . getIdentifier ( ProjectIdentifier : : symbol_obj_event_gfx_pointers ) ;
QMap < QString , QString > pointerHash = parser . readNamedIndexCArray ( pointersFilepath , pointersName ) ;
2019-08-27 16:30:35 +01:00
2022-02-06 02:31:54 +00:00
QStringList gfxNames = gfxDefines . keys ( ) ;
2022-09-27 23:29:16 +01:00
// The positions of each of the required members for the gfx info struct.
// For backwards compatibility if the struct doesn't use initializers.
2023-12-18 07:19:12 +00:00
static const auto gfxInfoMemberMap = QHash < int , QString > {
2022-09-27 23:29:16 +01:00
{ 8 , " inanimate " } ,
{ 11 , " oam " } ,
{ 12 , " subspriteTables " } ,
{ 14 , " images " } ,
} ;
2022-10-07 16:47:05 +01:00
QString filepath = projectConfig . getFilePath ( ProjectFilePath : : data_obj_event_gfx_info ) ;
2022-10-08 18:51:48 +01:00
const auto gfxInfos = parser . readCStructs ( filepath , " " , gfxInfoMemberMap ) ;
2023-01-17 06:17:24 +00:00
QMap < QString , QStringList > picTables = parser . readCArrayMulti ( projectConfig . getFilePath ( ProjectFilePath : : data_obj_event_pic_tables ) ) ;
QMap < QString , QString > graphicIncbins = parser . readCIncbinMulti ( projectConfig . getFilePath ( ProjectFilePath : : data_obj_event_gfx ) ) ;
2022-02-06 02:31:54 +00:00
for ( QString gfxName : gfxNames ) {
2022-01-24 22:17:31 +00:00
QString info_label = pointerHash [ gfxName ] . replace ( " & " , " " ) ;
2022-07-02 18:40:04 +01:00
if ( ! gfxInfos . contains ( info_label ) )
continue ;
2022-10-08 18:51:48 +01:00
const auto gfxInfoAttributes = gfxInfos [ info_label ] ;
2022-01-24 22:17:31 +00:00
2024-09-11 18:09:03 +01:00
auto eventGraphics = new EventGraphics ;
2022-09-28 01:17:55 +01:00
eventGraphics - > inanimate = ParseUtil : : gameStringToBool ( gfxInfoAttributes . value ( " inanimate " ) ) ;
2022-07-02 18:40:04 +01:00
QString pic_label = gfxInfoAttributes . value ( " images " ) ;
QString dimensions_label = gfxInfoAttributes . value ( " oam " ) ;
QString subsprites_label = gfxInfoAttributes . value ( " subspriteTables " ) ;
2022-01-24 22:17:31 +00:00
2023-01-17 06:17:24 +00:00
QString gfx_label = picTables [ pic_label ] . value ( 0 ) ;
2022-11-23 03:57:26 +00:00
static const QRegularExpression re_parens ( " [ \\ ( \\ ) ] " ) ;
gfx_label = gfx_label . section ( re_parens , 1 , 1 ) ;
2023-01-17 06:17:24 +00:00
QString path = graphicIncbins [ gfx_label ] ;
2022-01-24 22:17:31 +00:00
if ( ! path . isNull ( ) ) {
path = fixGraphicPath ( path ) ;
eventGraphics - > spritesheet = QImage ( root + " / " + path ) ;
if ( ! eventGraphics - > spritesheet . isNull ( ) ) {
// Infer the sprite dimensions from the OAM labels.
2022-11-23 03:57:26 +00:00
static const QRegularExpression re ( " \\ S+_( \\ d+) x ( \ \ d + ) " ) ;
2022-01-24 22:17:31 +00:00
QRegularExpressionMatch dimensionMatch = re . match ( dimensions_label ) ;
QRegularExpressionMatch oamTablesMatch = re . match ( subsprites_label ) ;
if ( oamTablesMatch . hasMatch ( ) ) {
2022-10-28 17:40:36 +01:00
eventGraphics - > spriteWidth = oamTablesMatch . captured ( 1 ) . toInt ( nullptr , 0 ) ;
eventGraphics - > spriteHeight = oamTablesMatch . captured ( 2 ) . toInt ( nullptr , 0 ) ;
2022-01-24 22:17:31 +00:00
} else if ( dimensionMatch . hasMatch ( ) ) {
2022-10-28 17:40:36 +01:00
eventGraphics - > spriteWidth = dimensionMatch . captured ( 1 ) . toInt ( nullptr , 0 ) ;
eventGraphics - > spriteHeight = dimensionMatch . captured ( 2 ) . toInt ( nullptr , 0 ) ;
2022-01-24 22:17:31 +00:00
} else {
eventGraphics - > spriteWidth = eventGraphics - > spritesheet . width ( ) ;
eventGraphics - > spriteHeight = eventGraphics - > spritesheet . height ( ) ;
2018-09-27 00:33:08 +01:00
}
}
2022-01-24 22:17:31 +00:00
} else {
eventGraphics - > spritesheet = QImage ( ) ;
eventGraphics - > spriteWidth = 16 ;
eventGraphics - > spriteHeight = 16 ;
2018-09-27 00:33:08 +01:00
}
2022-01-24 22:17:31 +00:00
eventGraphicsMap . insert ( gfxName , eventGraphics ) ;
2018-09-27 00:33:08 +01:00
}
2022-01-24 22:17:31 +00:00
return true ;
2018-09-27 00:33:08 +01:00
}
2020-02-12 15:13:58 +00:00
bool Project : : readSpeciesIconPaths ( ) {
2023-12-10 08:49:54 +00:00
this - > speciesToIconPath . clear ( ) ;
// Read map of species constants to icon names
const QString srcfilename = projectConfig . getFilePath ( ProjectFilePath : : pokemon_icon_table ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + srcfilename ) ;
2023-12-18 07:19:12 +00:00
const QString tableName = projectConfig . getIdentifier ( ProjectIdentifier : : symbol_pokemon_icon_table ) ;
const QMap < QString , QString > monIconNames = parser . readNamedIndexCArray ( srcfilename , tableName ) ;
2023-12-10 08:49:54 +00:00
2023-12-18 07:19:12 +00:00
// Read map of icon names to filepaths
2023-12-10 08:49:54 +00:00
const QString incfilename = projectConfig . getFilePath ( ProjectFilePath : : data_pokemon_gfx ) ;
2020-04-08 05:42:38 +01:00
fileWatcher . addPath ( root + " / " + incfilename ) ;
2023-12-10 08:49:54 +00:00
const QMap < QString , QString > iconIncbins = parser . readCIncbinMulti ( incfilename ) ;
// Read species constants. If this fails we can get them from the icon table (but we shouldn't rely on it).
2024-09-03 01:37:41 +01:00
const QStringList regexList = { QString ( " \\ b%1 " ) . arg ( projectConfig . getIdentifier ( ProjectIdentifier : : define_species_prefix ) ) } ;
2023-12-10 08:49:54 +00:00
const QString constantsFilename = projectConfig . getFilePath ( ProjectFilePath : : constants_species ) ;
fileWatcher . addPath ( root + " / " + constantsFilename ) ;
2024-08-28 21:06:28 +01:00
QStringList speciesNames = parser . readCDefineNames ( constantsFilename , regexList ) ;
2023-12-11 21:40:40 +00:00
if ( speciesNames . isEmpty ( ) )
speciesNames = monIconNames . keys ( ) ;
2023-12-10 08:49:54 +00:00
2023-12-18 07:19:12 +00:00
// For each species, use the information gathered above to find the icon image.
2023-12-10 08:49:54 +00:00
bool missingIcons = false ;
for ( auto species : speciesNames ) {
QString path = QString ( ) ;
if ( monIconNames . contains ( species ) & & iconIncbins . contains ( monIconNames . value ( species ) ) ) {
// We have the icon filepath from the icon table
path = QString ( " %1/%2 " ) . arg ( root ) . arg ( this - > fixGraphicPath ( iconIncbins [ monIconNames . value ( species ) ] ) ) ;
} else {
// Failed to read icon filepath from the icon table, check filepaths where icons are normally located.
// Try to use the icon name (if we have it) to determine the directory, then try the species name.
// The name permuting is overkill, but it's making up for some of the fragility in the way we find icon paths.
QStringList possibleDirNames ;
if ( monIconNames . contains ( species ) ) {
// Ex: For 'gMonIcon_QuestionMark' try 'question_mark'
static const QRegularExpression re ( " ([a-z]) ( [ A - Z0 - 9 ] ) " ) ;
QString iconName = monIconNames . value ( species ) ;
iconName = iconName . mid ( iconName . indexOf ( " _ " ) + 1 ) ; // jump past prefix ('gMonIcon')
possibleDirNames . append ( iconName . replace ( re , " \\ 1_ \\ 2 " ) . toLower ( ) ) ;
}
// Ex: For 'SPECIES_FOO_BAR_BAZ' try 'foo_bar_baz'
possibleDirNames . append ( species . mid ( 8 ) . toLower ( ) ) ;
// Permute paths with underscores.
// Ex: Try 'foo_bar/baz', 'foo/bar_baz', 'foobarbaz', 'foo_bar', and 'foo'
QStringList permutedNames ;
for ( auto dir : possibleDirNames ) {
if ( ! dir . contains ( " _ " ) ) continue ;
for ( int i = dir . indexOf ( " _ " ) ; i > - 1 ; i = dir . indexOf ( " _ " , i + 1 ) ) {
QString temp = dir ;
permutedNames . prepend ( temp . replace ( i , 1 , " / " ) ) ;
permutedNames . append ( dir . left ( i ) ) ; // Prepend the others so the most generic name ('foo') ends up last
}
permutedNames . prepend ( dir . remove ( " _ " ) ) ;
}
possibleDirNames . append ( permutedNames ) ;
possibleDirNames . removeDuplicates ( ) ;
for ( auto dir : possibleDirNames ) {
if ( dir . isEmpty ( ) ) continue ;
const QString stdPath = QString ( " %1/%2%3/icon.png " )
. arg ( root )
. arg ( projectConfig . getFilePath ( ProjectFilePath : : pokemon_gfx ) )
. arg ( dir ) ;
if ( QFile : : exists ( stdPath ) ) {
// Icon found at a normal filepath
path = stdPath ;
break ;
}
}
if ( path . isEmpty ( ) & & projectConfig . getPokemonIconPath ( species ) . isEmpty ( ) ) {
// Failed to find icon, this species will use a placeholder icon.
logWarn ( QString ( " Failed to find Pokémon icon for '%1' " ) . arg ( species ) ) ;
missingIcons = true ;
}
}
this - > speciesToIconPath . insert ( species , path ) ;
2019-06-13 03:20:28 +01:00
}
2023-12-10 08:49:54 +00:00
// Logging this alongside every warning (if there are multiple) is obnoxious, just do it once at the end.
if ( missingIcons ) logInfo ( " Pokémon icon filepaths can be specified under 'Options->Project Settings' " ) ;
2020-02-12 15:13:58 +00:00
return true ;
2019-06-13 03:20:28 +01:00
}
2018-09-27 00:33:08 +01:00
int Project : : getNumTilesPrimary ( )
{
return Project : : num_tiles_primary ;
}
int Project : : getNumTilesTotal ( )
{
return Project : : num_tiles_total ;
}
int Project : : getNumMetatilesPrimary ( )
{
return Project : : num_metatiles_primary ;
}
int Project : : getNumMetatilesTotal ( )
{
2023-12-13 07:30:22 +00:00
return Block : : getMaxMetatileId ( ) + 1 ;
2018-09-27 00:33:08 +01:00
}
int Project : : getNumPalettesPrimary ( )
{
return Project : : num_pals_primary ;
}
int Project : : getNumPalettesTotal ( )
{
return Project : : num_pals_total ;
}
2020-05-16 21:57:03 +01:00
int Project : : getMaxMapDataSize ( )
{
return Project : : max_map_data_size ;
}
int Project : : getMapDataSize ( int width , int height )
{
// + 15 and + 14 come from fieldmap.c in pokeruby/pokeemerald/pokefirered.
return ( width + 15 ) * ( height + 14 ) ;
}
2024-11-12 18:44:04 +00:00
int Project : : getDefaultMapDimension ( )
2020-05-16 21:57:03 +01:00
{
2024-11-12 18:44:04 +00:00
return Project : : default_map_dimension ;
2020-05-16 21:57:03 +01:00
}
int Project : : getMaxMapWidth ( )
{
return ( getMaxMapDataSize ( ) / ( 1 + 14 ) ) - 15 ;
}
int Project : : getMaxMapHeight ( )
{
return ( getMaxMapDataSize ( ) / ( 1 + 15 ) ) - 14 ;
}
2020-05-16 23:52:46 +01:00
bool Project : : mapDimensionsValid ( int width , int height ) {
return getMapDataSize ( width , height ) < = getMaxMapDataSize ( ) ;
}
2020-05-16 21:57:03 +01:00
// Get largest possible square dimensions for a map up to maximum of 20x20 (arbitrary)
bool Project : : calculateDefaultMapSize ( ) {
int max = getMaxMapDataSize ( ) ;
if ( max > = getMapDataSize ( 20 , 20 ) ) {
2024-11-12 18:44:04 +00:00
default_map_dimension = 20 ;
2020-05-16 21:57:03 +01:00
} else if ( max > = getMapDataSize ( 1 , 1 ) ) {
// Below equation derived from max >= (x + 15) * (x + 14)
2020-05-17 00:06:52 +01:00
// x^2 + 29x + (210 - max), then complete the square and simplify
2024-11-12 18:44:04 +00:00
default_map_dimension = qFloor ( ( qSqrt ( 4 * getMaxMapDataSize ( ) + 1 ) - 29 ) / 2 ) ;
2020-05-16 21:57:03 +01:00
} else {
2023-12-18 07:19:12 +00:00
logError ( QString ( " '%1' of %2 is too small to support a 1x1 map. Must be at least %3. " )
. arg ( projectConfig . getIdentifier ( ProjectIdentifier : : define_map_size ) )
2020-05-16 21:57:03 +01:00
. arg ( max )
. arg ( getMapDataSize ( 1 , 1 ) ) ) ;
return false ;
}
return true ;
}
2020-07-10 21:34:42 +01:00
int Project : : getMaxObjectEvents ( )
{
return Project : : max_object_events ;
}
2022-11-28 02:22:01 +00:00
2023-12-18 07:19:12 +00:00
QString Project : : getDynamicMapDefineName ( ) {
const QString prefix = projectConfig . getIdentifier ( ProjectIdentifier : : define_map_prefix ) ;
return prefix + projectConfig . getIdentifier ( ProjectIdentifier : : define_map_dynamic ) ;
}
2024-01-03 17:13:53 +00:00
// If the provided filepath is an absolute path to an existing file, return filepath.
// If not, and the provided filepath is a relative path from the project dir to an existing file, return the relative path.
// Otherwise return empty string.
QString Project : : getExistingFilepath ( QString filepath ) {
if ( filepath . isEmpty ( ) | | QFile : : exists ( filepath ) )
return filepath ;
2024-07-15 21:14:38 +01:00
filepath = QDir : : cleanPath ( projectConfig . projectDir + QDir : : separator ( ) + filepath ) ;
2024-01-03 17:13:53 +00:00
if ( QFile : : exists ( filepath ) )
return filepath ;
return QString ( ) ;
}
2023-12-16 08:36:26 +00:00
// The values of some config fields can limit the values of other config fields
// (for example, metatile attributes size limits the metatile attribute masks).
// Others depend on information in the project (for example the default metatile ID
// can be limited by fieldmap defines)
// Once we've read data from the project files we can adjust these accordingly.
void Project : : applyParsedLimits ( ) {
uint32_t maxMask = Metatile : : getMaxAttributesMask ( ) ;
2024-07-15 21:14:38 +01:00
projectConfig . metatileBehaviorMask & = maxMask ;
projectConfig . metatileTerrainTypeMask & = maxMask ;
projectConfig . metatileEncounterTypeMask & = maxMask ;
projectConfig . metatileLayerTypeMask & = maxMask ;
2023-12-16 08:36:26 +00:00
Block : : setLayout ( ) ;
Metatile : : setLayout ( this ) ;
Project : : num_metatiles_primary = qMin ( Project : : num_metatiles_primary , Block : : getMaxMetatileId ( ) + 1 ) ;
2024-07-15 21:14:38 +01:00
projectConfig . defaultMetatileId = qMin ( projectConfig . defaultMetatileId , Block : : getMaxMetatileId ( ) ) ;
projectConfig . defaultElevation = qMin ( projectConfig . defaultElevation , Block : : getMaxElevation ( ) ) ;
projectConfig . defaultCollision = qMin ( projectConfig . defaultCollision , Block : : getMaxCollision ( ) ) ;
projectConfig . collisionSheetHeight = qMin ( projectConfig . collisionSheetHeight , Block : : getMaxElevation ( ) + 1 ) ;
projectConfig . collisionSheetWidth = qMin ( projectConfig . collisionSheetWidth , Block : : getMaxCollision ( ) + 1 ) ;
2023-12-16 08:36:26 +00:00
}
2024-10-30 01:51:05 +00:00
bool Project : : hasUnsavedChanges ( ) {
if ( this - > hasUnsavedDataChanges )
return true ;
// Check layouts for unsaved changes
for ( auto i = this - > mapLayouts . constBegin ( ) ; i ! = this - > mapLayouts . constEnd ( ) ; i + + ) {
2024-10-30 02:18:16 +00:00
auto layout = i . value ( ) ;
if ( layout & & layout - > hasUnsavedChanges ( ) )
2024-10-30 01:51:05 +00:00
return true ;
}
// Check loaded maps for unsaved changes
for ( auto i = this - > mapCache . constBegin ( ) ; i ! = this - > mapCache . constEnd ( ) ; i + + ) {
2024-10-30 02:18:16 +00:00
auto map = i . value ( ) ;
if ( map & & map - > hasUnsavedChanges ( ) )
2024-10-30 01:51:05 +00:00
return true ;
}
return false ;
}