Merge branch 'dev' of https://github.com/huderlem/porymap into event-selection

This commit is contained in:
GriffinR 2025-02-12 16:10:01 -05:00
commit e67790a8d3
36 changed files with 533 additions and 717 deletions

View File

@ -9,8 +9,6 @@ on:
tags:
- '*'
pull_request:
branches:
- master
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

View File

@ -38,6 +38,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
- Reduced diff noise when saving maps.
- Map names and ``MAP_NAME`` constants are no longer required to match.
- Porymap will no longer overwrite ``include/constants/map_groups.h`` or ``include/constants/layouts.h``.
- Primary/secondary metatile images are now kept on separate rows, rather than blending together if the primary size is not divisible by 8.
### Fixed
- Fix `Add Region Map...` not updating the region map settings file.
@ -79,6 +80,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
- Fix some problems with tileset detection when importing maps from AdvanceMap.
- Fix certain input fields allowing invalid identifiers, like names starting with numbers.
- Fix crash in the Shortcuts Editor when applying changes after closing certain windows.
- Fix `Display Metatile Usage Counts` sometimes changing the counts after repeated use.
## [5.4.1] - 2024-03-21
### Fixed

View File

@ -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

View File

@ -2042,9 +2042,9 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_Healspots">
<widget class="QWidget" name="tab_HealLocations">
<attribute name="title">
<string>Healspots</string>
<string>Heal Locations</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_9">
<property name="leftMargin">
@ -2091,7 +2091,7 @@
</widget>
</item>
<item>
<spacer name="horizontalSpacer_Healspots">
<spacer name="horizontalSpacer_HealLocations">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
@ -2106,7 +2106,7 @@
</layout>
</item>
<item>
<widget class="QScrollArea" name="scrollArea_Healspots">
<widget class="QScrollArea" name="scrollArea_HealLocations">
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
@ -2116,7 +2116,7 @@
<property name="alignment">
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_Healspots">
<widget class="QWidget" name="scrollAreaWidgetContents_HealLocations">
<property name="geometry">
<rect>
<x>0</x>

View File

@ -54,6 +54,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_DisableEventWarning">
<property name="toolTip">
<string>If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted.</string>
</property>
<property name="text">
<string>Disable warning when deleting events with IDs</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -1119,9 +1119,9 @@
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="lineEdit_HealspotsIcon">
<widget class="QLineEdit" name="lineEdit_HealLocationsIcon">
<property name="toolTip">
<string>The icon that will be used to represent Healspot events</string>
<string>The icon that will be used to represent Heal Location events</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
@ -1136,9 +1136,9 @@
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_HealspotsIcon">
<widget class="QLabel" name="label_HealLocationsIcon">
<property name="text">
<string>Healspots</string>
<string>Heal Locations</string>
</property>
</widget>
</item>
@ -1224,7 +1224,7 @@
</widget>
</item>
<item row="4" column="2">
<widget class="QToolButton" name="button_HealspotsIcon">
<widget class="QToolButton" name="button_HealLocationsIcon">
<property name="text">
<string>...</string>
</property>

View File

@ -80,6 +80,7 @@ public:
this->paletteEditorBitDepth = 24;
this->projectSettingsTab = 0;
this->warpBehaviorWarningDisabled = false;
this->eventDeleteWarningDisabled = false;
this->checkForUpdates = true;
this->lastUpdateCheckTime = QDateTime();
this->lastUpdateCheckVersion = porymapVersion;
@ -134,6 +135,7 @@ public:
int paletteEditorBitDepth;
int projectSettingsTab;
bool warpBehaviorWarningDisabled;
bool eventDeleteWarningDisabled;
bool checkForUpdates;
QDateTime lastUpdateCheckTime;
QVersionNumber lastUpdateCheckVersion;
@ -187,11 +189,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,
@ -217,7 +214,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,
@ -250,6 +246,7 @@ enum ProjectFilePath {
json_map_groups,
json_layouts,
json_wild_encounters,
json_heal_locations,
json_region_map_entries,
json_region_porymap_cfg,
tilesets_headers,
@ -263,14 +260,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,

View File

@ -10,7 +10,6 @@
#include <QPointer>
#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<QString, QJsonValue> 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<QString> 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<QString> 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<QString> 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<QString> 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<QString> 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<QString> 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<QString> getExpectedFields() override { return QSet<QString>(); }
virtual QSet<QString> 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;

View File

@ -1,28 +0,0 @@
#pragma once
#ifndef HEALLOCATION_H
#define HEALLOCATION_H
#include <QString>
#include <QDebug>
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

View File

@ -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;

View File

@ -46,7 +46,6 @@ public:
public:
Ui::MainWindow* ui;
QObject *parent = nullptr;
QPointer<Project> project = nullptr;
QPointer<Map> map = nullptr;

View File

@ -247,4 +247,7 @@ protected:
} // namespace poryjson
using OrderedJson = poryjson::Json;
using OrderedJsonDoc = poryjson::JsonDoc;
#endif // ORDERED_JSON_H

View File

@ -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<QString, QStringList> groupNameToMapNames;
QList<HealLocation> healLocations;
QMap<QString, int> healLocationNameToValue;
QStringList healLocationSaveOrder;
QMap<QString, QList<HealLocationEvent*>> healLocations;
QMap<QString, QString> mapConstantsToMapNames;
QMap<QString, QString> mapNamesToMapConstants;
QMap<QString, QString> 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<QString, tsl::ordered_map<QString, WildPokemonHeader>> 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;

View File

@ -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;

View File

@ -79,6 +79,7 @@ private:
bool positionIsValid(const QPoint &pos) const;
bool selectionIsValid();
void hoverChanged();
int numPrimaryMetatilesRounded() const;
signals:
void hoveredMetatileSelectionChanged(uint16_t);

View File

@ -51,6 +51,7 @@ private:
void drawCounts();
QImage buildAllMetatilesImage();
QImage buildImage(int metatileIdStart, int numMetatiles);
int numPrimaryMetatilesRounded() const;
signals:
void hoveredMetatileChanged(uint16_t);

View File

@ -40,7 +40,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 \
@ -151,7 +150,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 \

View File

@ -75,11 +75,6 @@ const QMap<ProjectIdentifier, QPair<QString, QString>> 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<ProjectIdentifier, QPair<QString, QString>> 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<ProjectFilePath, QPair<QString, QString>> 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<ProjectFilePath, QPair<QString, QString>> 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") {
@ -483,6 +478,7 @@ QMap<QString, QString> 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());

View File

@ -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<QString> 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<QString> 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<QString> 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<QString> 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<QString> 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<QString> expectedHealLocationFields = {
"id",
"map"
};
QSet<QString> HealLocationEvent::getExpectedFields() {
QSet<QString> expectedFields = expectedHealLocationFields;
if (projectConfig.healLocationRespawnDataEnabled) {
expectedFields.insert("respawn_map");
expectedFields.insert("respawn_npc");
}
expectedFields << "x" << "y";
return expectedFields;
}

View File

@ -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<HealLocationEvent *>(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;
}

View File

@ -35,13 +35,8 @@ Map::Map(const Map &other, QObject *parent) : Map(parent) {
// Copy events
for (auto i = other.m_events.constBegin(); i != other.m_events.constEnd(); i++) {
QList<Event*> newEvents;
for (const auto &event : i.value()) {
auto newEvent = event->duplicate();
m_ownedEvents.insert(newEvent);
newEvents.append(newEvent);
}
m_events[i.key()] = newEvents;
for (const auto &event : i.value())
addEvent(event->duplicate());
}
// Duplicating the map connections is probably not desirable, so we skip them.

View File

@ -177,8 +177,6 @@ void Layout::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bo
}
void Layout::adjustDimensions(QMargins margins, bool setNewBlockdata) {
int oldWidth = this->width;
int oldHeight = this->height;
int newWidth = this->width + margins.left() + margins.right();
int newHeight = this->height + margins.top() + margins.bottom();
@ -190,7 +188,7 @@ void Layout::adjustDimensions(QMargins margins, bool setNewBlockdata) {
if ((x < margins.left()) || (x >= newWidth - margins.right()) || (y < margins.top()) || (y >= newHeight - margins.bottom())) {
newBlockdata.append(0);
} else {
int index = (y - margins.top()) * oldWidth + (x - margins.left());
int index = (y - margins.top()) * this->width + (x - margins.left());
newBlockdata.append(this->blockdata.value(index));
}
}

View File

@ -31,8 +31,6 @@ static const QMap<QString, int> globalDefineValues = {
{"UINT_MAX", UINT_MAX},
};
using OrderedJson = poryjson::Json;
ParseUtil::ParseUtil() { }
void ParseUtil::set_root(const QString &dir) {
@ -580,7 +578,7 @@ QMap<QString, QString> 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<QString> &
// 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())
{

View File

@ -12,6 +12,7 @@
#include "scripting.h"
#include "customattributesframe.h"
#include "validator.h"
#include "message.h"
#include <QCheckBox>
#include <QPainter>
#include <QMouseEvent>
@ -1366,13 +1367,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 eventsChanged();
selectMapEvent(newEvent);
}
DraggablePixmapItem *newEvent = addNewEvent(eventType);
if (newEvent) {
newEvent->move(pos.x(), pos.y());
emit eventsChanged();
selectMapEvent(newEvent);
}
}
} else if (eventEditAction == 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<Event *> selectedEvents;
int numDeleted = 0;
QList<Event*> 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<DraggablePixmapItem *>(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) {
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 {

View File

@ -57,9 +57,6 @@
#define RELEASE_PLATFORM
#endif
using OrderedJson = poryjson::Json;
using OrderedJsonDoc = poryjson::JsonDoc;
MainWindow::MainWindow(QWidget *parent) :
@ -858,6 +855,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."));
@ -1281,6 +1283,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.
@ -1302,12 +1308,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());
}
@ -1618,13 +1618,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);
@ -1744,10 +1737,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)
@ -1993,7 +1982,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::updateEvents() {
@ -2087,9 +2076,9 @@ void MainWindow::updateSelectedEvents() {
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);
@ -2154,11 +2143,11 @@ void MainWindow::updateSelectedEvents() {
Event::Group MainWindow::getEventGroupFromTabWidget(QWidget *tab) {
static const QMap<QWidget*,Event::Group> 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);
}
@ -2181,6 +2170,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;
}

View File

@ -23,9 +23,6 @@
#include <QRegularExpression>
#include <algorithm>
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<HealLocationEvent*> hlEvents = this->healLocations.value(map->constantName());
for (const auto &event : hlEvents) {
map->addEvent(event->duplicate());
}
*/
map->deleteConnections();
QJsonArray connectionsArr = mapObj["connections"].toArray();
@ -847,134 +825,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<QString, QList<OrderedJson::object>> 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<QString, int> healLocationsDupes;
QSet<QString> healLocationsUnique;
for (auto hl : this->healLocations) {
QString idName = hl.idName;
if (healLocationsUnique.contains(idName))
healLocationsDupes[idName] = 1;
else
healLocationsUnique.insert(idName);
}
// Create the definition text for each data table
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<int, QString> valuesToNames = QMap<int, QString>();
QStringList defineNames = this->healLocationNameToValue.keys();
QList<int> defineValues = this->healLocationNameToValue.values();
for (auto name : defineNames) {
int value = this->healLocationNameToValue.value(name);
if (valuesToNames.contains(value)) {
do { // Redefine duplicate as first available value
value = nextDefineValue++;
} while (defineValues.contains(value));
}
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<int, QString>::const_iterator i;
for (i = valuesToNames.constBegin(); i != valuesToNames.constEnd(); i++)
constantsText += QString("#define %1 %2\n").arg(i.value()).arg(i.key());
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) {
@ -1056,7 +967,7 @@ bool Project::loadLayoutTilesets(Layout *layout) {
layout->tileset_primary = getTileset(layout->tileset_primary_label);
if (!layout->tileset_primary) {
QString defaultTileset = this->getDefaultPrimaryTilesetLabel();
logWarn(QString("Map layout %1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->id).arg(layout->tileset_primary_label).arg(defaultTileset));
logWarn(QString("%1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_primary_label).arg(defaultTileset));
layout->tileset_primary_label = defaultTileset;
layout->tileset_primary = getTileset(layout->tileset_primary_label);
if (!layout->tileset_primary) {
@ -1068,7 +979,7 @@ bool Project::loadLayoutTilesets(Layout *layout) {
layout->tileset_secondary = getTileset(layout->tileset_secondary_label);
if (!layout->tileset_secondary) {
QString defaultTileset = this->getDefaultSecondaryTilesetLabel();
logWarn(QString("Map layout %1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->id).arg(layout->tileset_secondary_label).arg(defaultTileset));
logWarn(QString("%1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_secondary_label).arg(defaultTileset));
layout->tileset_secondary_label = defaultTileset;
layout->tileset_secondary = getTileset(layout->tileset_secondary_label);
if (!layout->tileset_secondary) {
@ -1137,7 +1048,8 @@ bool Project::loadBlockdata(Layout *layout) {
layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight());
if (layout->blockdata.count() != layout->getWidth() * layout->getHeight()) {
logWarn(QString("Layout blockdata length %1 does not match dimensions %2x%3 (should be %4). Resizing blockdata.")
logWarn(QString("%1 blockdata length %2 does not match dimensions %3x%4 (should be %5). Resizing blockdata.")
.arg(layout->name)
.arg(layout->blockdata.count())
.arg(layout->getWidth())
.arg(layout->getHeight())
@ -1174,7 +1086,8 @@ bool Project::loadLayoutBorder(Layout *layout) {
int borderLength = layout->getBorderWidth() * layout->getBorderHeight();
if (layout->border.count() != borderLength) {
logWarn(QString("Layout border blockdata length %1 must be %2. Resizing border blockdata.")
logWarn(QString("%1 border blockdata length %2 must be %3. Resizing border blockdata.")
.arg(layout->name)
.arg(layout->border.count())
.arg(borderLength));
layout->border.resize(borderLength);
@ -1337,6 +1250,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<HealLocationEvent*> hlEvents;
for (const auto &event : map->getEvents(Event::Group::Heal)) {
auto hl = static_cast<HealLocationEvent*>(event);
hlEvents.append(static_cast<HealLocationEvent*>(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++) {
@ -1349,7 +1272,6 @@ void Project::saveMap(Map *map) {
mapFile.close();
saveLayout(map->layout());
saveHealLocations(map);
map->setClean();
}
@ -1381,6 +1303,7 @@ void Project::saveAllDataStructures() {
saveMapLayouts();
saveMapGroups();
saveRegionMapSections();
saveHealLocations();
saveWildMonData();
saveConfig();
this->hasUnsavedDataChanges = false;
@ -1976,6 +1899,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;
}
@ -2035,18 +1967,6 @@ void Project::initNewLayoutSettings() {
this->newLayoutSettings.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel();
}
Project::DataQualifiers Project::getDataQualifiers(QString text, QString label) {
Project::DataQualifiers qualifiers;
QRegularExpression regex(QString("\\s*(?<static>static\\s*)?(?<const>const\\s*)?[A-Za-z0-9_\\s]*\\b%1\\b").arg(label));
QRegularExpressionMatch match = regex.match(text);
qualifiers.isStatic = match.captured("static").isNull() ? false : true;
qualifiers.isConst = match.captured("const").isNull() ? false : true;
return qualifiers;
}
QString Project::getDefaultPrimaryTilesetLabel() const {
QString defaultLabel = projectConfig.defaultPrimaryTileset;
if (!this->primaryTilesetLabels.contains(defaultLabel)) {
@ -2434,110 +2354,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+(?<name>[A-Za-z0-9_]+)\\[").arg(projectConfig.getIdentifier(ProjectIdentifier::symbol_heal_locations_type)));
const QRegularExpressionMatch tableNameMatch = tableNameExpr.match(text);
if (tableNameMatch.hasMatch()) {
// Found table name, record it and its qualifiers for output when saving.
this->healLocationsTableName = tableNameMatch.captured("name");
this->healLocationDataQualifiers = this->getDataQualifiers(text, this->healLocationsTableName);
} else {
// No table name found, initialize default name for output when saving.
this->healLocationsTableName = respawnEnabled ? projectConfig.getIdentifier(ProjectIdentifier::symbol_spawn_points)
: projectConfig.getIdentifier(ProjectIdentifier::symbol_heal_locations);
this->healLocationDataQualifiers = { .isStatic = true, .isConst = true };
}
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]+(?<map>[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\1)[\\s\\)]+";
// Pattern for an x, y number pair
const QString coordPattern = "\\s*(?<x>[0-9A-Fa-fx]+),\\s*(?<y>[0-9A-Fa-fx]+)";
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(?<npc>[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;
}

View File

@ -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<HealLocationEvent *>(this->event);
const QString localIdName = heal->getRespawnNPC();
int localId = 0; // TODO: Get value from localIdName
emit editor->warpEventDoubleClicked(heal->getRespawnMapName(), localId, Event::Group::Object);
}
}

View File

@ -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<int>::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.
}

View File

@ -9,12 +9,17 @@ QPoint MetatileSelector::getSelectionDimensions() {
return SelectablePixmapItem::getSelectionDimensions();
}
int MetatileSelector::numPrimaryMetatilesRounded() const {
// We round up the number of primary metatiles to keep the tilesets on separate rows.
return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide;
}
void MetatileSelector::draw() {
if (!this->primaryTileset || !this->secondaryTileset) {
this->setPixmap(QPixmap());
}
int primaryLength = this->primaryTileset->numMetatiles();
int primaryLength = this->numPrimaryMetatilesRounded();
int length_ = primaryLength + this->secondaryTileset->numMetatiles();
int height_ = length_ / this->numMetatilesWide;
if (length_ % this->numMetatilesWide != 0) {
@ -149,7 +154,7 @@ void MetatileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
void MetatileSelector::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
QPoint pos = this->getCellPos(event->pos());
if (!positionIsValid(pos) || this->cellPos == pos)
if (this->cellPos == pos)
return;
this->cellPos = pos;
@ -199,10 +204,11 @@ void MetatileSelector::updateExternalSelectedMetatiles() {
uint16_t MetatileSelector::getMetatileId(int x, int y) const {
int index = y * this->numMetatilesWide + x;
if (index < this->primaryTileset->numMetatiles()) {
int numPrimary = this->numPrimaryMetatilesRounded();
if (index < numPrimary) {
return static_cast<uint16_t>(index);
} else {
return static_cast<uint16_t>(Project::getNumMetatilesPrimary() + index - this->primaryTileset->numMetatiles());
return static_cast<uint16_t>(Project::getNumMetatilesPrimary() + index - numPrimary);
}
}
@ -215,7 +221,7 @@ QPoint MetatileSelector::getMetatileIdCoords(uint16_t metatileId) {
int index = metatileId < Project::getNumMetatilesPrimary()
? metatileId
: metatileId - Project::getNumMetatilesPrimary() + this->primaryTileset->numMetatiles();
: metatileId - Project::getNumMetatilesPrimary() + this->numPrimaryMetatilesRounded();
return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
}

View File

@ -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);

View File

@ -16,9 +16,6 @@
#include <QSpacerItem>
#include <QMessageBox>
using OrderedJson = poryjson::Json;
using OrderedJsonDoc = poryjson::JsonDoc;
const QString defaultFilepath = "prefabs.json";
void Prefab::loadPrefabs() {

View File

@ -55,6 +55,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() {
@ -69,6 +70,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();

View File

@ -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<QLineEdit*>())
lineEdit->setText(projectConfig.getCustomFilePath(lineEdit->objectName()));
for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren<QLineEdit*>())
@ -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<QLineEdit*>())
projectConfig.setFilePath(lineEdit->objectName(), lineEdit->text());
for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren<QLineEdit*>())

View File

@ -18,9 +18,6 @@
#include <QTransform>
#include <math.h>
using OrderedJson = poryjson::Json;
using OrderedJsonDoc = poryjson::JsonDoc;
RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) :
QMainWindow(parent),
ui(new Ui::RegionMapEditor)

View File

@ -29,13 +29,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
this->tileYFlip = ui->checkBox_yFlip->isChecked();
this->paletteId = ui->spinBox_paletteSelector->value();
// TODO: The dividing line at the moment is only accurate if the number of primary metatiles is divisible by 8.
// If it's not, the secondary metatiles will wrap above the line. This has other problems (like skewing
// metatile groups the user may have designed) so this should be fixed by filling the primary metatiles
// image with invalid magenta metatiles until it's divisible by 8. Then the line can be re-enabled as-is.
ui->actionShow_Tileset_Divider->setChecked(/*porymapConfig.showTilesetEditorDivider*/false);
ui->actionShow_Tileset_Divider->setVisible(false);
ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider);
ui->spinBox_paletteSelector->setMinimum(0);
ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1);
ui->lineEdit_metatileLabel->setValidator(new IdentifierValidator(this));
@ -759,7 +753,7 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered()
{
QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint);
dialog.setWindowTitle("Change Number of Metatiles");
dialog.setWindowModality(Qt::NonModal);
dialog.setWindowModality(Qt::WindowModal);
QFormLayout form(&dialog);
@ -1051,20 +1045,19 @@ void TilesetEditor::countMetatileUsage() {
// do not double count
metatileSelector->usedMetatiles.fill(0);
for (auto layout : this->project->mapLayouts.values()) {
bool usesPrimary = false;
bool usesSecondary = false;
for (auto layout : this->project->mapLayouts) {
// It's possible for a layout's tileset labels to change if they are invalid,
// so we need to load all the tilesets even if they aren't the tileset we're looking for.
// Otherwise the metatile usage counts may change because the layouts with invalid tilesets
// were updated to use a tileset we were looking for.
this->project->loadLayoutTilesets(layout);
if (layout->tileset_primary_label == this->primaryTileset->name) {
usesPrimary = true;
}
if (layout->tileset_secondary_label == this->secondaryTileset->name) {
usesSecondary = true;
}
bool usesPrimary = (layout->tileset_primary_label == this->primaryTileset->name);
bool usesSecondary = (layout->tileset_secondary_label == this->secondaryTileset->name);
if (usesPrimary || usesSecondary) {
this->project->loadLayout(layout);
if (!this->project->loadLayout(layout))
continue;
// for each block in the layout, mark in the vector that it is used
for (int i = 0; i < layout->blockdata.length(); i++) {
@ -1097,9 +1090,9 @@ void TilesetEditor::countTileUsage() {
QSet<Tileset*> secondaryTilesets;
for (auto layout : this->project->mapLayouts.values()) {
this->project->loadLayoutTilesets(layout);
if (layout->tileset_primary_label == this->primaryTileset->name
|| layout->tileset_secondary_label == this->secondaryTileset->name) {
this->project->loadLayoutTilesets(layout);
// need to check metatiles
if (layout->tileset_primary && layout->tileset_secondary) {
primaryTilesets.insert(layout->tileset_primary);

View File

@ -22,11 +22,16 @@ int TilesetEditorMetatileSelector::numRows(int numMetatiles) {
}
int TilesetEditorMetatileSelector::numRows() {
return this->numRows(this->primaryTileset->numMetatiles() + this->secondaryTileset->numMetatiles());
return this->numRows(this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles());
}
int TilesetEditorMetatileSelector::numPrimaryMetatilesRounded() const {
// We round up the number of primary metatiles to keep the tilesets on separate rows.
return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide;
}
QImage TilesetEditorMetatileSelector::buildAllMetatilesImage() {
return this->buildImage(0, this->primaryTileset->numMetatiles() + this->secondaryTileset->numMetatiles());
return this->buildImage(0, this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles());
}
QImage TilesetEditorMetatileSelector::buildPrimaryMetatilesImage() {
@ -39,11 +44,11 @@ QImage TilesetEditorMetatileSelector::buildSecondaryMetatilesImage() {
QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMetatiles) {
int numMetatilesHigh = this->numRows(numMetatiles);
int numPrimary = this->primaryTileset->numMetatiles();
int numPrimary = this->numPrimaryMetatilesRounded();
int maxPrimary = Project::getNumMetatilesPrimary();
bool includesPrimary = metatileIdStart < maxPrimary;
QImage image(this->numMetatilesWide * 32, numMetatilesHigh * 32, QImage::Format_RGBA8888);
QImage image(this->numMetatilesWide * this->cellWidth, numMetatilesHigh * this->cellHeight, QImage::Format_RGBA8888);
image.fill(Qt::magenta);
QPainter painter(&image);
for (int i = 0; i < numMetatiles; i++) {
@ -57,10 +62,10 @@ QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMet
this->layout->metatileLayerOrder,
this->layout->metatileLayerOpacity,
true)
.scaled(32, 32);
.scaled(this->cellWidth, this->cellHeight);
int map_y = i / this->numMetatilesWide;
int map_x = i % this->numMetatilesWide;
QPoint metatile_origin = QPoint(map_x * 32, map_y * 32);
QPoint metatile_origin = QPoint(map_x * this->cellWidth, map_y * this->cellHeight);
painter.drawImage(metatile_origin, metatile_image);
}
painter.end();
@ -107,10 +112,11 @@ uint16_t TilesetEditorMetatileSelector::getSelectedMetatileId() {
uint16_t TilesetEditorMetatileSelector::getMetatileId(int x, int y) {
int index = y * this->numMetatilesWide + x;
if (index < this->primaryTileset->numMetatiles()) {
int numPrimary = numPrimaryMetatilesRounded();
if (index < numPrimary) {
return static_cast<uint16_t>(index);
} else {
return static_cast<uint16_t>(Project::getNumMetatilesPrimary() + index - this->primaryTileset->numMetatiles());
return static_cast<uint16_t>(Project::getNumMetatilesPrimary() + index - numPrimary);
}
}
@ -156,7 +162,7 @@ QPoint TilesetEditorMetatileSelector::getMetatileIdCoords(uint16_t metatileId) {
}
int index = metatileId < Project::getNumMetatilesPrimary()
? metatileId
: metatileId - Project::getNumMetatilesPrimary() + this->primaryTileset->numMetatiles();
: metatileId - Project::getNumMetatilesPrimary() + this->numPrimaryMetatilesRounded();
return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
}
@ -176,12 +182,12 @@ void TilesetEditorMetatileSelector::drawGrid() {
const int numColumns = this->numMetatilesWide;
const int numRows = this->numRows();
for (int column = 1; column < numColumns; column++) {
int x = column * 32;
painter.drawLine(x, 0, x, numRows * 32);
int x = column * this->cellWidth;
painter.drawLine(x, 0, x, numRows * this->cellHeight);
}
for (int row = 1; row < numRows; row++) {
int y = row * 32;
painter.drawLine(0, y, numColumns * 32, y);
int y = row * this->cellHeight;
painter.drawLine(0, y, numColumns * this->cellWidth, y);
}
painter.end();
this->setPixmap(pixmap);
@ -191,12 +197,12 @@ void TilesetEditorMetatileSelector::drawDivider() {
if (!this->showDivider)
return;
const int y = this->numRows(this->primaryTileset->numMetatiles()) * 32;
const int y = this->numRows(this->numPrimaryMetatilesRounded()) * this->cellHeight;
QPixmap pixmap = this->pixmap();
QPainter painter(&pixmap);
painter.setPen(Qt::white);
painter.drawLine(0, y, this->numMetatilesWide * 32, y);
painter.drawLine(0, y, this->numMetatilesWide * this->cellWidth, y);
painter.end();
this->setPixmap(pixmap);
}
@ -212,7 +218,7 @@ void TilesetEditorMetatileSelector::drawFilters() {
void TilesetEditorMetatileSelector::drawUnused() {
// setup the circle with a line through it image to layer above unused metatiles
QPixmap redX(32, 32);
QPixmap redX(this->cellWidth, this->cellHeight);
redX.fill(Qt::transparent);
QPen whitePen(Qt::white);
@ -223,21 +229,21 @@ void TilesetEditorMetatileSelector::drawUnused() {
QPainter oPainter(&redX);
oPainter.setPen(whitePen);
oPainter.drawEllipse(QRect(1, 1, 30, 30));
oPainter.drawEllipse(QRect(1, 1, this->cellWidth - 2, this->cellHeight - 2));
oPainter.setPen(pinkPen);
oPainter.drawEllipse(QRect(2, 2, 28, 28));
oPainter.drawEllipse(QRect(3, 3, 26, 26));
oPainter.drawEllipse(QRect(2, 2, this->cellWidth - 4, this->cellHeight - 4));
oPainter.drawEllipse(QRect(3, 3, this->cellWidth - 6, this->cellHeight - 6));
oPainter.setPen(whitePen);
oPainter.drawEllipse(QRect(4, 4, 24, 24));
oPainter.drawEllipse(QRect(4, 4, this->cellHeight - 8, this->cellHeight - 8));
whitePen.setWidth(5);
oPainter.setPen(whitePen);
oPainter.drawLine(0, 0, 31, 31);
oPainter.drawLine(0, 0, this->cellWidth - 1, this->cellHeight - 1);
pinkPen.setWidth(3);
oPainter.setPen(pinkPen);
oPainter.drawLine(2, 2, 29, 29);
oPainter.drawLine(2, 2, this->cellWidth - 3, this->cellHeight - 3);
oPainter.end();
@ -247,19 +253,13 @@ void TilesetEditorMetatileSelector::drawUnused() {
QPainter unusedPainter(&metatilesPixmap);
unusedPainter.setOpacity(0.5);
int primaryLength = this->primaryTileset->numMetatiles();
int length_ = primaryLength + this->secondaryTileset->numMetatiles();
for (int i = 0; i < length_; i++) {
int tile = i;
if (i >= primaryLength) {
tile += Project::getNumMetatilesPrimary() - primaryLength;
}
if (!usedMetatiles[tile]) {
unusedPainter.drawPixmap((i % 8) * 32, (i / 8) * 32, redX);
}
for (int metatileId = 0; metatileId < this->usedMetatiles.size(); metatileId++) {
if (this->usedMetatiles.at(metatileId) || !Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset))
continue;
// Adjust position from center to top-left corner
QPoint pos = getMetatileIdCoordsOnWidget(metatileId) - QPoint(this->cellWidth / 2, this->cellHeight / 2);
unusedPainter.drawPixmap(pos.x(), pos.y(), redX);
}
unusedPainter.end();
this->setPixmap(metatilesPixmap);
@ -268,38 +268,28 @@ void TilesetEditorMetatileSelector::drawUnused() {
void TilesetEditorMetatileSelector::drawCounts() {
QPen blackPen(Qt::black);
blackPen.setWidth(1);
QPixmap metatilesPixmap = this->pixmap();
QPainter countPainter(&metatilesPixmap);
countPainter.setPen(blackPen);
for (int tile = 0; tile < this->usedMetatiles.size(); tile++) {
int count = usedMetatiles[tile];
QString countText = QString::number(count);
if (count > 1000) countText = ">1k";
countPainter.drawText((tile % 8) * 32, (tile / 8) * 32 + 32, countText);
}
// write in white and black for contrast
QPen whitePen(Qt::white);
whitePen.setWidth(1);
countPainter.setPen(whitePen);
int primaryLength = this->primaryTileset->numMetatiles();
int length_ = primaryLength + this->secondaryTileset->numMetatiles();
QPixmap metatilesPixmap = this->pixmap();
QPainter countPainter(&metatilesPixmap);
for (int i = 0; i < length_; i++) {
int tile = i;
if (i >= primaryLength) {
tile += Project::getNumMetatilesPrimary() - primaryLength;
}
int count = usedMetatiles[tile];
QString countText = QString::number(count);
if (count > 1000) countText = ">1k";
countPainter.drawText((i % 8) * 32 + 1, (i / 8) * 32 + 32 - 1, countText);
for (int metatileId = 0; metatileId < this->usedMetatiles.size(); metatileId++) {
if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset))
continue;
int count = this->usedMetatiles.at(metatileId);
QString countText = (count > 1000) ? QStringLiteral(">1k") : QString::number(count);
// Adjust position from center to bottom-left corner
QPoint pos = getMetatileIdCoordsOnWidget(metatileId) + QPoint(-(this->cellWidth / 2), this->cellHeight / 2);
// write in black and white for contrast
countPainter.setPen(blackPen);
countPainter.drawText(pos.x(), pos.y(), countText);
countPainter.setPen(whitePen);
countPainter.drawText(pos.x() + 1, pos.y() - 1, countText);
}
countPainter.end();
this->setPixmap(metatilesPixmap);