diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index fc720c22..5510c61d 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -36,12 +36,12 @@ The filepath that Porymap expects for each file can be overridden on the ``Files data/tilesets/metatiles.inc, yes, yes, ``tilesets_metatiles_asm``, only if ``tilesets_headers`` can't be found data/tilesets/[primary|secondary]/\*, yes, yes, ``data_tilesets_folders``, default tileset data location src/data/wild_encounters.json, yes, yes, ``json_wild_encounters``, optional (only required to use Wild Pokémon tab) + src/data/heal_locations.json, yes, yes, ``json_heal_locations``, src/data/object_events/object_event_graphics_info_pointers.h, yes, no, ``data_obj_event_gfx_pointers``, src/data/object_events/object_event_graphics_info.h, yes, no, ``data_obj_event_gfx_info``, src/data/object_events/object_event_pic_tables.h, yes, no, ``data_obj_event_pic_tables``, src/data/object_events/object_event_graphics.h, yes, no, ``data_obj_event_gfx``, src/data/graphics/pokemon.h, yes, no, ``data_pokemon_gfx``, for pokemon sprite icons - src/data/heal_locations.h, yes, yes, ``data_heal_locations``, src/data/region_map/region_map_sections.json, yes, yes, ``json_region_map_entries``, src/data/region_map/porymap_config.json, yes, yes, ``json_region_porymap_cfg``, include/constants/global.h, yes, no, ``constants_global``, reads ``define_obj_event_count`` @@ -50,7 +50,6 @@ The filepath that Porymap expects for each file can be overridden on the ``Files include/constants/vars.h, yes, no, ``constants_vars``, for Trigger events include/constants/weather.h, yes, no, ``constants_weather``, for map weather and Weather Triggers include/constants/songs.h, yes, no, ``constants_songs``, for map music - include/constants/heal_locations.h, yes, yes, ``constants_heal_locations``, include/constants/pokemon.h, yes, no, ``constants_pokemon``, reads ``define_min_level`` and ``define_max_level`` include/constants/map_types.h, yes, no, ``constants_map_types``, include/constants/trainer_types.h, yes, no, ``constants_trainer_types``, for Object events @@ -85,11 +84,6 @@ In addition to these files, there are some specific symbol and macro names that ``symbol_obj_event_gfx_pointers``, ``gObjectEventGraphicsInfoPointers``, to map Object Event graphics IDs to graphics data ``symbol_pokemon_icon_table``, ``gMonIconTable``, to map species constants to icon images ``symbol_wild_encounters``, ``gWildMonHeaders``, output as the ``label`` property for the top-level wild ecounters JSON object - ``symbol_heal_locations_type``, ``struct HealLocation``, the type for the Heal Locations table - ``symbol_heal_locations``, ``sHealLocations``, the default Heal Locations table name when ``Respawn Map/NPC`` is disabled - ``symbol_spawn_points``, ``sSpawnPoints``, the default Heal Locations table name when ``Respawn Map/NPC`` is enabled - ``symbol_spawn_maps``, ``u16 sWhiteoutRespawnHealCenterMapIdxs``, the type and table name for Heal Location ``Respawn Map`` values - ``symbol_spawn_npcs``, ``u8 sWhiteoutRespawnHealerNpcIds``, the type and table name for Heal Location ``Respawn NPC`` values ``symbol_attribute_table``, ``sMetatileAttrMasks``, optionally read to get settings on ``Tilesets`` tab ``symbol_tilesets_prefix``, ``gTileset_``, for new tileset names and to extract base tileset names ``symbol_dynamic_map_name``, ``Dynamic``, reserved map name to display for ``define_map_dynamic`` @@ -114,8 +108,7 @@ In addition to these files, there are some specific symbol and macro names that ``define_attribute_terrain``, ``METATILE_ATTRIBUTE_TERRAIN``, name used to extract setting from ``symbol_attribute_table`` ``define_attribute_encounter``, ``METATILE_ATTRIBUTE_ENCOUNTER_TYPE``, name used to extract setting from ``symbol_attribute_table`` ``define_metatile_label_prefix``, ``METATILE_``, expected prefix for metatile label macro names - ``define_heal_locations_prefix``, ``HEAL_LOCATION_``, output as prefix for Heal Location IDs if ``Respawn Map/NPC`` is disabled - ``define_spawn_prefix``, ``SPAWN_``, output as prefix for Heal Location IDs if ``Respawn Map/NPC`` is enabled + ``define_heal_locations_prefix``, ``HEAL_LOCATION_``, default prefix for heal location macro names ``define_map_prefix``, ``MAP_``, expected prefix for map macro names ``define_map_dynamic``, ``DYNAMIC``, macro name after prefix for Dynamic maps ``define_map_empty``, ``UNDEFINED``, macro name after prefix for empty maps diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 4a9e439a..a7a6d112 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2042,9 +2042,9 @@ - + - Healspots + Heal Locations @@ -2091,7 +2091,7 @@ - + Qt::Orientation::Horizontal @@ -2106,7 +2106,7 @@ - + QFrame::Shape::NoFrame @@ -2116,7 +2116,7 @@ Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop - + 0 diff --git a/forms/preferenceeditor.ui b/forms/preferenceeditor.ui index a7009e6e..2863a541 100644 --- a/forms/preferenceeditor.ui +++ b/forms/preferenceeditor.ui @@ -54,6 +54,16 @@ + + + + If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted. + + + Disable warning when deleting events with IDs + + + diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index fc156a72..da006eff 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -1119,9 +1119,9 @@ - + - The icon that will be used to represent Healspot events + The icon that will be used to represent Heal Location events true @@ -1136,9 +1136,9 @@ - + - Healspots + Heal Locations @@ -1224,7 +1224,7 @@ - + ... diff --git a/include/config.h b/include/config.h index 502ee1ec..d4bb055e 100644 --- a/include/config.h +++ b/include/config.h @@ -79,6 +79,7 @@ public: this->paletteEditorBitDepth = 24; this->projectSettingsTab = 0; this->warpBehaviorWarningDisabled = false; + this->eventDeleteWarningDisabled = false; this->checkForUpdates = true; this->lastUpdateCheckTime = QDateTime(); this->lastUpdateCheckVersion = porymapVersion; @@ -132,6 +133,7 @@ public: int paletteEditorBitDepth; int projectSettingsTab; bool warpBehaviorWarningDisabled; + bool eventDeleteWarningDisabled; bool checkForUpdates; QDateTime lastUpdateCheckTime; QVersionNumber lastUpdateCheckVersion; @@ -184,11 +186,6 @@ enum ProjectIdentifier { symbol_obj_event_gfx_pointers, symbol_pokemon_icon_table, symbol_wild_encounters, - symbol_heal_locations_type, - symbol_heal_locations, - symbol_spawn_points, - symbol_spawn_maps, - symbol_spawn_npcs, symbol_attribute_table, symbol_tilesets_prefix, symbol_dynamic_map_name, @@ -214,7 +211,6 @@ enum ProjectIdentifier { define_attribute_encounter, define_metatile_label_prefix, define_heal_locations_prefix, - define_spawn_prefix, define_map_prefix, define_map_dynamic, define_map_empty, @@ -247,6 +243,7 @@ enum ProjectFilePath { json_map_groups, json_layouts, json_wild_encounters, + json_heal_locations, json_region_map_entries, json_region_porymap_cfg, tilesets_headers, @@ -260,14 +257,12 @@ enum ProjectFilePath { data_obj_event_pic_tables, data_obj_event_gfx, data_pokemon_gfx, - data_heal_locations, constants_global, constants_items, constants_flags, constants_vars, constants_weather, constants_songs, - constants_heal_locations, constants_pokemon, constants_map_types, constants_trainer_types, diff --git a/include/core/events.h b/include/core/events.h index 081ff506..bdf9d5a2 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -10,7 +10,6 @@ #include #include "orderedjson.h" -using OrderedJson = poryjson::Json; class Project; @@ -125,7 +124,7 @@ public: // standard public methods public: - virtual Event *duplicate() = 0; + virtual Event *duplicate() const = 0; void setMap(Map *newMap) { this->map = newMap; } Map *getMap() const { return this->map; } @@ -155,7 +154,7 @@ public: Event::Type getEventType() const { return this->eventType; } virtual OrderedJson::object buildEventJson(Project *project) = 0; - virtual bool loadFromJson(QJsonObject json, Project *project) = 0; + virtual bool loadFromJson(const QJsonObject &json, Project *project) = 0; virtual void setDefaultValues(Project *project); @@ -168,10 +167,10 @@ public: virtual void loadPixmap(Project *project); void setPixmap(QPixmap newPixmap) { this->pixmap = newPixmap; } - QPixmap getPixmap() { return this->pixmap; } + QPixmap getPixmap() const { return this->pixmap; } void setPixmapItem(DraggablePixmapItem *item); - DraggablePixmapItem *getPixmapItem() { return this->pixmapItem; } + DraggablePixmapItem *getPixmapItem() const { return this->pixmapItem; } void setUsingSprite(bool newUsingSprite) { this->usingSprite = newUsingSprite; } bool getUsingSprite() const { return this->usingSprite; } @@ -184,6 +183,9 @@ public: int getEventIndex(); + void setIdName(QString newIdName) { this->idName = newIdName; } + QString getIdName() const { return this->idName; } + static QString eventGroupToString(Event::Group group); static QString eventTypeToString(Event::Type type); static Event::Type eventTypeFromString(QString type); @@ -206,6 +208,11 @@ protected: int spriteHeight = 16; bool usingSprite = false; + // Some events can have an associated #define name that should be unique to this event. + // e.g. object events can have a 'LOCALID', or Heal Locations have a 'HEAL_LOCATION' id. + // When deleting events like this we want to warn the user that the #define may also be deleted. + QString idName; + QMap customAttributes; QPixmap pixmap; @@ -227,14 +234,14 @@ public: } virtual ~ObjectEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual void accept(EventVisitor *visitor) override { visitor->visitObject(this); } virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -243,28 +250,28 @@ public: virtual void loadPixmap(Project *project) override; void setGfx(QString newGfx) { this->gfx = newGfx; } - QString getGfx() { return this->gfx; } + QString getGfx() const { return this->gfx; } void setMovement(QString newMovement) { this->movement = newMovement; } - QString getMovement() { return this->movement; } + QString getMovement() const { return this->movement; } void setRadiusX(int newRadiusX) { this->radiusX = newRadiusX; } - int getRadiusX() { return this->radiusX; } + int getRadiusX() const { return this->radiusX; } void setRadiusY(int newRadiusY) { this->radiusY = newRadiusY; } - int getRadiusY() { return this->radiusY; } + int getRadiusY() const { return this->radiusY; } void setTrainerType(QString newTrainerType) { this->trainerType = newTrainerType; } - QString getTrainerType() { return this->trainerType; } + QString getTrainerType() const { return this->trainerType; } void setSightRadiusBerryTreeID(QString newValue) { this->sightRadiusBerryTreeID = newValue; } - QString getSightRadiusBerryTreeID() { return this->sightRadiusBerryTreeID; } + QString getSightRadiusBerryTreeID() const { return this->sightRadiusBerryTreeID; } void setScript(QString newScript) { this->script = newScript; } - QString getScript() { return this->script; } + QString getScript() const { return this->script; } void setFlag(QString newFlag) { this->flag = newFlag; } - QString getFlag() { return this->flag; } + QString getFlag() const { return this->flag; } public: void setFrameFromMovement(QString movement); @@ -300,12 +307,12 @@ public: } virtual ~CloneObjectEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -314,10 +321,10 @@ public: virtual void loadPixmap(Project *project) override; void setTargetMap(QString newTargetMap) { this->targetMap = newTargetMap; } - QString getTargetMap() { return this->targetMap; } + QString getTargetMap() const { return this->targetMap; } void setTargetID(int newTargetID) { this->targetID = newTargetID; } - int getTargetID() { return this->targetID; } + int getTargetID() const { return this->targetID; } private: QString targetMap; @@ -338,22 +345,22 @@ public: } virtual ~WarpEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; void setDestinationMap(QString newDestinationMap) { this->destinationMap = newDestinationMap; } - QString getDestinationMap() { return this->destinationMap; } + QString getDestinationMap() const { return this->destinationMap; } void setDestinationWarpID(QString newDestinationWarpID) { this->destinationWarpID = newDestinationWarpID; } - QString getDestinationWarpID() { return this->destinationWarpID; } + QString getDestinationWarpID() const { return this->destinationWarpID; } void setWarningEnabled(bool enabled); @@ -373,12 +380,12 @@ public: CoordEvent() : Event() {} virtual ~CoordEvent() {} - virtual Event *duplicate() override = 0; + virtual Event *duplicate() const override = 0; virtual EventFrame *createEventFrame() override = 0; virtual OrderedJson::object buildEventJson(Project *project) override = 0; - virtual bool loadFromJson(QJsonObject json, Project *project) override = 0; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override = 0; virtual void setDefaultValues(Project *project) override = 0; @@ -399,27 +406,27 @@ public: } virtual ~TriggerEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual void accept(EventVisitor *visitor) override { visitor->visitTrigger(this); } virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; void setScriptVar(QString newScriptVar) { this->scriptVar = newScriptVar; } - QString getScriptVar() { return this->scriptVar; } + QString getScriptVar() const { return this->scriptVar; } void setScriptVarValue(QString newScriptVarValue) { this->scriptVarValue = newScriptVarValue; } - QString getScriptVarValue() { return this->scriptVarValue; } + QString getScriptVarValue() const { return this->scriptVarValue; } void setScriptLabel(QString newScriptLabel) { this->scriptLabel = newScriptLabel; } - QString getScriptLabel() { return this->scriptLabel; } + QString getScriptLabel() const { return this->scriptLabel; } private: QString scriptVar; @@ -441,19 +448,19 @@ public: } virtual ~WeatherTriggerEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; void setWeather(QString newWeather) { this->weather = newWeather; } - QString getWeather() { return this->weather; } + QString getWeather() const { return this->weather; } private: QString weather; @@ -472,12 +479,12 @@ public: } virtual ~BGEvent() {} - virtual Event *duplicate() override = 0; + virtual Event *duplicate() const override = 0; virtual EventFrame *createEventFrame() override = 0; virtual OrderedJson::object buildEventJson(Project *project) override = 0; - virtual bool loadFromJson(QJsonObject json, Project *project) override = 0; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override = 0; virtual void setDefaultValues(Project *project) override = 0; @@ -497,24 +504,24 @@ public: } virtual ~SignEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual void accept(EventVisitor *visitor) override { visitor->visitSign(this); } virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; void setFacingDirection(QString newFacingDirection) { this->facingDirection = newFacingDirection; } - QString getFacingDirection() { return this->facingDirection; } + QString getFacingDirection() const { return this->facingDirection; } void setScriptLabel(QString newScriptLabel) { this->scriptLabel = newScriptLabel; } - QString getScriptLabel() { return this->scriptLabel; } + QString getScriptLabel() const { return this->scriptLabel; } private: QString facingDirection; @@ -534,28 +541,28 @@ public: } virtual ~HiddenItemEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; void setItem(QString newItem) { this->item = newItem; } - QString getItem() { return this->item; } + QString getItem() const { return this->item; } void setFlag(QString newFlag) { this->flag = newFlag; } - QString getFlag() { return this->flag; } + QString getFlag() const { return this->flag; } void setQuantity(int newQuantity) { this->quantity = newQuantity; } - int getQuantity() { return this->quantity; } + int getQuantity() const { return this->quantity; } void setUnderfoot(bool newUnderfoot) { this->underfoot = newUnderfoot; } - bool getUnderfoot() { return this->underfoot; } + bool getUnderfoot() const { return this->underfoot; } private: QString item; @@ -579,19 +586,19 @@ public: } virtual ~SecretBaseEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject json, Project *project) override; + virtual bool loadFromJson(const QJsonObject &json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; void setBaseID(QString newBaseID) { this->baseID = newBaseID; } - QString getBaseID() { return this->baseID; } + QString getBaseID() const { return this->baseID; } private: QString baseID; @@ -611,38 +618,26 @@ public: } virtual ~HealLocationEvent() {} - virtual Event *duplicate() override { return nullptr; } + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject, Project *) override { return false; } + virtual bool loadFromJson(const QJsonObject &, Project *) override; virtual void setDefaultValues(Project *project) override; - virtual QSet getExpectedFields() override { return QSet(); } + virtual QSet getExpectedFields() override; - void setIndex(int newIndex) { this->index = newIndex; } - int getIndex() { return this->index; } + void setRespawnMapName(QString newRespawnMapName) { this->respawnMapName = newRespawnMapName; } + QString getRespawnMapName() const { return this->respawnMapName; } - void setLocationName(QString newLocationName) { this->locationName = newLocationName; } - QString getLocationName() { return this->locationName; } - - void setIdName(QString newIdName) { this->idName = newIdName; } - QString getIdName() { return this->idName; } - - void setRespawnMap(QString newRespawnMap) { this->respawnMap = newRespawnMap; } - QString getRespawnMap() { return this->respawnMap; } - - void setRespawnNPC(uint8_t newRespawnNPC) { this->respawnNPC = newRespawnNPC; } - uint8_t getRespawnNPC() { return this->respawnNPC; } + void setRespawnNPC(QString newRespawnNPC) { this->respawnNPC = newRespawnNPC; } + QString getRespawnNPC() const { return this->respawnNPC; } private: - int index = -1; - QString locationName; - QString idName; - QString respawnMap; - uint8_t respawnNPC = 0; + QString respawnMapName; + QString respawnNPC; }; @@ -656,7 +651,7 @@ public: virtual void visitTrigger(TriggerEvent *trigger) override { this->scripts << trigger->getScriptLabel(); }; virtual void visitSign(SignEvent *sign) override { this->scripts << sign->getScriptLabel(); }; - QStringList getScripts() { return this->scripts; } + QStringList getScripts() const { return this->scripts; } private: QStringList scripts; diff --git a/include/core/heallocation.h b/include/core/heallocation.h deleted file mode 100644 index 533f44b5..00000000 --- a/include/core/heallocation.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#ifndef HEALLOCATION_H -#define HEALLOCATION_H - -#include -#include - -class Event; - -class HealLocation { - -public: - HealLocation()=default; - HealLocation(QString, QString, int, int16_t, int16_t, QString = "", uint8_t = 1); - friend QDebug operator<<(QDebug debug, const HealLocation &hl); - -public: - QString idName; - QString mapName; - int index; - int16_t x; - int16_t y; - QString respawnMap; - uint8_t respawnNPC; - static HealLocation fromEvent(Event *); -}; - -#endif // HEALLOCATION_H diff --git a/include/core/map.h b/include/core/map.h index b223536d..a25db5e9 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -84,7 +84,7 @@ public: void openScript(QString label); void removeEvent(Event *); void addEvent(Event *); - int getIndexOfEvent(Event *) const; + int getIndexOfEvent(const Event *) const; void deleteConnections(); QList getConnections() const; diff --git a/include/core/parseutil.h b/include/core/parseutil.h index d184646b..b220597a 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -2,7 +2,6 @@ #ifndef PARSEUTIL_H #define PARSEUTIL_H -#include "heallocation.h" #include "log.h" #include "orderedjson.h" #include "orderedmap.h" @@ -78,11 +77,11 @@ public: static QString removeLineComments(QString text, const QStringList &commentSymbols); static QStringList splitShellCommand(QStringView command); - static int gameStringToInt(QString gameString, bool * ok = nullptr); - static bool gameStringToBool(QString gameString, bool * ok = nullptr); - static QString jsonToQString(QJsonValue value, bool * ok = nullptr); - static int jsonToInt(QJsonValue value, bool * ok = nullptr); - static bool jsonToBool(QJsonValue value, bool * ok = nullptr); + static int gameStringToInt(const QString &gameString, bool * ok = nullptr); + static bool gameStringToBool(const QString &gameString, bool * ok = nullptr); + static QString jsonToQString(const QJsonValue &value, bool * ok = nullptr); + static int jsonToInt(const QJsonValue &value, bool * ok = nullptr); + static bool jsonToBool(const QJsonValue &value, bool * ok = nullptr); private: QString root; diff --git a/include/editor.h b/include/editor.h index 81bf1611..104cda9a 100644 --- a/include/editor.h +++ b/include/editor.h @@ -46,7 +46,6 @@ public: public: Ui::MainWindow* ui; - QObject *parent = nullptr; QPointer project = nullptr; QPointer map = nullptr; diff --git a/include/lib/orderedjson.h b/include/lib/orderedjson.h index c13ec13c..386937f9 100644 --- a/include/lib/orderedjson.h +++ b/include/lib/orderedjson.h @@ -247,4 +247,7 @@ protected: } // namespace poryjson +using OrderedJson = poryjson::Json; +using OrderedJsonDoc = poryjson::JsonDoc; + #endif // ORDERED_JSON_H diff --git a/include/project.h b/include/project.h index aff38f03..fcdde462 100644 --- a/include/project.h +++ b/include/project.h @@ -4,7 +4,6 @@ #include "map.h" #include "blockdata.h" -#include "heallocation.h" #include "wildmoninfo.h" #include "parseutil.h" #include "orderedjson.h" @@ -35,8 +34,8 @@ public: QStringList mapNames; QStringList groupNames; QMap groupNameToMapNames; - QList healLocations; - QMap healLocationNameToValue; + QStringList healLocationSaveOrder; + QMap> healLocations; QMap mapConstantsToMapNames; QMap mapNamesToMapConstants; QMap mapNameToLayoutId; @@ -87,15 +86,7 @@ public: void clearTilesetCache(); void clearMapLayouts(); void clearEventGraphics(); - - struct DataQualifiers - { - bool isStatic; - bool isConst; - }; - DataQualifiers getDataQualifiers(QString, QString); - DataQualifiers healLocationDataQualifiers; - QString healLocationsTableName; + void clearHealLocations(); bool sanityCheck(); bool load(); @@ -142,7 +133,8 @@ public: bool isIdentifierUnique(const QString &identifier) const; bool isValidNewIdentifier(QString identifier) const; QString toUniqueIdentifier(const QString &identifier) const; - QString getProjectTitle(); + QString getProjectTitle() const; + QString getNewHealLocationName(const Map* map) const; bool readWildMonData(); tsl::ordered_map> wildMonData; @@ -187,7 +179,7 @@ public: void saveMapGroups(); void saveRegionMapSections(); void saveWildMonData(); - void saveHealLocations(Map*); + void saveHealLocations(); void saveTilesets(Tileset*, Tileset*); void saveTilesetMetatileLabels(Tileset*, Tileset*); void appendTilesetLabel(const QString &label, const QString &isSecondaryStr); @@ -207,7 +199,6 @@ public: bool readBgEventFacingDirections(); bool readTrainerTypes(); bool readMetatileBehaviors(); - bool readHealLocationConstants(); bool readHealLocations(); bool readMiscellaneousConstants(); bool readEventScriptLabels(); @@ -267,9 +258,6 @@ private: void setNewLayoutBlockdata(Layout *layout); void setNewLayoutBorder(Layout *layout); - void saveHealLocationsData(Map *map); - void saveHealLocationsConstants(); - void ignoreWatchedFileTemporarily(QString filepath); static int num_tiles_primary; diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index 2ad55750..cfde161c 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -267,10 +267,11 @@ public: virtual void populate(Project *project) override; public: + QLineEdit *line_edit_id; QFrame *hideable_respawn_map; QFrame *hideable_respawn_npc; NoScrollComboBox *combo_respawn_map; - NoScrollSpinBox *spinner_respawn_npc; + NoScrollComboBox *combo_respawn_npc; private: HealLocationEvent *healLocation; diff --git a/porymap.pro b/porymap.pro index 76d19d21..f3599c1a 100644 --- a/porymap.pro +++ b/porymap.pro @@ -39,7 +39,6 @@ SOURCES += src/core/advancemapparser.cpp \ src/core/blockdata.cpp \ src/core/events.cpp \ src/core/filedialog.cpp \ - src/core/heallocation.cpp \ src/core/imageexport.cpp \ src/core/map.cpp \ src/core/mapconnection.cpp \ @@ -150,7 +149,6 @@ HEADERS += include/core/advancemapparser.h \ include/core/blockdata.h \ include/core/events.h \ include/core/filedialog.h \ - include/core/heallocation.h \ include/core/history.h \ include/core/imageexport.h \ include/core/map.h \ diff --git a/src/config.cpp b/src/config.cpp index 2356f7a9..2511aa65 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -75,11 +75,6 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::symbol_obj_event_gfx_pointers, {"symbol_obj_event_gfx_pointers", "gObjectEventGraphicsInfoPointers"}}, {ProjectIdentifier::symbol_pokemon_icon_table, {"symbol_pokemon_icon_table", "gMonIconTable"}}, {ProjectIdentifier::symbol_wild_encounters, {"symbol_wild_encounters", "gWildMonHeaders"}}, - {ProjectIdentifier::symbol_heal_locations_type, {"symbol_heal_locations_type", "struct HealLocation"}}, - {ProjectIdentifier::symbol_heal_locations, {"symbol_heal_locations", "sHealLocations"}}, - {ProjectIdentifier::symbol_spawn_points, {"symbol_spawn_points", "sSpawnPoints"}}, - {ProjectIdentifier::symbol_spawn_maps, {"symbol_spawn_maps", "u16 sWhiteoutRespawnHealCenterMapIdxs"}}, - {ProjectIdentifier::symbol_spawn_npcs, {"symbol_spawn_npcs", "u8 sWhiteoutRespawnHealerNpcIds"}}, {ProjectIdentifier::symbol_attribute_table, {"symbol_attribute_table", "sMetatileAttrMasks"}}, {ProjectIdentifier::symbol_tilesets_prefix, {"symbol_tilesets_prefix", "gTileset_"}}, {ProjectIdentifier::symbol_dynamic_map_name, {"symbol_dynamic_map_name", "Dynamic"}}, @@ -106,7 +101,6 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::define_attribute_encounter, {"define_attribute_encounter", "METATILE_ATTRIBUTE_ENCOUNTER_TYPE"}}, {ProjectIdentifier::define_metatile_label_prefix, {"define_metatile_label_prefix", "METATILE_"}}, {ProjectIdentifier::define_heal_locations_prefix, {"define_heal_locations_prefix", "HEAL_LOCATION_"}}, - {ProjectIdentifier::define_spawn_prefix, {"define_spawn_prefix", "SPAWN_"}}, {ProjectIdentifier::define_map_prefix, {"define_map_prefix", "MAP_"}}, {ProjectIdentifier::define_map_dynamic, {"define_map_dynamic", "DYNAMIC"}}, {ProjectIdentifier::define_map_empty, {"define_map_empty", "UNDEFINED"}}, @@ -140,6 +134,7 @@ const QMap> ProjectConfig::defaultPaths {ProjectFilePath::json_map_groups, { "json_map_groups", "data/maps/map_groups.json"}}, {ProjectFilePath::json_layouts, { "json_layouts", "data/layouts/layouts.json"}}, {ProjectFilePath::json_wild_encounters, { "json_wild_encounters", "src/data/wild_encounters.json"}}, + {ProjectFilePath::json_heal_locations, { "json_heal_locations", "src/data/heal_locations.json"}}, {ProjectFilePath::json_region_map_entries, { "json_region_map_entries", "src/data/region_map/region_map_sections.json"}}, {ProjectFilePath::json_region_porymap_cfg, { "json_region_porymap_cfg", "src/data/region_map/porymap_config.json"}}, {ProjectFilePath::tilesets_headers, { "tilesets_headers", "src/data/tilesets/headers.h"}}, @@ -153,14 +148,12 @@ const QMap> ProjectConfig::defaultPaths {ProjectFilePath::data_obj_event_pic_tables, { "data_obj_event_pic_tables", "src/data/object_events/object_event_pic_tables.h"}}, {ProjectFilePath::data_obj_event_gfx, { "data_obj_event_gfx", "src/data/object_events/object_event_graphics.h"}}, {ProjectFilePath::data_pokemon_gfx, { "data_pokemon_gfx", "src/data/graphics/pokemon.h"}}, - {ProjectFilePath::data_heal_locations, { "data_heal_locations", "src/data/heal_locations.h"}}, {ProjectFilePath::constants_global, { "constants_global", "include/constants/global.h"}}, {ProjectFilePath::constants_items, { "constants_items", "include/constants/items.h"}}, {ProjectFilePath::constants_flags, { "constants_flags", "include/constants/flags.h"}}, {ProjectFilePath::constants_vars, { "constants_vars", "include/constants/vars.h"}}, {ProjectFilePath::constants_weather, { "constants_weather", "include/constants/weather.h"}}, {ProjectFilePath::constants_songs, { "constants_songs", "include/constants/songs.h"}}, - {ProjectFilePath::constants_heal_locations, { "constants_heal_locations", "include/constants/heal_locations.h"}}, {ProjectFilePath::constants_pokemon, { "constants_pokemon", "include/constants/pokemon.h"}}, {ProjectFilePath::constants_map_types, { "constants_map_types", "include/constants/map_types.h"}}, {ProjectFilePath::constants_trainer_types, { "constants_trainer_types", "include/constants/trainer_types.h"}}, @@ -399,6 +392,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->projectSettingsTab = getConfigInteger(key, value, 0); } else if (key == "warp_behavior_warning_disabled") { this->warpBehaviorWarningDisabled = getConfigBool(key, value); + } else if (key == "event_delete_warning_disabled") { + this->eventDeleteWarningDisabled = getConfigBool(key, value); } else if (key == "check_for_updates") { this->checkForUpdates = getConfigBool(key, value); } else if (key == "last_update_check_time") { @@ -475,6 +470,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("palette_editor_bit_depth", QString::number(this->paletteEditorBitDepth)); map.insert("project_settings_tab", QString::number(this->projectSettingsTab)); map.insert("warp_behavior_warning_disabled", QString::number(this->warpBehaviorWarningDisabled)); + map.insert("event_delete_warning_disabled", QString::number(this->eventDeleteWarningDisabled)); map.insert("check_for_updates", QString::number(this->checkForUpdates)); map.insert("last_update_check_time", this->lastUpdateCheckTime.toUTC().toString()); map.insert("last_update_check_version", this->lastUpdateCheckVersion.toString()); diff --git a/src/core/events.cpp b/src/core/events.cpp index 2da13da0..87421035 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -86,7 +86,7 @@ QString Event::eventGroupToString(Event::Group group) { case Event::Group::Bg: return "BG"; case Event::Group::Heal: - return "Healspot"; + return "Heal Location"; default: return ""; } @@ -111,7 +111,7 @@ QString Event::eventTypeToString(Event::Type type) { case Event::Type::SecretBase: return "event_secret_base"; case Event::Type::HealLocation: - return "event_healspot"; + return "event_heal_location"; default: return ""; } @@ -134,7 +134,7 @@ Event::Type Event::eventTypeFromString(QString type) { return Event::Type::HiddenItem; } else if (type == "event_secret_base") { return Event::Type::SecretBase; - } else if (type == "event_healspot") { + } else if (type == "event_heal_location") { return Event::Type::HealLocation; } else { return Event::Type::None; @@ -183,7 +183,7 @@ void Event::setIcons() { } -Event *ObjectEvent::duplicate() { +Event *ObjectEvent::duplicate() const { ObjectEvent *copy = new ObjectEvent(); copy->setX(this->getX()); @@ -232,7 +232,7 @@ OrderedJson::object ObjectEvent::buildEventJson(Project *) { return objectJson; } -bool ObjectEvent::loadFromJson(QJsonObject json, Project *) { +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"])); @@ -362,7 +362,7 @@ void ObjectEvent::setFrameFromMovement(QString facingDir) { -Event *CloneObjectEvent::duplicate() { +Event *CloneObjectEvent::duplicate() const { CloneObjectEvent *copy = new CloneObjectEvent(); copy->setX(this->getX()); @@ -399,7 +399,7 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { return cloneJson; } -bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { +bool CloneObjectEvent::loadFromJson(const QJsonObject &json, Project *project) { this->setX(ParseUtil::jsonToInt(json["x"])); this->setY(ParseUtil::jsonToInt(json["y"])); this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); @@ -469,7 +469,7 @@ void CloneObjectEvent::loadPixmap(Project *project) { -Event *WarpEvent::duplicate() { +Event *WarpEvent::duplicate() const { WarpEvent *copy = new WarpEvent(); copy->setX(this->getX()); @@ -506,7 +506,7 @@ OrderedJson::object WarpEvent::buildEventJson(Project *project) { return warpJson; } -bool WarpEvent::loadFromJson(QJsonObject json, Project *project) { +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"])); @@ -550,7 +550,7 @@ void WarpEvent::setWarningEnabled(bool enabled) { -Event *TriggerEvent::duplicate() { +Event *TriggerEvent::duplicate() const { TriggerEvent *copy = new TriggerEvent(); copy->setX(this->getX()); @@ -589,7 +589,7 @@ OrderedJson::object TriggerEvent::buildEventJson(Project *) { return triggerJson; } -bool TriggerEvent::loadFromJson(QJsonObject json, Project *) { +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"])); @@ -626,7 +626,7 @@ QSet TriggerEvent::getExpectedFields() { -Event *WeatherTriggerEvent::duplicate() { +Event *WeatherTriggerEvent::duplicate() const { WeatherTriggerEvent *copy = new WeatherTriggerEvent(); copy->setX(this->getX()); @@ -661,7 +661,7 @@ OrderedJson::object WeatherTriggerEvent::buildEventJson(Project *) { return weatherJson; } -bool WeatherTriggerEvent::loadFromJson(QJsonObject json, Project *) { +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"])); @@ -692,7 +692,7 @@ QSet WeatherTriggerEvent::getExpectedFields() { -Event *SignEvent::duplicate() { +Event *SignEvent::duplicate() const { SignEvent *copy = new SignEvent(); copy->setX(this->getX()); @@ -729,7 +729,7 @@ OrderedJson::object SignEvent::buildEventJson(Project *) { return signJson; } -bool SignEvent::loadFromJson(QJsonObject json, Project *) { +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"])); @@ -763,7 +763,7 @@ QSet SignEvent::getExpectedFields() { -Event *HiddenItemEvent::duplicate() { +Event *HiddenItemEvent::duplicate() const { HiddenItemEvent *copy = new HiddenItemEvent(); copy->setX(this->getX()); @@ -808,7 +808,7 @@ OrderedJson::object HiddenItemEvent::buildEventJson(Project *) { return hiddenItemJson; } -bool HiddenItemEvent::loadFromJson(QJsonObject json, Project *) { +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"])); @@ -859,7 +859,7 @@ QSet HiddenItemEvent::getExpectedFields() { -Event *SecretBaseEvent::duplicate() { +Event *SecretBaseEvent::duplicate() const { SecretBaseEvent *copy = new SecretBaseEvent(); copy->setX(this->getX()); @@ -894,7 +894,7 @@ OrderedJson::object SecretBaseEvent::buildEventJson(Project *) { return secretBaseJson; } -bool SecretBaseEvent::loadFromJson(QJsonObject json, Project *) { +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"])); @@ -925,6 +925,20 @@ QSet SecretBaseEvent::getExpectedFields() { +Event *HealLocationEvent::duplicate() const { + HealLocationEvent *copy = new HealLocationEvent(); + + copy->setX(this->getX()); + copy->setY(this->getY()); + copy->setIdName(this->getIdName()); + copy->setRespawnMapName(this->getRespawnMapName()); + copy->setRespawnNPC(this->getRespawnNPC()); + + copy->setCustomAttributes(this->getCustomAttributes()); + + return copy; +} + EventFrame *HealLocationEvent::createEventFrame() { if (!this->eventFrame) { this->eventFrame = new HealLocationFrame(this); @@ -933,22 +947,62 @@ EventFrame *HealLocationEvent::createEventFrame() { return this->eventFrame; } -OrderedJson::object HealLocationEvent::buildEventJson(Project *) { - return OrderedJson::object(); -} +OrderedJson::object HealLocationEvent::buildEventJson(Project *project) { + OrderedJson::object healLocationJson; -void HealLocationEvent::setDefaultValues(Project *) { - this->setElevation(projectConfig.defaultElevation); - if (!this->map) - return; - - bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; - const QString prefix = projectConfig.getIdentifier(respawnEnabled ? ProjectIdentifier::define_spawn_prefix - : ProjectIdentifier::define_heal_locations_prefix); - this->setLocationName(this->map->constantName()); - this->setIdName(prefix + this->map->constantName()); - if (respawnEnabled) { - this->setRespawnMap(this->map->name()); - this->setRespawnNPC(1); + 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["x"] = this->getX(); + healLocationJson["y"] = this->getY(); + if (projectConfig.healLocationRespawnDataEnabled) { + const QString mapName = this->getRespawnMapName(); + healLocationJson["respawn_map"] = project->mapNamesToMapConstants.value(mapName, mapName); + healLocationJson["respawn_npc"] = this->getRespawnNPC(); } + + this->addCustomAttributesTo(&healLocationJson); + + 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"])); + + 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"]); + 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->readCustomAttributes(json); + return true; +} + +void HealLocationEvent::setDefaultValues(Project *project) { + if (this->map) { + this->setIdName(project->getNewHealLocationName(this->map)); + this->setRespawnMapName(this->map->name()); + } + this->setRespawnNPC(QString::number(0 + this->getIndexOffset(Event::Group::Object))); +} + +const QSet expectedHealLocationFields = { + "id", + "map" +}; + +QSet HealLocationEvent::getExpectedFields() { + QSet expectedFields = expectedHealLocationFields; + if (projectConfig.healLocationRespawnDataEnabled) { + expectedFields.insert("respawn_map"); + expectedFields.insert("respawn_npc"); + } + expectedFields << "x" << "y"; + return expectedFields; } diff --git a/src/core/heallocation.cpp b/src/core/heallocation.cpp deleted file mode 100644 index d4f9a2c8..00000000 --- a/src/core/heallocation.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "heallocation.h" -#include "config.h" -#include "events.h" -#include "map.h" - -// TODO: Remove - -HealLocation::HealLocation(QString id, QString map, - int i, int16_t x, int16_t y, - QString respawnMap, uint8_t respawnNPC) { - this->idName = id; - this->mapName = map; - this->index = i; - this->x = x; - this->y = y; - this->respawnMap = respawnMap; - this->respawnNPC = respawnNPC; -} - -HealLocation HealLocation::fromEvent(Event *fromEvent) { - HealLocationEvent *event = dynamic_cast(fromEvent); - - HealLocation healLocation; - healLocation.idName = event->getIdName(); - healLocation.mapName = event->getLocationName(); - healLocation.index = event->getIndex(); - healLocation.x = event->getX(); - healLocation.y = event->getY(); - if (projectConfig.healLocationRespawnDataEnabled) { - healLocation.respawnNPC = event->getRespawnNPC(); - healLocation.respawnMap = Map::mapConstantFromName(event->getRespawnMap(), false); - } - return healLocation; -} - -QDebug operator<<(QDebug debug, const HealLocation &healLocation) { - debug << "HealLocation_" + healLocation.mapName << "(" << healLocation.x << ',' << healLocation.y << ")"; - return debug; -} diff --git a/src/core/map.cpp b/src/core/map.cpp index aac20138..e784ba5f 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -230,7 +230,7 @@ void Map::addEvent(Event *event) { if (!m_ownedEvents.contains(event)) m_ownedEvents.insert(event); } -int Map::getIndexOfEvent(Event *event) const { +int Map::getIndexOfEvent(const Event *event) const { return m_events.value(event->getEventGroup()).indexOf(event); } diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index a4a2018d..d67989ae 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -31,8 +31,6 @@ static const QMap globalDefineValues = { {"UINT_MAX", UINT_MAX}, }; -using OrderedJson = poryjson::Json; - ParseUtil::ParseUtil() { } void ParseUtil::set_root(const QString &dir) { @@ -580,7 +578,7 @@ QMap ParseUtil::readNamedIndexCArray(const QString &filename, return map; } -int ParseUtil::gameStringToInt(QString gameString, bool * ok) { +int ParseUtil::gameStringToInt(const QString &gameString, bool * ok) { if (ok) *ok = true; if (QString::compare(gameString, "TRUE", Qt::CaseInsensitive) == 0) return 1; @@ -589,7 +587,7 @@ int ParseUtil::gameStringToInt(QString gameString, bool * ok) { return gameString.toInt(ok, 0); } -bool ParseUtil::gameStringToBool(QString gameString, bool * ok) { +bool ParseUtil::gameStringToBool(const QString &gameString, bool * ok) { return gameStringToInt(gameString, ok) != 0; } @@ -705,7 +703,7 @@ bool ParseUtil::ensureFieldsExist(const QJsonObject &obj, const QList & // QJsonValues are strictly typed, and so will not attempt any implicit conversions. // The below functions are for attempting to convert a JSON value read from the user's // project to a QString, int, or bool (whichever Porymap expects). -QString ParseUtil::jsonToQString(QJsonValue value, bool * ok) { +QString ParseUtil::jsonToQString(const QJsonValue &value, bool * ok) { if (ok) *ok = true; switch (value.type()) { @@ -718,7 +716,7 @@ QString ParseUtil::jsonToQString(QJsonValue value, bool * ok) { return QString(); } -int ParseUtil::jsonToInt(QJsonValue value, bool * ok) { +int ParseUtil::jsonToInt(const QJsonValue &value, bool * ok) { if (ok) *ok = true; switch (value.type()) { @@ -731,7 +729,7 @@ int ParseUtil::jsonToInt(QJsonValue value, bool * ok) { return 0; } -bool ParseUtil::jsonToBool(QJsonValue value, bool * ok) { +bool ParseUtil::jsonToBool(const QJsonValue &value, bool * ok) { if (ok) *ok = true; switch (value.type()) { diff --git a/src/editor.cpp b/src/editor.cpp index 3c7fee27..ceb7aa67 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -12,6 +12,7 @@ #include "scripting.h" #include "customattributesframe.h" #include "validator.h" +#include "message.h" #include #include #include @@ -1376,13 +1377,11 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i if (this->selected_events->size() > 0) eventType = this->selected_events->first()->event->getEventType(); - if (eventType != Event::Type::HealLocation) { - DraggablePixmapItem *newEvent = addNewEvent(eventType); - if (newEvent) { - newEvent->move(pos.x(), pos.y()); - emit objectsChanged(); - selectMapEvent(newEvent); - } + DraggablePixmapItem *newEvent = addNewEvent(eventType); + if (newEvent) { + newEvent->move(pos.x(), pos.y()); + emit objectsChanged(); + selectMapEvent(newEvent); } } } else if (objectEditAction == EditAction::Select) { @@ -2112,15 +2111,7 @@ void Editor::duplicateSelectedEvents() { logWarn(QString("Skipping duplication, the map limit for events of type '%1' has been reached.").arg(Event::eventTypeToString(eventType))); continue; } - if (eventType == Event::Type::HealLocation) { - logWarn("Skipping duplication, event is a heal location."); - continue; - } Event *duplicate = original->duplicate(); - if (!duplicate) { - logError("Encountered a problem duplicating an event."); - continue; - } duplicate->setX(duplicate->getX() + 1); duplicate->setY(duplicate->getY() + 1); selectedEvents.append(duplicate); @@ -2138,13 +2129,6 @@ DraggablePixmapItem *Editor::addNewEvent(Event::Type type) { event->setMap(this->map); event->setDefaultValues(this->project); - - if (type == Event::Type::HealLocation) { - HealLocation healLocation = HealLocation::fromEvent(event); - project->healLocations.append(healLocation); - ((HealLocationEvent *)event)->setIndex(project->healLocations.length()); - } - map->commit(new EventCreate(this, map, event)); return event->getPixmapItem(); } @@ -2162,42 +2146,73 @@ void Editor::deleteSelectedEvents() { if (!this->selected_events || this->selected_events->length() == 0 || !this->map || this->editMode != EditMode::Events) return; - DraggablePixmapItem *nextSelectedEvent = nullptr; - QList selectedEvents; - int numDeleted = 0; + QList eventsToDelete; + bool skipWarning = porymapConfig.eventDeleteWarningDisabled; for (DraggablePixmapItem *item : *this->selected_events) { - Event::Group event_group = item->event->getEventGroup(); - if (event_group != Event::Group::Heal) { - numDeleted++; - item->event->setPixmapItem(item); - selectedEvents.append(item->event); - } - else { // don't allow deletion of heal locations - logWarn(QString("Cannot delete event of type '%1'").arg(Event::eventTypeToString(item->event->getEventType()))); - } - } - if (numDeleted) { - // Get the index for the event that should be selected after this event has been deleted. - // Select event at next smallest index when deleting a single event. - // If deleting multiple events, just let editor work out next selected. - if (numDeleted == 1) { - Event::Group event_group = selectedEvents[0]->getEventGroup(); - int index = this->map->getIndexOfEvent(selectedEvents[0]); - if (index != this->map->getNumEvents(event_group) - 1) - index++; - else - index--; - Event *event = this->map->getEvent(event_group, index); - for (QGraphicsItem *child : this->events_group->childItems()) { - DraggablePixmapItem *event_item = static_cast(child); - if (event_item->event == event) { - nextSelectedEvent = event_item; - break; + Event* event = item->event; + const QString idName = event->getIdName(); + if (skipWarning || idName.isEmpty()) { + eventsToDelete.append(event); + } else { + // If an event with a ID #define is deleted, its ID is also deleted (by the user's project, not Porymap). + // Warn the user about this and give them a chance to abort. + WarningMessage msgBox(QStringLiteral("Deleting this event may also delete the constant listed below. This can stop your project from compiling.\n\n" + "Are you sure you want to delete this event?"), + ui->graphicsView_Map); + msgBox.setInformativeText(idName); + msgBox.setIconPixmap(event->getPixmap()); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + msgBox.addButton(QStringLiteral("Delete"), QMessageBox::DestructiveRole); + msgBox.setCheckBox(new QCheckBox(QStringLiteral("Don't warn me again"))); + + QAbstractButton* deleteAllButton = nullptr; + if (this->selected_events->length() > 1) { + deleteAllButton = msgBox.addButton(QStringLiteral("Delete All"), QMessageBox::DestructiveRole); + msgBox.addButton(QStringLiteral("Skip"), QMessageBox::NoRole); + } + + msgBox.exec(); + auto clickedButton = msgBox.clickedButton(); + auto clickedRole = msgBox.buttonRole(clickedButton); + porymapConfig.eventDeleteWarningDisabled = msgBox.checkBox()->isChecked(); + if (clickedRole == QMessageBox::DestructiveRole) { + // Confirmed deleting this event. + eventsToDelete.append(event); + if (deleteAllButton && clickedButton == deleteAllButton) { + // Confirmed deleting all events, no more warning. + skipWarning = true; } + } else if (clickedRole == QMessageBox::NoRole) { + // Declined deleting this event. + continue; + } else if (clickedRole == QMessageBox::RejectRole) { + // Canceled delete. + return; } } - this->map->commit(new EventDelete(this, this->map, selectedEvents, nextSelectedEvent ? nextSelectedEvent->event : nullptr)); + // TODO: Are we just calling this to invalidate connections? + event->setPixmapItem(item); } + if (eventsToDelete.isEmpty()) + return; + + // Get the index for the event that should be selected after this event has been deleted. + // Select event at next smallest index when deleting a single event. + // If deleting multiple events, just let editor work out next selected. + Event *nextSelectedEvent = nullptr; + if (eventsToDelete.length() == 1) { + const Event *eventToDelete = eventsToDelete.first(); + Event::Group event_group = eventToDelete->getEventGroup(); + int index = this->map->getIndexOfEvent(eventToDelete); + if (index != this->map->getNumEvents(event_group) - 1) + index++; + else + index--; + nextSelectedEvent = this->map->getEvent(event_group, index); + } + + this->map->commit(new EventDelete(this, this->map, eventsToDelete, nextSelectedEvent)); } void Editor::openMapScripts() const { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d0ba8040..5e05517e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -56,9 +56,6 @@ #define RELEASE_PLATFORM #endif -using OrderedJson = poryjson::Json; -using OrderedJsonDoc = poryjson::JsonDoc; - MainWindow::MainWindow(QWidget *parent) : @@ -857,6 +854,11 @@ bool MainWindow::userSetMap(QString map_name) { if (editor->map && editor->map->name() == map_name) return true; // Already set + if (map_name.isEmpty()) { + WarningMessage::show(QStringLiteral("Cannot open map with empty name."), this); + return false; + } + if (map_name == editor->project->getDynamicMapName()) { WarningMessage msgBox(QString("Cannot open map '%1'.").arg(map_name), this); msgBox.setInformativeText(QStringLiteral("This map name is a placeholder to indicate that the warp's map will be set programmatically.")); @@ -1276,6 +1278,10 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { logInfo(QString("Created a new map named %1.").arg(newMap->name())); + if (newMap->needsHealLocation()) { + addNewEvent(Event::Type::HealLocation); + } + // TODO: Creating a new map shouldn't be automatically saved. // For one, it takes away the option to discard the new map. // For two, if the new map uses an existing layout, any unsaved changes to that layout will also be saved. @@ -1297,12 +1303,6 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { ui->comboBox_EmergeMap->insertItem(mapIndex, newMap->name()); } - if (newMap->needsHealLocation()) { - addNewEvent(Event::Type::HealLocation); - editor->project->saveHealLocations(newMap); - editor->save(); - } - userSetMap(newMap->name()); } @@ -1606,13 +1606,6 @@ void MainWindow::copy() { for (auto item : events) { Event *event = item->event; - - if (event->getEventType() == Event::Type::HealLocation) { - // no copy on heal locations - logWarn(QString("Copying heal location events is not allowed.")); - continue; - } - OrderedJson::object eventContainer; eventContainer["event_type"] = Event::eventTypeToString(event->getEventType()); OrderedJson::object eventJson = event->buildEventJson(editor->project); @@ -1732,10 +1725,6 @@ void MainWindow::paste() { logWarn(QString("Cannot paste event, the limit for type '%1' has been reached.").arg(typeString)); continue; } - if (type == Event::Type::HealLocation) { - logWarn(QString("Cannot paste events of type '%1'").arg(typeString)); - continue; - } Event *pasteEvent = Event::create(type); if (!pasteEvent) @@ -1981,7 +1970,7 @@ void MainWindow::displayEventTabs() { tryAddEventTab(ui->tab_Warps); tryAddEventTab(ui->tab_Triggers); tryAddEventTab(ui->tab_BGs); - tryAddEventTab(ui->tab_Healspots); + tryAddEventTab(ui->tab_HealLocations); } void MainWindow::updateObjects() { @@ -2075,9 +2064,9 @@ void MainWindow::updateSelectedObjects() { break; } case Event::Group::Heal: { - scrollTarget = ui->scrollArea_Healspots; - target = ui->scrollAreaWidgetContents_Healspots; - ui->tabWidget_EventType->setCurrentWidget(ui->tab_Healspots); + scrollTarget = ui->scrollArea_HealLocations; + target = ui->scrollAreaWidgetContents_HealLocations; + ui->tabWidget_EventType->setCurrentWidget(ui->tab_HealLocations); QSignalBlocker b(this->ui->spinner_HealID); this->ui->spinner_HealID->setMinimum(event_offs); @@ -2142,11 +2131,11 @@ void MainWindow::updateSelectedObjects() { Event::Group MainWindow::getEventGroupFromTabWidget(QWidget *tab) { static const QMap tabToGroup = { - {ui->tab_Objects, Event::Group::Object}, - {ui->tab_Warps, Event::Group::Warp}, - {ui->tab_Triggers, Event::Group::Coord}, - {ui->tab_BGs, Event::Group::Bg}, - {ui->tab_Healspots, Event::Group::Heal}, + {ui->tab_Objects, Event::Group::Object}, + {ui->tab_Warps, Event::Group::Warp}, + {ui->tab_Triggers, Event::Group::Coord}, + {ui->tab_BGs, Event::Group::Bg}, + {ui->tab_HealLocations, Event::Group::Heal}, }; return tabToGroup.value(tab, Event::Group::None); } @@ -2169,6 +2158,9 @@ void MainWindow::eventTabChanged(int index) { case Event::Group::Bg: ui->newEventToolButton->setDefaultAction(ui->newEventToolButton->newSignAction); break; + case Event::Group::Heal: + ui->newEventToolButton->setDefaultAction(ui->newEventToolButton->newHealLocationAction); + break; default: break; } diff --git a/src/project.cpp b/src/project.cpp index 9e0a8c3a..690ecdcc 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -23,9 +23,6 @@ #include #include -using OrderedJson = poryjson::Json; -using OrderedJsonDoc = poryjson::JsonDoc; - int Project::num_tiles_primary = 512; int Project::num_tiles_total = 1024; int Project::num_metatiles_primary = 512; @@ -47,6 +44,7 @@ Project::~Project() clearTilesetCache(); clearMapLayouts(); clearEventGraphics(); + clearHealLocations(); } void Project::set_root(QString dir) { @@ -97,7 +95,6 @@ bool Project::load() { && readFieldmapMasks() && readTilesetLabels() && readTilesetMetatileLabels() - && readHealLocations() && readMiscellaneousConstants() && readSpeciesIconPaths() && readWildMonData() @@ -105,7 +102,8 @@ bool Project::load() { && readObjEventGfxConstants() && readEventGraphics() && readSongNames() - && readMapGroups(); + && readMapGroups() + && readHealLocations(); if (success) { // No need to do this if something failed to load. @@ -118,7 +116,7 @@ bool Project::load() { return success; } -QString Project::getProjectTitle() { +QString Project::getProjectTitle() const { if (!root.isNull()) { return root.section('/', -1); } else { @@ -182,7 +180,6 @@ void Project::initTopLevelMapFields() { "warp_events", "coord_events", "bg_events", - "heal_locations", "shared_events_map", "shared_scripts_map", }; @@ -316,30 +313,11 @@ bool Project::loadMapData(Map* map) { } } - -/* TODO: Re-enable - const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); - for (auto it = healLocations.begin(); it != healLocations.end(); it++) { - HealLocation loc = *it; - //if TRUE map is flyable / has healing location - if (loc.mapName == Map::mapConstantFromName(map->name, false)) { - HealLocationEvent *heal = new HealLocationEvent(); - heal->setMap(map); - heal->setX(loc.x); - heal->setY(loc.y); - heal->setElevation(projectConfig.defaultElevation); - heal->setLocationName(loc.mapName); - heal->setIdName(loc.idName); - heal->setIndex(loc.index); - if (projectConfig.healLocationRespawnDataEnabled) { - heal->setRespawnMap(mapConstantsToMapNames.value(QString(mapPrefix + loc.respawnMap))); - heal->setRespawnNPC(loc.respawnNPC); - } - map->events[Event::Group::Heal].append(heal); - map->ownedEvents.append(heal); - } + // Heal locations are global. Populate the Map's heal location events using our global array. + const QList hlEvents = this->healLocations.value(map->constantName()); + for (const auto &event : hlEvents) { + map->addEvent(event->duplicate()); } -*/ map->deleteConnections(); QJsonArray connectionsArr = mapObj["connections"].toArray(); @@ -846,134 +824,67 @@ void Project::saveWildMonData() { wildEncountersFile.close(); } -void Project::saveHealLocations(Map *map) { - this->saveHealLocationsData(map); - this->saveHealLocationsConstants(); +// For a map with a constant of 'MAP_FOO', returns a unique 'HEAL_LOCATION_FOO'. +// Because of how event ID names are checked it doesn't guarantee that the name +// won't be in-use by some map that hasn't been loaded yet. +QString Project::getNewHealLocationName(const Map* map) const { + if (!map) return QString(); + + QString idName = map->constantName(); + const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); + if (idName.startsWith(mapPrefix)) { + idName.remove(0, mapPrefix.length()); + } + return toUniqueIdentifier(projectConfig.getIdentifier(ProjectIdentifier::define_heal_locations_prefix) + idName); } -// Saves heal location maps/coords/respawn data in root + /src/data/heal_locations.h -void Project::saveHealLocationsData(Map *) { -/* TODO: Will be re-implemented as part of changes to reading heal locations from map.json - // Update heal locations from map - if (map->events[Event::Group::Heal].length() > 0) { - for (Event *healEvent : map->events[Event::Group::Heal]) { - HealLocation hl = HealLocation::fromEvent(healEvent); - this->healLocations[hl.index - 1] = hl; +void Project::saveHealLocations() { + const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_heal_locations)); + QFile file(filepath); + if (!file.open(QIODevice::WriteOnly)) { + logError(QString("Could not open '%1' for writing").arg(filepath)); + return; + } + + // Build the JSON data for output. + QMap> idNameToJson; + for (auto i = this->healLocations.constBegin(); i != this->healLocations.constEnd(); i++) { + const QString mapConstant = i.key(); + for (const auto &event : i.value()) { + // Heal location events don't need to track the "map" field, we're already tracking it either with + // the keys in the healLocations map or by virtue of the event being added to a particular Map object. + // The global JSON data needs this field, so we add it back here. + auto eventJson = event->buildEventJson(this); + eventJson["map"] = mapConstant; + idNameToJson[event->getIdName()].append(eventJson); } } - - // Find any duplicate constant names - QMap healLocationsDupes; - QSet 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 - bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; - const QString qualifiers = QString(healLocationDataQualifiers.isStatic ? "static " : "") - + QString(healLocationDataQualifiers.isConst ? "const " : ""); - - QString locationTableText = QString("%1%2 %3[] =\n{\n").arg(qualifiers) - .arg(projectConfig.getIdentifier(ProjectIdentifier::symbol_heal_locations_type)) - .arg(this->healLocationsTableName); - QString respawnMapTableText, respawnNPCTableText; - if (respawnEnabled) { - 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)); - } - - // Populate the data tables with the heal location data - int i = 0; - const QString emptyMapName = projectConfig.getIdentifier(ProjectIdentifier::define_map_empty); - 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]); - healLocationsDupes[duplicateName]++; - this->healLocations[i].idName = hl.idName; // Update the name for writing constants later + // We store Heal Locations in a QMap with map name keys. This makes retrieval by map name easy, + // but it will sort them alphabetically. Project::healLocationSaveOrder lets us better support + // the (perhaps unlikely) user who has designed something with assumptions about the order of the data. + // This also avoids a bunch of diff noise on the first save from Porymap reordering the data. + OrderedJson::array eventJsonArr; + for (const auto &idName : this->healLocationSaveOrder) { + if (!idNameToJson.value(idName).isEmpty()) { + eventJsonArr.push_back(idNameToJson[idName].takeFirst()); + } + } + // Save any heal locations that weren't covered above (should be any new data). + for (auto i = idNameToJson.constBegin(); i != idNameToJson.constEnd(); i++) { + for (const auto &object : i.value()) { + eventJsonArr.push_back(object); } - - // 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); - - // 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); - } - i++; } - const QString tableEnd = QString("};\n"); - QString text = locationTableText + tableEnd; - if (respawnEnabled) - text += respawnMapTableText + tableEnd + respawnNPCTableText + tableEnd; - QString filepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::data_heal_locations); + OrderedJson::object object; + object["heal_locations"] = eventJsonArr; + ignoreWatchedFileTemporarily(filepath); - saveTextFile(filepath, text); - */ -} - -// 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 valuesToNames = QMap(); - QStringList defineNames = this->healLocationNameToValue.keys(); - QList 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)); - } - 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); - } - - // 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::const_iterator i; - for (i = valuesToNames.constBegin(); i != valuesToNames.constEnd(); i++) - constantsText += QString("#define %1 %2\n").arg(i.value()).arg(i.key()); - - constantsText += QString("\n#endif // %1\n").arg(guardName); - - QString filepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::constants_heal_locations); - ignoreWatchedFileTemporarily(filepath); - saveTextFile(filepath, constantsText); + OrderedJson json(object); + OrderedJsonDoc jsonDoc(&json); + jsonDoc.dump(&file); + file.close(); } void Project::saveTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { @@ -1336,6 +1247,16 @@ void Project::saveMap(Map *map) { mapObj["shared_scripts_map"] = map->sharedScriptsMap(); } + // Update the global heal locations array using the Map's heal location events. + // This won't get saved to disc until Project::saveHealLocations is called. + QList hlEvents; + for (const auto &event : map->getEvents(Event::Group::Heal)) { + auto hl = static_cast(event); + hlEvents.append(static_cast(hl->duplicate())); + } + qDeleteAll(this->healLocations[map->constantName()]); + this->healLocations[map->constantName()] = hlEvents; + // Custom header fields. const auto customAttributes = map->customAttributes(); for (auto i = customAttributes.constBegin(); i != customAttributes.constEnd(); i++) { @@ -1348,7 +1269,6 @@ void Project::saveMap(Map *map) { mapFile.close(); saveLayout(map->layout()); - saveHealLocations(map); map->setClean(); } @@ -1380,6 +1300,7 @@ void Project::saveAllDataStructures() { saveMapLayouts(); saveMapGroups(); saveRegionMapSections(); + saveHealLocations(); saveWildMonData(); saveConfig(); this->hasUnsavedDataChanges = false; @@ -1975,6 +1896,15 @@ bool Project::isIdentifierUnique(const QString &identifier) const { return false; if (this->encounterGroupLabels.contains(identifier)) return false; + // Check event IDs + for (const auto &map : this->mapCache) { + auto events = map->getEvents(); + for (const auto &event : events) { + QString idName = event->getIdName(); + if (!idName.isEmpty() && idName == identifier) + return false; + } + } return true; } @@ -2034,18 +1964,6 @@ void Project::initNewLayoutSettings() { this->newLayoutSettings.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel(); } -Project::DataQualifiers Project::getDataQualifiers(QString text, QString label) { - Project::DataQualifiers qualifiers; - - QRegularExpression regex(QString("\\s*(?static\\s*)?(?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; -} - QString Project::getDefaultPrimaryTilesetLabel() const { QString defaultLabel = projectConfig.defaultPrimaryTileset; if (!this->primaryTilesetLabels.contains(defaultLabel)) { @@ -2433,110 +2351,40 @@ void Project::setMapsecDisplayName(const QString &idName, const QString &display emit mapSectionDisplayNameChanged(idName, displayName); } -// Read the constants to preserve any "unused" heal locations when writing the file later -bool Project::readHealLocationConstants() { - this->healLocationNameToValue.clear(); - const QStringList regexList = { - QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_heal_locations_prefix)), - QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_spawn_prefix)) - }; - QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_heal_locations); - fileWatcher.addPath(root + "/" + constantsFilename); - this->healLocationNameToValue = parser.readCDefinesByRegex(constantsFilename, regexList); - // No need to check if empty, not finding any heal location constants is ok - return true; +void Project::clearHealLocations() { + for (auto &events : this->healLocations) { + qDeleteAll(events); + } + this->healLocations.clear(); + this->healLocationSaveOrder.clear(); } -// TODO: Simplify using the new C struct parsing functions (and indexed array parsing functions) bool Project::readHealLocations() { - this->healLocations.clear(); + clearHealLocations(); - if (!this->readHealLocationConstants()) + QJsonDocument doc; + const QString baseFilepath = projectConfig.getFilePath(ProjectFilePath::json_heal_locations); + const QString filepath = QString("%1/%2").arg(this->root).arg(baseFilepath); + if (!parser.tryParseJsonFile(&doc, filepath)) { + logError(QString("Failed to read heal locations from '%1'").arg(baseFilepath)); return false; - - QString filename = projectConfig.getFilePath(ProjectFilePath::data_heal_locations); - fileWatcher.addPath(root + "/" + filename); - QString text = parser.readTextFile(root + "/" + filename); - - // Strip comments - static const QRegularExpression re_comments("//.*?(\r\n?|\n)|/\\*.*?\\*/", QRegularExpression::DotMatchesEverythingOption); - text.replace(re_comments, ""); - - bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; - - // Search for the name of the main Heal Locations table - const QRegularExpression tableNameExpr(QString("%1\\s+(?[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 }; } + fileWatcher.addPath(filepath); - // Create regex pattern for the constants (ex: "SPAWN_PALLET_TOWN" or "HEAL_LOCATION_PETALBURG_CITY") - 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)); - - // 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. - // Any data entry that repeats a designated initializer will also be 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*-\\s*1\\s*\\]\\s*=\\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"); - int x = match.captured("x").toInt(nullptr, 0); - int y = match.captured("y").toInt(nullptr, 0); - healLocation = HealLocation(idName, mapName, this->healLocations.size() + 1, x, y); - } else { - // 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); + QJsonArray healLocations = doc.object()["heal_locations"].toArray(); + for (int i = 0; i < healLocations.size(); i++) { + QJsonObject healLocationObj = healLocations.at(i).toObject(); + static const QString mapField = QStringLiteral("map"); + if (!healLocationObj.contains(mapField)) { + logWarn(QString("Ignoring data for heal location %1 in '%2'. Missing required field \"%3\"").arg(i).arg(baseFilepath).arg(mapField)); + 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(nullptr, 0); - } - - this->healLocations.append(healLocation); + auto event = new HealLocationEvent(); + event->loadFromJson(healLocationObj, this); + this->healLocations[ParseUtil::jsonToQString(healLocationObj["map"])].append(event); + this->healLocationSaveOrder.append(event->getIdName()); } - // No need to check if empty, not finding any heal locations is ok return true; } diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index 78a00bb6..03f1bb99 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -107,6 +107,7 @@ void DraggablePixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) { } } +// Events with properties that specify a map will open that map when double-clicked. void DraggablePixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { Event::Type eventType = this->event->getEventType(); if (eventType == Event::Type::Warp) { @@ -126,4 +127,10 @@ void DraggablePixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { QString destMap = editor->project->mapConstantsToMapNames.value(mapPrefix + baseId.left(baseId.lastIndexOf("_"))); emit editor->warpEventDoubleClicked(destMap, 0, Event::Group::Warp); } + else if (eventType == Event::Type::HealLocation && projectConfig.healLocationRespawnDataEnabled) { + HealLocationEvent *heal = dynamic_cast(this->event); + const QString localIdName = heal->getRespawnNPC(); + int localId = 0; // TODO: Get value from localIdName + emit editor->warpEventDoubleClicked(heal->getRespawnMapName(), localId, Event::Group::Object); + } } diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 61794c41..40a545ab 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -947,6 +947,14 @@ void HealLocationFrame::setup() { this->hideable_label_z->setVisible(false); this->spinner_z->setVisible(false); + // ID + QFormLayout *l_form_id = new QFormLayout(); + this->line_edit_id = new QLineEdit(this); + this->line_edit_id->setToolTip("The unique identifier for this heal location."); + this->line_edit_id->setPlaceholderText(projectConfig.getIdentifier(ProjectIdentifier::define_heal_locations_prefix) + "MY_MAP"); + l_form_id->addRow("ID", this->line_edit_id); + this->layout_contents->addLayout(l_form_id); + // respawn map combo this->hideable_respawn_map = new QFrame; QFormLayout *l_form_respawn_map = new QFormLayout(hideable_respawn_map); @@ -960,10 +968,10 @@ void HealLocationFrame::setup() { this->hideable_respawn_npc = new QFrame; QFormLayout *l_form_respawn_npc = new QFormLayout(hideable_respawn_npc); l_form_respawn_npc->setContentsMargins(0, 0, 0, 0); - this->spinner_respawn_npc = new NoScrollSpinBox(hideable_respawn_npc); - this->spinner_respawn_npc->setToolTip("event_object ID of the NPC the player interacts with\n" + this->combo_respawn_npc = new NoScrollComboBox(hideable_respawn_npc); + this->combo_respawn_npc->setToolTip("event_object ID of the NPC the player interacts with\n" "upon respawning after whiteout."); - l_form_respawn_npc->addRow("Respawn NPC", this->spinner_respawn_npc); + l_form_respawn_npc->addRow("Respawn NPC", this->combo_respawn_npc); this->layout_contents->addWidget(hideable_respawn_npc); // custom attributes @@ -975,19 +983,23 @@ void HealLocationFrame::connectSignals(MainWindow *window) { EventFrame::connectSignals(window); - if (projectConfig.healLocationRespawnDataEnabled) { - this->combo_respawn_map->disconnect(); - connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this](const QString &text) { - this->healLocation->setRespawnMap(text); - this->healLocation->modify(); - }); + this->line_edit_id->disconnect(); + connect(this->line_edit_id, &QLineEdit::textChanged, [this](const QString &text) { + this->healLocation->setIdName(text); + this->healLocation->modify(); + }); - this->spinner_respawn_npc->disconnect(); - connect(this->spinner_respawn_npc, QOverload::of(&QSpinBox::valueChanged), [this](int value) { - this->healLocation->setRespawnNPC(value); - this->healLocation->modify(); - }); - } + this->combo_respawn_map->disconnect(); + connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this](const QString &text) { + this->healLocation->setRespawnMapName(text); + this->healLocation->modify(); + }); + + this->combo_respawn_npc->disconnect(); + connect(this->combo_respawn_npc, &QComboBox::currentTextChanged, [this](const QString &text) { + this->healLocation->setRespawnNPC(text); + this->healLocation->modify(); + }); } void HealLocationFrame::initialize() { @@ -996,12 +1008,11 @@ void HealLocationFrame::initialize() { const QSignalBlocker blocker(this); EventFrame::initialize(); - bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; - if (respawnEnabled) { - this->combo_respawn_map->setTextItem(this->healLocation->getRespawnMap()); - this->spinner_respawn_npc->setValue(this->healLocation->getRespawnNPC()); - } + this->line_edit_id->setText(this->healLocation->getIdName()); + this->combo_respawn_map->setTextItem(this->healLocation->getRespawnMapName()); + this->combo_respawn_npc->setTextItem(this->healLocation->getRespawnNPC()); + bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; this->hideable_respawn_map->setVisible(respawnEnabled); this->hideable_respawn_npc->setVisible(respawnEnabled); } @@ -1012,6 +1023,7 @@ void HealLocationFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - if (projectConfig.healLocationRespawnDataEnabled) - this->combo_respawn_map->addItems(project->mapNames); + this->combo_respawn_map->addItems(project->mapNames); + // TODO: We should dynamically populate combo_respawn_npc with the local IDs of the respawn_map + // Same for warp IDs. } diff --git a/src/ui/neweventtoolbutton.cpp b/src/ui/neweventtoolbutton.cpp index b569cec9..dee658e4 100644 --- a/src/ui/neweventtoolbutton.cpp +++ b/src/ui/neweventtoolbutton.cpp @@ -26,11 +26,9 @@ void NewEventToolButton::init() this->newWarpAction->setIcon(QIcon(":/icons/add.ico")); connect(this->newWarpAction, &QAction::triggered, this, &NewEventToolButton::newWarp); - /* // disable this functionality for now this->newHealLocationAction = new QAction("New Heal Location", this); this->newHealLocationAction->setIcon(QIcon(":/icons/add.ico")); - connect(this->newHealLocationAction, SIGNAL(triggered(bool)), this, SLOT(newHealLocation())); - */ + connect(this->newHealLocationAction, &QAction::triggered, this, &NewEventToolButton::newHealLocation); this->newTriggerAction = new QAction("New Trigger", this); this->newTriggerAction->setIcon(QIcon(":/icons/add.ico")); @@ -56,7 +54,7 @@ void NewEventToolButton::init() alignMenu->addAction(this->newObjectAction); alignMenu->addAction(this->newCloneObjectAction); alignMenu->addAction(this->newWarpAction); - //alignMenu->addAction(this->newHealLocationAction); + alignMenu->addAction(this->newHealLocationAction); alignMenu->addAction(this->newTriggerAction); alignMenu->addAction(this->newWeatherTriggerAction); alignMenu->addAction(this->newSignAction); diff --git a/src/ui/prefab.cpp b/src/ui/prefab.cpp index e1fc83e9..6a406072 100644 --- a/src/ui/prefab.cpp +++ b/src/ui/prefab.cpp @@ -16,9 +16,6 @@ #include #include -using OrderedJson = poryjson::Json; -using OrderedJsonDoc = poryjson::JsonDoc; - const QString defaultFilepath = "prefabs.json"; void Prefab::loadPrefabs() { diff --git a/src/ui/preferenceeditor.cpp b/src/ui/preferenceeditor.cpp index a51b614d..c8d4a323 100644 --- a/src/ui/preferenceeditor.cpp +++ b/src/ui/preferenceeditor.cpp @@ -50,6 +50,7 @@ void PreferenceEditor::updateFields() { ui->checkBox_MonitorProjectFiles->setChecked(porymapConfig.monitorFiles); ui->checkBox_OpenRecentProject->setChecked(porymapConfig.reopenOnLaunch); ui->checkBox_CheckForUpdates->setChecked(porymapConfig.checkForUpdates); + ui->checkBox_DisableEventWarning->setChecked(porymapConfig.eventDeleteWarningDisabled); } void PreferenceEditor::saveFields() { @@ -64,6 +65,7 @@ void PreferenceEditor::saveFields() { porymapConfig.monitorFiles = ui->checkBox_MonitorProjectFiles->isChecked(); porymapConfig.reopenOnLaunch = ui->checkBox_OpenRecentProject->isChecked(); porymapConfig.checkForUpdates = ui->checkBox_CheckForUpdates->isChecked(); + porymapConfig.eventDeleteWarningDisabled = ui->checkBox_DisableEventWarning->isChecked(); porymapConfig.save(); emit preferencesSaved(); diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 016480a7..1bb17307 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -61,7 +61,7 @@ void ProjectSettingsEditor::connectSignals() { connect(ui->button_WarpsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_WarpsIcon); }); connect(ui->button_TriggersIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_TriggersIcon); }); connect(ui->button_BGsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_BGsIcon); }); - connect(ui->button_HealspotsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_HealspotsIcon); }); + connect(ui->button_HealLocationsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_HealLocationsIcon); }); connect(ui->button_PokemonIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_PokemonIcon); }); @@ -476,7 +476,7 @@ void ProjectSettingsEditor::refresh() { ui->lineEdit_WarpsIcon->setText(projectConfig.getEventIconPath(Event::Group::Warp)); ui->lineEdit_TriggersIcon->setText(projectConfig.getEventIconPath(Event::Group::Coord)); ui->lineEdit_BGsIcon->setText(projectConfig.getEventIconPath(Event::Group::Bg)); - ui->lineEdit_HealspotsIcon->setText(projectConfig.getEventIconPath(Event::Group::Heal)); + ui->lineEdit_HealLocationsIcon->setText(projectConfig.getEventIconPath(Event::Group::Heal)); for (auto lineEdit : ui->scrollAreaContents_ProjectPaths->findChildren()) lineEdit->setText(projectConfig.getCustomFilePath(lineEdit->objectName())); for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren()) @@ -546,7 +546,7 @@ void ProjectSettingsEditor::save() { projectConfig.setEventIconPath(Event::Group::Warp, ui->lineEdit_WarpsIcon->text()); projectConfig.setEventIconPath(Event::Group::Coord, ui->lineEdit_TriggersIcon->text()); projectConfig.setEventIconPath(Event::Group::Bg, ui->lineEdit_BGsIcon->text()); - projectConfig.setEventIconPath(Event::Group::Heal, ui->lineEdit_HealspotsIcon->text()); + projectConfig.setEventIconPath(Event::Group::Heal, ui->lineEdit_HealLocationsIcon->text()); for (auto lineEdit : ui->scrollAreaContents_ProjectPaths->findChildren()) projectConfig.setFilePath(lineEdit->objectName(), lineEdit->text()); for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren()) diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index af0e220b..21b5c76d 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -18,9 +18,6 @@ #include #include -using OrderedJson = poryjson::Json; -using OrderedJsonDoc = poryjson::JsonDoc; - RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) : QMainWindow(parent), ui(new Ui::RegionMapEditor)