From 43d0dc1e7b7e832bd260d290157cd48956ccee41 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 9 Sep 2022 17:41:50 -0400 Subject: [PATCH] Rewrite heal location data reading --- include/core/events.h | 6 +- include/core/heallocation.h | 8 +- include/project.h | 4 +- src/core/heallocation.cpp | 4 +- src/project.cpp | 141 +++++++++++++++++++++++------------- 5 files changed, 103 insertions(+), 60 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index 51e82340..912fe0c7 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -626,15 +626,15 @@ public: void setRespawnMap(QString newRespawnMap) { this->respawnMap = newRespawnMap; } QString getRespawnMap() { return this->respawnMap; } - void setRespawnNPC(uint16_t newRespawnNPC) { this->respawnNPC = newRespawnNPC; } - uint16_t getRespawnNPC() { return this->respawnNPC; } + void setRespawnNPC(uint8_t newRespawnNPC) { this->respawnNPC = newRespawnNPC; } + uint8_t getRespawnNPC() { return this->respawnNPC; } private: int index = -1; QString locationName; QString idName; QString respawnMap; - uint16_t respawnNPC = 0; + uint8_t respawnNPC = 0; }; diff --git a/include/core/heallocation.h b/include/core/heallocation.h index fbb93308..16873dc6 100644 --- a/include/core/heallocation.h +++ b/include/core/heallocation.h @@ -11,17 +11,17 @@ class HealLocation { public: HealLocation()=default; - HealLocation(QString, QString, int, uint16_t, uint16_t, QString = "", uint16_t = 0); + HealLocation(QString, QString, int, int16_t, int16_t, QString = "", uint8_t = 0); friend QDebug operator<<(QDebug debug, const HealLocation &hl); public: QString idName; QString mapName; int index; - uint16_t x; - uint16_t y; + int16_t x; + int16_t y; QString respawnMap; - uint16_t respawnNPC; + uint8_t respawnNPC; static HealLocation fromEvent(Event *); }; diff --git a/include/project.h b/include/project.h index 29486ba4..0678b811 100644 --- a/include/project.h +++ b/include/project.h @@ -49,6 +49,7 @@ public: QStringList mapNames; QMap miscConstants; QList healLocations; + QMap healLocationNameToValue; QMap mapConstantsToMapNames; QMap mapNamesToMapConstants; QStringList mapLayoutsTable; @@ -97,7 +98,7 @@ public: bool isConst; }; DataQualifiers getDataQualifiers(QString, QString); - QMap dataQualifiers; + DataQualifiers healLocationDataQualifiers; QMap mapCache; Map* loadMap(QString); @@ -187,6 +188,7 @@ public: bool readBgEventFacingDirections(); bool readTrainerTypes(); bool readMetatileBehaviors(); + bool readHealLocationConstants(); bool readHealLocations(); bool readMiscellaneousConstants(); bool readEventScriptLabels(); diff --git a/src/core/heallocation.cpp b/src/core/heallocation.cpp index 7dc59a19..0d5e287c 100644 --- a/src/core/heallocation.cpp +++ b/src/core/heallocation.cpp @@ -4,8 +4,8 @@ #include "map.h" HealLocation::HealLocation(QString id, QString map, - int i, uint16_t x, uint16_t y, - QString respawnMap, uint16_t respawnNPC) { + int i, int16_t x, int16_t y, + QString respawnMap, uint8_t respawnNPC) { this->idName = id; this->mapName = map; this->index = i; diff --git a/src/project.cpp b/src/project.cpp index 1f148305..5e092c66 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -789,10 +789,10 @@ void Project::saveHealLocationStruct(Map *map) { arrayName = "sHealLocations"; } - QString data_text = QString("%1%2struct HealLocation %3[] =\n{\n") - .arg(dataQualifiers.value("heal_locations").isStatic ? "static " : "") - .arg(dataQualifiers.value("heal_locations").isConst ? "const " : "") - .arg(arrayName); + const QString qualifiers = QString(healLocationDataQualifiers.isStatic ? "static " : "") + + QString(healLocationDataQualifiers.isConst ? "const " : ""); + + QString data_text = QString("%1struct HealLocation %2[] =\n{\n").arg(qualifiers).arg(arrayName); QString constants_text = QString("#ifndef GUARD_CONSTANTS_HEAL_LOCATIONS_H\n"); constants_text += QString("#define GUARD_CONSTANTS_HEAL_LOCATIONS_H\n\n"); @@ -801,7 +801,7 @@ void Project::saveHealLocationStruct(Map *map) { QSet healLocationsUnique; // set healLocationsDupes and healLocationsUnique - for (auto it = healLocations.begin(); it != healLocations.end(); it++) { + for (auto it = this->healLocations.begin(); it != this->healLocations.end(); it++) { HealLocation loc = *it; QString xname = loc.idName; if (healLocationsUnique.contains(xname)) { @@ -814,12 +814,12 @@ void Project::saveHealLocationStruct(Map *map) { if (map->events[Event::Group::Heal].length() > 0) { for (Event *healEvent : map->events[Event::Group::Heal]) { HealLocation hl = HealLocation::fromEvent(healEvent); - healLocations[hl.index - 1] = hl; + this->healLocations[hl.index - 1] = hl; } } int i = 1; - for (auto map_in : healLocations) { + for (auto map_in : this->healLocations) { // add numbered suffix for duplicate constants if (healLocationsDupes.keys().contains(map_in.idName)) { QString duplicateName = map_in.idName; @@ -851,10 +851,8 @@ void Project::saveHealLocationStruct(Map *map) { } if (projectConfig.getHealLocationRespawnDataEnabled()) { // Save second array (map where player respawns for each heal location) - data_text += QString("};\n\n%1%2u16 sWhiteoutRespawnHealCenterMapIdxs[][2] =\n{\n") - .arg(dataQualifiers.value("heal_locations").isStatic ? "static " : "") - .arg(dataQualifiers.value("heal_locations").isConst ? "const " : ""); - for (auto map_in : healLocations) { + data_text += QString("};\n\n%1u16 sWhiteoutRespawnHealCenterMapIdxs[][2] =\n{\n").arg(qualifiers); + for (auto map_in : this->healLocations) { data_text += QString(" [%1%2 - 1] = {MAP_GROUP(%3), MAP_NUM(%3)},\n") .arg(constantPrefix) .arg(map_in.idName) @@ -862,10 +860,8 @@ void Project::saveHealLocationStruct(Map *map) { } // Save third array (object id of NPC player speaks to upon respawning for each heal location) - data_text += QString("};\n\n%1%2u8 sWhiteoutRespawnHealerNpcIds[] =\n{\n") - .arg(dataQualifiers.value("heal_locations").isStatic ? "static " : "") - .arg(dataQualifiers.value("heal_locations").isConst ? "const " : ""); - for (auto map_in : healLocations) { + data_text += QString("};\n\n%1u8 sWhiteoutRespawnHealerNpcIds[] =\n{\n").arg(qualifiers); + for (auto map_in : this->healLocations) { data_text += QString(" [%1%2 - 1] = %3,\n") .arg(constantPrefix) .arg(map_in.idName) @@ -2002,51 +1998,96 @@ bool Project::readRegionMapSections() { return true; } +// Read the constants to preserve any "unused" heal locations when writing the file later +bool Project::readHealLocationConstants() { + this->healLocationNameToValue.clear(); + QStringList prefixes{ "\\bSPAWN_", "\\bHEAL_LOCATION_" }; + QString constantsFilename = "include/constants/heal_locations.h"; + fileWatcher.addPath(root + "/" + constantsFilename); + this->healLocationNameToValue = parser.readCDefines(constantsFilename, prefixes); + // No need to check if empty, not finding any heal location constants is ok + return true; +} + bool Project::readHealLocations() { - dataQualifiers.clear(); - healLocations.clear(); + this->healLocationDataQualifiers = {}; + this->healLocations.clear(); + + if (!this->readHealLocationConstants()) + return false; + QString filename = projectConfig.getFilePath(ProjectFilePath::data_heal_locations); fileWatcher.addPath(root + "/" + filename); QString text = parser.readTextFile(root + "/" + filename); + + // Strip comments text.replace(QRegularExpression("//.*?(\r\n?|\n)|/\\*.*?\\*/", QRegularExpression::DotMatchesEverythingOption), ""); - if (projectConfig.getHealLocationRespawnDataEnabled()) { - dataQualifiers.insert("heal_locations", getDataQualifiers(text, "sSpawnPoints")); - QRegularExpression spawnRegex("SPAWN_(?[A-Za-z0-9_]+)\\s*- 1\\]\\s* = \\{MAP_GROUP[\\(\\s]+(?[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\2)[\\s\\)]+,\\s*(?[0-9A-Fa-fx]+),\\s*(?[0-9A-Fa-fx]+)"); - QRegularExpression respawnMapRegex("SPAWN_(?[A-Za-z0-9_]+)\\s*- 1\\]\\s* = \\{MAP_GROUP[\\(\\s]+(?[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\2)[\\s\\)]+}"); - QRegularExpression respawnNPCRegex("SPAWN_(?[A-Za-z0-9_]+)\\s*- 1\\]\\s* = (?[0-9]+)"); - QRegularExpressionMatchIterator spawns = spawnRegex.globalMatch(text); - QRegularExpressionMatchIterator respawnMaps = respawnMapRegex.globalMatch(text); - QRegularExpressionMatchIterator respawnNPCs = respawnNPCRegex.globalMatch(text); + bool respawnEnabled = projectConfig.getHealLocationRespawnDataEnabled(); - // This would be better if idName was used to look up data from the other two arrays - // As it is, element total and order needs to be the same in the 3 arrays to work. This should always be true though - for (int i = 1; spawns.hasNext(); i++) { - QRegularExpressionMatch spawn = spawns.next(); - QRegularExpressionMatch respawnMap = respawnMaps.next(); - QRegularExpressionMatch respawnNPC = respawnNPCs.next(); - QString idName = spawn.captured("id"); - QString mapName = spawn.captured("map"); - QString respawnMapName = respawnMap.captured("map"); - unsigned x = spawn.captured("x").toUShort(); - unsigned y = spawn.captured("y").toUShort(); - unsigned npc = respawnNPC.captured("npc").toUShort(); - healLocations.append(HealLocation(idName, mapName, i, x, y, respawnMapName, npc)); - } - } else { - dataQualifiers.insert("heal_locations", getDataQualifiers(text, "sHealLocations")); + // Get data qualifiers for the location data table + QString tableName = respawnEnabled ? "sSpawnPoints" : "sHealLocations"; + this->healLocationDataQualifiers = this->getDataQualifiers(text, tableName); - QRegularExpression regex("HEAL_LOCATION_(?[A-Za-z0-9_]+)\\s*- 1\\]\\s* = \\{MAP_GROUP[\\(\\s]+(?[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\2)[\\s\\)]+,\\s*(?[0-9A-Fa-fx]+),\\s*(?[0-9A-Fa-fx]+)"); - QRegularExpressionMatchIterator iter = regex.globalMatch(text); - for (int i = 1; iter.hasNext(); i++) { - QRegularExpressionMatch match = iter.next(); - QString idName = match.captured("id"); + // Create regex pattern for e.g. SPAWN_PALLET_TOWN or HEAL_LOCATION_PETALBURG_CITY + // TODO: Make this config agnostic, e.g. either should be valid + QString prefix = respawnEnabled ? "SPAWN_" : "HEAL_LOCATION_"; + QRegularExpression constantsExpr = QRegularExpression(QString("%1[A-Za-z0-9_]+").arg(prefix)); + + // 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. + QStringList constants = QStringList(); + QRegularExpressionMatchIterator constantsMatch = constantsExpr.globalMatch(text); + while (constantsMatch.hasNext()) + constants << constantsMatch.next().captured(); + constants.removeDuplicates(); + + // Pattern for a map value pair. ex: "MAP_GROUP(PALLET_TOWN), MAP_NUM(PALLET_TOWN)" + const QString mapPattern = "MAP_GROUP[\\(\\s]+(?[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\1)[\\s\\)]+"; + // Pattern for an x, y number pair + const QString coordPattern = "\\s*(?[0-9A-Fa-fx]+),\\s*(?[0-9A-Fa-fx]+)"; + + for (const auto idName : constants) { + // Create regex pattern for e.g. "SPAWN_PALLET_TOWN - 1] = " + const QString initializerPattern = QString("%1\\s*- 1\\]\\s* = ").arg(idName); + + // 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"); - unsigned x = match.captured("x").toUShort(); - unsigned y = match.captured("y").toUShort(); - healLocations.append(HealLocation(idName, mapName, i, x, y)); + int x = match.captured("x").toInt(); + int y = match.captured("y").toInt(); + healLocation = HealLocation(idName, mapName, this->healLocations.size() + 1, x, y); + } else { + // If the heal location is missing from the location table it can be skipped. + // Even if it has data in the other tables, Porymap would never display it. + // TODO: Don't skip these, preserve their data + continue; } + + // 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(?[0-9]+)").arg(initializerPattern)); + match = respawnNPCRegex.match(text); + if (match.hasMatch()) + healLocation.respawnNPC = match.captured("npc").toInt(); + } + + this->healLocations.append(healLocation); } + // No need to check if empty, not finding any heal locations is ok return true; } @@ -2463,7 +2504,7 @@ void Project::saveMapHealEvents(Map *map) { if (map->events[Event::Group::Heal].length() > 0) { for (Event *healEvent : map->events[Event::Group::Heal]) { HealLocation hl = HealLocation::fromEvent(healEvent); - healLocations[hl.index - 1] = hl; + this->healLocations[hl.index - 1] = hl; } } saveHealLocationStruct(map);