diff --git a/docsrc/manual/scripting-capabilities.rst b/docsrc/manual/scripting-capabilities.rst
index 7c5452b1..8673a4a6 100644
--- a/docsrc/manual/scripting-capabilities.rst
+++ b/docsrc/manual/scripting-capabilities.rst
@@ -2289,7 +2289,7 @@ All constants are accessible via the global ``constants`` object.
.. js:attribute:: constants.base_game_version
- The string value of the config setting ``base_game_version``. This will either be ``pokeruby``, ``pokefirered``, or ``pokeemerald``.
+ The string value of the config setting ``base_game_version``. This will either be ``pokeruby``, ``pokefirered``, ``pokeemerald``, or an empty string.
.. js:attribute:: constants.version.major
diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui
index 980b5351..303e11a1 100644
--- a/forms/newlayoutdialog.ui
+++ b/forms/newlayoutdialog.ui
@@ -6,8 +6,8 @@
0
0
- 264
- 173
+ 387
+ 357
@@ -36,8 +36,8 @@
0
0
- 240
- 109
+ 363
+ 293
diff --git a/forms/newlayoutform.ui b/forms/newlayoutform.ui
index b3c0649b..86107fb6 100644
--- a/forms/newlayoutform.ui
+++ b/forms/newlayoutform.ui
@@ -2,6 +2,14 @@
NewLayoutForm
+
+
+ 0
+ 0
+ 196
+ 331
+
+
Form
@@ -168,6 +176,9 @@
QComboBox::InsertPolicy::NoInsert
+
+ QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon
+
-
@@ -204,6 +215,9 @@
QComboBox::InsertPolicy::NoInsert
+
+ QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon
+
-
diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui
index ec8c2612..9479b6dd 100644
--- a/forms/newmapdialog.ui
+++ b/forms/newmapdialog.ui
@@ -6,8 +6,8 @@
0
0
- 559
- 614
+ 467
+ 428
@@ -39,8 +39,8 @@
0
0
- 535
- 550
+ 443
+ 364
@@ -129,6 +129,9 @@
QComboBox::InsertPolicy::NoInsert
+
+ QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon
+
-
@@ -171,6 +174,9 @@
QComboBox::InsertPolicy::NoInsert
+
+ QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon
+
-
diff --git a/include/config.h b/include/config.h
deleted file mode 100644
index 4907014f..00000000
--- a/include/config.h
+++ /dev/null
@@ -1,521 +0,0 @@
-#pragma once
-#ifndef CONFIG_H
-#define CONFIG_H
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "events.h"
-#include "gridsettings.h"
-
-extern const QVersionNumber porymapVersion;
-
-// Distance in pixels from the edge of a GBA screen (240x160) to the center 16x16 pixels.
-#define GBA_H_DIST_TO_CENTER ((240-16)/2)
-#define GBA_V_DIST_TO_CENTER ((160-16)/2)
-
-#define CONFIG_BACKWARDS_COMPATABILITY
-
-enum ScriptAutocompleteMode {
- MapOnly,
- MapAndCommon,
- All,
-};
-
-class KeyValueConfigBase
-{
-public:
- bool save();
- bool load(const QString &dir = QString());
-
- void setRoot(const QString &dir);
- QString root() const { return m_root; }
- QString filepath() const { return m_filepath; }
- QString filename() const { return m_filename; }
-
- explicit KeyValueConfigBase(const QString &filename)
- : m_root(QString()),
- m_filename(filename),
- m_filepath(filename)
- { };
- virtual ~KeyValueConfigBase() {};
- virtual void reset() = 0;
-protected:
- virtual void parseConfigKeyValue(QString key, QString value) = 0;
- virtual QMap getKeyValueMap() = 0;
- virtual void init() = 0;
- virtual void setUnreadKeys() = 0;
-
- static bool getConfigBool(const QString &key, const QString &value);
- static int getConfigInteger(const QString &key, const QString &value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0);
- static uint32_t getConfigUint32(const QString &key, const QString &value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0);
- static QColor getConfigColor(const QString &key, const QString &value, const QColor &defaultValue = QColor(Qt::black));
- static QString toConfigColor(const QColor &color);
-
- QString m_root;
- QString m_filename;
- QString m_filepath;
-};
-
-class PorymapConfig: public KeyValueConfigBase
-{
-public:
- PorymapConfig();
- virtual void reset() override;
- void addRecentProject(QString project);
- void setRecentProjects(QStringList projects);
- QString getRecentProject();
- QStringList getRecentProjects();
- void setMainGeometry(QByteArray, QByteArray, QByteArray, QByteArray, QByteArray);
- void setTilesetEditorGeometry(QByteArray, QByteArray, QByteArray);
- void setPaletteEditorGeometry(QByteArray, QByteArray);
- void setRegionMapEditorGeometry(QByteArray, QByteArray);
- void setProjectSettingsEditorGeometry(QByteArray, QByteArray);
- void setCustomScriptsEditorGeometry(QByteArray, QByteArray);
- QMap getMainGeometry();
- QMap getTilesetEditorGeometry();
- QMap getPaletteEditorGeometry();
- QMap getRegionMapEditorGeometry();
- QMap getProjectSettingsEditorGeometry();
- QMap getCustomScriptsEditorGeometry();
-
- static QFont defaultMapListFont() { return QFontDatabase::systemFont(QFontDatabase::FixedFont); }
-
- bool reopenOnLaunch;
- bool projectManuallyClosed;
- int mapListTab;
- bool mapListEditGroupsEnabled;
- QMap mapListHideEmptyEnabled;
- bool mapListLayoutsSorted;
- bool mapListLocationsSorted;
- bool prettyCursors;
- bool mirrorConnectingMaps;
- bool showDiveEmergeMaps;
- int diveEmergeMapOpacity;
- int diveMapOpacity;
- int emergeMapOpacity;
- int collisionOpacity;
- int collisionZoom;
- int metatilesZoom;
- int tilesetEditorMetatilesZoom;
- int tilesetEditorTilesZoom;
- Qt::Orientation tilesetEditorLayerOrientation;
- bool showPlayerView;
- bool showCursorTile;
- bool showBorder;
- bool showGrid;
- bool showTilesetEditorMetatileGrid;
- bool showTilesetEditorLayerGrid;
- bool showTilesetEditorDivider;
- bool showTilesetEditorRawAttributes;
- bool showPaletteEditorUnusedColors;
- bool monitorFiles;
- bool tilesetCheckerboardFill;
- bool newMapHeaderSectionExpanded;
- QString theme;
- QString wildMonChartTheme;
- QString textEditorOpenFolder;
- QString textEditorGotoLine;
- int paletteEditorBitDepth;
- int projectSettingsTab;
- ScriptAutocompleteMode scriptAutocompleteMode;
- bool warpBehaviorWarningDisabled;
- bool eventDeleteWarningDisabled;
- bool eventOverlayEnabled;
- bool checkForUpdates;
- bool showProjectLoadingScreen;
- QDateTime lastUpdateCheckTime;
- QVersionNumber lastUpdateCheckVersion;
- QMap rateLimitTimes;
- QGraphicsPixmapItem::ShapeMode eventSelectionShapeMode;
- QByteArray wildMonChartGeometry;
- QByteArray newMapDialogGeometry;
- QByteArray newLayoutDialogGeometry;
- bool shownInGameReloadMessage;
- GridSettings gridSettings;
- // Prefer over QSet to prevent shuffling elements when writing the config file.
- std::set statusBarLogTypes;
- QFont applicationFont;
- QFont mapListFont;
- int imageExportColorSpaceId;
- QMap trustedScriptHashes;
-
-protected:
- virtual void parseConfigKeyValue(QString key, QString value) override;
- virtual QMap getKeyValueMap() override;
- virtual void init() override {};
- virtual void setUnreadKeys() override {};
-
-private:
- QString stringFromByteArray(const QByteArray&);
- QByteArray bytesFromString(const QString&);
-
- QStringList recentProjects;
- QByteArray mainWindowGeometry;
- QByteArray mainWindowState;
- QByteArray mapSplitterState;
- QByteArray mainSplitterState;
- QByteArray metatilesSplitterState;
- QByteArray tilesetEditorGeometry;
- QByteArray tilesetEditorState;
- QByteArray tilesetEditorSplitterState;
- QByteArray paletteEditorGeometry;
- QByteArray paletteEditorState;
- QByteArray regionMapEditorGeometry;
- QByteArray regionMapEditorState;
- QByteArray projectSettingsEditorGeometry;
- QByteArray projectSettingsEditorState;
- QByteArray customScriptsEditorGeometry;
- QByteArray customScriptsEditorState;
-};
-
-extern PorymapConfig porymapConfig;
-
-enum BaseGameVersion {
- none,
- pokeruby,
- pokefirered,
- pokeemerald,
-};
-
-enum ProjectIdentifier {
- symbol_facing_directions,
- symbol_obj_event_gfx_pointers,
- symbol_pokemon_icon_table,
- symbol_attribute_table,
- symbol_tilesets_prefix,
- symbol_dynamic_map_name,
- define_obj_event_count,
- define_min_level,
- define_max_level,
- define_max_encounter_rate,
- define_tiles_primary,
- define_tiles_total,
- define_metatiles_primary,
- define_pals_primary,
- define_pals_total,
- define_tiles_per_metatile,
- define_map_size,
- define_map_offset_width,
- define_map_offset_height,
- define_mask_metatile,
- define_mask_collision,
- define_mask_elevation,
- define_mask_behavior,
- define_mask_layer,
- define_attribute_behavior,
- define_attribute_layer,
- define_attribute_terrain,
- define_attribute_encounter,
- define_metatile_label_prefix,
- define_heal_locations_prefix,
- define_layout_prefix,
- define_map_prefix,
- define_map_dynamic,
- define_map_empty,
- define_map_section_prefix,
- define_map_section_empty,
- define_species_prefix,
- define_species_empty,
- regex_behaviors,
- regex_obj_event_gfx,
- regex_items,
- regex_flags,
- regex_vars,
- regex_movement_types,
- regex_map_types,
- regex_battle_scenes,
- regex_weather,
- regex_coord_event_weather,
- regex_secret_bases,
- regex_sign_facing_directions,
- regex_trainer_types,
- regex_music,
- regex_encounter_types,
- regex_terrain_types,
- pals_output_extension,
- tiles_output_extension,
-};
-
-enum ProjectFilePath {
- data_map_folders,
- data_scripts_folders,
- data_layouts_folders,
- data_primary_tilesets_folders,
- data_secondary_tilesets_folders,
- data_event_scripts,
- json_map_groups,
- json_layouts,
- json_wild_encounters,
- json_heal_locations,
- json_region_map_entries,
- json_region_porymap_cfg,
- tilesets_headers,
- tilesets_graphics,
- tilesets_metatiles,
- tilesets_headers_asm,
- tilesets_graphics_asm,
- tilesets_metatiles_asm,
- data_obj_event_gfx_pointers,
- data_obj_event_gfx_info,
- data_obj_event_pic_tables,
- data_obj_event_gfx,
- data_pokemon_gfx,
- constants_global,
- constants_items,
- constants_flags,
- constants_vars,
- constants_weather,
- constants_songs,
- constants_pokemon,
- constants_map_types,
- constants_trainer_types,
- constants_secret_bases,
- constants_obj_event_movement,
- constants_obj_events,
- constants_event_bg,
- constants_metatile_labels,
- constants_metatile_behaviors,
- constants_species,
- constants_fieldmap,
- global_fieldmap,
- fieldmap,
- initial_facing_table,
- wild_encounter,
- pokemon_icon_table,
- pokemon_gfx,
-};
-
-class ProjectConfig: public KeyValueConfigBase
-{
-public:
- ProjectConfig();
- virtual void reset() override {
- this->baseGameVersion = BaseGameVersion::pokeemerald;
- // Reset non-version-specific settings
- this->usePoryScript = false;
- this->tripleLayerMetatilesEnabled = false;
- this->defaultMetatileId = 1;
- this->defaultElevation = 3;
- this->defaultCollision = 0;
- this->defaultMapSize = QSize(20,20);
- this->defaultPrimaryTileset = "gTileset_General";
- this->prefabFilepath = QString();
- this->prefabImportPrompted = false;
- this->tilesetsHaveCallback = true;
- this->tilesetsHaveIsCompressed = true;
- this->transparencyColor = QColor(Qt::black);
- this->preserveMatchingOnlyData = false;
- this->filePaths.clear();
- this->eventIconPaths.clear();
- this->pokemonIconPaths.clear();
- this->eventsTabIconPath = QString();
- this->collisionSheetPath = QString();
- this->collisionSheetSize = QSize(2, 16);
- this->playerViewDistance = QMargins(GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER, GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER);
- this->blockMetatileIdMask = 0x03FF;
- this->blockCollisionMask = 0x0C00;
- this->blockElevationMask = 0xF000;
- this->unusedTileNormal = 0x3014;
- this->unusedTileCovered = 0x0000;
- this->unusedTileSplit = 0x0000;
- this->maxEventsPerGroup = 255;
- this->forcedMajorVersion = 0;
- this->metatileSelectorWidth = 8;
- this->globalConstantsFilepaths.clear();
- this->globalConstants.clear();
- this->identifiers.clear();
- this->readKeys.clear();
- }
- static const QMap> defaultIdentifiers;
- static const QMap> defaultPaths;
- static const QStringList versionStrings;
- static BaseGameVersion stringToBaseGameVersion(const QString &string);
-
- static QString getPlayerIconPath(BaseGameVersion baseGameVersion, int character);
- static QIcon getPlayerIcon(BaseGameVersion baseGameVersion, int character);
-
- QString projectDir() const { return m_root; } // Alias for root()
- void reset(BaseGameVersion baseGameVersion);
- void setFilePath(ProjectFilePath pathId, const QString &path);
- void setFilePath(const QString &pathId, const QString &path);
- QString getCustomFilePath(ProjectFilePath pathId);
- QString getCustomFilePath(const QString &pathId);
- QString getFilePath(ProjectFilePath pathId);
- void setIdentifier(ProjectIdentifier id, QString text);
- void setIdentifier(const QString &id, const QString &text);
- QString getCustomIdentifier(ProjectIdentifier id);
- QString getCustomIdentifier(const QString &id);
- QString getIdentifier(ProjectIdentifier id);
- QString getBaseGameVersionString(BaseGameVersion version);
- QString getBaseGameVersionString();
- int getNumLayersInMetatile();
- int getNumTilesInMetatile();
- void setEventIconPath(Event::Group group, const QString &path);
- QString getEventIconPath(Event::Group group);
- void setPokemonIconPath(const QString &species, const QString &path);
- QString getPokemonIconPath(const QString &species);
- QMap getPokemonIconPaths();
-
- BaseGameVersion baseGameVersion;
- bool usePoryScript;
- bool useCustomBorderSize;
- bool eventWeatherTriggerEnabled;
- bool eventSecretBaseEnabled;
- bool hiddenItemQuantityEnabled;
- bool hiddenItemRequiresItemfinderEnabled;
- bool healLocationRespawnDataEnabled;
- bool eventCloneObjectEnabled;
- bool floorNumberEnabled;
- bool createMapTextFileEnabled;
- bool tripleLayerMetatilesEnabled;
- uint16_t defaultMetatileId;
- uint16_t defaultElevation;
- uint16_t defaultCollision;
- QSize defaultMapSize;
- QList newMapBorderMetatileIds;
- QString defaultPrimaryTileset;
- QString defaultSecondaryTileset;
- QString prefabFilepath;
- bool prefabImportPrompted;
- bool tilesetsHaveCallback;
- bool tilesetsHaveIsCompressed;
- QColor transparencyColor;
- bool preserveMatchingOnlyData;
- int metatileAttributesSize;
- uint32_t metatileBehaviorMask;
- uint32_t metatileTerrainTypeMask;
- uint32_t metatileEncounterTypeMask;
- uint32_t metatileLayerTypeMask;
- uint16_t blockMetatileIdMask;
- uint16_t blockCollisionMask;
- uint16_t blockElevationMask;
- uint16_t unusedTileNormal;
- uint16_t unusedTileCovered;
- uint16_t unusedTileSplit;
- bool mapAllowFlagsEnabled;
- QString eventsTabIconPath;
- QString collisionSheetPath;
- QSize collisionSheetSize;
- QMargins playerViewDistance;
- QList warpBehaviors;
- int maxEventsPerGroup;
- int forcedMajorVersion;
- int metatileSelectorWidth;
- QStringList globalConstantsFilepaths;
- QMap globalConstants;
-
-protected:
- virtual void parseConfigKeyValue(QString key, QString value) override;
- virtual QMap getKeyValueMap() override;
- virtual void init() override;
- virtual void setUnreadKeys() override;
-
-private:
- QStringList readKeys;
- QMap identifiers;
- QMap filePaths;
- QMap eventIconPaths;
- QMap pokemonIconPaths;
-};
-
-extern ProjectConfig projectConfig;
-
-class UserConfig: public KeyValueConfigBase
-{
-public:
- UserConfig();
- virtual void reset() override {
- this->recentMapOrLayout = QString();
- this->useEncounterJson = true;
- this->customScripts.clear();
- this->readKeys.clear();
- }
-
- QString projectDir() const { return m_root; } // Alias for root()
- void parseCustomScripts(QString input);
- QString outputCustomScripts();
- void setCustomScripts(QStringList scripts, QList enabled);
- QStringList getCustomScriptPaths();
- QList getCustomScriptsEnabled();
-
- QString recentMapOrLayout;
- bool useEncounterJson;
-
-protected:
- virtual void parseConfigKeyValue(QString key, QString value) override;
- virtual QMap getKeyValueMap() override;
- virtual void init() override;
- virtual void setUnreadKeys() override;
-#ifdef CONFIG_BACKWARDS_COMPATABILITY
- friend class ProjectConfig;
-#endif
-
-private:
- QStringList readKeys;
- QMap customScripts;
-};
-
-extern UserConfig userConfig;
-
-class QAction;
-class Shortcut;
-
-class ShortcutsConfig : public KeyValueConfigBase
-{
-public:
- ShortcutsConfig();
-
- virtual void reset() override {
- setRoot(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
- user_shortcuts.clear();
- }
-
- // Call this before applying user shortcuts so that the user can restore defaults.
- void setDefaultShortcuts(const QObjectList &objects);
- QList defaultShortcuts(const QObject *object) const;
-
- void setUserShortcuts(const QObjectList &objects);
- void setUserShortcuts(const QMultiMap &objects_keySequences);
- QList userShortcuts(const QObject *object) const;
-
-protected:
- virtual void parseConfigKeyValue(QString key, QString value) override;
- virtual QMap getKeyValueMap() override;
- virtual void init() override { };
- virtual void setUnreadKeys() override { };
-
-private:
- QMultiMap user_shortcuts;
- QMultiMap default_shortcuts;
-
- enum StoreType {
- User,
- Default
- };
-
- QString cfgKey(const QObject *object) const;
- QList currentShortcuts(const QObject *object) const;
-
- void storeShortcutsFromList(StoreType storeType, const QObjectList &objects);
- void storeShortcuts(
- StoreType storeType,
- const QString &cfgKey,
- const QList &keySequences);
-};
-
-extern ShortcutsConfig shortcutsConfig;
-
-#endif // CONFIG_H
diff --git a/include/config/config.h b/include/config/config.h
new file mode 100644
index 00000000..723f59cc
--- /dev/null
+++ b/include/config/config.h
@@ -0,0 +1,5 @@
+#pragma once
+#include "porymapconfig.h"
+#include "projectconfig.h"
+#include "userconfig.h"
+#include "shortcutsconfig.h"
diff --git a/include/config/keyvalueconfigbase.h b/include/config/keyvalueconfigbase.h
new file mode 100644
index 00000000..f7db0d96
--- /dev/null
+++ b/include/config/keyvalueconfigbase.h
@@ -0,0 +1,48 @@
+#pragma once
+#ifndef KEYVALUECONFIGBASE_H
+#define KEYVALUECONFIGBASE_H
+
+#include
+#include
+
+#include "fieldmanager.h"
+
+class KeyValueConfigBase
+{
+public:
+ explicit KeyValueConfigBase(const QString& filename)
+ : m_root(QString()),
+ m_filename(filename),
+ m_filepath(filename)
+ { };
+ virtual ~KeyValueConfigBase() {};
+
+ virtual bool save();
+ virtual bool load();
+
+ virtual QJsonObject toJson();
+ virtual void loadFromJson(const QJsonObject& obj);
+
+ void setRoot(const QString& dir);
+ QString root() const { return m_root; }
+ QString filepath() const { return m_filepath; }
+ QString filename() const { return m_filename; }
+protected:
+ virtual void initializeFromEmpty() {};
+ virtual QJsonObject getDefaultJson() const { return QJsonObject(); }
+
+ virtual FieldManager* getFieldManager() { return nullptr; }
+
+ virtual bool parseJsonKeyValue(const QString& key, const QJsonValue& value);
+ virtual bool parseLegacyKeyValue(const QString& , const QString& ) {return false;}
+
+ QString m_root;
+ QString m_filename;
+ QString m_filepath;
+private:
+ bool loadLegacy();
+
+ bool m_saveAllFields = false;
+};
+
+#endif // KEYVALUECONFIGBASE_H
diff --git a/include/config/porymapconfig.h b/include/config/porymapconfig.h
new file mode 100644
index 00000000..3e1bf5d7
--- /dev/null
+++ b/include/config/porymapconfig.h
@@ -0,0 +1,189 @@
+#pragma once
+#ifndef PORYMAPCONFIG_H
+#define PORYMAPCONFIG_H
+
+#include "keyvalueconfigbase.h"
+#include "block.h"
+#include "events.h"
+#include "log.h"
+
+#include
+#include
+#include
+#include
+
+enum ScriptAutocompleteMode {
+ MapOnly,
+ MapAndCommon,
+ All,
+};
+
+class PorymapConfig: public KeyValueConfigBase
+{
+public:
+ PorymapConfig() : KeyValueConfigBase(QStringLiteral("settings.json")) {
+ setRoot(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
+
+ // Initialize defaults not available at compile time.
+ this->mapListFont = defaultMapListFont();
+ }
+
+ virtual bool save() override;
+
+ virtual void loadFromJson(const QJsonObject& obj) override;
+
+ void addRecentProject(const QString& project);
+ void setRecentProjects(const QStringList& projects);
+ QString getRecentProject() const;
+ const QStringList& getRecentProjects() const;
+
+ void saveGeometry(const QWidget* widget, const QString& keyPrefix = QString(), bool recursive = true);
+ bool restoreGeometry(QWidget* widget, const QString& keyPrefix = QString(), bool recursive = true) const;
+
+ static QFont defaultMapListFont() { return QFontDatabase::systemFont(QFontDatabase::FixedFont); }
+
+ bool reopenOnLaunch = true;
+ bool projectManuallyClosed = false;
+ int mapListTab = 0;
+ bool mapListEditGroupsEnabled = false;
+ OrderedSet mapListTabsHidingEmptyFolders;
+ bool mapListLayoutsSorted = true;
+ bool mapListLocationsSorted = true;
+ bool prettyCursors = true;
+ bool mirrorConnectingMaps = true;
+ bool showDiveEmergeMaps = false;
+ int diveEmergeMapOpacity = 30;
+ int diveMapOpacity = 15;
+ int emergeMapOpacity = 15;
+ int collisionOpacity = 50;
+ int collisionZoom = 30;
+ int metatilesZoom = 30;
+ int tilesetEditorMetatilesZoom = 30;
+ int tilesetEditorTilesZoom = 30;
+ Qt::Orientation tilesetEditorLayerOrientation = Qt::Vertical;
+ bool showPlayerView = false;
+ bool showCursorTile = true;
+ bool showBorder = true;
+ bool showGrid = false;
+ bool showTilesetEditorMetatileGrid = false;
+ bool showTilesetEditorLayerGrid = true;
+ bool showTilesetEditorDivider = true;
+ bool showTilesetEditorRawAttributes = false;
+ bool showPaletteEditorUnusedColors = false;
+ bool monitorFiles = true;
+ bool tilesetCheckerboardFill = true;
+ bool newMapHeaderSectionExpanded = false;
+ bool displayIdsHexadecimal = true;
+ QString theme = QStringLiteral("default");
+ QString wildMonChartTheme;
+ QString textEditorOpenFolder;
+ QString textEditorGotoLine;
+ int paletteEditorBitDepth = 24;
+ int projectSettingsTab = 0;
+ ScriptAutocompleteMode scriptAutocompleteMode = ScriptAutocompleteMode::MapOnly;
+ bool warpBehaviorWarningDisabled = false;
+ bool eventDeleteWarningDisabled = false;
+ bool eventOverlayEnabled = false;
+ bool checkForUpdates = true;
+ bool showProjectLoadingScreen = true;
+ QDateTime lastUpdateCheckTime;
+ QVersionNumber lastUpdateCheckVersion;
+ QMap rateLimitTimes;
+ QGraphicsPixmapItem::ShapeMode eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape;
+ bool shownInGameReloadMessage = false;
+ GridSettings gridSettings;
+ OrderedSet statusBarLogTypes = { LogType::LOG_ERROR, LogType::LOG_WARN };
+ QFont applicationFont;
+ QFont mapListFont;
+#ifdef Q_OS_MACOS
+ // Since the release of the Retina display, Apple products use the Display P3 color space by default.
+ // If we don't use this for exported images (which by default will either have no color space or the sRGB
+ // color space) then they may appear to have different colors than the same image displayed in Porymap.
+ std::optional imageExportColorSpace = QColorSpace::DisplayP3;
+#else
+ // As of writing Qt has no way to get a reasonable color space from the user's environment,
+ // so we export images without one and let them handle it.
+ std::optional imageExportColorSpace = {};
+#endif
+ QMap trustedScriptHashes;
+
+ FieldManager* getFieldManager() override {
+ if (!m_fm) {
+ m_fm = std::make_shared();
+ m_fm->addField(&this->reopenOnLaunch, "reopen_on_launch");
+ m_fm->addField(&this->projectManuallyClosed, "project_manually_closed");
+ m_fm->addField(&this->mapListTab, "map_list_tab", 0, 2);
+ m_fm->addField(&this->mapListEditGroupsEnabled, "map_list_edit_groups_enabled");
+ m_fm->addField(&this->mapListTabsHidingEmptyFolders, "map_list_tabs_hiding_empty_folders");
+ m_fm->addField(&this->mapListLayoutsSorted, "map_list_layouts_sorted");
+ m_fm->addField(&this->mapListLocationsSorted, "map_list_locations_sorted");
+ m_fm->addField(&this->prettyCursors, "pretty_cursors");
+ m_fm->addField(&this->mirrorConnectingMaps, "mirror_connecting_maps");
+ m_fm->addField(&this->showDiveEmergeMaps, "show_dive_emerge_maps");
+ m_fm->addField(&this->diveEmergeMapOpacity, "dive_emerge_map_opacity", 10, 90);
+ m_fm->addField(&this->diveMapOpacity, "dive_map_opacity", 10, 90);
+ m_fm->addField(&this->emergeMapOpacity, "emerge_map_opacity", 10, 90);
+ m_fm->addField(&this->collisionOpacity, "collision_opacity", 0, 100);
+ m_fm->addField(&this->collisionZoom, "collision_zoom", 10, 100);
+ m_fm->addField(&this->metatilesZoom, "metatiles_zoom", 10, 100);
+ m_fm->addField(&this->tilesetEditorMetatilesZoom, "tileset_editor_metatiles_zoom", 10, 100);
+ m_fm->addField(&this->tilesetEditorTilesZoom, "tileset_editor_tiles_zoom", 10, 100);
+ m_fm->addField(&this->tilesetEditorLayerOrientation, "tileset_editor_layer_orientation");
+ m_fm->addField(&this->showPlayerView, "show_player_view");
+ m_fm->addField(&this->showCursorTile, "show_cursor_tile");
+ m_fm->addField(&this->showBorder, "show_border");
+ m_fm->addField(&this->showGrid, "show_grid");
+ m_fm->addField(&this->showTilesetEditorMetatileGrid, "show_tileset_editor_metatile_grid");
+ m_fm->addField(&this->showTilesetEditorLayerGrid, "show_tileset_editor_layer_grid");
+ m_fm->addField(&this->showTilesetEditorDivider, "show_tileset_editor_divider");
+ m_fm->addField(&this->showTilesetEditorRawAttributes, "show_tileset_editor_raw_attributes");
+ m_fm->addField(&this->showPaletteEditorUnusedColors, "show_palette_editor_unused_colors");
+ m_fm->addField(&this->monitorFiles, "monitor_files");
+ m_fm->addField(&this->tilesetCheckerboardFill, "tileset_checkerboard_fill");
+ m_fm->addField(&this->newMapHeaderSectionExpanded, "new_map_header_section_expanded");
+ m_fm->addField(&this->displayIdsHexadecimal, "display_ids_hexadecimal");
+ m_fm->addField(&this->theme, "theme");
+ m_fm->addField(&this->wildMonChartTheme, "wild_mon_chart_theme");
+ m_fm->addField(&this->textEditorOpenFolder, "text_editor_open_folder");
+ m_fm->addField(&this->textEditorGotoLine, "text_editor_goto_line");
+ m_fm->addField(&this->paletteEditorBitDepth, "palette_editor_bit_depth", {24,15});
+ m_fm->addField(&this->projectSettingsTab, "project_settings_tab");
+ m_fm->addField(&this->scriptAutocompleteMode, "script_autocomplete_mode");
+ m_fm->addField(&this->warpBehaviorWarningDisabled, "warp_behavior_warning_disabled");
+ m_fm->addField(&this->eventDeleteWarningDisabled, "event_delete_warning_disabled");
+ m_fm->addField(&this->eventOverlayEnabled, "event_overlay_enabled");
+ m_fm->addField(&this->checkForUpdates, "check_for_updates");
+ m_fm->addField(&this->showProjectLoadingScreen, "show_project_loading_screen");
+ m_fm->addField(&this->lastUpdateCheckTime, "last_update_check_time");
+ m_fm->addField(&this->lastUpdateCheckVersion, "last_update_check_version");
+ m_fm->addField(&this->rateLimitTimes, "rate_limit_times");
+ m_fm->addField(&this->eventSelectionShapeMode, "event_selection_shape_mode");
+ m_fm->addField(&this->shownInGameReloadMessage, "shown_in_game_reload_message");
+ m_fm->addField(&this->gridSettings, "map_grid");
+ m_fm->addField(&this->statusBarLogTypes, "status_bar_log_types");
+ m_fm->addField(&this->applicationFont, "application_font");
+ m_fm->addField(&this->mapListFont, "map_list_font");
+ m_fm->addField(&this->imageExportColorSpace, "image_export_color_space");
+ m_fm->addField(&this->trustedScriptHashes, "trusted_script_hashes");
+
+ m_fm->addField(&this->recentProjects, "recent_projects");
+ m_fm->addField(&this->savedGeometryMap, "geometry");
+ m_fm->addField(&this->geometryVersion, "geometry_version");
+ }
+ return m_fm.get();
+ };
+
+protected:
+ virtual bool parseLegacyKeyValue(const QString& key, const QString& value) override;
+ virtual QJsonObject getDefaultJson() const override;
+
+private:
+ std::shared_ptr m_fm = nullptr;
+ QStringList recentProjects;
+ QMap savedGeometryMap;
+ int geometryVersion = 0;
+};
+
+extern PorymapConfig porymapConfig;
+
+#endif // PORYMAPCONFIG_H
diff --git a/include/config/projectconfig.h b/include/config/projectconfig.h
new file mode 100644
index 00000000..8e5e1f8d
--- /dev/null
+++ b/include/config/projectconfig.h
@@ -0,0 +1,276 @@
+#pragma once
+#ifndef PROJECTCONFIG_H
+#define PROJECTCONFIG_H
+
+#include "keyvalueconfigbase.h"
+#include "block.h"
+#include "events.h"
+
+enum ProjectIdentifier {
+ symbol_facing_directions,
+ symbol_obj_event_gfx_pointers,
+ symbol_pokemon_icon_table,
+ symbol_attribute_table,
+ symbol_tilesets_prefix,
+ symbol_dynamic_map_name,
+ define_obj_event_count,
+ define_min_level,
+ define_max_level,
+ define_max_encounter_rate,
+ define_tiles_primary,
+ define_tiles_total,
+ define_metatiles_primary,
+ define_pals_primary,
+ define_pals_total,
+ define_tiles_per_metatile,
+ define_map_size,
+ define_map_offset_width,
+ define_map_offset_height,
+ define_mask_metatile,
+ define_mask_collision,
+ define_mask_elevation,
+ define_mask_behavior,
+ define_mask_layer,
+ define_attribute_behavior,
+ define_attribute_layer,
+ define_attribute_terrain,
+ define_attribute_encounter,
+ define_metatile_label_prefix,
+ define_heal_locations_prefix,
+ define_layout_prefix,
+ define_map_prefix,
+ define_map_dynamic,
+ define_map_empty,
+ define_map_section_prefix,
+ define_map_section_empty,
+ define_species_prefix,
+ define_species_empty,
+ regex_behaviors,
+ regex_obj_event_gfx,
+ regex_items,
+ regex_flags,
+ regex_vars,
+ regex_movement_types,
+ regex_map_types,
+ regex_battle_scenes,
+ regex_weather,
+ regex_coord_event_weather,
+ regex_secret_bases,
+ regex_sign_facing_directions,
+ regex_trainer_types,
+ regex_music,
+ regex_encounter_types,
+ regex_terrain_types,
+ pals_output_extension,
+ tiles_output_extension,
+};
+
+enum ProjectFilePath {
+ data_map_folders,
+ data_scripts_folders,
+ data_layouts_folders,
+ data_primary_tilesets_folders,
+ data_secondary_tilesets_folders,
+ data_event_scripts,
+ json_map_groups,
+ json_layouts,
+ json_wild_encounters,
+ json_heal_locations,
+ json_region_map_entries,
+ json_region_porymap_cfg,
+ tilesets_headers,
+ tilesets_graphics,
+ tilesets_metatiles,
+ tilesets_headers_asm,
+ tilesets_graphics_asm,
+ tilesets_metatiles_asm,
+ data_obj_event_gfx_pointers,
+ data_obj_event_gfx_info,
+ data_obj_event_pic_tables,
+ data_obj_event_gfx,
+ data_pokemon_gfx,
+ constants_global,
+ constants_items,
+ constants_flags,
+ constants_vars,
+ constants_weather,
+ constants_songs,
+ constants_pokemon,
+ constants_map_types,
+ constants_trainer_types,
+ constants_secret_bases,
+ constants_obj_event_movement,
+ constants_obj_events,
+ constants_event_bg,
+ constants_metatile_labels,
+ constants_metatile_behaviors,
+ constants_species,
+ constants_fieldmap,
+ global_fieldmap,
+ fieldmap,
+ initial_facing_table,
+ wild_encounter,
+ pokemon_icon_table,
+ pokemon_gfx,
+};
+
+// Distance in pixels from the edge of a GBA screen (240x160) to the center 16x16 pixels.
+#define GBA_H_DIST_TO_CENTER ((240-16)/2)
+#define GBA_V_DIST_TO_CENTER ((160-16)/2)
+
+class ProjectConfig: public KeyValueConfigBase
+{
+public:
+ ProjectConfig(const QString& root = QString()) : KeyValueConfigBase(QStringLiteral("porymap.project.json")) {
+ setRoot(root);
+ }
+ ProjectConfig(BaseGame::Version version, const QString& root = QString()) : ProjectConfig(root) {
+ setVersionSpecificDefaults(version);
+ }
+
+ virtual bool save() override;
+
+ virtual void loadFromJson(const QJsonObject& obj) override;
+
+ QString projectDir() const { return m_root; } // Alias for root()
+ void setVersionSpecificDefaults(BaseGame::Version baseGameVersion);
+
+ void setFilePath(ProjectFilePath pathId, const QString& path);
+ void setFilePath(const QString& pathId, const QString& path);
+ QString getCustomFilePath(ProjectFilePath pathId);
+ QString getCustomFilePath(const QString& pathId);
+ QString getFilePath(ProjectFilePath pathId);
+
+ void setIdentifier(ProjectIdentifier id, const QString& text);
+ void setIdentifier(const QString& id, const QString& text);
+ QString getCustomIdentifier(ProjectIdentifier id);
+ QString getCustomIdentifier(const QString& id);
+ QString getIdentifier(ProjectIdentifier id);
+
+ static const QMap> defaultIdentifiers;
+ static const QMap> defaultPaths;
+
+ BaseGame::Version baseGameVersion = BaseGame::Version::none;
+ bool usePoryScript = false;
+ bool useCustomBorderSize = false;
+ bool eventWeatherTriggerEnabled = false;
+ bool eventSecretBaseEnabled = false;
+ bool hiddenItemQuantityEnabled = false;
+ bool hiddenItemRequiresItemfinderEnabled = false;
+ bool healLocationRespawnDataEnabled = false;
+ bool eventCloneObjectEnabled = false;
+ bool floorNumberEnabled = false;
+ bool createMapTextFileEnabled = false;
+ bool tripleLayerMetatilesEnabled = false;
+ uint16_t defaultMetatileId = 1;
+ uint16_t defaultElevation = 3;
+ uint16_t defaultCollision = 0;
+ QSize defaultMapSize = QSize(20,20);
+ QList newMapBorderMetatileIds;
+ QString defaultPrimaryTileset = QStringLiteral("gTileset_General");
+ QString defaultSecondaryTileset;
+ bool tilesetsHaveCallback = true;
+ bool tilesetsHaveIsCompressed = true;
+ QColor transparencyColor = QColorConstants::Black;
+ bool preserveMatchingOnlyData = false;
+ int metatileAttributesSize = 2;
+ uint32_t metatileBehaviorMask = 0;
+ uint32_t metatileTerrainTypeMask = 0;
+ uint32_t metatileEncounterTypeMask = 0;
+ uint32_t metatileLayerTypeMask = 0;
+ uint16_t blockMetatileIdMask = Block::DefaultMetatileIdMask;
+ uint16_t blockCollisionMask = Block::DefaultCollisionMask;
+ uint16_t blockElevationMask = Block::DefaultElevationMask;
+ uint16_t unusedTileNormal = 0x3014;
+ uint16_t unusedTileCovered = 0x0000;
+ uint16_t unusedTileSplit = 0x0000;
+ bool mapAllowFlagsEnabled = true;
+ QString eventsTabIconPath;
+ QString collisionSheetPath;
+ QSize collisionSheetSize = QSize(2, 16);
+ QMargins playerViewDistance = QMargins(GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER, GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER);
+ OrderedSet warpBehaviors;
+ int maxEventsPerGroup = 255;
+ int metatileSelectorWidth = 8;
+ QStringList globalConstantsFilepaths;
+ QMap globalConstants;
+ QList customScripts;
+ QMap eventIconPaths;
+ QMap pokemonIconPaths;
+ QVersionNumber minimumVersion;
+
+ FieldManager* getFieldManager() override {
+ if (!m_fm) {
+ m_fm = std::make_shared();
+ m_fm->addField(&this->baseGameVersion, "base_game_version");
+ m_fm->addField(&this->usePoryScript, "use_poryscript");
+ m_fm->addField(&this->useCustomBorderSize, "use_custom_border_size");
+ m_fm->addField(&this->eventWeatherTriggerEnabled, "enable_event_weather_trigger");
+ m_fm->addField(&this->eventSecretBaseEnabled, "enable_event_secret_base");
+ m_fm->addField(&this->hiddenItemQuantityEnabled, "enable_hidden_item_quantity");
+ m_fm->addField(&this->hiddenItemRequiresItemfinderEnabled, "enable_hidden_item_requires_itemfinder");
+ m_fm->addField(&this->healLocationRespawnDataEnabled, "enable_heal_location_respawn_data");
+ m_fm->addField(&this->eventCloneObjectEnabled, "enable_event_clone_object");
+ m_fm->addField(&this->floorNumberEnabled, "enable_floor_number");
+ m_fm->addField(&this->createMapTextFileEnabled, "create_map_text_file");
+ m_fm->addField(&this->tripleLayerMetatilesEnabled, "enable_triple_layer_metatiles");
+ m_fm->addField(&this->defaultMetatileId, "default_metatile_id", 0, Block::MaxValue);
+ m_fm->addField(&this->defaultElevation, "default_elevation", 0, Block::MaxValue);
+ m_fm->addField(&this->defaultCollision, "default_collision", 0, Block::MaxValue);
+ m_fm->addField(&this->defaultMapSize, "default_map_size");
+ m_fm->addField(&this->newMapBorderMetatileIds, "new_map_border_metatiles");
+ m_fm->addField(&this->defaultPrimaryTileset, "default_primary_tileset");
+ m_fm->addField(&this->defaultSecondaryTileset, "default_secondary_tileset");
+ m_fm->addField(&this->tilesetsHaveCallback, "tilesets_have_callback");
+ m_fm->addField(&this->tilesetsHaveIsCompressed, "tilesets_have_is_compressed");
+ m_fm->addField(&this->transparencyColor, "transparency_color");
+ m_fm->addField(&this->preserveMatchingOnlyData, "preserve_matching_only_data");
+ m_fm->addField(&this->metatileAttributesSize, "metatile_attributes_size");
+ m_fm->addField(&this->metatileBehaviorMask, "metatile_behavior_mask");
+ m_fm->addField(&this->metatileTerrainTypeMask, "metatile_terrain_type_mask");
+ m_fm->addField(&this->metatileEncounterTypeMask, "metatile_encounter_type_mask");
+ m_fm->addField(&this->metatileLayerTypeMask, "metatile_layer_type_mask");
+ m_fm->addField(&this->blockMetatileIdMask, "block_metatile_id_mask", 0, Block::MaxValue);
+ m_fm->addField(&this->blockCollisionMask, "block_collision_mask", 0, Block::MaxValue);
+ m_fm->addField(&this->blockElevationMask, "block_elevation_mask", 0, Block::MaxValue);
+ m_fm->addField(&this->unusedTileNormal, "unused_tile_normal", 0, Tile::MaxValue);
+ m_fm->addField(&this->unusedTileCovered, "unused_tile_covered", 0, Tile::MaxValue);
+ m_fm->addField(&this->unusedTileSplit, "unused_tile_split", 0, Tile::MaxValue);
+ m_fm->addField(&this->mapAllowFlagsEnabled, "enable_map_allow_flags");
+ m_fm->addField(&this->eventsTabIconPath, "events_tab_icon_path");
+ m_fm->addField(&this->collisionSheetPath, "collision_sheet_path");
+ m_fm->addField(&this->collisionSheetSize, "collision_sheet_size", QSize(1,1), QSize(Block::MaxValue, Block::MaxValue));
+ m_fm->addField(&this->playerViewDistance, "player_view_distance", QMargins(0,0,0,0), QMargins(INT_MAX, INT_MAX, INT_MAX, INT_MAX));
+ m_fm->addField(&this->warpBehaviors, "warp_behaviors");
+ m_fm->addField(&this->maxEventsPerGroup, "max_events_per_group", 1, INT_MAX);
+ m_fm->addField(&this->metatileSelectorWidth, "metatile_selector_width", 1, INT_MAX);
+ m_fm->addField(&this->globalConstantsFilepaths, "global_constants_filepaths");
+ m_fm->addField(&this->globalConstants, "global_constants");
+ m_fm->addField(&this->customScripts, "custom_scripts");
+ m_fm->addField(&this->eventIconPaths, "event_icon_paths");
+ m_fm->addField(&this->pokemonIconPaths, "pokemon_icon_paths");
+ m_fm->addField(&this->minimumVersion, "minimum_version");
+
+ m_fm->addField(&this->identifiers, "custom_identifiers");
+ m_fm->addField(&this->filePaths, "custom_file_paths");
+ }
+ return m_fm.get();
+ }
+
+protected:
+ virtual bool parseLegacyKeyValue(const QString& key, const QString& value) override;
+ virtual QJsonObject getDefaultJson() const override;
+ virtual void initializeFromEmpty() override;
+
+private:
+ ProjectFilePath reverseDefaultPaths(const QString& str);
+ ProjectIdentifier reverseDefaultIdentifier(const QString& str);
+
+ std::shared_ptr m_fm = nullptr;
+ QMap identifiers;
+ QMap filePaths;
+};
+
+extern ProjectConfig projectConfig;
+
+#endif // PROJECTCONFIG_H
diff --git a/include/config/shortcutsconfig.h b/include/config/shortcutsconfig.h
new file mode 100644
index 00000000..5cf67348
--- /dev/null
+++ b/include/config/shortcutsconfig.h
@@ -0,0 +1,55 @@
+#pragma once
+#ifndef SHORTCUTSCONFIG_H
+#define SHORTCUTSCONFIG_H
+
+#include "keyvalueconfigbase.h"
+
+#include
+
+class QAction;
+class Shortcut;
+
+class ShortcutsConfig : public KeyValueConfigBase
+{
+public:
+ ShortcutsConfig() : KeyValueConfigBase(QStringLiteral("shortcuts.json")) {
+ setRoot(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
+ }
+
+ virtual QJsonObject toJson() override;
+ virtual void loadFromJson(const QJsonObject& obj) override;
+
+ // Call this before applying user shortcuts so that the user can restore defaults.
+ void setDefaultShortcuts(const QObjectList& objects);
+ QList defaultShortcuts(const QObject *object) const;
+
+ void setUserShortcuts(const QObjectList& objects);
+ void setUserShortcuts(const QMultiMap& objects_keySequences);
+ QList userShortcuts(const QObject *object) const;
+
+protected:
+ virtual bool parseLegacyKeyValue(const QString& key, const QString& value) override;
+ virtual QJsonObject getDefaultJson() const override;
+
+private:
+ QMultiMap user_shortcuts;
+ QMultiMap default_shortcuts;
+
+ enum StoreType {
+ User,
+ Default
+ };
+
+ QString cfgKey(const QObject *object) const;
+ QList currentShortcuts(const QObject *object) const;
+
+ void storeShortcutsFromList(StoreType storeType, const QObjectList& objects);
+ void storeShortcuts(
+ StoreType storeType,
+ const QString& cfgKey,
+ const QList& keySequences);
+};
+
+extern ShortcutsConfig shortcutsConfig;
+
+#endif // SHORTCUTSCONFIG_H
diff --git a/include/config/userconfig.h b/include/config/userconfig.h
new file mode 100644
index 00000000..01433fc1
--- /dev/null
+++ b/include/config/userconfig.h
@@ -0,0 +1,46 @@
+#pragma once
+#ifndef USERCONFIG_H
+#define USERCONFIG_H
+
+#include "keyvalueconfigbase.h"
+
+class UserConfig: public KeyValueConfigBase
+{
+public:
+ UserConfig(const QString& root = QString()) : KeyValueConfigBase(QStringLiteral("porymap.user.json")) {
+ setRoot(root);
+ }
+
+ virtual void loadFromJson(const QJsonObject& obj) override;
+
+ QString projectDir() const { return m_root; } // Alias for root()
+
+ QString recentMapOrLayout;
+ QString prefabsFilepath;
+ bool prefabsImportPrompted = false;
+ bool useEncounterJson = true;
+ QList customScripts;
+
+protected:
+ virtual bool parseLegacyKeyValue(const QString& key, const QString& value) override;
+ virtual QJsonObject getDefaultJson() const override;
+
+ FieldManager* getFieldManager() override {
+ if (!m_fm) {
+ m_fm = std::make_shared();
+ m_fm->addField(&this->recentMapOrLayout, "recent_map_or_layout");
+ m_fm->addField(&this->prefabsFilepath, "prefabs_filepath");
+ m_fm->addField(&this->prefabsImportPrompted, "prefabs_import_prompted");
+ m_fm->addField(&this->useEncounterJson, "use_encounter_json");
+ m_fm->addField(&this->customScripts, "custom_scripts");
+ }
+ return m_fm.get();
+ }
+
+private:
+ std::shared_ptr m_fm = nullptr;
+};
+
+extern UserConfig userConfig;
+
+#endif // USERCONFIG_H
diff --git a/include/core/basegame.h b/include/core/basegame.h
new file mode 100644
index 00000000..06732847
--- /dev/null
+++ b/include/core/basegame.h
@@ -0,0 +1,22 @@
+#pragma once
+#ifndef BASEGAMEVERSION_H
+#define BASEGAMEVERSION_H
+
+#include
+#include
+
+namespace BaseGame {
+ enum Version {
+ none,
+ pokeruby,
+ pokefirered,
+ pokeemerald,
+ };
+ Version stringToVersion(const QString &string);
+ QString versionToString(Version version);
+
+ QString getPlayerIconPath(Version version, int character);
+ QIcon getPlayerIcon(Version version, int character);
+};
+
+#endif // BASEGAMEVERSION_H
diff --git a/include/core/block.h b/include/core/block.h
index 94c674c3..5735add1 100644
--- a/include/core/block.h
+++ b/include/core/block.h
@@ -26,7 +26,12 @@ public:
static uint16_t getMaxCollision();
static uint16_t getMaxElevation();
- static const uint16_t maxValue;
+ // Upper limit for metatile ID, collision, and elevation masks. Used externally.
+ static constexpr uint16_t MaxValue = 0xFFFF;
+
+ static constexpr uint16_t DefaultMetatileIdMask = 0x03FF;
+ static constexpr uint16_t DefaultCollisionMask = 0x0C00;
+ static constexpr uint16_t DefaultElevationMask = 0xF000;
private:
uint16_t m_metatileId;
diff --git a/include/core/converter.h b/include/core/converter.h
new file mode 100644
index 00000000..5a5e86b5
--- /dev/null
+++ b/include/core/converter.h
@@ -0,0 +1,401 @@
+#pragma once
+#ifndef CONVERTER_H
+#define CONVERTER_H
+
+#include
+#include
+#include
+#include
+#include
+
+#include "magic_enum.hpp"
+#include "orderedset.h"
+#include "scriptsettings.h"
+#include "gridsettings.h"
+#include "basegame.h"
+
+/*
+ These are templates for type conversion to/from JSON,
+ though other type conversions can be implemented here too.
+
+ This is mostly useful when converting the type is complicated,
+ or when the type is generalized away.
+
+
+ ## Example Usage ##
+ QSize size;
+ QJsonValue json = Converter::toJson(size);
+ QSize sameSize = Converter::fromJson(json);
+
+
+ ## Adding a new conversion ##
+ To add a new type conversion, add a new 'Converter' template:
+
+ template <>
+ struct Converter : DefaultConverter {
+ // And re-implement any of the desired conversion functions.
+ // Any functions not implemented will be inherited from DefaultConverter.
+ static QJsonValue toJson(const NewType& value) {
+ // your conversion to JSON
+ return QJsonValue();
+ }
+ static NewType fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ // your conversion from JSON
+ return NewType();
+ }
+ };
+
+ Note: When serializing to/from JSON, anything that can be serialized to/from
+ a string is trivially serializable for JSON. In this case, rather than
+ inheriting from 'DefaultConverter' and reimplementing 'toJson' and 'fromJson',
+ you can inherit from 'DefaultStringConverter' and/or reimplement 'toString'/'fromString'.
+ Appropriately implementing 'toString'/'fromString' has the added benefit that your type
+ can automatically be used as a JSON key if it for example appears as the key in a QMap.
+
+ If a type doesn't support the '<'/'>' operators, 'clamp' can be reimplemented as well to allow
+ the type to be used in range validation.
+
+*/
+
+
+template
+struct DefaultConverter {
+ // Defaults to straightforward QJsonValue construction.
+ // This handles most of the primitive types.
+ static QJsonValue toJson(const T& value) {
+ return QJsonValue(value);
+ }
+ static T fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ const QVariant v = json.toVariant();
+ if (!v.canConvert()) {
+ if (errors) errors->append(QString("Can't convert from type '%1'").arg(v.typeName()));
+ // Failed conversion will return a default-constructed object below
+ }
+ return v.value();
+ }
+
+ // Default to identity
+ static QString toString(const T& value) {return value;}
+ static T fromString(const QString& string, QStringList* = nullptr) {return string;}
+
+ static T clamp(const T& value, const T& min, const T& max, QStringList* errors = nullptr) {
+ Q_ASSERT(min <= max);
+ if (value < min) {
+ if (errors) errors->append("Value too low");
+ return min;
+ }
+ if (value > max) {
+ if (errors) errors->append("Value too high");
+ return max;
+ }
+ return value;
+ }
+};
+
+template
+struct Converter : DefaultConverter {};
+
+// This template implements JSON conversion by first converting the data to/from a string.
+// This allows any type that can describe how to stringify itself to automatically also
+// support JSON conversion with no additional work.
+template
+struct DefaultStringConverter : DefaultConverter {
+ static QJsonValue toJson(const T& value) {
+ return Converter::toJson(Converter::toString(value));
+ }
+ static T fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ const auto string = Converter::fromJson(json, errors);
+ return Converter::fromString(string, errors);
+ }
+ // Many types have a 'toString' function, so we default to trying that.
+ static QString toString(const T& value) {
+ return value.toString();
+ }
+};
+
+template <>
+struct Converter : DefaultStringConverter {};
+
+template <>
+struct Converter : DefaultStringConverter {};
+
+template <>
+struct Converter : DefaultConverter {
+ // Constructing a QJsonValue from uint32_t is ambiguous, so we need an explicit cast.
+ static QJsonValue toJson(uint32_t value) {
+ return QJsonValue{static_cast(value)};
+ }
+};
+
+// Template for generic enum values.
+// Converts JSON -> string/int -> enum, handling the unsafe conversion if the int is out of range of the enum.
+// Qt has a system for this (Q_ENUM) but they don't use it for all their internal enums, so we use magic_enum instead.
+template
+struct Converter>> : DefaultStringConverter {
+ static QString toString(const T& value) {
+ const std::string s = std::string(magic_enum::enum_name(value));
+ return QString::fromStdString(s);
+ }
+ static T fromString(const QString& string, QStringList* errors = nullptr) {
+ auto e = magic_enum::enum_cast(string.toStdString(), magic_enum::case_insensitive);
+ if (!e.has_value()) {
+ if (errors) errors->append(QString("'%1' is not a named enum value.").arg(string));
+ return magic_enum::enum_value(0);
+ }
+ return e.value();
+ }
+ // When reading from JSON, handle either the named enum or an enum's number value.
+ static T fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ if (json.isString()) return Converter::fromString(json.toString());
+ auto value = Converter::fromJson(json, errors);
+ auto e = magic_enum::enum_cast(value);
+ if (!e.has_value()) {
+ if (errors) errors->append(QString("'%1' is out of range of enum.").arg(QString::number(value)));
+ return magic_enum::enum_value(0);
+ }
+ return e.value();
+ }
+};
+
+template <>
+struct Converter : DefaultStringConverter {
+ static QVersionNumber fromString(const QString& string, QStringList* = nullptr) {
+ return QVersionNumber::fromString(string);
+ }
+};
+
+template <>
+struct Converter : DefaultStringConverter {
+ static QString toString(const QDateTime& value) {
+ return value.toUTC().toString();
+ }
+ static QDateTime fromString(const QString& string, QStringList* = nullptr) {
+ return QDateTime::fromString(string).toLocalTime();
+ }
+};
+
+template <>
+struct Converter : DefaultStringConverter {
+ static QString toString(const QColor& value) {
+ return value.name();
+ }
+ static QColor fromString(const QString& string, QStringList* errors = nullptr) {
+ const QColor color(string);
+ if (!color.isValid()) {
+ if (errors) errors->append(QString("'%1' is not a valid color.").arg(string));
+ return QColorConstants::Black;
+ }
+ return color;
+ }
+};
+
+template <>
+struct Converter : DefaultStringConverter {
+ static QFont fromString(const QString& string, QStringList* errors = nullptr) {
+ QFont font;
+ if (!font.fromString(string) && errors) {
+ errors->append(QString("'%1' is not a valid font description.").arg(string));
+ }
+ return font;
+ }
+};
+
+template <>
+struct Converter : DefaultStringConverter {
+ static QString toString(const BaseGame::Version& value) {
+ return BaseGame::versionToString(value);
+ }
+ static BaseGame::Version fromString(const QString& string, QStringList* = nullptr) {
+ return BaseGame::stringToVersion(string);
+ }
+};
+
+template
+struct Converter> : DefaultConverter> {
+ static QJsonValue toJson(const std::optional& optional) {
+ return optional.has_value() ? Converter::toJson(optional.value()) : QJsonValue();
+ }
+ static std::optional fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ if (json.isNull()) return {};
+ return Converter::fromJson(json, errors);
+ }
+};
+
+template
+struct ListConverter : DefaultConverter> {
+ static QJsonValue toJson(const QList& list) {
+ QJsonArray arr;
+ for (auto& elem : list) arr.append(Converter::toJson(elem));
+ return arr;
+ }
+ static QList fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ const auto arr = Converter::fromJson(json, errors);
+ QList list;
+ for (const auto& elem : arr) list.append(Converter::fromJson(elem, errors));
+ return list;
+ }
+};
+
+template
+struct Converter> : ListConverter {};
+
+// Only needed for Qt5
+template <>
+struct Converter : ListConverter {};
+
+template
+struct Converter> : DefaultConverter> {
+ static QJsonObject toJson(const QMap& map) {
+ QJsonObject obj;
+ for (auto it = map.begin(); it != map.end(); it++) {
+ const QString key = Converter::toString(it.key());
+ obj[key] = Converter::toJson(it.value());
+ }
+ return obj;
+ }
+ static QMap fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ const auto obj = Converter::fromJson(json, errors);
+ QMap map;
+ for (auto it = obj.begin(); it != obj.end(); it++) {
+ const auto key = Converter::fromString(it.key(), errors);
+ map.insert(key, Converter::fromJson(it.value(), errors));
+ }
+ return map;
+ }
+};
+
+template
+struct Converter> : DefaultConverter> {
+ static QJsonObject toJson(const QMultiMap& map) {
+ QJsonObject obj;
+ for (const auto& uniqueKey : map.uniqueKeys()) {
+ const QString key = Converter::toString(uniqueKey);
+ obj[key] = Converter>::toJson(map.values(uniqueKey));
+ }
+ return obj;
+ }
+ static QMultiMap fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ const auto obj = Converter::fromJson(json, errors);
+ QMultiMap map;
+ for (auto it = obj.begin(); it != obj.end(); it++) {
+ const auto key = Converter::fromString(it.key(), errors);
+ const auto values = Converter>::fromJson(it.value(), errors);
+ for (const auto& value : values) map.insert(key, value);
+ }
+ return map;
+ }
+};
+
+template
+struct Converter> : DefaultConverter> {
+ static QJsonValue toJson(const OrderedSet& set) {
+ QJsonArray arr;
+ for (auto& elem : set) arr.append(Converter::toJson(elem));
+ return arr;
+ }
+ static OrderedSet fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ const auto arr = Converter::fromJson(json, errors);
+ OrderedSet set;
+ for (const auto& elem : arr) set.insert(Converter::fromJson(elem, errors));
+ return set;
+ }
+};
+
+template <>
+struct Converter : DefaultConverter {
+ static QJsonValue toJson(const QSize& value) {
+ QJsonObject obj;
+ obj["width"] = value.width();
+ obj["height"] = value.height();
+ return obj;
+ }
+ static QSize fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ const auto obj = Converter::fromJson(json, errors);
+ QSize size;
+ size.setWidth(obj.value("width").toInt());
+ size.setHeight(obj.value("height").toInt());
+ return size;
+ }
+ static QSize clamp(const QSize& value, const QSize& min, const QSize& max, const QStringList* = nullptr) {
+ Q_ASSERT(min.width() <= max.width());
+ Q_ASSERT(min.height() <= max.height());
+ QSize size = value;
+ if (value.width() < min.width() || value.height() < min.height()) size = value.expandedTo(min);
+ if (value.width() > max.width() || value.height() > max.height()) size = value.boundedTo(max);
+ return size;
+ }
+};
+
+template <>
+struct Converter : DefaultConverter {
+ static QJsonValue toJson(const QMargins& value) {
+ QJsonObject obj;
+ obj["top"] = value.top();
+ obj["bottom"] = value.bottom();
+ obj["left"] = value.left();
+ obj["right"] = value.right();
+ return obj;
+ }
+ static QMargins fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ const auto obj = Converter::fromJson(json, errors);
+ QMargins margins;
+ margins.setTop(obj.value("top").toInt());
+ margins.setBottom(obj.value("bottom").toInt());
+ margins.setLeft(obj.value("left").toInt());
+ margins.setRight(obj.value("right").toInt());
+ return margins;
+ }
+ static QMargins clamp(const QMargins& value, const QMargins& min, const QMargins& max, const QStringList* = nullptr) {
+ Q_ASSERT(min.left() <= max.left());
+ Q_ASSERT(min.right() <= max.right());
+ Q_ASSERT(min.top() <= max.top());
+ Q_ASSERT(min.bottom() <= max.bottom());
+ QMargins margins = value;
+ margins.setLeft( std::clamp(value.left(), min.left(), max.left()));
+ margins.setRight( std::clamp(value.right(), min.right(), max.right()));
+ margins.setTop( std::clamp(value.top(), min.top(), max.top()));
+ margins.setBottom(std::clamp(value.bottom(), min.bottom(), max.bottom()));
+ return margins;
+ }
+};
+
+template <>
+struct Converter : DefaultConverter {
+ static QJsonValue toJson(const ScriptSettings& value) {
+ QJsonObject obj;
+ obj["path"] = value.path;
+ obj["enabled"] = value.enabled;
+ return obj;
+ }
+ static ScriptSettings fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ const auto obj = Converter::fromJson(json, errors);
+ ScriptSettings settings;
+ settings.path = obj.value("path").toString();
+ settings.enabled = obj.value("enabled").toBool();
+ return settings;
+ }
+};
+
+template <>
+struct Converter : DefaultConverter {
+ static QJsonValue toJson(const GridSettings& value) {
+ return value.toJson();
+ }
+ static GridSettings fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ const auto obj = Converter::fromJson(json, errors);
+ return GridSettings::fromJson(obj);
+ }
+};
+
+template <>
+struct Converter : DefaultConverter {
+ static QJsonValue toJson(const QByteArray& value) {
+ return QString::fromLocal8Bit(value.toBase64());
+ }
+ static QByteArray fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
+ const auto s = Converter::fromJson(json, errors);
+ return QByteArray::fromBase64(s.toLocal8Bit());
+ }
+};
+
+#endif // CONVERTER_H
diff --git a/include/core/fieldmanager.h b/include/core/fieldmanager.h
new file mode 100644
index 00000000..ed2fcccd
--- /dev/null
+++ b/include/core/fieldmanager.h
@@ -0,0 +1,167 @@
+#include "converter.h"
+
+// A FieldInterface provides a simple interface for converting a field to/from JSON.
+// It's constructed with a pointer to some data, and has two functions:
+// - 'get' returns the pointed-to data, converted to JSON
+// - 'set' assigns the pointed-to data to a given QJsonValue, with appropriate conversion.
+// Returns any errors that occur during conversion/assignment.
+//
+// A FieldInterface is normally constructed using the 'makeFieldInterface' function:
+// Example:
+// int someField = 0;
+// FieldInterface* fi = makeFieldInterface(&someField);
+// fi->set(QJsonValue("5")); // someField is now 5
+//
+// There are additional implementations of 'makeFieldInterface' that let you specify what valid values are.
+// Example:
+// int someField = 0;
+// int min = 1, max = 4;
+// FieldInterface* fi = makeFieldInterface(&someField, min, max);
+// fi->set(QJsonValue("5")); // someField is now 4 (defaults to closest bound), error messages returned
+// or
+// QString someField = "";
+// QList options = {"hello","hi there"};
+// FieldInterface* fi = makeFieldInterface(&someField, options);
+// fi->set(QJsonValue("5")); // someField is now "hello" (defaults to first element), error messages returned
+
+// Base class lets us use the interface without any type information.
+class FieldInterface {
+public:
+ FieldInterface(){};
+ virtual ~FieldInterface() {};
+ virtual QJsonValue get() const = 0;
+ virtual QStringList set(const QJsonValue& json) const = 0;
+};
+
+template
+class BasicFieldInterface : public FieldInterface {
+public:
+ BasicFieldInterface(T* field) : m_field(field) {
+ Q_ASSERT(m_field);
+ };
+ virtual ~BasicFieldInterface() {};
+ virtual QJsonValue get() const override {return Converter::toJson(*m_field);}
+ virtual QStringList set(const QJsonValue& json) const override {
+ QStringList errors;
+ auto value = Converter::fromJson(json, &errors);
+ if (errors.isEmpty()) *m_field = value; // Don't bother changing the value if conversion failed
+ return errors;
+ }
+protected:
+ T* m_field;
+};
+
+template
+static FieldInterface* makeFieldInterface(T* field) {
+ return new BasicFieldInterface(field);
+}
+
+// Create a regular FieldInterface, but override 'set' to use the given min/max.
+template
+static FieldInterface* makeFieldInterface(T* field, const T& min, const T& max) {
+ class BoundedFieldInterface : public BasicFieldInterface {
+ public:
+ BoundedFieldInterface(T* field, const T& min, const T& max)
+ : BasicFieldInterface(field), m_min(min), m_max(max) {};
+ virtual ~BoundedFieldInterface() {};
+ virtual QStringList set(const QJsonValue& json) const override {
+ QStringList errors;
+ auto value = Converter::fromJson(json, &errors);
+ if (errors.isEmpty()) { // Don't bother changing the value if conversion failed
+ value = Converter::clamp(value, m_min, m_max, &errors);
+ *this->m_field = value;
+ }
+ return errors;
+ }
+ private:
+ const T m_min;
+ const T m_max;
+ };
+ return new BoundedFieldInterface(field, min, max);
+}
+
+// Create a regular FieldInterface, but override 'set' to use the given 'acceptableValues'.
+template
+static FieldInterface* makeFieldInterface(T* field, const QList& acceptableValues) {
+ Q_ASSERT(!acceptableValues.isEmpty());
+ class BoundedFieldInterface : public BasicFieldInterface {
+ public:
+ BoundedFieldInterface(T* field, const QList& acceptableValues)
+ : BasicFieldInterface(field),
+ m_acceptableValues(acceptableValues.begin(), acceptableValues.end()),
+ m_defaultValue(acceptableValues.first()) {};
+ virtual ~BoundedFieldInterface() {};
+ virtual QStringList set(const QJsonValue& json) const override {
+ QStringList errors;
+ auto value = Converter::fromJson(json, &errors);
+ if (errors.isEmpty()) {
+ if (!m_acceptableValues.contains(value)) {
+ value = m_defaultValue;
+ errors.append("Invalid value.");
+ }
+ *this->m_field = value;
+ }
+ return errors;
+ }
+ private:
+ // The order of the list only matters for determining the default value,
+ // so save that separately and convert the list to a set for better lookup speed.
+ const QSet m_acceptableValues;
+ const T m_defaultValue;
+ };
+ return new BoundedFieldInterface(field, acceptableValues);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// FieldManager manages a QHash mapping string keys to FieldInterfaces.
+// This makes it easy to map many fields to/from JSON without explicitly serializing anything.
+
+class FieldManager {
+public:
+ ~FieldManager(){ clear(); }
+
+ QStringList setField(const QString& key, const QJsonValue& value) const {
+ auto it = m_fields.find(key);
+ return (it != m_fields.end()) ? it.value()->set(value) : QStringList();
+ }
+
+ QJsonValue getField(const QString& key) const {
+ auto it = m_fields.find(key);
+ return (it != m_fields.end()) ? it.value()->get() : QJsonValue();
+ }
+
+ QJsonObject getFields() const {
+ QJsonObject obj;
+ for (auto it = m_fields.constBegin(); it != m_fields.constEnd(); it++) {
+ obj[it.key()] = it.value()->get();
+ }
+ return obj;
+ }
+
+ void clear() {
+ qDeleteAll(m_fields);
+ m_fields.clear();
+ }
+
+ bool hasField(const QString& key) const {return m_fields.contains(key);}
+
+ template
+ void addField(T* field, const QString& key) {
+ Q_ASSERT(!m_fields.contains(key));
+ m_fields.insert(key, makeFieldInterface(field));
+ }
+ template
+ void addField(T* field, const QString& key, const T& min, const T& max) {
+ Q_ASSERT(!m_fields.contains(key));
+ m_fields.insert(key, makeFieldInterface(field, min, max));
+ }
+ template
+ void addField(T* field, const QString& key, const QList& acceptableValues) {
+ Q_ASSERT(!m_fields.contains(key));
+ m_fields.insert(key, makeFieldInterface(field, acceptableValues));
+ }
+
+private:
+ QHash m_fields;
+};
diff --git a/include/core/maplayout.h b/include/core/maplayout.h
index 3095574b..1e9f6030 100644
--- a/include/core/maplayout.h
+++ b/include/core/maplayout.h
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
class Map;
class LayoutPixmapItem;
diff --git a/include/core/metatile.h b/include/core/metatile.h
index eba4afed..250e3fa4 100644
--- a/include/core/metatile.h
+++ b/include/core/metatile.h
@@ -3,11 +3,13 @@
#define METATILE_H
#include "tile.h"
-#include "config.h"
+#include "basegame.h"
#include "bitpacker.h"
#include
#include
#include
+#include
+#include
class Project;
@@ -41,7 +43,7 @@ public:
uint32_t getAttributes() const;
uint32_t getAttribute(Metatile::Attr attr) const { return this->attributes.value(attr, 0); }
void setAttributes(uint32_t data);
- void setAttributes(uint32_t data, BaseGameVersion version);
+ void setAttributes(uint32_t data, BaseGame::Version version);
void setAttribute(Metatile::Attr attr, uint32_t value);
// For convenience
@@ -56,17 +58,18 @@ public:
static int getIndexInTileset(int);
static QPoint coordFromPixmapCoord(const QPointF &pixelCoord);
- static uint32_t getDefaultAttributesMask(BaseGameVersion version, Metatile::Attr attr);
+ static uint32_t getDefaultAttributesMask(BaseGame::Version version, Metatile::Attr attr);
static uint32_t getMaxAttributesMask();
- static int getDefaultAttributesSize(BaseGameVersion version);
+ static int getDefaultAttributesSize(BaseGame::Version version);
static void setLayout(Project*);
static QString getMetatileIdString(uint16_t metatileId);
static QString getMetatileIdStrings(const QList &metatileIds);
static QString getLayerName(int layerNum);
-
+ static int numLayers();
static constexpr int tileWidth() { return 2; }
static constexpr int tileHeight() { return 2; }
static constexpr int tilesPerLayer() { return Metatile::tileWidth() * Metatile::tileHeight(); }
+ static int maxTiles() { return Metatile::numLayers() * Metatile::tilesPerLayer(); }
static constexpr int pixelWidth() { return Metatile::tileWidth() * Tile::pixelWidth(); }
static constexpr int pixelHeight() { return Metatile::tileHeight() * Tile::pixelHeight(); }
static constexpr QSize pixelSize() { return QSize(pixelWidth(), pixelHeight()); }
diff --git a/include/core/orderedset.h b/include/core/orderedset.h
new file mode 100644
index 00000000..8f5bb185
--- /dev/null
+++ b/include/core/orderedset.h
@@ -0,0 +1,31 @@
+#pragma once
+#ifndef ORDERED_SET_H
+#define ORDERED_SET_H
+
+#include
+#include
+
+template
+class OrderedSet : public std::set
+{
+ using std::set::set;
+
+public:
+ // Not introduced to std::set until C++20
+#if __cplusplus < 202002L
+ bool contains(const T& value) const {
+ return this->find(value) != this->end();
+ }
+#endif
+ QSet