diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index a088d87e..179392dd 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -39,7 +39,7 @@ 0 0 559 - 568 + 589 @@ -66,6 +66,16 @@ + + + + <html><head/><body><p>If enabled, Porymap will not discard data like &quot;connections_include_order&quot; or &quot;name_clone&quot;, which serve no purpose other than recreating the original game.</p></body></html> + + + Preserve data only needed to match the original game + + + @@ -1084,7 +1094,7 @@ 0 0 559 - 788 + 840 diff --git a/include/config.h b/include/config.h index eb9c9ac3..6746a0f4 100644 --- a/include/config.h +++ b/include/config.h @@ -323,6 +323,7 @@ public: this->tilesetsHaveCallback = true; this->tilesetsHaveIsCompressed = true; this->setTransparentPixelsBlack = true; + this->preserveMatchingOnlyData = false; this->filePaths.clear(); this->eventIconPaths.clear(); this->pokemonIconPaths.clear(); @@ -389,6 +390,7 @@ public: bool tilesetsHaveCallback; bool tilesetsHaveIsCompressed; bool setTransparentPixelsBlack; + bool preserveMatchingOnlyData; int metatileAttributesSize; uint32_t metatileBehaviorMask; uint32_t metatileTerrainTypeMask; diff --git a/include/core/events.h b/include/core/events.h index 85116860..7c74514f 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -10,6 +10,7 @@ #include #include "orderedjson.h" +#include "parseutil.h" class Project; @@ -139,15 +140,14 @@ public: Event::Type getEventType() const { return this->eventType; } virtual OrderedJson::object buildEventJson(Project *project) = 0; - virtual bool loadFromJson(const QJsonObject &json, Project *project) = 0; + virtual bool loadFromJson(QJsonObject json, Project *project) = 0; virtual void setDefaultValues(Project *project); virtual QSet getExpectedFields() = 0; - void readCustomAttributes(const QJsonObject &json); - void addCustomAttributesTo(OrderedJson::object *obj) const; - const QMap getCustomAttributes() const { return this->customAttributes; } - void setCustomAttributes(const QMap newCustomAttributes) { this->customAttributes = newCustomAttributes; } + + QJsonObject getCustomAttributes() const { return this->customAttributes; } + void setCustomAttributes(const QJsonObject &newCustomAttributes) { this->customAttributes = newCustomAttributes; } virtual void loadPixmap(Project *project); @@ -191,12 +191,16 @@ protected: // When deleting events like this we want to warn the user that the #define may also be deleted. QString idName; - QMap customAttributes; + QJsonObject customAttributes; QPixmap pixmap; DraggablePixmapItem *pixmapItem = nullptr; QPointer eventFrame; + + static QString readString(QJsonObject *object, const QString &key) { return ParseUtil::jsonToQString(object->take(key)); } + static int readInt(QJsonObject *object, const QString &key) { return ParseUtil::jsonToInt(object->take(key)); } + static bool readBool(QJsonObject *object, const QString &key) { return ParseUtil::jsonToBool(object->take(key)); } }; @@ -219,7 +223,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -286,7 +290,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -324,7 +328,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -361,7 +365,7 @@ public: virtual EventFrame *createEventFrame() override = 0; virtual OrderedJson::object buildEventJson(Project *project) override = 0; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override = 0; + virtual bool loadFromJson(QJsonObject json, Project *project) override = 0; virtual void setDefaultValues(Project *project) override = 0; @@ -389,7 +393,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -429,7 +433,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -460,7 +464,7 @@ public: virtual EventFrame *createEventFrame() override = 0; virtual OrderedJson::object buildEventJson(Project *project) override = 0; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override = 0; + virtual bool loadFromJson(QJsonObject json, Project *project) override = 0; virtual void setDefaultValues(Project *project) override = 0; @@ -487,7 +491,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -522,7 +526,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -567,7 +571,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -599,12 +603,15 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &, Project *) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; + void setHostMapName(QString newHostMapName) { this->hostMapName = newHostMapName; } + QString getHostMapName() const; + void setRespawnMapName(QString newRespawnMapName) { this->respawnMapName = newRespawnMapName; } QString getRespawnMapName() const { return this->respawnMapName; } @@ -614,6 +621,7 @@ public: private: QString respawnMapName; QString respawnNPC; + QString hostMapName; // Only needed if the host map fails to load. }; diff --git a/include/core/map.h b/include/core/map.h index 9a4c2185..aaf5b8b7 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -100,8 +100,8 @@ public: bool hasUnsavedChanges() const; void pruneEditHistory(); - void setCustomAttributes(const QMap &attributes) { m_customAttributes = attributes; } - QMap customAttributes() const { return m_customAttributes; } + void setCustomAttributes(const QJsonObject &attributes) { m_customAttributes = attributes; } + QJsonObject customAttributes() const { return m_customAttributes; } private: QString m_name; @@ -110,7 +110,7 @@ private: QString m_sharedScriptsMap = ""; QStringList m_scriptsFileLabels; - QMap m_customAttributes; + QJsonObject m_customAttributes; MapHeader *m_header = nullptr; Layout *m_layout = nullptr; diff --git a/include/core/mapconnection.h b/include/core/mapconnection.h index 496e884f..5df071bd 100644 --- a/include/core/mapconnection.h +++ b/include/core/mapconnection.h @@ -5,6 +5,7 @@ #include #include #include +#include class Project; class Map; @@ -34,6 +35,9 @@ public: int offset() const { return m_offset; } void setOffset(int offset, bool mirror = true); + QJsonObject customData() const { return m_customData; } + void setCustomData(const QJsonObject &customData) { m_customData = customData; } + MapConnection* findMirror(); MapConnection* createMirror(); @@ -55,6 +59,7 @@ private: QString m_targetMapName; QString m_direction; int m_offset; + QJsonObject m_customData; void markMapEdited(); Map* getMap(const QString& mapName) const; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 3b6bbf12..40a3a035 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -42,6 +42,8 @@ public: Tileset *tileset_primary = nullptr; Tileset *tileset_secondary = nullptr; + QJsonObject customData; + Blockdata blockdata; QImage image; diff --git a/include/core/parseutil.h b/include/core/parseutil.h index 4c19a27c..e02d0504 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -58,7 +58,7 @@ public: QMap readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); QMap readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); - tsl::ordered_map> readCStructs(const QString &, const QString & = "", const QHash& = {}); + OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); bool tryParseJsonFile(QJsonDocument *out, const QString &filepath, QString *error = nullptr); diff --git a/include/core/regionmap.h b/include/core/regionmap.h index 18362663..c8afb1b2 100644 --- a/include/core/regionmap.h +++ b/include/core/regionmap.h @@ -56,8 +56,8 @@ public: bool loadLayout(poryjson::Json); bool loadEntries(); - void setEntries(QMap *entries) { this->region_map_entries = entries; } - void setEntries(const QMap &entries) { *(this->region_map_entries) = entries; } + void setEntries(QHash *entries) { this->region_map_entries = entries; } + void setEntries(const QHash &entries) { *(this->region_map_entries) = entries; } void clearEntries() { this->region_map_entries->clear(); } MapSectionEntry getEntry(QString section); void setEntry(QString section, MapSectionEntry entry); @@ -151,7 +151,7 @@ signals: void mapNeedsDisplaying(); private: - QMap *region_map_entries = nullptr; + QHash *region_map_entries = nullptr; QString alias = ""; diff --git a/include/core/regionmapeditcommands.h b/include/core/regionmapeditcommands.h index 05b12bc3..e47fdb7b 100644 --- a/include/core/regionmapeditcommands.h +++ b/include/core/regionmapeditcommands.h @@ -153,7 +153,7 @@ private: /// ClearEntries class ClearEntries : public QUndoCommand { public: - ClearEntries(RegionMap *map, QMap, QUndoCommand *parent = nullptr); + ClearEntries(RegionMap *map, QHash, QUndoCommand *parent = nullptr); void undo() override; void redo() override; @@ -163,7 +163,7 @@ public: private: RegionMap *map; - QMap entries; + QHash entries; }; #endif // REGIONMAPEDITCOMMANDS_H diff --git a/include/core/wildmoninfo.h b/include/core/wildmoninfo.h index 3c94fb17..a3665b7d 100644 --- a/include/core/wildmoninfo.h +++ b/include/core/wildmoninfo.h @@ -3,7 +3,7 @@ #define GUARD_WILDMONINFO_H #include -#include "orderedmap.h" +#include "orderedjson.h" class WildPokemon { public: @@ -13,22 +13,26 @@ public: int minLevel; int maxLevel; QString species; + OrderedJson::object customData; }; struct WildMonInfo { bool active = false; int encounterRate = 0; QVector wildPokemon; + OrderedJson::object customData; }; struct WildPokemonHeader { - tsl::ordered_map wildMons; + OrderedMap wildMons; + OrderedJson::object customData; }; struct EncounterField { QString name; // Ex: "fishing_mons" QVector encounterRates; - tsl::ordered_map> groups; // Ex: "good_rod", {2, 3, 4} + OrderedMap> groups; // Ex: "good_rod", {2, 3, 4} + OrderedJson::object customData; }; typedef QVector EncounterFields; diff --git a/include/lib/orderedjson.h b/include/lib/orderedjson.h index 544112f1..cb5139d3 100644 --- a/include/lib/orderedjson.h +++ b/include/lib/orderedjson.h @@ -99,7 +99,7 @@ public: // Array and object typedefs typedef QVector array; - typedef tsl::ordered_map object; + typedef OrderedMap object; // Constructors for the various types of JSON value. Json() noexcept; // NUL @@ -132,7 +132,22 @@ public: int>::type = 0> Json(const V & v) : Json(array(v.begin(), v.end())) {} - static const Json fromQJsonValue(QJsonValue value); + static Json fromQJsonValue(const QJsonValue &value); + + static void append(Json::array *array, const QJsonArray &addendum) { + for (const auto &i : addendum) array->push_back(fromQJsonValue(i)); + } + static void append(Json::array *array, const Json::array &addendum) { + for (const auto &i : addendum) array->push_back(i); + } + static void append(Json::object *object, const QJsonObject &addendum) { + for (auto it = addendum.constBegin(); it != addendum.constEnd(); it++) + (*object)[it.key()] = fromQJsonValue(it.value()); + } + static void append(Json::object *object, const Json::object &addendum) { + for (auto it = addendum.cbegin(); it != addendum.cend(); it++) + (*object)[it.key()] = it.value(); + } // This prevents Json(some_pointer) from accidentally producing a bool. Use // Json(bool(some_pointer)) if that behavior is desired. diff --git a/include/lib/orderedmap.h b/include/lib/orderedmap.h index 40882d9f..33fcfc50 100644 --- a/include/lib/orderedmap.h +++ b/include/lib/orderedmap.h @@ -1977,6 +1977,14 @@ public: size_type erase(const K& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } + + // Naive solution for take, should probably be replaced with one that does a single lookup and no unnecessary insertion. + // We want to mirror the behavior of QMap::take, which returns a default-constructed value if the key is not present. + T take(const key_type& key) { + typename ValueSelect::value_type value = m_ht[key]; + m_ht.erase(key); + return value; + } @@ -2404,4 +2412,7 @@ private: } // end namespace tsl +template +using OrderedMap = tsl::ordered_map; + #endif diff --git a/include/project.h b/include/project.h index 9a031c0d..c5aed0a3 100644 --- a/include/project.h +++ b/include/project.h @@ -62,7 +62,6 @@ public: QStringList mapSectionIdNames; QMap encounterTypeToName; QMap terrainTypeToName; - QMap regionMapEntries; QMap> metatileLabelsMap; QMap unusedMetatileLabels; QMap metatileBehaviorMap; @@ -72,7 +71,6 @@ public: QSet modifiedFiles; bool usingAsmTilesets; QSet disabledSettingsNames; - QSet topLevelMapFields; int pokemonMinLevel; int pokemonMaxLevel; int maxEncounterRate; @@ -144,12 +142,11 @@ public: QString getNewHealLocationName(const Map* map) const; bool readWildMonData(); - tsl::ordered_map> wildMonData; + OrderedMap> wildMonData; QString wildMonTableName; QVector wildMonFields; QVector encounterGroupLabels; - QVector extraEncounterGroups; bool readSpeciesIconPaths(); QString getDefaultSpeciesIconPath(const QString &species); @@ -157,15 +154,14 @@ public: bool addNewMapsec(const QString &idName, const QString &displayName = QString()); void removeMapsec(const QString &idName); - QString getMapsecDisplayName(const QString &idName) const { return this->mapSectionDisplayNames.value(idName); } + QString getMapsecDisplayName(const QString &idName) const { return this->locationData.value(idName).displayName; } void setMapsecDisplayName(const QString &idName, const QString &displayName); bool hasUnsavedChanges(); bool hasUnsavedDataChanges = false; - void initTopLevelMapFields(); bool readMapJson(const QString &mapName, QJsonDocument * out); - bool loadMapEvent(Map *map, const QJsonObject &json, Event::Type defaultType = Event::Type::None); + bool loadMapEvent(Map *map, QJsonObject json, Event::Type defaultType = Event::Type::None); bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); @@ -240,6 +236,11 @@ public: static QString getExistingFilepath(QString filepath); void applyParsedLimits(); + void setRegionMapEntries(const QHash &entries); + QHash getRegionMapEntries() const; + + QSet getTopLevelMapFields() const; + static QString getEmptyMapDefineName(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); @@ -262,12 +263,20 @@ public: static QString getMapGroupPrefix(); private: - QHash mapSectionDisplayNames; QMap modifiedFileTimestamps; QMap facingDirections; QHash speciesToIconPath; QHash maps; + // Fields for preserving top-level JSON data that Porymap isn't expecting. + QJsonObject customLayoutsData; + QJsonObject customMapSectionsData; + QJsonObject customMapGroupsData; + QJsonObject customHealLocationsData; + OrderedJson::object customWildMonData; + OrderedJson::object customWildMonGroupData; + OrderedJson::array extraEncounterGroups; + // Maps/layouts represented in these sets have been fully loaded from the project. // If a valid map name / layout id is not in these sets, a Map / Layout object exists // for it in Project::maps / Project::mapLayouts, but it has been minimally populated @@ -291,6 +300,15 @@ private: }; QMap eventGraphicsMap; + // The extra data that can be associated with each MAPSEC name. + struct LocationData + { + MapSectionEntry map; + QString displayName; + QJsonObject custom; + }; + QHash locationData; + void updateLayout(Layout *); void setNewLayoutBlockdata(Layout *layout); diff --git a/include/ui/customattributestable.h b/include/ui/customattributestable.h index 21cac4de..780f441d 100644 --- a/include/ui/customattributestable.h +++ b/include/ui/customattributestable.h @@ -13,8 +13,8 @@ public: explicit CustomAttributesTable(QWidget *parent = nullptr); ~CustomAttributesTable() {}; - QMap getAttributes() const; - void setAttributes(const QMap &attributes); + QJsonObject getAttributes() const; + void setAttributes(const QJsonObject &attributes); void addNewAttribute(const QString &key, const QJsonValue &value); bool deleteSelectedAttributes(); diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 97947872..432eaab6 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -54,7 +54,7 @@ private: Project *project; RegionMap *region_map = nullptr; - tsl::ordered_map region_maps; + OrderedMap region_maps; QString configFilepath; @@ -95,7 +95,7 @@ private: void saveConfig(); bool loadRegionMapEntries(); bool saveRegionMapEntries(); - QMap region_map_entries; + QHash region_map_entries; bool buildConfigDialog(); poryjson::Json configRegionMapDialog(); diff --git a/src/config.cpp b/src/config.cpp index 42a59149..5c5feb54 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -809,6 +809,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->tilesetsHaveIsCompressed = getConfigBool(key, value); } else if (key == "set_transparent_pixels_black") { this->setTransparentPixelsBlack = getConfigBool(key, value); + } else if (key == "preserve_matching_only_data") { + this->preserveMatchingOnlyData = getConfigBool(key, value); } else if (key == "event_icon_path_object") { this->eventIconPaths[Event::Group::Object] = value; } else if (key == "event_icon_path_warp") { @@ -899,6 +901,7 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("tilesets_have_callback", QString::number(this->tilesetsHaveCallback)); map.insert("tilesets_have_is_compressed", QString::number(this->tilesetsHaveIsCompressed)); map.insert("set_transparent_pixels_black", QString::number(this->setTransparentPixelsBlack)); + map.insert("preserve_matching_only_data", QString::number(this->preserveMatchingOnlyData)); map.insert("metatile_attributes_size", QString::number(this->metatileAttributesSize)); map.insert("metatile_behavior_mask", Util::toHexString(this->metatileBehaviorMask)); map.insert("metatile_terrain_type_mask", Util::toHexString(this->metatileTerrainTypeMask)); diff --git a/src/core/events.cpp b/src/core/events.cpp index 164cc34d..7fac9ece 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -51,24 +51,6 @@ void Event::setDefaultValues(Project *) { this->setElevation(projectConfig.defaultElevation); } -void Event::readCustomAttributes(const QJsonObject &json) { - this->customAttributes.clear(); - const QSet expectedFields = this->getExpectedFields(); - for (auto i = json.constBegin(); i != json.constEnd(); i++) { - if (!expectedFields.contains(i.key())) { - this->customAttributes[i.key()] = i.value(); - } - } -} - -void Event::addCustomAttributesTo(OrderedJson::object *obj) const { - for (auto i = this->customAttributes.constBegin(); i != this->customAttributes.constEnd(); i++) { - if (!obj->contains(i.key())) { - (*obj)[i.key()] = OrderedJson::fromQJsonValue(i.value()); - } - } -} - void Event::modify() { this->map->modify(); } @@ -187,27 +169,26 @@ OrderedJson::object ObjectEvent::buildEventJson(Project *) { objectJson["trainer_sight_or_berry_tree_id"] = this->getSightRadiusBerryTreeID(); objectJson["script"] = this->getScript(); objectJson["flag"] = this->getFlag(); - this->addCustomAttributesTo(&objectJson); + OrderedJson::append(&objectJson, this->getCustomAttributes()); return objectJson; } -bool ObjectEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setIdName(ParseUtil::jsonToQString(json["local_id"])); - this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); - this->setMovement(ParseUtil::jsonToQString(json["movement_type"])); - this->setRadiusX(ParseUtil::jsonToInt(json["movement_range_x"])); - this->setRadiusY(ParseUtil::jsonToInt(json["movement_range_y"])); - this->setTrainerType(ParseUtil::jsonToQString(json["trainer_type"])); - this->setSightRadiusBerryTreeID(ParseUtil::jsonToQString(json["trainer_sight_or_berry_tree_id"])); - this->setScript(ParseUtil::jsonToQString(json["script"])); - this->setFlag(ParseUtil::jsonToQString(json["flag"])); +bool ObjectEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setIdName(readString(&json, "local_id")); + this->setGfx(readString(&json, "graphics_id")); + this->setMovement(readString(&json, "movement_type")); + this->setRadiusX(readInt(&json, "movement_range_x")); + this->setRadiusY(readInt(&json, "movement_range_y")); + this->setTrainerType(readString(&json, "trainer_type")); + this->setSightRadiusBerryTreeID(readString(&json, "trainer_sight_or_berry_tree_id")); + this->setScript(readString(&json, "script")); + this->setFlag(readString(&json, "flag")); - this->readCustomAttributes(json); - + this->setCustomAttributes(json); return true; } @@ -222,26 +203,24 @@ void ObjectEvent::setDefaultValues(Project *project) { this->setSightRadiusBerryTreeID("0"); } -const QSet expectedObjectFields = { - "local_id", - "graphics_id", - "elevation", - "movement_type", - "movement_range_x", - "movement_range_y", - "trainer_type", - "trainer_sight_or_berry_tree_id", - "script", - "flag", -}; - QSet ObjectEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedObjectFields; + QSet expectedFields = { + "x", + "y", + "local_id", + "graphics_id", + "elevation", + "movement_type", + "movement_range_x", + "movement_range_y", + "trainer_type", + "trainer_sight_or_berry_tree_id", + "script", + "flag", + }; if (projectConfig.eventCloneObjectEnabled) { expectedFields.insert("type"); } - expectedFields << "x" << "y"; return expectedFields; } @@ -292,26 +271,25 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { cloneJson["target_local_id"] = this->getTargetID(); const QString mapName = this->getTargetMap(); cloneJson["target_map"] = project->getMapConstant(mapName, mapName); - this->addCustomAttributesTo(&cloneJson); + OrderedJson::append(&cloneJson, this->getCustomAttributes()); return cloneJson; } -bool CloneObjectEvent::loadFromJson(const QJsonObject &json, Project *project) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setIdName(ParseUtil::jsonToQString(json["local_id"])); - this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); - this->setTargetID(ParseUtil::jsonToInt(json["target_local_id"])); +bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setIdName(readString(&json, "local_id")); + this->setGfx(readString(&json, "graphics_id")); + this->setTargetID(readInt(&json, "target_local_id")); // Log a warning if "target_map" isn't a known map ID, but don't overwrite user data. - const QString mapConstant = ParseUtil::jsonToQString(json["target_map"]); + const QString mapConstant = readString(&json, "target_map"); if (!project->mapConstantsToMapNames.contains(mapConstant)) logWarn(QString("Unknown Target Map constant '%1'.").arg(mapConstant)); this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); - this->readCustomAttributes(json); - + this->setCustomAttributes(json); return true; } @@ -321,18 +299,16 @@ void CloneObjectEvent::setDefaultValues(Project *project) { if (this->getMap()) this->setTargetMap(this->getMap()->name()); } -const QSet expectedCloneObjectFields = { - "type", - "local_id", - "graphics_id", - "target_local_id", - "target_map", -}; - QSet CloneObjectEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedCloneObjectFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "local_id", + "graphics_id", + "target_local_id", + "target_map", + }; return expectedFields; } @@ -389,25 +365,23 @@ OrderedJson::object WarpEvent::buildEventJson(Project *project) { warpJson["dest_map"] = project->getMapConstant(mapName, mapName); warpJson["dest_warp_id"] = this->getDestinationWarpID(); - this->addCustomAttributesTo(&warpJson); - + OrderedJson::append(&warpJson, this->getCustomAttributes()); return warpJson; } -bool WarpEvent::loadFromJson(const QJsonObject &json, Project *project) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setDestinationWarpID(ParseUtil::jsonToQString(json["dest_warp_id"])); +bool WarpEvent::loadFromJson(QJsonObject json, Project *project) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setDestinationWarpID(readString(&json, "dest_warp_id")); // Log a warning if "dest_map" isn't a known map ID, but don't overwrite user data. - const QString mapConstant = ParseUtil::jsonToQString(json["dest_map"]); + const QString mapConstant = readString(&json, "dest_map"); if (!project->mapConstantsToMapNames.contains(mapConstant)) logWarn(QString("Unknown Destination Map constant '%1'.").arg(mapConstant)); this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); - this->readCustomAttributes(json); - + this->setCustomAttributes(json); return true; } @@ -417,16 +391,14 @@ void WarpEvent::setDefaultValues(Project *) { this->setElevation(0); } -const QSet expectedWarpFields = { - "elevation", - "dest_map", - "dest_warp_id", -}; - QSet WarpEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedWarpFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "elevation", + "dest_map", + "dest_warp_id", + }; return expectedFields; } @@ -476,21 +448,19 @@ OrderedJson::object TriggerEvent::buildEventJson(Project *) { triggerJson["var_value"] = this->getScriptVarValue(); triggerJson["script"] = this->getScriptLabel(); - this->addCustomAttributesTo(&triggerJson); - + OrderedJson::append(&triggerJson, this->getCustomAttributes()); return triggerJson; } -bool TriggerEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setScriptVar(ParseUtil::jsonToQString(json["var"])); - this->setScriptVarValue(ParseUtil::jsonToQString(json["var_value"])); - this->setScriptLabel(ParseUtil::jsonToQString(json["script"])); - - this->readCustomAttributes(json); +bool TriggerEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setScriptVar(readString(&json, "var")); + this->setScriptVarValue(readString(&json, "var_value")); + this->setScriptLabel(readString(&json, "script")); + this->setCustomAttributes(json); return true; } @@ -501,18 +471,16 @@ void TriggerEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedTriggerFields = { - "type", - "elevation", - "var", - "var_value", - "script", -}; - QSet TriggerEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedTriggerFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "var", + "var_value", + "script", + }; return expectedFields; } @@ -548,19 +516,17 @@ OrderedJson::object WeatherTriggerEvent::buildEventJson(Project *) { weatherJson["elevation"] = this->getElevation(); weatherJson["weather"] = this->getWeather(); - this->addCustomAttributesTo(&weatherJson); - + OrderedJson::append(&weatherJson, this->getCustomAttributes()); return weatherJson; } -bool WeatherTriggerEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setWeather(ParseUtil::jsonToQString(json["weather"])); - - this->readCustomAttributes(json); +bool WeatherTriggerEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setWeather(readString(&json, "weather")); + this->setCustomAttributes(json); return true; } @@ -569,16 +535,14 @@ void WeatherTriggerEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedWeatherTriggerFields = { - "type", - "elevation", - "weather", -}; - QSet WeatherTriggerEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedWeatherTriggerFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "weather", + }; return expectedFields; } @@ -616,20 +580,18 @@ OrderedJson::object SignEvent::buildEventJson(Project *) { signJson["player_facing_dir"] = this->getFacingDirection(); signJson["script"] = this->getScriptLabel(); - this->addCustomAttributesTo(&signJson); - + OrderedJson::append(&signJson, this->getCustomAttributes()); return signJson; } -bool SignEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setFacingDirection(ParseUtil::jsonToQString(json["player_facing_dir"])); - this->setScriptLabel(ParseUtil::jsonToQString(json["script"])); - - this->readCustomAttributes(json); +bool SignEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setFacingDirection(readString(&json, "player_facing_dir")); + this->setScriptLabel(readString(&json, "script")); + this->setCustomAttributes(json); return true; } @@ -639,17 +601,15 @@ void SignEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedSignFields = { - "type", - "elevation", - "player_facing_dir", - "script", -}; - QSet SignEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedSignFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "player_facing_dir", + "script", + }; return expectedFields; } @@ -695,26 +655,24 @@ OrderedJson::object HiddenItemEvent::buildEventJson(Project *) { hiddenItemJson["underfoot"] = this->getUnderfoot(); } - this->addCustomAttributesTo(&hiddenItemJson); - + OrderedJson::append(&hiddenItemJson, this->getCustomAttributes()); return hiddenItemJson; } -bool HiddenItemEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setItem(ParseUtil::jsonToQString(json["item"])); - this->setFlag(ParseUtil::jsonToQString(json["flag"])); +bool HiddenItemEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setItem(readString(&json, "item")); + this->setFlag(readString(&json, "flag")); if (projectConfig.hiddenItemQuantityEnabled) { - this->setQuantity(ParseUtil::jsonToInt(json["quantity"])); + this->setQuantity(readInt(&json, "quantity")); } if (projectConfig.hiddenItemRequiresItemfinderEnabled) { - this->setUnderfoot(ParseUtil::jsonToBool(json["underfoot"])); + this->setUnderfoot(readBool(&json, "underfoot")); } - this->readCustomAttributes(json); - + this->setCustomAttributes(json); return true; } @@ -729,23 +687,21 @@ void HiddenItemEvent::setDefaultValues(Project *project) { } } -const QSet expectedHiddenItemFields = { - "type", - "elevation", - "item", - "flag", -}; - QSet HiddenItemEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedHiddenItemFields; + QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "item", + "flag", + }; if (projectConfig.hiddenItemQuantityEnabled) { expectedFields << "quantity"; } if (projectConfig.hiddenItemRequiresItemfinderEnabled) { expectedFields << "underfoot"; } - expectedFields << "x" << "y"; return expectedFields; } @@ -781,19 +737,17 @@ OrderedJson::object SecretBaseEvent::buildEventJson(Project *) { secretBaseJson["elevation"] = this->getElevation(); secretBaseJson["secret_base_id"] = this->getBaseID(); - this->addCustomAttributesTo(&secretBaseJson); - + OrderedJson::append(&secretBaseJson, this->getCustomAttributes()); return secretBaseJson; } -bool SecretBaseEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setBaseID(ParseUtil::jsonToQString(json["secret_base_id"])); - - this->readCustomAttributes(json); +bool SecretBaseEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setBaseID(readString(&json, "secret_base_id")); + this->setCustomAttributes(json); return true; } @@ -802,16 +756,14 @@ void SecretBaseEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedSecretBaseFields = { - "type", - "elevation", - "secret_base_id", -}; - QSet SecretBaseEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedSecretBaseFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "secret_base_id", + }; return expectedFields; } @@ -839,12 +791,15 @@ EventFrame *HealLocationEvent::createEventFrame() { return this->eventFrame; } +QString HealLocationEvent::getHostMapName() const { + return this->getMap() ? this->getMap()->constantName() : this->hostMapName; +} + OrderedJson::object HealLocationEvent::buildEventJson(Project *project) { OrderedJson::object healLocationJson; healLocationJson["id"] = this->getIdName(); - // This field doesn't need to be stored in the Event itself, so it's output only. - healLocationJson["map"] = this->getMap() ? this->getMap()->constantName() : QString(); + healLocationJson["map"] = this->getHostMapName(); healLocationJson["x"] = this->getX(); healLocationJson["y"] = this->getY(); if (projectConfig.healLocationRespawnDataEnabled) { @@ -853,26 +808,26 @@ OrderedJson::object HealLocationEvent::buildEventJson(Project *project) { healLocationJson["respawn_npc"] = this->getRespawnNPC(); } - this->addCustomAttributesTo(&healLocationJson); - + OrderedJson::append(&healLocationJson, this->getCustomAttributes()); return healLocationJson; } -bool HealLocationEvent::loadFromJson(const QJsonObject &json, Project *project) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setIdName(ParseUtil::jsonToQString(json["id"])); +bool HealLocationEvent::loadFromJson(QJsonObject json, Project *project) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setIdName(readString(&json, "id")); + this->setHostMapName(readString(&json, "map")); if (projectConfig.healLocationRespawnDataEnabled) { // Log a warning if "respawn_map" isn't a known map ID, but don't overwrite user data. - const QString mapConstant = ParseUtil::jsonToQString(json["respawn_map"]); + const QString mapConstant = readString(&json, "respawn_map"); if (!project->mapConstantsToMapNames.contains(mapConstant)) logWarn(QString("Unknown Respawn Map constant '%1'.").arg(mapConstant)); this->setRespawnMapName(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); - this->setRespawnNPC(ParseUtil::jsonToQString(json["respawn_npc"])); + this->setRespawnNPC(readString(&json, "respawn_npc")); } - this->readCustomAttributes(json); + this->setCustomAttributes(json); return true; } @@ -885,16 +840,19 @@ void HealLocationEvent::setDefaultValues(Project *project) { } const QSet expectedHealLocationFields = { - "id", - "map" + }; QSet HealLocationEvent::getExpectedFields() { - QSet expectedFields = expectedHealLocationFields; + QSet expectedFields = { + "x", + "y", + "id", + "map", + }; if (projectConfig.healLocationRespawnDataEnabled) { expectedFields.insert("respawn_map"); expectedFields.insert("respawn_npc"); } - expectedFields << "x" << "y"; return expectedFields; } diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 86ddda4f..45b35f91 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -31,6 +31,7 @@ void Layout::copyFrom(const Layout *other) { this->tileset_secondary = other->tileset_secondary; this->blockdata = other->blockdata; this->border = other->border; + this->customData = other->customData; } QString Layout::layoutConstantFromName(const QString &name) { diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index e172ea42..da2a8f8e 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -610,12 +610,12 @@ bool ParseUtil::gameStringToBool(const QString &gameString, bool * ok) { return gameStringToInt(gameString, ok) != 0; } -tsl::ordered_map> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash &memberMap) { +OrderedMap> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash &memberMap) { QString filePath = pathWithRoot(filename); auto cParser = fex::Parser(); auto tokens = fex::Lexer().LexFile(filePath); auto topLevelObjects = cParser.ParseTopLevelObjects(tokens); - tsl::ordered_map> structs; + OrderedMap> structs; for (auto it = topLevelObjects.begin(); it != topLevelObjects.end(); it++) { QString structLabel = QString::fromStdString(it->first); if (structLabel.isEmpty()) continue; diff --git a/src/core/regionmapeditcommands.cpp b/src/core/regionmapeditcommands.cpp index 7a12cbc6..f21f1d72 100644 --- a/src/core/regionmapeditcommands.cpp +++ b/src/core/regionmapeditcommands.cpp @@ -260,7 +260,7 @@ void ResizeTilemap::undo() { /// -ClearEntries::ClearEntries(RegionMap *map, QMap entries, QUndoCommand *parent) +ClearEntries::ClearEntries(RegionMap *map, QHash entries, QUndoCommand *parent) : QUndoCommand(parent) { setText("Clear Entries"); diff --git a/src/editor.cpp b/src/editor.cpp index a9067a79..1cb3a304 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -596,7 +596,7 @@ void Editor::configureEncounterJSON(QWidget *window) { if (newNameDialog.exec() == QDialog::Accepted) { QString newFieldName = newNameEdit->text(); QVector newFieldRates(1, 100); - tempFields.append({newFieldName, newFieldRates, {}}); + tempFields.append({newFieldName, newFieldRates, {}, {}}); fieldChoices->addItem(newFieldName); fieldChoices->setCurrentIndex(fieldChoices->count() - 1); } @@ -675,7 +675,7 @@ void Editor::saveEncounterTabData() { if (!stack->count()) return; - tsl::ordered_map &encounterMap = project->wildMonData[map->constantName()]; + OrderedMap &encounterMap = project->wildMonData[map->constantName()]; for (int groupIndex = 0; groupIndex < stack->count(); groupIndex++) { MonTabWidget *tabWidget = static_cast(stack->widget(groupIndex)); diff --git a/src/lib/orderedjson.cpp b/src/lib/orderedjson.cpp index 24bb264a..d6ba4dbd 100644 --- a/src/lib/orderedjson.cpp +++ b/src/lib/orderedjson.cpp @@ -311,33 +311,26 @@ const Json & JsonArray::operator[] (int i) const { else return m_value[i]; } -const Json Json::fromQJsonValue(QJsonValue value) { +Json Json::fromQJsonValue(const QJsonValue &value) { switch (value.type()) { case QJsonValue::String: return value.toString(); case QJsonValue::Double: return value.toInt(); case QJsonValue::Bool: return value.toBool(); - case QJsonValue::Array: - { - QJsonArray qArr = value.toArray(); - Json::array arr; - for (const auto &i: qArr) - arr.push_back(Json::fromQJsonValue(i)); - return arr; + case QJsonValue::Array: { + Json::array array; + Json::append(&array, value.toArray()); + return array; } - case QJsonValue::Object: - { - QJsonObject qObj = value.toObject(); - Json::object obj; - for (auto it = qObj.constBegin(); it != qObj.constEnd(); it++) - obj[it.key()] = Json::fromQJsonValue(it.value()); - return obj; + case QJsonValue::Object: { + Json::object object; + Json::append(&object, value.toObject()); + return object; } default: return static_null(); } } - /* * * * * * * * * * * * * * * * * * * * * Comparison */ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a2cb1f67..20ecdaa8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1200,7 +1200,7 @@ bool MainWindow::setProjectUI() { ui->layoutList->setModel(layoutListProxyModel); ui->layoutList->sortByColumn(0, Qt::SortOrder::AscendingOrder); - ui->mapCustomAttributesFrame->table()->setRestrictedKeys(project->topLevelMapFields); + ui->mapCustomAttributesFrame->table()->setRestrictedKeys(project->getTopLevelMapFields()); return true; } diff --git a/src/project.cpp b/src/project.cpp index bbbc052c..6f225ff6 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -177,8 +177,8 @@ Map* Project::loadMap(const QString &mapName) { return map; } -void Project::initTopLevelMapFields() { - static const QSet defaultTopLevelMapFields = { +QSet Project::getTopLevelMapFields() const { + QSet fields = { "id", "name", "layout", @@ -197,15 +197,15 @@ void Project::initTopLevelMapFields() { "shared_events_map", "shared_scripts_map", }; - this->topLevelMapFields = defaultTopLevelMapFields; if (projectConfig.mapAllowFlagsEnabled) { - this->topLevelMapFields.insert("allow_cycling"); - this->topLevelMapFields.insert("allow_escaping"); - this->topLevelMapFields.insert("allow_running"); + fields.insert("allow_cycling"); + fields.insert("allow_escaping"); + fields.insert("allow_running"); } if (projectConfig.floorNumberEnabled) { - this->topLevelMapFields.insert("floor_number"); + fields.insert("floor_number"); } + return fields; } bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { @@ -218,8 +218,8 @@ bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { return true; } -bool Project::loadMapEvent(Map *map, const QJsonObject &json, Event::Type defaultType) { - QString typeString = ParseUtil::jsonToQString(json["type"]); +bool Project::loadMapEvent(Map *map, QJsonObject json, Event::Type defaultType) { + QString typeString = ParseUtil::jsonToQString(json.take("type")); Event::Type type = typeString.isEmpty() ? defaultType : Event::typeFromJsonKey(typeString); Event* event = Event::create(type); if (!event) { @@ -245,10 +245,10 @@ bool Project::loadMapData(Map* map) { QJsonObject mapObj = mapDoc.object(); // We should already know the map constant ID from the initial project launch, but we'll ensure it's correct here anyway. - map->setConstantName(ParseUtil::jsonToQString(mapObj["id"])); + map->setConstantName(ParseUtil::jsonToQString(mapObj.take("id"))); this->mapConstantsToMapNames.insert(map->constantName(), map->name()); - const QString layoutId = ParseUtil::jsonToQString(mapObj["layout"]); + const QString layoutId = ParseUtil::jsonToQString(mapObj.take("layout")); Layout* layout = this->mapLayouts.value(layoutId); if (!layout) { // We've already verified layout IDs on project launch and ignored maps with invalid IDs, so this shouldn't happen. @@ -257,24 +257,24 @@ bool Project::loadMapData(Map* map) { } map->setLayout(layout); - map->header()->setSong(ParseUtil::jsonToQString(mapObj["music"])); - map->header()->setLocation(ParseUtil::jsonToQString(mapObj["region_map_section"])); - map->header()->setRequiresFlash(ParseUtil::jsonToBool(mapObj["requires_flash"])); - map->header()->setWeather(ParseUtil::jsonToQString(mapObj["weather"])); - map->header()->setType(ParseUtil::jsonToQString(mapObj["map_type"])); - map->header()->setShowsLocationName(ParseUtil::jsonToBool(mapObj["show_map_name"])); - map->header()->setBattleScene(ParseUtil::jsonToQString(mapObj["battle_scene"])); + map->header()->setSong(ParseUtil::jsonToQString(mapObj.take("music"))); + map->header()->setLocation(ParseUtil::jsonToQString(mapObj.take("region_map_section"))); + map->header()->setRequiresFlash(ParseUtil::jsonToBool(mapObj.take("requires_flash"))); + map->header()->setWeather(ParseUtil::jsonToQString(mapObj.take("weather"))); + map->header()->setType(ParseUtil::jsonToQString(mapObj.take("map_type"))); + map->header()->setShowsLocationName(ParseUtil::jsonToBool(mapObj.take("show_map_name"))); + map->header()->setBattleScene(ParseUtil::jsonToQString(mapObj.take("battle_scene"))); if (projectConfig.mapAllowFlagsEnabled) { - map->header()->setAllowsBiking(ParseUtil::jsonToBool(mapObj["allow_cycling"])); - map->header()->setAllowsEscaping(ParseUtil::jsonToBool(mapObj["allow_escaping"])); - map->header()->setAllowsRunning(ParseUtil::jsonToBool(mapObj["allow_running"])); + map->header()->setAllowsBiking(ParseUtil::jsonToBool(mapObj.take("allow_cycling"))); + map->header()->setAllowsEscaping(ParseUtil::jsonToBool(mapObj.take("allow_escaping"))); + map->header()->setAllowsRunning(ParseUtil::jsonToBool(mapObj.take("allow_running"))); } if (projectConfig.floorNumberEnabled) { - map->header()->setFloorNumber(ParseUtil::jsonToInt(mapObj["floor_number"])); + map->header()->setFloorNumber(ParseUtil::jsonToInt(mapObj.take("floor_number"))); } - map->setSharedEventsMap(ParseUtil::jsonToQString(mapObj["shared_events_map"])); - map->setSharedScriptsMap(ParseUtil::jsonToQString(mapObj["shared_scripts_map"])); + map->setSharedEventsMap(ParseUtil::jsonToQString(mapObj.take("shared_events_map"))); + map->setSharedScriptsMap(ParseUtil::jsonToQString(mapObj.take("shared_scripts_map"))); // Events map->resetEvents(); @@ -289,7 +289,7 @@ bool Project::loadMapData(Map* map) { for (auto i = defaultEventTypes.constBegin(); i != defaultEventTypes.constEnd(); i++) { QString eventGroupKey = i.key(); Event::Type defaultType = i.value(); - const QJsonArray eventsJsonArr = mapObj[eventGroupKey].toArray(); + const QJsonArray eventsJsonArr = mapObj.take(eventGroupKey).toArray(); for (int i = 0; i < eventsJsonArr.size(); i++) { if (!loadMapEvent(map, eventsJsonArr.at(i).toObject(), defaultType)) { logError(QString("Failed to load event for %1, in %2 at index %3.").arg(map->name()).arg(eventGroupKey).arg(i)); @@ -304,24 +304,19 @@ bool Project::loadMapData(Map* map) { } map->deleteConnections(); - QJsonArray connectionsArr = mapObj["connections"].toArray(); + QJsonArray connectionsArr = mapObj.take("connections").toArray(); if (!connectionsArr.isEmpty()) { for (int i = 0; i < connectionsArr.size(); i++) { QJsonObject connectionObj = connectionsArr[i].toObject(); - const QString direction = ParseUtil::jsonToQString(connectionObj["direction"]); - const int offset = ParseUtil::jsonToInt(connectionObj["offset"]); - const QString mapConstant = ParseUtil::jsonToQString(connectionObj["map"]); - map->loadConnection(new MapConnection(this->mapConstantsToMapNames.value(mapConstant, mapConstant), direction, offset)); + const QString direction = ParseUtil::jsonToQString(connectionObj.take("direction")); + const int offset = ParseUtil::jsonToInt(connectionObj.take("offset")); + const QString mapConstant = ParseUtil::jsonToQString(connectionObj.take("map")); + auto connection = new MapConnection(this->mapConstantsToMapNames.value(mapConstant, mapConstant), direction, offset); + connection->setCustomData(connectionObj); + map->loadConnection(connection); } } - - QMap customAttributes; - for (auto i = mapObj.constBegin(); i != mapObj.constEnd(); i++) { - if (!this->topLevelMapFields.contains(i.key())) { - customAttributes.insert(i.key(), i.value()); - } - } - map->setCustomAttributes(customAttributes); + map->setCustomAttributes(mapObj); return true; } @@ -473,6 +468,7 @@ void Project::clearMapLayouts() { this->layoutIds.clear(); this->layoutIdsMaster.clear(); this->loadedLayoutIds.clear(); + this->customLayoutsData = QJsonObject(); } bool Project::readMapLayouts() { @@ -489,7 +485,7 @@ bool Project::readMapLayouts() { QJsonObject layoutsObj = layoutsDoc.object(); - this->layoutsLabel = ParseUtil::jsonToQString(layoutsObj["layouts_table_label"]); + this->layoutsLabel = ParseUtil::jsonToQString(layoutsObj.take("layouts_table_label")); if (this->layoutsLabel.isEmpty()) { this->layoutsLabel = "gMapLayouts"; logWarn(QString("'layouts_table_label' value is missing from %1. Defaulting to %2") @@ -497,13 +493,13 @@ bool Project::readMapLayouts() { .arg(layoutsLabel)); } - QJsonArray layouts = layoutsObj["layouts"].toArray(); + QJsonArray layouts = layoutsObj.take("layouts").toArray(); for (int i = 0; i < layouts.size(); i++) { QJsonObject layoutObj = layouts[i].toObject(); if (layoutObj.isEmpty()) continue; Layout *layout = new Layout(); - layout->id = ParseUtil::jsonToQString(layoutObj["id"]); + layout->id = ParseUtil::jsonToQString(layoutObj.take("id")); if (layout->id.isEmpty()) { logError(QString("Missing 'id' value on layout %1 in %2").arg(i).arg(layoutsFilepath)); delete layout; @@ -514,20 +510,20 @@ bool Project::readMapLayouts() { delete layout; continue; } - layout->name = ParseUtil::jsonToQString(layoutObj["name"]); + layout->name = ParseUtil::jsonToQString(layoutObj.take("name")); if (layout->name.isEmpty()) { logError(QString("Missing 'name' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } - int lwidth = ParseUtil::jsonToInt(layoutObj["width"]); + int lwidth = ParseUtil::jsonToInt(layoutObj.take("width")); if (lwidth <= 0) { logError(QString("Invalid 'width' value '%1' for %2 in %3. Must be greater than 0.").arg(lwidth).arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } layout->width = lwidth; - int lheight = ParseUtil::jsonToInt(layoutObj["height"]); + int lheight = ParseUtil::jsonToInt(layoutObj.take("height")); if (lheight <= 0) { logError(QString("Invalid 'height' value '%1' for %2 in %3. Must be greater than 0.").arg(lheight).arg(layout->id).arg(layoutsFilepath)); delete layout; @@ -535,12 +531,12 @@ bool Project::readMapLayouts() { } layout->height = lheight; if (projectConfig.useCustomBorderSize) { - int bwidth = ParseUtil::jsonToInt(layoutObj["border_width"]); + int bwidth = ParseUtil::jsonToInt(layoutObj.take("border_width")); if (bwidth <= 0) { // 0 is an expected border width/height that should be handled, GF used it for the RS layouts in FRLG bwidth = DEFAULT_BORDER_WIDTH; } layout->border_width = bwidth; - int bheight = ParseUtil::jsonToInt(layoutObj["border_height"]); + int bheight = ParseUtil::jsonToInt(layoutObj.take("border_height")); if (bheight <= 0) { bheight = DEFAULT_BORDER_HEIGHT; } @@ -549,30 +545,31 @@ bool Project::readMapLayouts() { layout->border_width = DEFAULT_BORDER_WIDTH; layout->border_height = DEFAULT_BORDER_HEIGHT; } - layout->tileset_primary_label = ParseUtil::jsonToQString(layoutObj["primary_tileset"]); + layout->tileset_primary_label = ParseUtil::jsonToQString(layoutObj.take("primary_tileset")); if (layout->tileset_primary_label.isEmpty()) { logError(QString("Missing 'primary_tileset' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } - layout->tileset_secondary_label = ParseUtil::jsonToQString(layoutObj["secondary_tileset"]); + layout->tileset_secondary_label = ParseUtil::jsonToQString(layoutObj.take("secondary_tileset")); if (layout->tileset_secondary_label.isEmpty()) { logError(QString("Missing 'secondary_tileset' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } - layout->border_path = ParseUtil::jsonToQString(layoutObj["border_filepath"]); + layout->border_path = ParseUtil::jsonToQString(layoutObj.take("border_filepath")); if (layout->border_path.isEmpty()) { logError(QString("Missing 'border_filepath' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } - layout->blockdata_path = ParseUtil::jsonToQString(layoutObj["blockdata_filepath"]); + layout->blockdata_path = ParseUtil::jsonToQString(layoutObj.take("blockdata_filepath")); if (layout->blockdata_path.isEmpty()) { logError(QString("Missing 'blockdata_filepath' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } + layout->customData = layoutObj; this->mapLayouts.insert(layout->id, layout); this->mapLayoutsMaster.insert(layout->id, layout->copy()); @@ -585,6 +582,8 @@ bool Project::readMapLayouts() { return false; } + this->customLayoutsData = layoutsObj; + return true; } @@ -615,12 +614,14 @@ void Project::saveMapLayouts() { layoutObj["secondary_tileset"] = layout->tileset_secondary_label; layoutObj["border_filepath"] = layout->border_path; layoutObj["blockdata_filepath"] = layout->blockdata_path; + OrderedJson::append(&layoutObj, layout->customData); layoutsArr.push_back(layoutObj); } + layoutsObj["layouts"] = layoutsArr; + OrderedJson::append(&layoutsObj, this->customLayoutsData); ignoreWatchedFileTemporarily(layoutsFilepath); - layoutsObj["layouts"] = layoutsArr; OrderedJson layoutJson(layoutsObj); OrderedJsonDoc jsonDoc(&layoutJson); jsonDoc.dump(&layoutsFile); @@ -677,6 +678,7 @@ void Project::saveMapGroups() { } mapGroupsObj[groupName] = groupArr; } + OrderedJson::append(&mapGroupsObj, this->customMapGroupsData); ignoreWatchedFileTemporarily(mapGroupsFilepath); @@ -696,26 +698,29 @@ void Project::saveRegionMapSections() { OrderedJson::array mapSectionArray; for (const auto &idName : this->mapSectionIdNamesSaveOrder) { + const LocationData location = this->locationData.value(idName); + OrderedJson::object mapSectionObj; mapSectionObj["id"] = idName; - if (this->mapSectionDisplayNames.contains(idName)) { - mapSectionObj["name"] = this->mapSectionDisplayNames.value(idName); + if (!location.displayName.isEmpty()) { + mapSectionObj["name"] = location.displayName; } - if (this->regionMapEntries.contains(idName)) { - MapSectionEntry entry = this->regionMapEntries.value(idName); - mapSectionObj["x"] = entry.x; - mapSectionObj["y"] = entry.y; - mapSectionObj["width"] = entry.width; - mapSectionObj["height"] = entry.height; + if (location.map.valid) { + mapSectionObj["x"] = location.map.x; + mapSectionObj["y"] = location.map.y; + mapSectionObj["width"] = location.map.width; + mapSectionObj["height"] = location.map.height; } + OrderedJson::append(&mapSectionObj, location.custom); mapSectionArray.append(mapSectionObj); } OrderedJson::object object; object["map_sections"] = mapSectionArray; + OrderedJson::append(&object, this->customMapSectionsData); ignoreWatchedFileTemporarily(filepath); OrderedJson json(object); @@ -742,7 +747,7 @@ void Project::saveWildMonData() { monHeadersObject["for_maps"] = true; OrderedJson::array fieldsInfoArray; - for (EncounterField fieldInfo : wildMonFields) { + for (EncounterField fieldInfo : this->wildMonFields) { OrderedJson::object fieldObject; OrderedJson::array rateArray; @@ -765,48 +770,52 @@ void Project::saveWildMonData() { } if (!groupsObject.empty()) fieldObject["groups"] = groupsObject; + OrderedJson::append(&fieldObject, fieldInfo.customData); fieldsInfoArray.append(fieldObject); } monHeadersObject["fields"] = fieldsInfoArray; OrderedJson::array encountersArray; - for (auto keyPair : wildMonData) { + for (auto keyPair : this->wildMonData) { QString key = keyPair.first; - for (auto grouplLabelPair : wildMonData[key]) { + for (auto grouplLabelPair : this->wildMonData[key]) { QString groupLabel = grouplLabelPair.first; OrderedJson::object encounterObject; encounterObject["map"] = key; encounterObject["base_label"] = groupLabel; - WildPokemonHeader encounterHeader = wildMonData[key][groupLabel]; + WildPokemonHeader encounterHeader = this->wildMonData[key][groupLabel]; for (auto fieldNamePair : encounterHeader.wildMons) { QString fieldName = fieldNamePair.first; - OrderedJson::object fieldObject; + OrderedJson::object monInfoObject; WildMonInfo monInfo = encounterHeader.wildMons[fieldName]; - fieldObject["encounter_rate"] = monInfo.encounterRate; + monInfoObject["encounter_rate"] = monInfo.encounterRate; OrderedJson::array monArray; for (WildPokemon wildMon : monInfo.wildPokemon) { OrderedJson::object monEntry; monEntry["min_level"] = wildMon.minLevel; monEntry["max_level"] = wildMon.maxLevel; monEntry["species"] = wildMon.species; + OrderedJson::append(&monEntry, wildMon.customData); monArray.push_back(monEntry); } - fieldObject["mons"] = monArray; - encounterObject[fieldName] = fieldObject; + monInfoObject["mons"] = monArray; + OrderedJson::append(&monInfoObject, monInfo.customData); + + encounterObject[fieldName] = monInfoObject; + OrderedJson::append(&encounterObject, encounterHeader.customData); } encountersArray.push_back(encounterObject); } } monHeadersObject["encounters"] = encountersArray; - wildEncounterGroups.push_back(monHeadersObject); + OrderedJson::append(&monHeadersObject, this->customWildMonGroupData); - // add extra Json objects that are not associated with maps to the file - for (auto extraObject : extraEncounterGroups) { - wildEncounterGroups.push_back(extraObject); - } + wildEncounterGroups.push_back(monHeadersObject); + OrderedJson::append(&wildEncounterGroups, this->extraEncounterGroups); wildEncountersObject["wild_encounter_groups"] = wildEncounterGroups; + OrderedJson::append(&wildEncountersObject, this->customWildMonData); ignoreWatchedFileTemporarily(wildEncountersJsonFilepath); OrderedJson encounterJson(wildEncountersObject); @@ -870,6 +879,7 @@ void Project::saveHealLocations() { OrderedJson::object object; object["heal_locations"] = eventJsonArr; + OrderedJson::append(&object, this->customHealLocationsData); ignoreWatchedFileTemporarily(filepath); OrderedJson json(object); @@ -1203,6 +1213,7 @@ void Project::saveMap(Map *map, bool skipLayout) { connectionObj["map"] = getMapConstant(connection->targetMapName(), connection->targetMapName()); connectionObj["offset"] = connection->offset(); connectionObj["direction"] = connection->direction(); + OrderedJson::append(&connectionObj, connection->customData()); connectionsArr.append(connectionObj); } mapObj["connections"] = connectionsArr; @@ -1258,10 +1269,7 @@ void Project::saveMap(Map *map, bool skipLayout) { this->healLocations[map->constantName()] = hlEvents; // Custom header fields. - const auto customAttributes = map->customAttributes(); - for (auto i = customAttributes.constBegin(); i != customAttributes.constEnd(); i++) { - mapObj[i.key()] = OrderedJson::fromQJsonValue(i.value()); - } + OrderedJson::append(&mapObj, map->customAttributes()); OrderedJson mapJson(mapObj); OrderedJsonDoc jsonDoc(&mapJson); @@ -1603,6 +1611,8 @@ bool Project::readWildMonData() { this->pokemonMaxLevel = 100; this->maxEncounterRate = 2880/16; this->wildEncountersLoaded = false; + this->customWildMonData = OrderedJson::object(); + this->customWildMonGroupData = OrderedJson::object(); if (!userConfig.useEncounterJson) { return true; } @@ -1648,7 +1658,8 @@ bool Project::readWildMonData() { QMap> encounterRateFrequencyMaps; // Parse "wild_encounter_groups". This is the main object array containing all the data in this file. - for (OrderedJson mainArrayJson : wildMonObj["wild_encounter_groups"].array_items()) { + OrderedJson::array mainArray = wildMonObj.take("wild_encounter_groups").array_items(); + for (const OrderedJson &mainArrayJson : mainArray) { OrderedJson::object mainArrayObject = mainArrayJson.object_items(); // We're only interested in wild encounter data that's associated with maps ("for_maps" == true). @@ -1657,10 +1668,14 @@ bool Project::readWildMonData() { if (!mainArrayObject["for_maps"].bool_value()) { this->extraEncounterGroups.push_back(mainArrayObject); continue; + } else { + // Note: We don't call 'take' above, we don't want to strip data from extraEncounterGroups. + // We do want to strip it from the main group, because it shouldn't be treated as custom data. + mainArrayObject.erase("for_maps"); } // If multiple "for_maps" data sets are found they will be collapsed into a single set. - QString label = mainArrayObject["label"].string_value(); + QString label = mainArrayObject.take("label").string_value(); if (this->wildMonTableName.isEmpty()) { this->wildMonTableName = label; } else { @@ -1673,24 +1688,25 @@ bool Project::readWildMonData() { // Each element describes a type of wild encounter Porymap can expect to find, and we represent this data with an EncounterField. // They should contain a name ("type"), the number of encounter slots and the ratio at which they occur ("encounter_rates"), // and whether the encounters are divided into groups (like fishing rods). - for (const OrderedJson &fieldJson : mainArrayObject["fields"].array_items()) { + for (const OrderedJson &fieldJson : mainArrayObject.take("fields").array_items()) { OrderedJson::object fieldObject = fieldJson.object_items(); EncounterField encounterField; - encounterField.name = fieldObject["type"].string_value(); + encounterField.name = fieldObject.take("type").string_value(); - for (auto val : fieldObject["encounter_rates"].array_items()) { + for (auto val : fieldObject.take("encounter_rates").array_items()) { encounterField.encounterRates.append(val.int_value()); } // Each element of the "groups" array is an object with the group name as the key (e.g. "old_rod") // and an array of slot numbers indicating which encounter slots in this encounter type belong to that group. - for (auto groupPair : fieldObject["groups"].object_items()) { + for (auto groupPair : fieldObject.take("groups").object_items()) { const QString groupName = groupPair.first; for (auto slotNum : groupPair.second.array_items()) { encounterField.groups[groupName].append(slotNum.int_value()); } } + encounterField.customData = fieldObject; encounterRateFrequencyMaps.insert(encounterField.name, QMap()); this->wildMonFields.append(encounterField); @@ -1700,7 +1716,7 @@ bool Project::readWildMonData() { // Each element is an object that will tell us which map it's associated with, // its symbol name (which we will display in the Groups dropdown) and a list of // pokémon associated with any of the encounter types described by the data we parsed above. - for (const auto &encounterJson : mainArrayObject["encounters"].array_items()) { + for (const auto &encounterJson : mainArrayObject.take("encounters").array_items()) { OrderedJson::object encounterObj = encounterJson.object_items(); WildPokemonHeader header; @@ -1708,29 +1724,31 @@ bool Project::readWildMonData() { // Check for each possible encounter type. for (const EncounterField &monField : this->wildMonFields) { const QString field = monField.name; - if (encounterObj[field].is_null()) { + if (!encounterObj.contains(field)) { // Encounter type isn't present continue; } - OrderedJson::object encounterFieldObj = encounterObj[field].object_items(); + OrderedJson::object encounterFieldObj = encounterObj.take(field).object_items(); WildMonInfo monInfo; monInfo.active = true; // Read encounter rate - monInfo.encounterRate = encounterFieldObj["encounter_rate"].int_value(); + monInfo.encounterRate = encounterFieldObj.take("encounter_rate").int_value(); encounterRateFrequencyMaps[field][monInfo.encounterRate]++; // Read wild pokémon list - for (auto monJson : encounterFieldObj["mons"].array_items()) { + for (const auto &monJson : encounterFieldObj.take("mons").array_items()) { OrderedJson::object monObj = monJson.object_items(); WildPokemon newMon; - newMon.minLevel = monObj["min_level"].int_value(); - newMon.maxLevel = monObj["max_level"].int_value(); - newMon.species = monObj["species"].string_value(); + newMon.minLevel = monObj.take("min_level").int_value(); + newMon.maxLevel = monObj.take("max_level").int_value(); + newMon.species = monObj.take("species").string_value(); + newMon.customData = monObj; monInfo.wildPokemon.append(newMon); } + monInfo.customData = encounterFieldObj; // If the user supplied too few pokémon for this group then we fill in the rest with default values. for (int i = monInfo.wildPokemon.length(); i < monField.encounterRates.length(); i++) { @@ -1738,13 +1756,15 @@ bool Project::readWildMonData() { } header.wildMons[field] = monInfo; } - - const QString mapConstant = encounterObj["map"].string_value(); - const QString baseLabel = encounterObj["base_label"].string_value(); + const QString mapConstant = encounterObj.take("map").string_value(); + const QString baseLabel = encounterObj.take("base_label").string_value(); + header.customData = encounterObj; this->wildMonData[mapConstant].insert({baseLabel, header}); this->encounterGroupLabels.append(baseLabel); } + this->customWildMonGroupData = mainArrayObject; } + this->customWildMonData = wildMonObj; // For each encounter type, set default encounter rate to most common value. // Iterate over map of encounter type names to frequency maps... @@ -1772,8 +1792,7 @@ bool Project::readMapGroups() { this->mapNames.clear(); this->groupNames.clear(); this->groupNameToMapNames.clear(); - - this->initTopLevelMapFields(); + this->customMapGroupsData = QJsonObject(); const QString filepath = projectConfig.getFilePath(ProjectFilePath::json_map_groups); fileWatcher.addPath(root + "/" + filepath); @@ -1785,7 +1804,7 @@ bool Project::readMapGroups() { } QJsonObject mapGroupsObj = mapGroupsDoc.object(); - QJsonArray mapGroupOrder = mapGroupsObj["group_order"].toArray(); + QJsonArray mapGroupOrder = mapGroupsObj.take("group_order").toArray(); const QString dynamicMapName = getDynamicMapName(); const QString dynamicMapConstant = getDynamicMapDefineName(); @@ -1794,7 +1813,12 @@ bool Project::readMapGroups() { QStringList failedMapNames; for (int groupIndex = 0; groupIndex < mapGroupOrder.size(); groupIndex++) { const QString groupName = ParseUtil::jsonToQString(mapGroupOrder.at(groupIndex)); - const QJsonArray mapNamesJson = mapGroupsObj.value(groupName).toArray(); + if (this->groupNames.contains(groupName)) { + logWarn(QString("Ignoring repeated map group name '%1'.").arg(groupName)); + continue; + } + + const QJsonArray mapNamesJson = mapGroupsObj.take(groupName).toArray(); this->groupNames.append(groupName); // Process the names in this map group @@ -1888,6 +1912,14 @@ bool Project::readMapGroups() { this->mapConstantsToMapNames.insert(dynamicMapConstant, dynamicMapName); this->mapNames.append(dynamicMapName); + // Chuck the "connections_include_order" field, this is only for matching. + if (!projectConfig.preserveMatchingOnlyData) { + mapGroupsObj.remove("connections_include_order"); + } + + // Preserve any remaining fields for when we save. + this->customMapGroupsData = mapGroupsObj; + return true; } @@ -2317,10 +2349,11 @@ bool Project::readFieldmapMasks() { } bool Project::readRegionMapSections() { + this->locationData.clear(); this->mapSectionIdNames.clear(); this->mapSectionIdNamesSaveOrder.clear(); - this->mapSectionDisplayNames.clear(); - this->regionMapEntries.clear(); + this->customMapSectionsData = QJsonObject(); + const QString defaultName = getEmptyMapsecName(); const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); @@ -2333,7 +2366,8 @@ bool Project::readRegionMapSections() { } fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(filepath)); - QJsonArray mapSections = doc.object()["map_sections"].toArray(); + QJsonObject mapSectionsGlobalObj = doc.object(); + QJsonArray mapSections = mapSectionsGlobalObj.take("map_sections").toArray(); for (int i = 0; i < mapSections.size(); i++) { QJsonObject mapSectionObj = mapSections.at(i).toObject(); @@ -2351,7 +2385,7 @@ bool Project::readRegionMapSections() { continue; } } - const QString idName = ParseUtil::jsonToQString(mapSectionObj[idField]); + const QString idName = ParseUtil::jsonToQString(mapSectionObj.take(idField)); if (!idName.startsWith(requiredPrefix)) { logWarn(QString("Ignoring data for map section '%1' in '%2'. IDs must start with the prefix '%3'").arg(idName).arg(filepath).arg(requiredPrefix)); continue; @@ -2360,8 +2394,10 @@ bool Project::readRegionMapSections() { this->mapSectionIdNames.append(idName); this->mapSectionIdNamesSaveOrder.append(idName); - if (mapSectionObj.contains("name")) - this->mapSectionDisplayNames.insert(idName, ParseUtil::jsonToQString(mapSectionObj["name"])); + LocationData location; + if (mapSectionObj.contains("name")) { + location.displayName = ParseUtil::jsonToQString(mapSectionObj.take("name")); + } // 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. @@ -2373,17 +2409,25 @@ bool Project::readRegionMapSections() { break; } } - if (!hasRegionMapData) - continue; + if (hasRegionMapData) { + location.map.x = ParseUtil::jsonToInt(mapSectionObj.take("x")); + location.map.y = ParseUtil::jsonToInt(mapSectionObj.take("y")); + location.map.width = ParseUtil::jsonToInt(mapSectionObj.take("width")); + location.map.height = ParseUtil::jsonToInt(mapSectionObj.take("height")); + location.map.valid = true; + } - MapSectionEntry entry; - 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; + // Chuck the "name_clone" field, this is only for matching. + if (!projectConfig.preserveMatchingOnlyData) { + mapSectionObj.remove("name_clone"); + } + + // Preserve any remaining fields for when we save. + location.custom = mapSectionObj; + + this->locationData.insert(idName, location); } + this->customMapSectionsData = mapSectionsGlobalObj; // Make sure the default name is present in the list. if (!this->mapSectionIdNames.contains(defaultName)) { @@ -2394,6 +2438,20 @@ bool Project::readRegionMapSections() { return true; } +void Project::setRegionMapEntries(const QHash &entries) { + for (auto it = entries.constBegin(); it != entries.constEnd(); it++) { + this->locationData[it.key()].map = it.value(); + } +} + +QHash Project::getRegionMapEntries() const { + QHash entries; + for (auto it = this->locationData.constBegin(); it != this->locationData.constEnd(); it++) { + entries[it.key()] = it.value().map; + } + return entries; +} + QString Project::getEmptyMapsecName() { return projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_empty); } @@ -2444,9 +2502,9 @@ void Project::removeMapsec(const QString &idName) { } void Project::setMapsecDisplayName(const QString &idName, const QString &displayName) { - if (this->mapSectionDisplayNames.value(idName) == displayName) + if (getMapsecDisplayName(idName) == displayName) return; - this->mapSectionDisplayNames[idName] = displayName; + this->locationData[idName].displayName = displayName; this->hasUnsavedDataChanges = true; emit mapSectionDisplayNameChanged(idName, displayName); } @@ -2457,6 +2515,7 @@ void Project::clearHealLocations() { } this->healLocations.clear(); this->healLocationSaveOrder.clear(); + this->customHealLocationsData = QJsonObject(); } bool Project::readHealLocations() { @@ -2471,7 +2530,8 @@ bool Project::readHealLocations() { } fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(filepath)); - QJsonArray healLocations = doc.object()["heal_locations"].toArray(); + QJsonObject healLocationsObj = doc.object(); + QJsonArray healLocations = healLocationsObj.take("heal_locations").toArray(); for (int i = 0; i < healLocations.size(); i++) { QJsonObject healLocationObj = healLocations.at(i).toObject(); static const QString mapField = QStringLiteral("map"); @@ -2482,9 +2542,10 @@ bool Project::readHealLocations() { auto event = new HealLocationEvent(); event->loadFromJson(healLocationObj, this); - this->healLocations[ParseUtil::jsonToQString(healLocationObj["map"])].append(event); + this->healLocations[event->getHostMapName()].append(event); this->healLocationSaveOrder.append(event->getIdName()); } + this->customHealLocationsData = healLocationsObj; return true; } diff --git a/src/ui/customattributestable.cpp b/src/ui/customattributestable.cpp index 65381443..28153b4d 100644 --- a/src/ui/customattributestable.cpp +++ b/src/ui/customattributestable.cpp @@ -39,8 +39,8 @@ CustomAttributesTable::CustomAttributesTable(QWidget *parent) : }); } -QMap CustomAttributesTable::getAttributes() const { - QMap fields; +QJsonObject CustomAttributesTable::getAttributes() const { + QJsonObject fields; for (int row = 0; row < this->rowCount(); row++) { auto keyValuePair = this->getAttribute(row); if (!keyValuePair.first.isEmpty()) @@ -145,10 +145,10 @@ void CustomAttributesTable::addNewAttribute(const QString &key, const QJsonValue } // For programmatically populating the table -void CustomAttributesTable::setAttributes(const QMap &attributes) { +void CustomAttributesTable::setAttributes(const QJsonObject &attributes) { m_keys.clear(); this->setRowCount(0); // Clear old values - for (auto it = attributes.cbegin(); it != attributes.cend(); it++) + for (auto it = attributes.constBegin(); it != attributes.constEnd(); it++) this->addAttribute(it.key(), it.value()); this->resizeVertically(); } diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 29521974..365567f3 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -444,6 +444,7 @@ void ProjectSettingsEditor::refresh() { ui->checkBox_OutputCallback->setChecked(projectConfig.tilesetsHaveCallback); ui->checkBox_OutputIsCompressed->setChecked(projectConfig.tilesetsHaveIsCompressed); ui->checkBox_DisableWarning->setChecked(porymapConfig.warpBehaviorWarningDisabled); + ui->checkBox_PreserveMatchingOnlyData->setChecked(projectConfig.preserveMatchingOnlyData); // Radio buttons if (projectConfig.setTransparentPixelsBlack) @@ -525,6 +526,7 @@ void ProjectSettingsEditor::save() { projectConfig.tilesetsHaveIsCompressed = ui->checkBox_OutputIsCompressed->isChecked(); porymapConfig.warpBehaviorWarningDisabled = ui->checkBox_DisableWarning->isChecked(); projectConfig.setTransparentPixelsBlack = ui->radioButton_RenderBlack->isChecked(); + projectConfig.preserveMatchingOnlyData = ui->checkBox_PreserveMatchingOnlyData->isChecked(); // Save spin box settings projectConfig.defaultElevation = ui->spinBox_Elevation->value(); diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 32d0be75..b66d442b 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -112,12 +112,12 @@ void RegionMapEditor::applyUserShortcuts() { } bool RegionMapEditor::loadRegionMapEntries() { - this->region_map_entries = this->project->regionMapEntries; + this->region_map_entries = this->project->getRegionMapEntries(); return true; } bool RegionMapEditor::saveRegionMapEntries() { - this->project->regionMapEntries = this->region_map_entries; + this->project->setRegionMapEntries(this->region_map_entries); this->project->saveRegionMapSections(); return true; }