New JSON config format

This commit is contained in:
GriffinR 2026-02-06 23:47:06 -05:00
parent e3e42f39f0
commit 3571b4ea4b
56 changed files with 2005 additions and 1883 deletions

View File

@ -17,8 +17,19 @@
#include <QColorSpace>
#include <set>
#include "converter.h"
#include "events.h"
#include "gridsettings.h"
#include "scriptsettings.h"
#include "log.h"
#include "basegame.h"
#include "orderedset.h"
#include "block.h"
// TODO: Go through and re-test the default window geometries.
// TODO: Value validation? Make sure anything no longer validated can't cause problems.
// TODO: Documentation
// TODO: File splits
extern const QVersionNumber porymapVersion;
@ -26,8 +37,6 @@ extern const QVersionNumber porymapVersion;
#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,
@ -37,159 +46,241 @@ enum ScriptAutocompleteMode {
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)
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<QString, QString> 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);
// Writes the contents of the config to disk.
// Returns true if saving was successful, false otherwise.
virtual bool save();
// Loads the contents of the config from disk.
// The file to load from depends on the file name given to
// the constructor, and the root given to setRoot, if any.
// A successful load includes initializing an empty or non-existing file.
virtual bool load();
virtual QJsonObject toJson() const;
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(); }
struct FieldManager {
QJsonValue (*get)(const KeyValueConfigBase*);
QStringList (*set)(KeyValueConfigBase*, const QJsonValue&);
};
template <typename ConfigType, auto Member>
static FieldManager makeFieldManager() {
// Deduce the type from the pointer to the member variable.
using T = std::remove_reference_t<decltype(std::declval<ConfigType>().*Member)>;
return FieldManager{
.get = [](const KeyValueConfigBase* base) -> QJsonValue {
auto c = static_cast<const ConfigType*>(base);
return Converter<T>::toJson(c->*Member);
},
.set = [](KeyValueConfigBase* base, const QJsonValue& json) {
QStringList errors;
const T value = Converter<T>::fromJson(json, &errors);
if (errors.isEmpty()) static_cast<ConfigType*>(base)->*Member = value;
return errors;
}
};
}
virtual const QHash<QString,FieldManager>& registeredFields() const {
static QHash<QString,FieldManager> empty;
return empty;
}
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();
// TODO: Make this setting accessible somewhere
bool m_saveAllFields = true;
};
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<QString, QByteArray> getMainGeometry();
QMap<QString, QByteArray> getTilesetEditorGeometry();
QMap<QString, QByteArray> getPaletteEditorGeometry();
QMap<QString, QByteArray> getRegionMapEditorGeometry();
QMap<QString, QByteArray> getProjectSettingsEditorGeometry();
QMap<QString, QByteArray> getCustomScriptsEditorGeometry();
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;
bool projectManuallyClosed;
int mapListTab;
bool mapListEditGroupsEnabled;
QMap<int, bool> 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;
bool reopenOnLaunch = true;
bool projectManuallyClosed = false;
int mapListTab = 0;
bool mapListEditGroupsEnabled = false;
OrderedSet<int> 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;
int projectSettingsTab;
ScriptAutocompleteMode scriptAutocompleteMode;
bool warpBehaviorWarningDisabled;
bool eventDeleteWarningDisabled;
bool eventOverlayEnabled;
bool checkForUpdates;
bool showProjectLoadingScreen;
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<QUrl, QDateTime> rateLimitTimes;
QGraphicsPixmapItem::ShapeMode eventSelectionShapeMode;
QByteArray wildMonChartGeometry;
QByteArray newMapDialogGeometry;
QByteArray newLayoutDialogGeometry;
bool shownInGameReloadMessage;
QGraphicsPixmapItem::ShapeMode eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape;
bool shownInGameReloadMessage = false;
GridSettings gridSettings;
// Prefer over QSet to prevent shuffling elements when writing the config file.
std::set<LogType> statusBarLogTypes;
OrderedSet<LogType> statusBarLogTypes = { LogType::LOG_ERROR, LogType::LOG_WARN };
QFont applicationFont;
QFont mapListFont;
int imageExportColorSpaceId;
#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.
int imageExportColorSpaceId = static_cast<int>(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.
int imageExportColorSpaceId = 0;
#endif
QMap<QString,QString> trustedScriptHashes;
#define REGISTER(key, member) {QStringLiteral(key), makeFieldManager<PorymapConfig, &PorymapConfig::member>()}
const QHash<QString,FieldManager>& registeredFields() const override {
static const QHash<QString,FieldManager> fields = {
REGISTER("reopen_on_launch", reopenOnLaunch),
REGISTER("project_manually_closed", projectManuallyClosed),
REGISTER("map_list_tab", mapListTab),
REGISTER("map_list_edit_groups_enabled", mapListEditGroupsEnabled),
REGISTER("map_list_tabs_hiding_empty_folders", mapListTabsHidingEmptyFolders),
REGISTER("map_list_layouts_sorted", mapListLayoutsSorted),
REGISTER("map_list_locations_sorted", mapListLocationsSorted),
REGISTER("pretty_cursors", prettyCursors),
REGISTER("mirror_connecting_maps", mirrorConnectingMaps),
REGISTER("show_dive_emerge_maps", showDiveEmergeMaps),
REGISTER("dive_emerge_map_opacity", diveEmergeMapOpacity),
REGISTER("dive_map_opacity", diveMapOpacity),
REGISTER("emerge_map_opacity", emergeMapOpacity),
REGISTER("collision_opacity", collisionOpacity),
REGISTER("collision_zoom", collisionZoom),
REGISTER("metatiles_zoom", metatilesZoom),
REGISTER("tileset_editor_metatiles_zoom", tilesetEditorMetatilesZoom),
REGISTER("tileset_editor_tiles_zoom", tilesetEditorTilesZoom),
REGISTER("tileset_editor_layer_orientation", tilesetEditorLayerOrientation),
REGISTER("show_player_view", showPlayerView),
REGISTER("show_cursor_tile", showCursorTile),
REGISTER("show_border", showBorder),
REGISTER("show_grid", showGrid),
REGISTER("show_tileset_editor_metatile_grid", showTilesetEditorMetatileGrid),
REGISTER("show_tileset_editor_layer_grid", showTilesetEditorLayerGrid),
REGISTER("show_tileset_editor_divider", showTilesetEditorDivider),
REGISTER("show_tileset_editor_raw_attributes", showTilesetEditorRawAttributes),
REGISTER("show_palette_editor_unused_colors", showPaletteEditorUnusedColors),
REGISTER("monitor_files", monitorFiles),
REGISTER("tileset_checkerboard_fill", tilesetCheckerboardFill),
REGISTER("new_map_header_section_expanded", newMapHeaderSectionExpanded),
REGISTER("display_ids_hexadecimal", displayIdsHexadecimal),
REGISTER("theme", theme),
REGISTER("wild_mon_chart_theme", wildMonChartTheme),
REGISTER("text_editor_open_folder", textEditorOpenFolder),
REGISTER("text_editor_goto_line", textEditorGotoLine),
REGISTER("palette_editor_bit_depth", paletteEditorBitDepth),
REGISTER("project_settings_tab", projectSettingsTab),
REGISTER("script_autocomplete_mode", scriptAutocompleteMode),
REGISTER("warp_behavior_warning_disabled", warpBehaviorWarningDisabled),
REGISTER("event_delete_warning_disabled", eventDeleteWarningDisabled),
REGISTER("event_overlay_enabled", eventOverlayEnabled),
REGISTER("check_for_updates", checkForUpdates),
REGISTER("show_project_loading_screen", showProjectLoadingScreen),
REGISTER("last_update_check_time", lastUpdateCheckTime),
REGISTER("last_update_check_version", lastUpdateCheckVersion),
REGISTER("rate_limit_times", rateLimitTimes),
REGISTER("event_selection_shape_mode", eventSelectionShapeMode),
REGISTER("shown_in_game_reload_message", shownInGameReloadMessage),
REGISTER("map_grid", gridSettings),
REGISTER("status_bar_log_types", statusBarLogTypes),
REGISTER("application_font", applicationFont),
REGISTER("map_list_font", mapListFont),
REGISTER("image_export_color_space_id", imageExportColorSpaceId),
REGISTER("trusted_script_hashes", trustedScriptHashes),
REGISTER("recent_projects", recentProjects),
REGISTER("geometry", savedGeometryMap),
REGISTER("geometry_version", geometryVersion),
};
return fields;
};
#undef REGISTER
protected:
virtual void parseConfigKeyValue(QString key, QString value) override;
virtual QMap<QString, QString> getKeyValueMap() override;
virtual void init() override {};
virtual void setUnreadKeys() override {};
virtual bool parseLegacyKeyValue(const QString& key, const QString& value) override;
virtual QJsonObject getDefaultJson() const 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;
QMap<QString, QByteArray> savedGeometryMap;
int geometryVersion = 0;
};
extern PorymapConfig porymapConfig;
enum BaseGameVersion {
none,
pokeruby,
pokefirered,
pokeemerald,
};
enum ProjectIdentifier {
symbol_facing_directions,
symbol_obj_event_gfx_pointers,
@ -301,134 +392,154 @@ enum ProjectFilePath {
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();
ProjectConfig(const QString& root = QString()) : KeyValueConfigBase(QStringLiteral("porymap.project.json")) {
setRoot(root);
}
ProjectConfig(BaseGame::Version version, const QString& root = QString()) : ProjectConfig(root) {
setVersionSpecificDefaults(version);
}
static const QMap<ProjectIdentifier, QPair<QString, QString>> defaultIdentifiers;
static const QMap<ProjectFilePath, QPair<QString, QString>> 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);
virtual bool save() override;
virtual void loadFromJson(const QJsonObject& obj) override;
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<QString, QString> getPokemonIconPaths();
void setVersionSpecificDefaults(BaseGame::Version baseGameVersion);
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;
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<ProjectIdentifier, QPair<QString, QString>> defaultIdentifiers;
static const QMap<ProjectFilePath, QPair<QString, QString>> 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<uint16_t> newMapBorderMetatileIds;
QString defaultPrimaryTileset;
QString defaultPrimaryTileset = QStringLiteral("gTileset_General");
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;
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;
QMargins playerViewDistance;
QList<uint32_t> warpBehaviors;
int maxEventsPerGroup;
int forcedMajorVersion;
int metatileSelectorWidth;
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<uint32_t> warpBehaviors;
int maxEventsPerGroup = 255;
int metatileSelectorWidth = 8;
QStringList globalConstantsFilepaths;
QMap<QString,QString> globalConstants;
protected:
virtual void parseConfigKeyValue(QString key, QString value) override;
virtual QMap<QString, QString> getKeyValueMap() override;
virtual void init() override;
virtual void setUnreadKeys() override;
private:
QStringList readKeys;
QMap<ProjectIdentifier, QString> identifiers;
QMap<ProjectFilePath, QString> filePaths;
QList<ScriptSettings> customScripts;
QMap<Event::Group, QString> eventIconPaths;
QMap<QString, QString> pokemonIconPaths;
QVersionNumber minimumVersion;
#define REGISTER(key, member) {QStringLiteral(key), makeFieldManager<ProjectConfig, &ProjectConfig::member>()}
const QHash<QString,FieldManager>& registeredFields() const override {
static const QHash<QString,FieldManager> fields = {
REGISTER("base_game_version", baseGameVersion),
REGISTER("use_poryscript", usePoryScript),
REGISTER("use_custom_border_size", useCustomBorderSize),
REGISTER("enable_event_weather_trigger", eventWeatherTriggerEnabled),
REGISTER("enable_event_secret_base", eventSecretBaseEnabled),
REGISTER("enable_hidden_item_quantity", hiddenItemQuantityEnabled),
REGISTER("enable_hidden_item_requires_itemfinder", hiddenItemRequiresItemfinderEnabled),
REGISTER("enable_heal_location_respawn_data", healLocationRespawnDataEnabled),
REGISTER("enable_event_clone_object", eventCloneObjectEnabled),
REGISTER("enable_floor_number", floorNumberEnabled),
REGISTER("create_map_text_file", createMapTextFileEnabled),
REGISTER("enable_triple_layer_metatiles", tripleLayerMetatilesEnabled),
REGISTER("default_metatile_id", defaultMetatileId),
REGISTER("default_elevation", defaultElevation),
REGISTER("default_collision", defaultCollision),
REGISTER("default_map_size", defaultMapSize),
REGISTER("new_map_border_metatiles", newMapBorderMetatileIds),
REGISTER("default_primary_tileset", defaultPrimaryTileset),
REGISTER("default_secondary_tileset", defaultSecondaryTileset),
REGISTER("tilesets_have_callback", tilesetsHaveCallback),
REGISTER("tilesets_have_is_compressed", tilesetsHaveIsCompressed),
REGISTER("transparency_color", transparencyColor),
REGISTER("preserve_matching_only_data", preserveMatchingOnlyData),
REGISTER("metatile_attributes_size", metatileAttributesSize),
REGISTER("metatile_behavior_mask", metatileBehaviorMask),
REGISTER("metatile_terrain_type_mask", metatileTerrainTypeMask),
REGISTER("metatile_encounter_type_mask", metatileEncounterTypeMask),
REGISTER("metatile_layer_type_mask", metatileLayerTypeMask),
REGISTER("block_metatile_id_mask", blockMetatileIdMask),
REGISTER("block_collision_mask", blockCollisionMask),
REGISTER("block_elevation_mask", blockElevationMask),
REGISTER("unused_tile_normal", unusedTileNormal),
REGISTER("unused_tile_covered", unusedTileCovered),
REGISTER("unused_tile_split", unusedTileSplit),
REGISTER("enable_map_allow_flags", mapAllowFlagsEnabled),
REGISTER("events_tab_icon_path", eventsTabIconPath),
REGISTER("collision_sheet_path", collisionSheetPath),
REGISTER("collision_sheet_size", collisionSheetSize),
REGISTER("player_view_distance", playerViewDistance),
REGISTER("warp_behaviors", warpBehaviors),
REGISTER("max_events_per_group", maxEventsPerGroup),
REGISTER("metatile_selector_width", metatileSelectorWidth),
REGISTER("global_constants_filepaths", globalConstantsFilepaths),
REGISTER("global_constants", globalConstants),
REGISTER("custom_scripts", customScripts),
REGISTER("event_icon_paths", eventIconPaths),
REGISTER("pokemon_icon_paths", pokemonIconPaths),
REGISTER("minimum_version", minimumVersion),
REGISTER("custom_identifiers", identifiers),
REGISTER("custom_file_paths", filePaths),
};
return fields;
}
#undef REGISTER
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);
QMap<ProjectIdentifier, QString> identifiers;
QMap<ProjectFilePath, QString> filePaths;
};
extern ProjectConfig projectConfig;
@ -436,36 +547,36 @@ 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();
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()
void parseCustomScripts(QString input);
QString outputCustomScripts();
void setCustomScripts(QStringList scripts, QList<bool> enabled);
QStringList getCustomScriptPaths();
QList<bool> getCustomScriptsEnabled();
QString recentMapOrLayout;
bool useEncounterJson;
QString prefabsFilepath;
bool prefabsImportPrompted = false;
bool useEncounterJson = true;
QList<ScriptSettings> customScripts;
protected:
virtual void parseConfigKeyValue(QString key, QString value) override;
virtual QMap<QString, QString> getKeyValueMap() override;
virtual void init() override;
virtual void setUnreadKeys() override;
#ifdef CONFIG_BACKWARDS_COMPATABILITY
friend class ProjectConfig;
#endif
virtual bool parseLegacyKeyValue(const QString& key, const QString& value) override;
virtual QJsonObject getDefaultJson() const override;
private:
QStringList readKeys;
QMap<QString, bool> customScripts;
#define REGISTER(key, member) {QStringLiteral(key), makeFieldManager<UserConfig, &UserConfig::member>()}
const QHash<QString,FieldManager>& registeredFields() const override {
static const QHash<QString,FieldManager> fields = {
REGISTER("recent_map_or_layout", recentMapOrLayout),
REGISTER("prefabs_filepath", prefabsFilepath),
REGISTER("prefabs_import_prompted", prefabsImportPrompted),
REGISTER("use_encounter_json", useEncounterJson),
REGISTER("custom_scripts", customScripts),
};
return fields;
}
#undef REGISTER
};
extern UserConfig userConfig;
@ -476,26 +587,24 @@ class Shortcut;
class ShortcutsConfig : public KeyValueConfigBase
{
public:
ShortcutsConfig();
virtual void reset() override {
ShortcutsConfig() : KeyValueConfigBase(QStringLiteral("shortcuts.json")) {
setRoot(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
user_shortcuts.clear();
}
virtual QJsonObject toJson() const 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);
void setDefaultShortcuts(const QObjectList& objects);
QList<QKeySequence> defaultShortcuts(const QObject *object) const;
void setUserShortcuts(const QObjectList &objects);
void setUserShortcuts(const QMultiMap<const QObject *, QKeySequence> &objects_keySequences);
void setUserShortcuts(const QObjectList& objects);
void setUserShortcuts(const QMultiMap<const QObject *, QKeySequence>& objects_keySequences);
QList<QKeySequence> userShortcuts(const QObject *object) const;
protected:
virtual void parseConfigKeyValue(QString key, QString value) override;
virtual QMap<QString, QString> getKeyValueMap() override;
virtual void init() override { };
virtual void setUnreadKeys() override { };
virtual bool parseLegacyKeyValue(const QString& key, const QString& value) override;
virtual QJsonObject getDefaultJson() const override;
private:
QMultiMap<QString, QKeySequence> user_shortcuts;
@ -509,11 +618,11 @@ private:
QString cfgKey(const QObject *object) const;
QList<QKeySequence> currentShortcuts(const QObject *object) const;
void storeShortcutsFromList(StoreType storeType, const QObjectList &objects);
void storeShortcutsFromList(StoreType storeType, const QObjectList& objects);
void storeShortcuts(
StoreType storeType,
const QString &cfgKey,
const QList<QKeySequence> &keySequences);
const QString& cfgKey,
const QList<QKeySequence>& keySequences);
};
extern ShortcutsConfig shortcutsConfig;

22
include/core/basegame.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#ifndef BASEGAMEVERSION_H
#define BASEGAMEVERSION_H
#include <QString>
#include <QIcon>
namespace BaseGame {
enum Version {
none, // TODO: Go through and make sure this is a valid state
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

View File

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

342
include/core/converter.h Normal file
View File

@ -0,0 +1,342 @@
#pragma once
#ifndef CONVERTER_H
#define CONVERTER_H
#include <QJsonValue>
#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<QSize>::toJson(size);
QSize sameSize = Converter<QSize>::fromJson(json);
## Adding a new conversion ##
To add a new type conversion, add a new 'Converter' template:
template <>
struct Converter<NewType> : DefaultConverter<NewType> {
// 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.
*/
template <typename T>
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<T>()) {
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<T>();
}
// Default to identity
static QString toString(const T& value) {return value;}
static T fromString(const QString& string, QStringList* = nullptr) {return string;}
};
template <typename T, typename Enable = void>
struct Converter : DefaultConverter<T> {};
// 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 <typename T>
struct DefaultStringConverter : DefaultConverter<T> {
static QJsonValue toJson(const T& value) {
return Converter<QString>::toJson(Converter<T>::toString(value));
}
static T fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto string = Converter<QString>::fromJson(json, errors);
return Converter<T>::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<QUrl> : DefaultStringConverter<QUrl> {};
template <>
struct Converter<QKeySequence> : DefaultStringConverter<QKeySequence> {};
template <>
struct Converter<uint32_t> : DefaultConverter<uint32_t> {
// Constructing a QJsonValue from uint32_t is ambiguous, so we need an explicit cast.
static QJsonValue toJson(uint32_t value) {
return QJsonValue{static_cast<qint64>(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 <typename T>
struct Converter<T, std::enable_if_t<std::is_enum_v<T>>> : DefaultStringConverter<T> {
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<T>(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<T>(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<T>::fromString(json.toString());
auto value = Converter<int>::fromJson(json, errors);
auto e = magic_enum::enum_cast<T>(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<T>(0);
}
return e.value();
}
};
template <>
struct Converter<QVersionNumber> : DefaultStringConverter<QVersionNumber> {
static QVersionNumber fromString(const QString& string, QStringList* = nullptr) {
return QVersionNumber::fromString(string);
}
};
template <>
struct Converter<QDateTime> : DefaultStringConverter<QDateTime> {
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<QColor> : DefaultStringConverter<QColor> {
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<QFont> : DefaultStringConverter<QFont> {
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<BaseGame::Version> : DefaultStringConverter<BaseGame::Version> {
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 <typename T>
struct Converter<QList<T>> : DefaultConverter<QList<T>> {
static QJsonValue toJson(const QList<T>& list) {
QJsonArray arr;
for (auto& elem : list) arr.append(Converter<T>::toJson(elem));
return arr;
}
static QList<T> fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto arr = Converter<QJsonArray>::fromJson(json, errors);
QList<T> list;
for (auto& elem : arr) list.append(Converter<T>::fromJson(elem, errors));
return list;
}
};
template <typename K, typename V>
struct Converter<QMap<K,V>> : DefaultConverter<QMap<K,V>> {
static QJsonObject toJson(const QMap<K,V>& map) {
QJsonObject obj;
for (auto it = map.begin(); it != map.end(); it++) {
const QString key = Converter<K>::toString(it.key());
obj[key] = Converter<V>::toJson(it.value());
}
return obj;
}
static QMap<K,V> fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
QMap<K,V> map;
for (auto it = obj.begin(); it != obj.end(); it++) {
const auto key = Converter<K>::fromString(it.key(), errors);
map.insert(key, Converter<V>::fromJson(it.value(), errors));
}
return map;
}
};
template <typename K, typename V>
struct Converter<QMultiMap<K,V>> : DefaultConverter<QMultiMap<K,V>> {
static QJsonObject toJson(const QMultiMap<K,V>& map) {
QJsonObject obj;
for (const auto& uniqueKey : map.uniqueKeys()) {
const QString key = Converter<K>::toString(uniqueKey);
obj[key] = Converter<QList<V>>::toJson(map.values(uniqueKey));
}
return obj;
}
static QMultiMap<K,V> fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
QMultiMap<K,V> map;
for (auto it = obj.begin(); it != obj.end(); it++) {
const auto key = Converter<K>::fromString(it.key(), errors);
const auto values = Converter<QList<V>>::fromJson(it.value(), errors);
for (const auto& value : values) map.insert(key, value);
}
return map;
}
};
template <typename T>
struct Converter<OrderedSet<T>> : DefaultConverter<OrderedSet<T>> {
static QJsonValue toJson(const OrderedSet<T>& set) {
QJsonArray arr;
for (auto& elem : set) arr.append(Converter<T>::toJson(elem));
return arr;
}
static OrderedSet<T> fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto arr = Converter<QJsonArray>::fromJson(json, errors);
OrderedSet<T> set;
for (auto& elem : arr) set.insert(Converter<T>::fromJson(elem, errors));
return set;
}
};
template <>
struct Converter<QSize> : DefaultConverter<QSize> {
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<QJsonObject>::fromJson(json, errors);
QSize size;
size.setWidth(obj.value("width").toInt());
size.setHeight(obj.value("height").toInt());
return size;
}
};
template <>
struct Converter<QMargins> : DefaultConverter<QMargins> {
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<QJsonObject>::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;
}
};
template <>
struct Converter<ScriptSettings> : DefaultConverter<ScriptSettings> {
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<QJsonObject>::fromJson(json, errors);
ScriptSettings settings;
settings.path = obj.value("path").toString();
settings.enabled = obj.value("enabled").toBool();
return settings;
}
};
template <>
struct Converter<GridSettings> : DefaultConverter<GridSettings> {
static QJsonValue toJson(const GridSettings& value) {
return value.toJson();
}
static GridSettings fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
return GridSettings::fromJson(obj);
}
};
template <>
struct Converter<QByteArray> : DefaultConverter<QByteArray> {
static QJsonValue toJson(const QByteArray& value) {
return QString::fromLocal8Bit(value.toBase64());
}
static QByteArray fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
const auto s = Converter<QString>::fromJson(json, errors);
return QByteArray::fromBase64(s.toLocal8Bit());
}
};
#endif // CONVERTER_H

View File

@ -8,6 +8,7 @@
#include <QPixmap>
#include <QString>
#include <QUndoStack>
#include <QJsonObject>
class Map;
class LayoutPixmapItem;

View File

@ -3,11 +3,13 @@
#define METATILE_H
#include "tile.h"
#include "config.h"
#include "basegame.h"
#include "bitpacker.h"
#include <QImage>
#include <QPoint>
#include <QString>
#include <QMap>
#include <QSet>
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<uint16_t> &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()); }

30
include/core/orderedset.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#ifndef ORDERED_SET_H
#define ORDERED_SET_H
#include <QJsonArray>
template <typename T>
class OrderedSet : public std::set<T>
{
using std::set<T>::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<T> toQSet() const {
return QSet<T>(this->begin(), this->end());
}
static QSet<T> fromQSet(const QSet<T>& set) {
return OrderedSet<T>(set.begin(), set.end());
}
bool isEmpty() const {
return this->empty();
}
};
#endif // ORDERED_SET_H

View File

@ -5,6 +5,7 @@
#include "map.h"
#include "tilemaptileselector.h"
#include "history.h"
#include "config.h"
#include <QStringList>
#include <QString>

View File

@ -0,0 +1,27 @@
#pragma once
#ifndef SCRIPTSETTINGS_H
#define SCRIPTSETTINGS_H
#include <QString>
#include <QList>
// Holds the basic user-provided information about a plug-in script.
struct ScriptSettings {
QString path;
bool enabled = true;
// Scripts can either by specific to the project, or specific to the user.
// This allows projects to send scripts downstream to their users,
// while still allowing them to use their own personal scripts.
bool userOnly = true;
static QStringList filter(const QList<ScriptSettings>& scripts) {
QStringList paths;
for (auto& script : scripts) {
if (script.enabled) paths.append(script.path);
}
return paths;
}
};
#endif // SCRIPTSETTINGS_H

View File

@ -5,9 +5,6 @@
#include <QObject>
#include <QSize>
// TODO: Replace once config refactoring is complete.
extern bool ConfigDisplayIdsHexadecimal;
class Tile
{
public:
@ -30,7 +27,8 @@ public:
QString toString() const;
static QString getTileIdString(uint16_t tileId);
static const uint16_t maxValue;
// Upper limit for raw value (i.e., uint16_t max).
static constexpr uint16_t MaxValue = 0xFFFF;
static constexpr int pixelWidth() { return 8; }
static constexpr int pixelHeight() { return 8; }

View File

@ -21,6 +21,19 @@ namespace Util {
QColorSpace toColorSpace(int colorSpaceInt);
QString mkpath(const QString& dirPath);
QString getFileHash(const QString &filepath);
// Given a QMap<T,QString>, erases all entries with empty strings.
// Returns the number of entries erased.
template <typename T>
int removeEmptyStrings(QMap<T,QString> *map) {
if (!map) return 0;
int numRemoved = 0;
for (auto it = map->begin(); it != map->end();) {
if (it.value().isEmpty()) it = map->erase(it);
else {it++; numRemoved++;}
}
return numRemoved;
}
}
#endif // UTILITY_H

View File

@ -440,7 +440,7 @@ private:
void initShortcuts();
void initExtraShortcuts();
void loadUserSettings();
void restoreWindowState();
void resizeWithinScreen();
void setTheme(QString);
void updateTilesetEditor();
Event::Group getEventGroupFromTabWidget(QWidget *tab);

View File

@ -105,7 +105,7 @@ public:
void clearHealLocations();
bool sanityCheck();
int getSupportedMajorVersion(QString *errorOut = nullptr);
QVersionNumber getMinimumVersion(QString *errorOut = nullptr) const;
bool load();
QMap<QString, Tileset*> tilesetCache;

View File

@ -50,7 +50,7 @@ public:
Q_INVOKABLE bool getBorderVisibility();
Q_INVOKABLE void setSmartPathsEnabled(bool visible);
Q_INVOKABLE bool getSmartPathsEnabled();
Q_INVOKABLE QList<QString> getCustomScripts();
static Q_INVOKABLE QList<QString> getCustomScripts();
Q_INVOKABLE QList<int> getMetatileLayerOrder();
Q_INVOKABLE void setMetatileLayerOrder(const QList<int> &order);
Q_INVOKABLE QList<float> getMetatileLayerOpacity();

View File

@ -1,56 +0,0 @@
#ifndef CITYMAPPIXMAPITEM_H
#define CITYMAPPIXMAPITEM_H
#include "tilemaptileselector.h"
#include <QGraphicsPixmapItem>
#include <QByteArray>
#include <QVector>
class CityMapPixmapItem : public QObject, public QGraphicsPixmapItem {
Q_OBJECT
private:
using QGraphicsPixmapItem::paint;
public:
CityMapPixmapItem(QString fname, TilemapTileSelector *tile_selector) {
this->file = fname;
this->tile_selector = tile_selector;
setAcceptHoverEvents(true);
init();
}
TilemapTileSelector *tile_selector;
QString file;
QByteArray data;
void init();
void save();
void create(QString);
virtual void paint(QGraphicsSceneMouseEvent *);
virtual void draw();
int getIndexAt(int, int);
int width();
int height();
QVector<uint8_t> getTiles();
void setTiles(QVector<uint8_t>);
private:
int width_;
int height_;
signals:
void mouseEvent(QGraphicsSceneMouseEvent *, CityMapPixmapItem *);
void hoveredRegionMapTileChanged(int x, int y);
void hoveredRegionMapTileCleared();
protected:
void mousePressEvent(QGraphicsSceneMouseEvent*);
void mouseMoveEvent(QGraphicsSceneMouseEvent*);
void mouseReleaseEvent(QGraphicsSceneMouseEvent*);
};
#endif // CITYMAPPIXMAPITEM_H

View File

@ -33,7 +33,7 @@ private:
bool hasUnsavedChanges = false;
const QString baseDir;
void displayScript(const QString &filepath, bool enabled);
void displayScript(const ScriptSettings &settings);
void displayNewScript(QString filepath);
QString chooseScript(QString dir);
void removeScript(QListWidgetItem * item);
@ -46,7 +46,6 @@ private:
int prompt(const QString &text, QMessageBox::StandardButton defaultButton);
void save();
void closeEvent(QCloseEvent*);
void restoreWindowState();
void initShortcuts();
QObjectList shortcutableObjects() const;
void openManual();

View File

@ -2,6 +2,7 @@
#define CUSTOMSCRIPTSLISTITEM_H
#include <QFrame>
#include "scriptsettings.h"
namespace Ui {
class CustomScriptsListItem;
@ -13,9 +14,26 @@ class CustomScriptsListItem : public QFrame
public:
explicit CustomScriptsListItem(QWidget *parent = nullptr);
explicit CustomScriptsListItem(const ScriptSettings& settings, QWidget *parent = nullptr);
~CustomScriptsListItem();
public:
void setSettings(const ScriptSettings& settings);
ScriptSettings getSettings() const;
void setPath(const QString& text);
QString path() const;
void setScriptEnabled(bool enabled);
bool scriptEnabled() const;
signals:
void clickedChooseScript();
void clickedEditScript();
void clickedDeleteScript();
void toggledEnable(bool checked);
void pathEdited(const QString& text);
private:
Ui::CustomScriptsListItem *ui;
};

View File

@ -1,5 +1,6 @@
#include <QObject>
#include <QEvent>
#include <QSet>
/// Ctrl+Wheel = zoom
@ -16,7 +17,6 @@ public slots:
};
/// Emits a signal when a window gets activated / regains focus
class ActiveWindowFilter : public QObject {
Q_OBJECT
@ -27,3 +27,15 @@ public:
signals:
void activated();
};
class GeometrySaver : public QObject {
Q_OBJECT
public:
GeometrySaver(QObject *parent, bool enableLogging = true)
: QObject(parent), m_loggingEnabled(enableLogging) {}
bool eventFilter(QObject *obj, QEvent *event) override;
private:
bool m_loggingEnabled = true;
QSet<QObject*> m_wasShown;
};

View File

@ -3,10 +3,12 @@
#include <QDialog>
#include <QAbstractButton>
#include <QJsonObject>
#include "metatile.h"
class GridSettings {
public:
explicit GridSettings() {};
constexpr GridSettings() {};
~GridSettings() {};
enum Style {
@ -17,15 +19,18 @@ public:
Dots,
};
uint width = 16;
uint height = 16;
uint width = Metatile::pixelWidth();
uint height = Metatile::pixelHeight();
int offsetX = 0;
int offsetY = 0;
Style style = Style::Solid;
QColor color = Qt::black;
QColor color = QColorConstants::Black;
QVector<qreal> getHorizontalDashPattern() const { return this->getDashPattern(this->width); }
QVector<qreal> getVerticalDashPattern() const { return this->getDashPattern(this->height); }
QJsonObject toJson() const;
static GridSettings fromJson(const QJsonObject &obj);
static QString getStyleName(Style style);
static GridSettings::Style getStyleFromName(const QString &name);
private:

View File

@ -84,7 +84,12 @@ private:
QImage m_previewImage;
bool m_previewUpdateQueued = false;
QList<int> m_layerOrder;
ProjectConfig m_savedConfig;
struct SavedConfigSettings {
QColor transparencyColor;
uint16_t unusedTileNormal = 0;
uint16_t unusedTileCovered = 0;
uint16_t unusedTileSplit = 0;
} m_savedConfig;
QList<QRadioButton*> m_transparencyButtons;
void populate(const Settings &settings);

View File

@ -54,7 +54,6 @@ private:
void commitEditHistory();
void commitEditHistory(int paletteId);
void updateEditHistoryActions();
void restoreWindowState();
void invalidateCache();
void closeEvent(QCloseEvent*);
void setColorInputTitles(bool show);

View File

@ -25,7 +25,7 @@ public:
void addPrefab(MetatileSelection selection, Layout *layout, QString name);
void updatePrefabUi(QPointer<Layout> layout);
void clearPrefabUi();
bool tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version, QString filepath = "");
bool tryImportDefaultPrefabs(QWidget * parent, BaseGame::Version version, QString filepath = "");
private:
QPointer<MetatileSelector> selector;

View File

@ -40,7 +40,6 @@ private:
void initUi();
void connectSignals();
void restoreWindowState();
void save();
void refresh();
void closeEvent(QCloseEvent*);

View File

@ -2,7 +2,6 @@
#define REGIONMAPEDITOR_H
#include "regionmappixmapitem.h"
#include "citymappixmapitem.h"
#include "regionmaplayoutpixmapitem.h"
#include "regionmapentriespixmapitem.h"
#include "regionmap.h"
@ -118,7 +117,6 @@ private:
void setRegionMap(RegionMap *map);
void setLocations(const QStringList &locations);
void restoreWindowState();
void closeEvent(QCloseEvent* event);
void setTileHFlip(bool enabled);

View File

@ -114,7 +114,6 @@ private:
void initMetatileLayersItem();
void initShortcuts();
void initExtraShortcuts();
void restoreWindowState();
void initMetatileHistory();
void setTilesets(QString primaryTilesetLabel, QString secondaryTilesetLabel);
void reset();

View File

@ -19,8 +19,6 @@ public:
explicit WildMonChart(QWidget *parent, const EncounterTableModel *table);
~WildMonChart();
virtual void closeEvent(QCloseEvent *event) override;
public slots:
void setTable(const EncounterTableModel *table);
void clearTable();

View File

@ -47,8 +47,9 @@ VERSION = 6.3.0
DEFINES += PORYMAP_VERSION=\\\"$$VERSION\\\"
SOURCES += src/core/advancemapparser.cpp \
src/config/legacy.cpp \
src/core/basegame.cpp \
src/core/block.cpp \
src/ui/resizelayoutpopup.cpp \
src/core/bitpacker.cpp \
src/core/blockdata.cpp \
src/core/events.cpp \
@ -113,7 +114,7 @@ SOURCES += src/core/advancemapparser.cpp \
src/ui/layoutpixmapitem.cpp \
src/ui/prefabcreationdialog.cpp \
src/ui/regionmappixmapitem.cpp \
src/ui/citymappixmapitem.cpp \
src/ui/resizelayoutpopup.cpp \
src/ui/mapheaderform.cpp \
src/ui/metatilelayersitem.cpp \
src/ui/metatileselector.cpp \
@ -168,6 +169,7 @@ HEADERS += include/core/advancemapparser.h \
include/core/block.h \
include/core/bitpacker.h \
include/core/blockdata.h \
include/core/converter.h \
include/core/events.h \
include/core/filedialog.h \
include/core/history.h \
@ -233,7 +235,6 @@ HEADERS += include/core/advancemapparser.h \
include/ui/mapview.h \
include/ui/prefabcreationdialog.h \
include/ui/regionmappixmapitem.h \
include/ui/citymappixmapitem.h \
include/ui/colorinputwidget.h \
include/ui/metatilelayersitem.h \
include/ui/metatileselector.h \

File diff suppressed because it is too large Load Diff

423
src/config/legacy.cpp Normal file
View File

@ -0,0 +1,423 @@
#include "config.h"
#include "log.h"
#include "tile.h"
#include "block.h"
#include <QString>
#include <QDir>
#include <QColor>
QString getLegacyFilename(const QString &newFilename) {
static const QMap<QString, QString> map = {
{QStringLiteral("settings.json"), QStringLiteral("porymap.cfg")},
{QStringLiteral("shortcuts.json"), QStringLiteral("porymap.shortcuts.cfg")},
{QStringLiteral("porymap.project.json"), QStringLiteral("porymap.project.cfg")},
{QStringLiteral("porymap.user.json"), QStringLiteral("porymap.user.cfg")},
};
return map.value(newFilename);
}
bool KeyValueConfigBase::loadLegacy() {
const QString oldFilename = getLegacyFilename(filename());
if (oldFilename.isEmpty()) return false;
QDir dir(root());
QFile file(dir.absoluteFilePath(oldFilename));
if (!file.exists() || !file.open(QIODevice::ReadOnly)) return false;
QTextStream in(&file);
static const QRegularExpression re("^(?<key>[^=]+)=(?<value>.*)$");
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
int commentIndex = line.indexOf("#");
if (commentIndex >= 0) {
line = line.left(commentIndex).trimmed();
}
if (line.length() == 0) {
continue;
}
QRegularExpressionMatch match = re.match(line);
if (match.hasMatch()) {
parseLegacyKeyValue(match.captured("key").trimmed(), match.captured("value").trimmed());
}
}
logInfo(QString("Loaded legacy config file '%1'").arg(oldFilename));
// Save before deleting the old config file to ensure no data is lost.
if (save()/* && !file.remove()*/) { // TODO: Once non-legacy loading is complete, we can uncomment this.
//logWarn(QString("Failed to delete old config file '%1'.").arg(oldFilename));
}
return true;
}
bool toBool(const QString &value) {
return (value.toInt(nullptr, 0) != 0);
}
int toInt(const QString &value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0) {
bool ok;
int result = value.toInt(&ok, 0);
if (!ok) result = defaultValue;
return qBound(min, result, max);
}
uint32_t toUInt(const QString &value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0) {
bool ok;
uint32_t result = value.toUInt(&ok, 0);
if (!ok) result = defaultValue;
return qBound(min, result, max);
}
QColor toColor(const QString &value, const QColor &defaultValue = QColor(Qt::black)) {
if (value.isEmpty()) return QColor();
QColor color = QColor("#" + value);
if (!color.isValid()) color = defaultValue;
return color;
}
bool PorymapConfig::parseLegacyKeyValue(const QString &key, const QString &value) {
if (key == "recent_project") {
this->recentProjects = value.split(",", Qt::SkipEmptyParts);
this->recentProjects.removeDuplicates();
} else if (key == "project_manually_closed") {
this->projectManuallyClosed = toBool(value);
} else if (key == "reopen_on_launch") {
this->reopenOnLaunch = toBool(value);
} else if (key == "pretty_cursors") {
this->prettyCursors = toBool(value);
} else if (key == "map_list_tab") {
this->mapListTab = toInt(value, 0, 2, 0);
} else if (key == "map_list_edit_groups_enabled") {
this->mapListEditGroupsEnabled = toBool(value);
} else if (key.startsWith("map_list_hide_empty_enabled/")) {
bool ok;
int tab = key.mid(QStringLiteral("map_list_hide_empty_enabled/").length()).toInt(&ok, 0);
if (ok && toBool(value)) this->mapListTabsHidingEmptyFolders.insert(tab);
} else if (key == "map_list_layouts_sorted") {
this->mapListLayoutsSorted = toBool(value);
} else if (key == "map_list_locations_sorted") {
this->mapListLocationsSorted = toBool(value);
} else if (key == "mirror_connecting_maps") {
this->mirrorConnectingMaps = toBool(value);
} else if (key == "show_dive_emerge_maps") {
this->showDiveEmergeMaps = toBool(value);
} else if (key == "dive_emerge_map_opacity") {
this->diveEmergeMapOpacity = toInt(value, 10, 90, 30);
} else if (key == "dive_map_opacity") {
this->diveMapOpacity = toInt(value, 10, 90, 15);
} else if (key == "emerge_map_opacity") {
this->emergeMapOpacity = toInt(value, 10, 90, 15);
} else if (key == "collision_opacity") {
this->collisionOpacity = toInt(value, 0, 100, 50);
} else if (key == "tileset_editor_geometry") {
} else if (key == "metatiles_zoom") {
this->metatilesZoom = toInt(value, 10, 100, 30);
} else if (key == "collision_zoom") {
this->collisionZoom = toInt(value, 10, 100, 30);
} else if (key == "tileset_editor_metatiles_zoom") {
this->tilesetEditorMetatilesZoom = toInt(value, 10, 100, 30);
} else if (key == "tileset_editor_tiles_zoom") {
this->tilesetEditorTilesZoom = toInt(value, 10, 100, 30);
} else if (key == "tileset_editor_layer_orientation") {
// Being explicit here to avoid casting out-of-range values.
this->tilesetEditorLayerOrientation = (toInt(value) == static_cast<int>(Qt::Horizontal)) ? Qt::Horizontal : Qt::Vertical;
} else if (key == "show_player_view") {
this->showPlayerView = toBool(value);
} else if (key == "show_cursor_tile") {
this->showCursorTile = toBool(value);
} else if (key == "show_border") {
this->showBorder = toBool(value);
} else if (key == "show_grid") {
this->showGrid = toBool(value);
} else if (key == "show_tileset_editor_metatile_grid") {
this->showTilesetEditorMetatileGrid = toBool(value);
} else if (key == "show_tileset_editor_layer_grid") {
this->showTilesetEditorLayerGrid = toBool(value);
} else if (key == "show_tileset_editor_divider") {
this->showTilesetEditorDivider = toBool(value);
} else if (key == "show_tileset_editor_raw_attributes") {
this->showTilesetEditorRawAttributes = toBool(value);
} else if (key == "show_palette_editor_unused_colors") {
this->showPaletteEditorUnusedColors = toBool(value);
} else if (key == "monitor_files") {
this->monitorFiles = toBool(value);
} else if (key == "tileset_checkerboard_fill") {
this->tilesetCheckerboardFill = toBool(value);
} else if (key == "new_map_header_section_expanded") {
this->newMapHeaderSectionExpanded = toBool(value);
} else if (key == "theme") {
this->theme = value;
} else if (key == "wild_mon_chart_theme") {
this->wildMonChartTheme = value;
} else if (key == "text_editor_open_directory") {
this->textEditorOpenFolder = value;
} else if (key == "text_editor_goto_line") {
this->textEditorGotoLine = value;
} else if (key == "palette_editor_bit_depth") {
this->paletteEditorBitDepth = toInt(value, 15, 24, 24);
if (this->paletteEditorBitDepth != 15 && this->paletteEditorBitDepth != 24){
this->paletteEditorBitDepth = 24;
}
} else if (key == "project_settings_tab") {
this->projectSettingsTab = toInt(value, 0);
} else if (key == "load_all_event_scripts") { // Old setting replaced by script_autocomplete_mode
this->scriptAutocompleteMode = toBool(value) ? ScriptAutocompleteMode::All : ScriptAutocompleteMode::MapOnly;
} else if (key == "script_autocomplete_mode") {
this->scriptAutocompleteMode = static_cast<ScriptAutocompleteMode>(toInt(value, ScriptAutocompleteMode::MapOnly, ScriptAutocompleteMode::All));
} else if (key == "warp_behavior_warning_disabled") {
this->warpBehaviorWarningDisabled = toBool(value);
} else if (key == "event_delete_warning_disabled") {
this->eventDeleteWarningDisabled = toBool(value);
} else if (key == "event_overlay_enabled") {
this->eventOverlayEnabled = toBool(value);
} else if (key == "check_for_updates") {
this->checkForUpdates = toBool(value);
} else if (key == "show_project_loading_screen") {
this->showProjectLoadingScreen = toBool(value);
} else if (key == "last_update_check_time") {
this->lastUpdateCheckTime = QDateTime::fromString(value).toLocalTime();
} else if (key == "last_update_check_version") {
auto version = QVersionNumber::fromString(value);
if (version.segmentCount() != 3) {
this->lastUpdateCheckVersion = porymapVersion;
} else {
this->lastUpdateCheckVersion = version;
}
} else if (key.startsWith("rate_limit_time/")) {
static const QRegularExpression regex("\\brate_limit_time/(?<url>.+)");
QRegularExpressionMatch match = regex.match(key);
if (match.hasMatch()) {
this->rateLimitTimes.insert(match.captured("url"), QDateTime::fromString(value).toLocalTime());
}
} else if (key == "event_selection_shape_mode") {
if (value == "mask") {
this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape;
} else if (value == "bounding_rect") {
this->eventSelectionShapeMode = QGraphicsPixmapItem::BoundingRectShape;
}
} else if (key == "shown_in_game_reload_message") {
this->shownInGameReloadMessage = toBool(value);
} else if (key == "grid_width") {
this->gridSettings.width = toUInt(value);
} else if (key == "grid_height") {
this->gridSettings.height = toUInt(value);
} else if (key == "grid_x") {
this->gridSettings.offsetX = toInt(value, 0, 999);
} else if (key == "grid_y") {
this->gridSettings.offsetY = toInt(value, 0, 999);
} else if (key == "grid_style") {
this->gridSettings.style = GridSettings::getStyleFromName(value);
} else if (key == "grid_color") {
this->gridSettings.color = toColor(value);
} else if (key == "status_bar_log_types") {
this->statusBarLogTypes.clear();
auto typeStrings = value.split(",", Qt::SkipEmptyParts);
for (const auto &typeString : typeStrings) {
LogType type = static_cast<LogType>(toInt(typeString, 0, 2));
this->statusBarLogTypes.insert(type);
}
} else if (key == "application_font") {
this->applicationFont = QFont();
this->applicationFont.fromString(value);
} else if (key == "map_list_font") {
this->mapListFont = QFont();
this->mapListFont.fromString(value);
} else if (key == "image_export_color_space_id") {
this->imageExportColorSpaceId = toInt(value, 0, 8);
} else {
return false;
}
return true;
}
bool ProjectConfig::parseLegacyKeyValue(const QString &key, const QString &value_) {
QString value(value_);
if (key == "base_game_version") {
this->baseGameVersion = BaseGame::stringToVersion(value);
} else if (key == "use_poryscript") {
this->usePoryScript = toBool(value);
} else if (key == "use_custom_border_size") {
this->useCustomBorderSize = toBool(value);
} else if (key == "enable_event_weather_trigger") {
this->eventWeatherTriggerEnabled = toBool(value);
} else if (key == "enable_event_secret_base") {
this->eventSecretBaseEnabled = toBool(value);
} else if (key == "enable_hidden_item_quantity") {
this->hiddenItemQuantityEnabled = toBool(value);
} else if (key == "enable_hidden_item_requires_itemfinder") {
this->hiddenItemRequiresItemfinderEnabled = toBool(value);
} else if (key == "enable_heal_location_respawn_data") {
this->healLocationRespawnDataEnabled = toBool(value);
} else if (key == "enable_event_clone_object" || key == "enable_object_event_in_connection") {
this->eventCloneObjectEnabled = toBool(value);
} else if (key == "enable_floor_number") {
this->floorNumberEnabled = toBool(value);
} else if (key == "create_map_text_file") {
this->createMapTextFileEnabled = toBool(value);
} else if (key == "enable_triple_layer_metatiles") {
this->tripleLayerMetatilesEnabled = toBool(value);
} else if (key == "default_metatile") {
this->defaultMetatileId = toUInt(value, 0, Block::MaxValue);
} else if (key == "default_elevation") {
this->defaultElevation = toUInt(value, 0, Block::MaxValue);
} else if (key == "default_collision") {
this->defaultCollision = toUInt(value, 0, Block::MaxValue);
} else if (key == "default_map_width") {
this->defaultMapSize.setWidth(toInt(value, 1));
} else if (key == "default_map_height") {
this->defaultMapSize.setHeight(toInt(value, 1));
} else if (key == "new_map_border_metatiles") {
this->newMapBorderMetatileIds.clear();
QList<QString> metatileIds = value.split(",");
for (int i = 0; i < metatileIds.size(); i++) {
int metatileId = toUInt(metatileIds.at(i), 0, Block::MaxValue);
this->newMapBorderMetatileIds.append(metatileId);
}
} else if (key == "default_primary_tileset") {
this->defaultPrimaryTileset = value;
} else if (key == "default_secondary_tileset") {
this->defaultSecondaryTileset = value;
} else if (key == "metatile_attributes_size") {
int size = toInt(value, 1, 4, 2);
if (!(size & (size - 1))) this->metatileAttributesSize = size;
} else if (key == "metatile_behavior_mask") {
this->metatileBehaviorMask = toUInt(value);
} else if (key == "metatile_terrain_type_mask") {
this->metatileTerrainTypeMask = toUInt(value);
} else if (key == "metatile_encounter_type_mask") {
this->metatileEncounterTypeMask = toUInt(value);
} else if (key == "metatile_layer_type_mask") {
this->metatileLayerTypeMask = toUInt(value);
} else if (key == "block_metatile_id_mask") {
this->blockMetatileIdMask = toUInt(value, 0, Block::MaxValue);
} else if (key == "block_collision_mask") {
this->blockCollisionMask = toUInt(value, 0, Block::MaxValue);
} else if (key == "block_elevation_mask") {
this->blockElevationMask = toUInt(value, 0, Block::MaxValue);
} else if (key == "unused_tile_normal") {
this->unusedTileNormal = toUInt(value, 0, Tile::MaxValue);
} else if (key == "unused_tile_covered") {
this->unusedTileCovered = toUInt(value, 0, Tile::MaxValue);
} else if (key == "unused_tile_split") {
this->unusedTileSplit = toUInt(value, 0, Tile::MaxValue);
} else if (key == "enable_map_allow_flags") {
this->mapAllowFlagsEnabled = toBool(value);
} else if (key.startsWith("path/")) {
auto k = reverseDefaultPaths(key.mid(QStringLiteral("path/").length()));
if (k != static_cast<ProjectFilePath>(-1)) {
this->setFilePath(k, value);
}
} else if (key.startsWith("ident/")) {
auto identifierId = reverseDefaultIdentifier(key.mid(QStringLiteral("ident/").length()));
if (identifierId != static_cast<ProjectIdentifier>(-1)) {
this->setIdentifier(identifierId, value);
}
} else if (key.startsWith("global_constant/")) {
this->globalConstants.insert(key.mid(QStringLiteral("global_constant/").length()), value);
} else if (key == "global_constants_filepaths") {
this->globalConstantsFilepaths = value.split(",", Qt::SkipEmptyParts);
} else if (key == "tilesets_have_callback") {
this->tilesetsHaveCallback = toBool(value);
} else if (key == "tilesets_have_is_compressed") {
this->tilesetsHaveIsCompressed = toBool(value);
} else if (key == "set_transparent_pixels_black") { // Old setting replaced by transparency_color
this->transparencyColor = toBool(value) ? QColor(Qt::black) : QColor();
} else if (key == "transparency_color") {
this->transparencyColor = toColor(value);
} else if (key == "preserve_matching_only_data") {
this->preserveMatchingOnlyData = toBool(value);
} else if (key == "event_icon_path_object") {
this->eventIconPaths[Event::Group::Object] = value;
} else if (key == "event_icon_path_warp") {
this->eventIconPaths[Event::Group::Warp] = value;
} else if (key == "event_icon_path_coord") {
this->eventIconPaths[Event::Group::Coord] = value;
} else if (key == "event_icon_path_bg") {
this->eventIconPaths[Event::Group::Bg] = value;
} else if (key == "event_icon_path_heal") {
this->eventIconPaths[Event::Group::Heal] = value;
} else if (key.startsWith("pokemon_icon_path/")) {
this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()), value);
} else if (key == "events_tab_icon_path") {
this->eventsTabIconPath = value;
} else if (key == "collision_sheet_path") {
this->collisionSheetPath = value;
} else if (key == "collision_sheet_width") {
this->collisionSheetSize.setWidth(toInt(value, 1, Block::MaxValue));
} else if (key == "collision_sheet_height") {
this->collisionSheetSize.setHeight(toInt(value, 1, Block::MaxValue));
} else if (key == "player_view_north") {
this->playerViewDistance.setTop(toInt(value, 0, INT_MAX, GBA_V_DIST_TO_CENTER));
} else if (key == "player_view_south") {
this->playerViewDistance.setBottom(toInt(value, 0, INT_MAX, GBA_V_DIST_TO_CENTER));
} else if (key == "player_view_west") {
this->playerViewDistance.setLeft(toInt(value, 0, INT_MAX, GBA_H_DIST_TO_CENTER));
} else if (key == "player_view_east") {
this->playerViewDistance.setRight(toInt(value, 0, INT_MAX, GBA_H_DIST_TO_CENTER));
} else if (key == "warp_behaviors") {
this->warpBehaviors.clear();
value.remove(" ");
const QStringList behaviorList = value.split(",", Qt::SkipEmptyParts);
for (auto s : behaviorList)
this->warpBehaviors.insert(toUInt(s));
} else if (key == "max_events_per_group") {
this->maxEventsPerGroup = toInt(value, 1, INT_MAX, 255);
} else if (key == "metatile_selector_width") {
this->metatileSelectorWidth = toInt(value, 1, INT_MAX, 8);
} else {
return false;
}
return true;
}
// Read input from the config to get the script paths and whether each is enabled or disbled.
// The format is a comma-separated list of paths. Each path can be followed (before the comma)
// by a :0 or :1 to indicate whether it should be disabled or enabled, respectively. If neither
// follow, it's assumed the script should be enabled.
QList<ScriptSettings> parseCustomScripts(const QString &input) {
QMap<QString,bool> customScripts;
const QList<QString> paths = input.split(",", Qt::SkipEmptyParts);
for (QString path : paths) {
// Read and remove suffix
bool enabled = !path.endsWith(":0");
if (!enabled || path.endsWith(":1"))
path.chop(2);
if (!path.isEmpty()) {
// If a path is repeated only its last instance will be considered.
customScripts.insert(path, enabled);
}
}
QList<ScriptSettings> settingsList;
for (auto it = customScripts.begin(); it != customScripts.end(); it++) {
settingsList.append({
.path = it.key(),
.enabled = it.value(),
.userOnly = true,
});
}
return settingsList;
}
bool UserConfig::parseLegacyKeyValue(const QString &key, const QString &value) {
if (key == "recent_map_or_layout") {
this->recentMapOrLayout = value;
} else if (key == "use_encounter_json") {
this->useEncounterJson = toBool(value);
} else if (key == "custom_scripts") {
this->customScripts = parseCustomScripts(value);
} else {
return false;
}
return true;
}
bool ShortcutsConfig::parseLegacyKeyValue(const QString &key, const QString &value) {
QStringList keySequences = value.split(' ');
for (auto keySequence : keySequences)
user_shortcuts.insert(key, keySequence);
return true;
}

View File

@ -2,6 +2,7 @@
#include "log.h"
#include "project.h"
#include "maplayout.h"
#include "config.h"
Layout *AdvanceMapParser::parseLayout(const QString &filepath, bool *error, const Project *project)
{
@ -118,18 +119,18 @@ QList<Metatile*> AdvanceMapParser::parseMetatiles(const QString &filepath, bool
int projIdOffset = in.length() - 4;
int metatileSize = 16;
BaseGameVersion version;
BaseGame::Version version;
if (in.at(projIdOffset + 0) == 'R'
&& in.at(projIdOffset + 1) == 'S'
&& in.at(projIdOffset + 2) == 'E'
&& in.at(projIdOffset + 3) == ' ') {
// ruby and emerald are handled equally here.
version = BaseGameVersion::pokeemerald;
version = BaseGame::Version::pokeemerald;
} else if (in.at(projIdOffset + 0) == 'F'
&& in.at(projIdOffset + 1) == 'R'
&& in.at(projIdOffset + 2) == 'L'
&& in.at(projIdOffset + 3) == 'G') {
version = BaseGameVersion::pokefirered;
version = BaseGame::Version::pokefirered;
} else {
*error = true;
logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE ' or 'FRLG'."));

63
src/core/basegame.cpp Normal file
View File

@ -0,0 +1,63 @@
#include "basegame.h"
#include <QList>
#include <QMap>
// If a string exclusively contains one version name we assume its identity,
// otherwise we leave it unknown and we'll need the user to tell us the version.
BaseGame::Version BaseGame::stringToVersion(const QString &input_) {
static const QMap<Version, QStringList> versionDetectNames = {
{Version::pokeruby, {"ruby", "sapphire"}},
{Version::pokefirered, {"firered", "leafgreen"}},
{Version::pokeemerald, {"emerald"}},
};
const QString input(input_.toLower());
BaseGame::Version version = BaseGame::Version::none;
for (auto it = versionDetectNames.begin(); it != versionDetectNames.end(); it++) {
// Compare the given string to all the possible names for this game version
for (const auto &name : it.value()) {
if (input.contains(name)) {
if (version != BaseGame::Version::none) {
// The given string matches multiple versions, so we can't be sure which it is.
return BaseGame::Version::none;
}
version = it.key();
break;
}
}
}
// We finished checking the names for each version; the name either matched 1 version or none.
return version;
}
// TODO: Make sure empty string is ok everywhere this is used
QString BaseGame::versionToString(BaseGame::Version version) {
static const QMap<Version, QString> map = {
{Version::pokeruby, "pokeruby"},
{Version::pokefirered, "pokefirered"},
{Version::pokeemerald, "pokeemerald"},
};
return map.value(version);
}
QString BaseGame::getPlayerIconPath(BaseGame::Version version, int character) {
if (version == BaseGame::Version::pokeemerald) {
static const QStringList paths = { QStringLiteral(":/icons/player/brendan_em.ico"),
QStringLiteral(":/icons/player/may_em.ico"), };
return paths.value(character);
} else if (version == BaseGame::Version::pokefirered) {
static const QStringList paths = { QStringLiteral(":/icons/player/red.ico"),
QStringLiteral(":/icons/player/green.ico"), };
return paths.value(character);
} else if (version == BaseGame::Version::pokeruby) {
static const QStringList paths = { QStringLiteral(":/icons/player/brendan_rs.ico"),
QStringLiteral(":/icons/player/may_rs.ico"), };
return paths.value(character);
}
return QString();
}
QIcon BaseGame::getPlayerIcon(BaseGame::Version baseGameVersion, int character) {
return QIcon(getPlayerIconPath(baseGameVersion, character));
}

View File

@ -2,12 +2,10 @@
#include "bitpacker.h"
#include "config.h"
// Upper limit for metatile ID, collision, and elevation masks. Used externally.
const uint16_t Block::maxValue = 0xFFFF;
static BitPacker bitsMetatileId = BitPacker(0x3FF);
static BitPacker bitsCollision = BitPacker(0xC00);
static BitPacker bitsElevation = BitPacker(0xF000);
static BitPacker bitsMetatileId = BitPacker(Block::DefaultMetatileIdMask);
static BitPacker bitsCollision = BitPacker(Block::DefaultCollisionMask);
static BitPacker bitsElevation = BitPacker(Block::DefaultElevationMask);
Block::Block() :
m_metatileId(0),

View File

@ -45,7 +45,7 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
static int numMetatileIdChars = 4;
QString Metatile::getMetatileIdString(uint16_t metatileId) {
return /*porymapConfig.displayIdsHexadecimal*/ConfigDisplayIdsHexadecimal
return porymapConfig.displayIdsHexadecimal
? Util::toHexString(metatileId, numMetatileIdChars)
: QString::number(metatileId);
};
@ -62,6 +62,10 @@ QString Metatile::getLayerName(int layerNum) {
return layerTitles.value(layerNum);
}
int Metatile::numLayers() {
return projectConfig.tripleLayerMetatilesEnabled ? 3 : 2;
}
// Read and pack together this metatile's attributes.
uint32_t Metatile::getAttributes() const {
uint32_t data = 0;
@ -81,8 +85,8 @@ void Metatile::setAttributes(uint32_t data) {
}
// Unpack and insert metatile attributes from the given data using a vanilla layout. For AdvanceMap import
void Metatile::setAttributes(uint32_t data, BaseGameVersion version) {
const auto vanillaPackers = (version == BaseGameVersion::pokefirered) ? attributePackersFRLG : attributePackersRSE;
void Metatile::setAttributes(uint32_t data, BaseGame::Version version) {
const auto vanillaPackers = (version == BaseGame::Version::pokefirered) ? attributePackersFRLG : attributePackersRSE;
for (auto i = vanillaPackers.cbegin(), end = vanillaPackers.cend(); i != end; i++){
const auto packer = i.value();
this->setAttribute(i.key(), packer.unpack(data));
@ -95,12 +99,12 @@ void Metatile::setAttribute(Metatile::Attr attr, uint32_t value) {
this->attributes.insert(attr, packer.clamp(value));
}
int Metatile::getDefaultAttributesSize(BaseGameVersion version) {
return (version == BaseGameVersion::pokefirered) ? 4 : 2;
int Metatile::getDefaultAttributesSize(BaseGame::Version version) {
return (version == BaseGame::Version::pokefirered) ? 4 : 2;
}
uint32_t Metatile::getDefaultAttributesMask(BaseGameVersion version, Metatile::Attr attr) {
const auto vanillaPackers = (version == BaseGameVersion::pokefirered) ? attributePackersFRLG : attributePackersRSE;
uint32_t Metatile::getDefaultAttributesMask(BaseGame::Version version, Metatile::Attr attr) {
const auto vanillaPackers = (version == BaseGame::Version::pokefirered) ? attributePackersFRLG : attributePackersRSE;
return vanillaPackers.value(attr).mask();
}

View File

@ -2,11 +2,6 @@
#include "project.h"
#include "bitpacker.h"
bool ConfigDisplayIdsHexadecimal = true;
// Upper limit for raw value (i.e., uint16_t max).
const uint16_t Tile::maxValue = 0xFFFF;
// At the moment these are fixed, and not exposed to the user.
// We're only using them for convenience when converting between raw values.
// The actual job of clamping Tile's members to correct values is handled by the widths in the bit field.
@ -76,7 +71,7 @@ QString Tile::toString() const {
}
QString Tile::getTileIdString(uint16_t tileId) {
return /*porymapConfig.displayIdsHexadecimal*/ConfigDisplayIdsHexadecimal
return porymapConfig.displayIdsHexadecimal
? Util::toHexString(tileId, 3)
: QString::number(tileId);
}

View File

@ -89,7 +89,7 @@ void Tileset::resizeMetatiles(int newNumMetatiles) {
while (m_metatiles.length() > newNumMetatiles) {
delete m_metatiles.takeLast();
}
const int numTiles = projectConfig.getNumTilesInMetatile();
const int numTiles = Metatile::maxTiles();
while (m_metatiles.length() < newNumMetatiles) {
m_metatiles.append(new Metatile(numTiles));
}
@ -322,7 +322,7 @@ bool Tileset::appendToHeaders(const QString &filepath, const QString &friendlyNa
dataString.append(QString("\t.4byte gTilesetTiles_%1\n").arg(friendlyName));
dataString.append(QString("\t.4byte gTilesetPalettes_%1\n").arg(friendlyName));
dataString.append(QString("\t.4byte gMetatiles_%1\n").arg(friendlyName));
if (projectConfig.baseGameVersion == BaseGameVersion::pokefirered) {
if (projectConfig.baseGameVersion == BaseGame::Version::pokefirered) {
dataString.append("\t.4byte NULL @ animation callback\n");
dataString.append(QString("\t.4byte gMetatileAttributes_%1\n").arg(friendlyName));
} else {
@ -439,7 +439,7 @@ QHash<int, QString> Tileset::getHeaderMemberMap(bool usingAsm)
int paddingOffset = usingAsm ? 1 : 0;
// The position of metatileAttributes changes between games
bool isPokefirered = (projectConfig.baseGameVersion == BaseGameVersion::pokefirered);
bool isPokefirered = (projectConfig.baseGameVersion == BaseGame::Version::pokefirered);
int metatileAttrPosition = (isPokefirered ? 6 : 5) + paddingOffset;
auto map = QHash<int, QString>();
@ -461,7 +461,7 @@ bool Tileset::loadMetatiles() {
}
QByteArray data = file.readAll();
int tilesPerMetatile = projectConfig.getNumTilesInMetatile();
int tilesPerMetatile = Metatile::maxTiles();
int bytesPerMetatile = Tile::sizeInBytes() * tilesPerMetatile;
int numMetatiles = data.length() / bytesPerMetatile;
if (numMetatiles > maxMetatiles()) {
@ -493,7 +493,7 @@ bool Tileset::saveMetatiles() {
}
QByteArray data;
int numTiles = projectConfig.getNumTilesInMetatile();
int numTiles = Metatile::maxTiles();
for (const auto &metatile : m_metatiles) {
for (int i = 0; i < numTiles; i++) {
uint16_t tile = metatile->tiles.value(i).rawValue();

View File

@ -81,6 +81,7 @@ MainWindow::MainWindow(QWidget *parent) :
void MainWindow::initialize() {
this->initWindow();
this->installEventFilter(new GeometrySaver(this));
if (porymapConfig.reopenOnLaunch && !porymapConfig.projectManuallyClosed && this->openProject(porymapConfig.getRecentProject(), true)) {
on_toolButton_Paint_clicked();
}
@ -92,7 +93,7 @@ void MainWindow::initialize() {
if (porymapConfig.checkForUpdates)
this->checkForUpdates(false);
this->restoreWindowState();
this->resizeWithinScreen();
this->show();
}
@ -109,13 +110,6 @@ MainWindow::~MainWindow()
}
void MainWindow::saveGlobalConfigs() {
porymapConfig.setMainGeometry(
this->saveGeometry(),
this->saveState(),
this->ui->splitter_map->saveState(),
this->ui->splitter_main->saveState(),
this->ui->splitter_Metatiles->saveState()
);
porymapConfig.save();
shortcutsConfig.save();
}
@ -185,6 +179,7 @@ void MainWindow::setWindowDisabled(bool disabled) {
}
void MainWindow::initWindow() {
porymapConfig = PorymapConfig();
porymapConfig.load();
this->initLogStatusBar();
this->initCustomUI();
@ -227,6 +222,7 @@ void MainWindow::initWindow() {
void MainWindow::initShortcuts() {
initExtraShortcuts();
shortcutsConfig = ShortcutsConfig();
shortcutsConfig.load();
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
applyUserShortcuts();
@ -303,9 +299,8 @@ void MainWindow::applyUserShortcuts() {
void MainWindow::initLogStatusBar() {
removeLogStatusBar(this->statusBar());
auto logTypes = QSet<LogType>(porymapConfig.statusBarLogTypes.begin(), porymapConfig.statusBarLogTypes.end());
if (!logTypes.isEmpty()) {
addLogStatusBar(this->statusBar(), logTypes);
if (!porymapConfig.statusBarLogTypes.isEmpty()) {
addLogStatusBar(this->statusBar(), porymapConfig.statusBarLogTypes.toQSet());
}
}
@ -320,7 +315,7 @@ void MainWindow::initCustomUI() {
static const QMap<int, QIcon> mainTabIcons = {
{MainTab::Map, QIcon(QStringLiteral(":/icons/minimap.ico"))},
{MainTab::Events, ProjectConfig::getPlayerIcon(BaseGameVersion::pokefirered, 0)}, // Arbitrary default
{MainTab::Events, BaseGame::getPlayerIcon(BaseGame::Version::pokefirered, 0)}, // Arbitrary default
{MainTab::Header, QIcon(QStringLiteral(":/icons/application_form_edit.ico"))},
{MainTab::Connections, QIcon(QStringLiteral(":/icons/connections.ico"))},
{MainTab::WildPokemon, QIcon(QStringLiteral(":/icons/tall_grass.ico"))},
@ -591,9 +586,9 @@ void MainWindow::initMapList() {
// Initialize settings from config
ui->mapListToolBar_Groups->setEditsAllowed(porymapConfig.mapListEditGroupsEnabled);
for (auto i = porymapConfig.mapListHideEmptyEnabled.constBegin(); i != porymapConfig.mapListHideEmptyEnabled.constEnd(); i++) {
auto toolbar = getMapListToolBar(i.key());
if (toolbar) toolbar->setEmptyFoldersVisible(!i.value());
for (const auto &tab : porymapConfig.mapListTabsHidingEmptyFolders) {
auto toolbar = getMapListToolBar(tab);
if (toolbar) toolbar->setEmptyFoldersVisible(false);
}
// Update config if map list settings change
@ -601,13 +596,16 @@ void MainWindow::initMapList() {
porymapConfig.mapListEditGroupsEnabled = allowed;
});
connect(ui->mapListToolBar_Groups, &MapListToolBar::emptyFoldersVisibleChanged, [](bool visible) {
porymapConfig.mapListHideEmptyEnabled[MapListTab::Groups] = !visible;
if (visible) porymapConfig.mapListTabsHidingEmptyFolders.erase(MapListTab::Groups);
else porymapConfig.mapListTabsHidingEmptyFolders.insert(MapListTab::Groups);
});
connect(ui->mapListToolBar_Locations, &MapListToolBar::emptyFoldersVisibleChanged, [](bool visible) {
porymapConfig.mapListHideEmptyEnabled[MapListTab::Locations] = !visible;
if (visible) porymapConfig.mapListTabsHidingEmptyFolders.erase(MapListTab::Locations);
else porymapConfig.mapListTabsHidingEmptyFolders.insert(MapListTab::Locations);
});
connect(ui->mapListToolBar_Layouts, &MapListToolBar::emptyFoldersVisibleChanged, [](bool visible) {
porymapConfig.mapListHideEmptyEnabled[MapListTab::Layouts] = !visible;
if (visible) porymapConfig.mapListTabsHidingEmptyFolders.erase(MapListTab::Layouts);
else porymapConfig.mapListTabsHidingEmptyFolders.insert(MapListTab::Layouts);
});
// When map list search filter is cleared we want the current map/layout in the editor to be visible in the list.
@ -729,19 +727,8 @@ void MainWindow::loadUserSettings() {
refreshRecentProjectsMenu();
}
void MainWindow::restoreWindowState() {
QMap<QString, QByteArray> geometry = porymapConfig.getMainGeometry();
const QByteArray mainWindowGeometry = geometry.value("main_window_geometry");
if (!mainWindowGeometry.isEmpty()) {
logInfo("Restoring main window geometry from previous session.");
restoreGeometry(mainWindowGeometry);
restoreState(geometry.value("main_window_state"));
ui->splitter_map->restoreState(geometry.value("map_splitter_state"));
ui->splitter_main->restoreState(geometry.value("main_splitter_state"));
ui->splitter_Metatiles->restoreState(geometry.value("metatiles_splitter_state"));
}
// Resize the window if it exceeds the available screen size.
// Resize the window if it exceeds the available screen size.
void MainWindow::resizeWithinScreen() {
auto screen = windowHandle() ? windowHandle()->screen() : QGuiApplication::primaryScreen();
if (!screen) return;
const QRect screenGeometry = screen->availableGeometry();
@ -800,14 +787,14 @@ bool MainWindow::openProject(QString dir, bool initial) {
logInfo("Aborted project open.");
return false;
}
const QString openMessage = QString("Opening %1").arg(projectString);
logInfo(openMessage);
logInfo(QString("Opening %1").arg(projectString));
if (porymapConfig.showProjectLoadingScreen) porysplash->start();
porysplash->showLoadingMessage("config");
if (!projectConfig.load(dir) || !userConfig.load(dir)) {
projectConfig = ProjectConfig(dir);
userConfig = UserConfig(dir);
if (!projectConfig.load() || !userConfig.load()) {
showProjectOpenFailure();
porysplash->stop();
return false;
@ -845,9 +832,6 @@ bool MainWindow::openProject(QString dir, bool initial) {
porysplash->stop();
return false;
}
// Only create the config files once the project has opened successfully in case the user selected an invalid directory
this->editor->project->saveConfig();
updateWindowTitle();
@ -899,24 +883,24 @@ bool MainWindow::checkProjectSanity(Project *project) {
bool MainWindow::checkProjectVersion(Project *project) {
QString error;
int projectVersion = project->getSupportedMajorVersion(&error);
if (projectVersion < 0) {
// Failed to identify a supported major version.
QVersionNumber minimumVersion = project->getMinimumVersion(&error);
if (!error.isEmpty()) {
// Failed to identify a supported version.
// We can't draw any conclusions from this, so we don't consider the project to be invalid.
QString msg = QStringLiteral("Failed to check project version");
logWarn(error.isEmpty() ? msg : QString("%1: '%2'").arg(msg).arg(error));
logWarn(QString("Failed to check project version: '%1'").arg(error));
} else {
QString msg = QStringLiteral("Successfully checked project version. ");
logInfo(msg + ((projectVersion != 0) ? QString("Supports at least Porymap v%1").arg(projectVersion)
: QStringLiteral("Too old for any Porymap version")));
if (projectVersion < porymapVersion.majorVersion() && projectConfig.forcedMajorVersion < porymapVersion.majorVersion()) {
if (minimumVersion.isNull()) {
logInfo(QStringLiteral("Successfully checked project version. Too old for any Porymap version"));
} else {
logInfo(QString("Successfully checked project version. Supports at least Porymap v%1").arg(minimumVersion.toString()));
}
if (minimumVersion > porymapVersion || minimumVersion.majorVersion() != porymapVersion.majorVersion()) {
// We were unable to find the necessary changes for Porymap's current major version.
// Unless they have explicitly suppressed this message, warn the user that this might mean their project is missing breaking changes.
// Note: Do not report 'projectVersion' to the user in this message. We've already logged it for troubleshooting.
// Note: Do not report 'minimumVersion' to the user in this message. We've already logged it for troubleshooting.
// It is very plausible that the user may have reproduced the required changes in an
// unknown commit, rather than merging the required changes directly from the base repo.
// In this case the 'projectVersion' may actually be too old to use for their repo.
// In this case the 'minimumVersion' may actually be too old to use for their repo.
ErrorMessage msgBox(QStringLiteral("Your project may be incompatible!"), porysplash);
msgBox.setTextFormat(Qt::RichText);
msgBox.setInformativeText(QString("Make sure '%1' has all the required changes for Porymap version %2.<br>"
@ -929,7 +913,7 @@ bool MainWindow::checkProjectVersion(Project *project) {
return false;
}
// User opted to try with this version anyway. Don't warn them about this version again.
projectConfig.forcedMajorVersion = porymapVersion.majorVersion();
projectConfig.minimumVersion = QVersionNumber(porymapVersion.majorVersion());
}
}
return true;
@ -939,7 +923,7 @@ void MainWindow::showProjectOpenFailure() {
if (!this->isVisible()){
// The main window is not visible during the initial project open; the splash screen is busy providing visual feedback.
// If project opening fails we can immediately display the empty main window (which we need anyway to parent messages to).
restoreWindowState();
resizeWithinScreen();
show();
}
RecentErrorMessage::show(QStringLiteral("There was an error opening the project."), this);
@ -1491,7 +1475,7 @@ bool MainWindow::setProjectUI() {
this->mapGroupModel = new MapGroupModel(editor->project);
this->groupListProxyModel = new FilterChildrenProxyModel();
this->groupListProxyModel->setSourceModel(this->mapGroupModel);
this->groupListProxyModel->setHideEmpty(porymapConfig.mapListHideEmptyEnabled[MapListTab::Groups]);
this->groupListProxyModel->setHideEmpty(porymapConfig.mapListTabsHidingEmptyFolders.contains(MapListTab::Groups));
ui->mapList->setModel(groupListProxyModel);
this->ui->mapList->setItemDelegateForColumn(0, new GroupNameDelegate(this->editor->project, this));
@ -1500,14 +1484,14 @@ bool MainWindow::setProjectUI() {
this->mapLocationModel = new MapLocationModel(editor->project);
this->locationListProxyModel = new FilterChildrenProxyModel();
this->locationListProxyModel->setSourceModel(this->mapLocationModel);
this->locationListProxyModel->setHideEmpty(porymapConfig.mapListHideEmptyEnabled[MapListTab::Locations]);
this->locationListProxyModel->setHideEmpty(porymapConfig.mapListTabsHidingEmptyFolders.contains(MapListTab::Locations));
ui->locationList->setModel(locationListProxyModel);
setMapListSorted(ui->locationList, porymapConfig.mapListLocationsSorted);
this->layoutTreeModel = new LayoutTreeModel(editor->project);
this->layoutListProxyModel = new FilterChildrenProxyModel();
this->layoutListProxyModel->setSourceModel(this->layoutTreeModel);
this->layoutListProxyModel->setHideEmpty(porymapConfig.mapListHideEmptyEnabled[MapListTab::Layouts]);
this->layoutListProxyModel->setHideEmpty(porymapConfig.mapListTabsHidingEmptyFolders.contains(MapListTab::Layouts));
ui->layoutList->setModel(layoutListProxyModel);
setMapListSorted(ui->layoutList, porymapConfig.mapListLayoutsSorted);
@ -1524,7 +1508,7 @@ bool MainWindow::setProjectUI() {
if (eventTabIcon.isNull()) {
// We randomly choose between the available characters for ~flavor~.
// For now, this correctly assumes all versions have 2 icons.
eventTabIcon = ProjectConfig::getPlayerIcon(projectConfig.baseGameVersion, QRandomGenerator::global()->bounded(0, 2));
eventTabIcon = BaseGame::getPlayerIcon(projectConfig.baseGameVersion, QRandomGenerator::global()->bounded(0, 2));
}
ui->mainTabBar->setTabIcon(MainTab::Events, eventTabIcon);
@ -2259,7 +2243,7 @@ void MainWindow::on_mapViewTab_tabBarClicked(int index)
} else if (index == MapViewTab::Collision) {
refreshCollisionSelector();
} else if (index == MapViewTab::Prefabs) {
if (projectConfig.prefabFilepath.isEmpty() && !projectConfig.prefabImportPrompted) {
if (userConfig.prefabsFilepath.isEmpty() && !userConfig.prefabsImportPrompted) {
// User hasn't set up prefabs and hasn't been prompted before.
// Ask if they'd like to import the default prefabs file.
if (prefab.tryImportDefaultPrefabs(this, projectConfig.baseGameVersion))
@ -3299,7 +3283,8 @@ bool MainWindow::closeProject() {
return false;
}
}
editor->closeProject();
logInfo(QString("Closing project '%1'").arg(this->editor->project->root));
this->editor->closeProject();
clearProjectUI();
refreshRecentProjectsMenu();
setWindowDisabled(true);

View File

@ -46,6 +46,11 @@ Project::~Project()
}
void Project::setRoot(const QString &dir) {
// This is not currently designed to actually change the root folder.
// It will not appropriately update instances of the root stored elsewhere,
// like in projectConfig or userConfig.
Q_ASSERT(this->root.isEmpty());
this->root = dir;
FileDialog::setDirectory(dir);
this->parser.setRoot(dir);
@ -72,20 +77,21 @@ bool Project::sanityCheck() {
return false;
}
// Porymap projects have no standardized way for Porymap to determine whether they're compatible as of the latest breaking changes.
// We can use the project's git history (if it has one, and we're able to get it) to make a reasonable guess.
// We know the hashes of the commits in the base repos that contain breaking changes, so if we find one of these then the project
// should support at least up to that Porymap major version. If this fails for any reason it returns a version of -1.
int Project::getSupportedMajorVersion(QString *errorOut) {
QVersionNumber Project::getMinimumVersion(QString *errorOut) const {
if (!projectConfig.minimumVersion.isNull()) return projectConfig.minimumVersion;
// No explicitly supported version, we can use the project's git history (if it has one, and we're able to get it) to make a reasonable guess.
// We know the hashes of the commits in the base repos that contain breaking changes, so if we find one of these then the project
// should support at least up to that Porymap major version. If this fails for any reason it returns a version of -1.
// This has relatively tight timeout windows (500ms for each process, compared to the default 30,000ms). This version check
// is not important enough to significantly slow down project launch, we'd rather just timeout.
const int timeoutLimit = 500;
const int failureVersion = -1;
QString gitName = "git";
constexpr int TimeoutLimit = 500;
const QString gitName = QStringLiteral("git");
QString gitPath = QStandardPaths::findExecutable(gitName);
if (gitPath.isEmpty()) {
if (errorOut) *errorOut = QString("Unable to locate %1.").arg(gitName);
return failureVersion;
return QVersionNumber();
}
QProcess process;
@ -98,7 +104,7 @@ int Project::getSupportedMajorVersion(QString *errorOut) {
// We'll get the root commit, then compare it to the known root commits for the base project repos.
process.setArguments({ "-c", QString("safe.directory=%1").arg(this->root), "rev-list", "--max-parents=0", "HEAD" });
process.start();
if (!process.waitForFinished(timeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit || process.exitCode() != 0) {
if (!process.waitForFinished(TimeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit || process.exitCode() != 0) {
if (errorOut) {
*errorOut = QStringLiteral("Failed to identify commit history");
if (process.error() != QProcess::UnknownError && !process.errorString().isEmpty()) {
@ -109,7 +115,7 @@ int Project::getSupportedMajorVersion(QString *errorOut) {
if (!error.isEmpty()) errorOut->append(QString(": %1").arg(error));
}
}
return failureVersion;
return QVersionNumber();
}
const QString rootCommit = QString(process.readLine()).remove('\n');
@ -145,22 +151,22 @@ int Project::getSupportedMajorVersion(QString *errorOut) {
if (!historyMap.contains(rootCommit)) {
// Either this repo does not share history with one of the base repos, or we got some unexpected result.
if (errorOut) *errorOut = QStringLiteral("Unrecognized commit history");
return failureVersion;
return QVersionNumber();
}
// We now know which base repo that the user's repo shares history with.
// Next we check to see if it contains the changes required to support particular major versions of Porymap.
// We'll start with the most recent major version and work backwards.
for (const auto &pair : historyMap.value(rootCommit)) {
int versionNum = pair.first;
QString commitHash = pair.second;
const QVersionNumber version = QVersionNumber(pair.first);
const QString commitHash = pair.second;
if (commitHash.isEmpty()) {
// An empty commit hash means 'consider any point in the history a supported version'
return versionNum;
return version;
}
process.setArguments({ "-c", QString("safe.directory=%1").arg(this->root), "merge-base", "--is-ancestor", commitHash, "HEAD" });
process.start();
if (!process.waitForFinished(timeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit) {
if (!process.waitForFinished(TimeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit) {
if (errorOut) {
*errorOut = QStringLiteral("Failed to search commit history");
if (process.error() != QProcess::UnknownError && !process.errorString().isEmpty()) {
@ -171,15 +177,15 @@ int Project::getSupportedMajorVersion(QString *errorOut) {
if (!error.isEmpty()) errorOut->append(QString(": %1").arg(error));
}
}
return failureVersion;
return QVersionNumber();
}
if (process.exitCode() == 0) {
// Identified a supported major version
return versionNum;
return version;
}
}
// We recognized the commit history, but it's too old for any version of Porymap to support.
return 0;
return QVersionNumber();
}
bool Project::load() {
@ -1553,7 +1559,7 @@ Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboa
tileset->loadTilesImage(&tilesImage);
// Create default metatiles
const int tilesPerMetatile = projectConfig.getNumTilesInMetatile();
const int tilesPerMetatile = Metatile::maxTiles();
for (int i = 0; i < tileset->maxMetatiles(); ++i) {
auto metatile = new Metatile();
for(int j = 0; j < tilesPerMetatile; ++j){
@ -1645,8 +1651,8 @@ bool Project::readTilesetMetatileLabels() {
for (auto i = defines.constBegin(); i != defines.constEnd(); i++) {
QString label = i.key();
uint32_t metatileId = i.value();
if (metatileId > Block::maxValue) {
metatileId &= Block::maxValue;
if (metatileId > Block::MaxValue) {
metatileId &= Block::MaxValue;
logWarn(QString("Value of metatile label '%1' truncated to %2").arg(label).arg(Metatile::getMetatileIdString(metatileId)));
}
QString tilesetName = findMetatileLabelsTileset(label);
@ -3205,7 +3211,7 @@ QPixmap Project::getEventPixmap(Event::Group group) {
QPixmap defaultIcon = QPixmap(defaultIcons.copy(static_cast<int>(group) * defaultWidth, 0, defaultWidth, defaultHeight));
// Custom event icons may be provided by the user.
QString customIconPath = projectConfig.getEventIconPath(group);
QString customIconPath = projectConfig.eventIconPaths.value(group);
if (customIconPath.isEmpty()) {
// No custom icon specified, use the default icon.
pixmap = defaultIcon;
@ -3297,7 +3303,7 @@ QString Project::getDefaultSpeciesIconPath(const QString &species) {
// We failed to find a default icon path, this species will use a placeholder icon.
// If the user has no custom icon path for this species, tell them they can provide one.
if (path.isEmpty() && projectConfig.getPokemonIconPath(species).isEmpty()) {
if (path.isEmpty() && projectConfig.pokemonIconPaths.value(species).isEmpty()) {
logWarn(QString("Failed to find Pokémon icon for '%1'. The filepath can be specified under 'Options->Project Settings'").arg(species));
}
return path;
@ -3338,7 +3344,7 @@ QPixmap Project::getSpeciesIcon(const QString &species) {
QPixmap pixmap;
if (!QPixmapCache::find(species, &pixmap)) {
// Prefer path from config. If not present, use the path parsed from project files
QString path = Project::getExistingFilepath(projectConfig.getPokemonIconPath(species));
QString path = Project::getExistingFilepath(projectConfig.pokemonIconPaths.value(species));
if (path.isEmpty()) {
path = getDefaultSpeciesIconPath(species);
}

View File

@ -715,7 +715,7 @@ void MainWindow::setMetatileAttributes(int metatileId, int attributes) {
}
int MainWindow::calculateTileBounds(int * tileStart, int * tileEnd) {
int maxNumTiles = projectConfig.getNumTilesInMetatile();
int maxNumTiles = Metatile::maxTiles();
if (*tileEnd >= maxNumTiles || *tileEnd < 0)
*tileEnd = maxNumTiles - 1;
if (*tileStart >= maxNumTiles || *tileStart < 0)

View File

@ -198,7 +198,12 @@ bool ScriptUtility::getSmartPathsEnabled() {
}
QList<QString> ScriptUtility::getCustomScripts() {
return userConfig.getCustomScriptPaths();
QList<QString> paths;
for (const auto& settings : userConfig.customScripts)
paths.append(settings.path);
for (const auto& settings : projectConfig.customScripts)
paths.append(settings.path);
return paths;
}
QList<int> ScriptUtility::getMetatileLayerOrder() {

View File

@ -22,11 +22,11 @@ Scripting::Scripting(MainWindow *mainWindow)
: QObject(mainWindow), mainWindow(mainWindow), engine(new QJSEngine(this))
{
this->engine->installExtensions(QJSEngine::ConsoleExtension);
const QStringList paths = userConfig.getCustomScriptPaths();
const QList<bool> enabled = userConfig.getCustomScriptsEnabled();
for (int i = 0; i < paths.length(); i++) {
if (enabled.value(i, true))
loadScript(paths.at(i));
const QStringList paths = ScriptSettings::filter(userConfig.customScripts)
+ ScriptSettings::filter(projectConfig.customScripts);
for (const auto& path : paths) {
loadScript(path);
}
}
@ -103,10 +103,10 @@ void Scripting::populateGlobalObject() {
constants.setProperty("max_secondary_metatiles", Project::getNumMetatilesSecondary());
constants.setProperty("num_primary_palettes", Project::getNumPalettesPrimary());
constants.setProperty("num_secondary_palettes", Project::getNumPalettesSecondary());
constants.setProperty("layers_per_metatile", projectConfig.getNumLayersInMetatile());
constants.setProperty("tiles_per_metatile", projectConfig.getNumTilesInMetatile());
constants.setProperty("layers_per_metatile", Metatile::numLayers());
constants.setProperty("tiles_per_metatile", Metatile::maxTiles());
constants.setProperty("base_game_version", projectConfig.getBaseGameVersionString());
constants.setProperty("base_game_version", BaseGame::versionToString(projectConfig.baseGameVersion));
// Read out behavior values into constants object
QJSValue behaviorsArray = instance->engine->newObject();

View File

@ -1,112 +0,0 @@
#include "citymappixmapitem.h"
#include "imageproviders.h"
#include "config.h"
#include "log.h"
#include <QFile>
#include <QPainter>
#include <QDebug>
void CityMapPixmapItem::init() {
width_ = 10;
height_ = 10;
QFile binFile(file);
if (!binFile.open(QIODevice::ReadOnly)) return;
data = binFile.readAll();
if (projectConfig.baseGameVersion == BaseGameVersion::pokeruby) {
for (int i = 0; i < data.size(); i++)
data[i] = data[i] ^ 0x80;
}
binFile.close();
}
void CityMapPixmapItem::draw() {
QImage image(width_ * 8, height_ * 8, QImage::Format_RGBA8888);
// TODO: construct temporary tile from this based on the id?
// QPainter painter(&image);
// for (int i = 0; i < data.size() / 2; i++) {
// QImage img = this->tile_selector->tileImg(data[i * 2]);// need to skip every other tile
// int x = i % width_;
// int y = i / width_;
// QPoint pos = QPoint(x * 8, y * 8);
// painter.drawImage(pos, img);
// }
// painter.end();
this->setPixmap(QPixmap::fromImage(image));
}
void CityMapPixmapItem::save() {
QFile binFile(file);
if (!binFile.open(QIODevice::WriteOnly)) {
logError(QString("Cannot save city map tilemap to %1.").arg(file));
return;
}
if (projectConfig.baseGameVersion == BaseGameVersion::pokeruby) {
for (int i = 0; i < data.size(); i++)
data[i] = data[i] ^ 0x80;
}
binFile.write(data);
binFile.close();
}
void CityMapPixmapItem::paint(QGraphicsSceneMouseEvent *event) {
QPointF pos = event->pos();
int x = static_cast<int>(pos.x()) / 8;
int y = static_cast<int>(pos.y()) / 8;
int index = getIndexAt(x, y);
data[index] = static_cast<uint8_t>(this->tile_selector->selectedTile);
draw();
}
void CityMapPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
emit mouseEvent(event, this);
}
void CityMapPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
QPointF pos = event->pos();
int x = static_cast<int>(pos.x()) / 8;
int y = static_cast<int>(pos.y()) / 8;
if (x < width_ && x >= 0
&& y < height_ && y >= 0) {
emit this->hoveredRegionMapTileChanged(x, y);
emit mouseEvent(event, this);
}
}
void CityMapPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
emit mouseEvent(event, this);
}
QVector<uint8_t> CityMapPixmapItem::getTiles() {
QVector<uint8_t> tiles;
for (auto tile : data) {
tiles.append(tile);
}
return tiles;
}
void CityMapPixmapItem::setTiles(QVector<uint8_t> tiles) {
QByteArray newData;
for (auto tile : tiles) {
newData.append(tile);
}
this->data = newData;
}
int CityMapPixmapItem::getIndexAt(int x, int y) {
return 2 * (x + y * this->width_);
}
int CityMapPixmapItem::width() {
return this->width_;
}
int CityMapPixmapItem::height() {
return this->height_;
}

View File

@ -5,6 +5,7 @@
#include "editor.h"
#include "shortcut.h"
#include "filedialog.h"
#include "eventfilters.h"
#include <QDir>
@ -18,10 +19,10 @@ CustomScriptsEditor::CustomScriptsEditor(QWidget *parent) :
// This property seems to be reset if we don't set it programmatically
ui->list->setDragDropMode(QAbstractItemView::NoDragDrop);
const QStringList paths = userConfig.getCustomScriptPaths();
const QList<bool> enabled = userConfig.getCustomScriptsEnabled();
for (int i = 0; i < paths.length(); i++)
this->displayScript(paths.at(i), enabled.value(i, true));
for (const auto& settings : projectConfig.customScripts)
displayScript(settings);
for (const auto& settings : userConfig.customScripts)
displayScript(settings);
connect(ui->button_Help, &QAbstractButton::clicked, this, &CustomScriptsEditor::openManual);
connect(ui->button_CreateNewScript, &QAbstractButton::clicked, this, &CustomScriptsEditor::createNewScript);
@ -29,8 +30,8 @@ CustomScriptsEditor::CustomScriptsEditor(QWidget *parent) :
connect(ui->button_RefreshScripts, &QAbstractButton::clicked, this, &CustomScriptsEditor::userRefreshScripts);
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &CustomScriptsEditor::dialogButtonClicked);
this->initShortcuts();
this->restoreWindowState();
initShortcuts();
installEventFilter(new GeometrySaver(this));
}
CustomScriptsEditor::~CustomScriptsEditor()
@ -60,7 +61,6 @@ void CustomScriptsEditor::initShortcuts() {
shortcut_refresh->setObjectName("shortcut_refresh");
shortcut_refresh->setWhatsThis("Refresh Scripts");
shortcutsConfig.load();
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
applyUserShortcuts();
}
@ -87,26 +87,16 @@ void CustomScriptsEditor::applyUserShortcuts() {
shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut));
}
void CustomScriptsEditor::restoreWindowState() {
logInfo("Restoring custom scripts editor geometry from previous session.");
const QMap<QString, QByteArray> geometry = porymapConfig.getCustomScriptsEditorGeometry();
this->restoreGeometry(geometry.value("custom_scripts_editor_geometry"));
this->restoreState(geometry.value("custom_scripts_editor_state"));
}
void CustomScriptsEditor::displayScript(const QString &filepath, bool enabled) {
auto item = new QListWidgetItem();
auto widget = new CustomScriptsListItem();
widget->ui->checkBox_Enable->setChecked(enabled);
widget->ui->lineEdit_filepath->setText(filepath);
void CustomScriptsEditor::displayScript(const ScriptSettings& settings) {
auto item = new QListWidgetItem(ui->list);
auto widget = new CustomScriptsListItem(settings, ui->list);
item->setSizeHint(widget->sizeHint());
connect(widget->ui->b_Choose, &QAbstractButton::clicked, [this, item](bool) { this->replaceScript(item); });
connect(widget->ui->b_Edit, &QAbstractButton::clicked, [this, item](bool) { this->openScript(item); });
connect(widget->ui->b_Delete, &QAbstractButton::clicked, [this, item](bool) { this->removeScript(item); });
connect(widget->ui->checkBox_Enable, &QCheckBox::toggled, this, &CustomScriptsEditor::markEdited);
connect(widget->ui->lineEdit_filepath, &QLineEdit::textEdited, this, &CustomScriptsEditor::markEdited);
connect(widget, &CustomScriptsListItem::clickedChooseScript, [this, item] { this->replaceScript(item); });
connect(widget, &CustomScriptsListItem::clickedEditScript, [this, item] { this->openScript(item); });
connect(widget, &CustomScriptsListItem::clickedDeleteScript, [this, item] { this->removeScript(item); });
connect(widget, &CustomScriptsListItem::toggledEnable, this, &CustomScriptsEditor::markEdited);
connect(widget, &CustomScriptsListItem::pathEdited, this, &CustomScriptsEditor::markEdited);
// Per the Qt manual, for performance reasons QListWidget::setItemWidget shouldn't be used with non-static items.
// There's an assumption here that users won't have enough scripts for that to be a problem.
@ -122,7 +112,7 @@ QString CustomScriptsEditor::getScriptFilepath(QListWidgetItem * item, bool abso
auto widget = dynamic_cast<CustomScriptsListItem *>(ui->list->itemWidget(item));
if (!widget) return QString();
QString path = widget->ui->lineEdit_filepath->text();
QString path = widget->path();
if (absolutePath) {
QFileInfo fileInfo(path);
if (fileInfo.isRelative())
@ -134,13 +124,13 @@ QString CustomScriptsEditor::getScriptFilepath(QListWidgetItem * item, bool abso
void CustomScriptsEditor::setScriptFilepath(QListWidgetItem * item, QString filepath) const {
auto widget = dynamic_cast<CustomScriptsListItem *>(ui->list->itemWidget(item));
if (widget) {
widget->ui->lineEdit_filepath->setText(Util::stripPrefix(filepath, this->baseDir));
widget->setPath(Util::stripPrefix(filepath, this->baseDir));
}
}
bool CustomScriptsEditor::getScriptEnabled(QListWidgetItem * item) const {
auto widget = dynamic_cast<CustomScriptsListItem *>(ui->list->itemWidget(item));
return widget && widget->ui->checkBox_Enable->isChecked();
return widget && widget->scriptEnabled();
}
QString CustomScriptsEditor::chooseScript(QString dir) {
@ -187,7 +177,9 @@ void CustomScriptsEditor::displayNewScript(QString filepath) {
}
}
this->displayScript(filepath, true);
ScriptSettings settings;
settings.path = filepath;
this->displayScript(settings);
this->markEdited();
}
@ -253,19 +245,23 @@ void CustomScriptsEditor::save() {
if (!this->hasUnsavedChanges)
return;
QStringList paths;
QList<bool> enabledStates;
QList<ScriptSettings> userScripts;
QList<ScriptSettings> projectScripts;
for (int i = 0; i < ui->list->count(); i++) {
auto item = ui->list->item(i);
const QString path = this->getScriptFilepath(item, false);
if (!path.isEmpty()) {
paths.append(path);
enabledStates.append(this->getScriptEnabled(item));
}
auto widget = dynamic_cast<CustomScriptsListItem *>(ui->list->itemWidget(item));
if (!widget) continue;
const ScriptSettings settings = widget->getSettings();
if (settings.userOnly) userScripts.append(settings);
else projectScripts.append(settings);
}
userConfig.setCustomScripts(paths, enabledStates);
userConfig.customScripts = userScripts;
userConfig.save();
projectConfig.customScripts = projectScripts;
projectConfig.save();
this->hasUnsavedChanges = false;
this->refreshScripts();
}
@ -295,9 +291,4 @@ void CustomScriptsEditor::closeEvent(QCloseEvent* event) {
if (result == QMessageBox::Yes)
this->save();
}
porymapConfig.setCustomScriptsEditorGeometry(
this->saveGeometry(),
this->saveState()
);
}

View File

@ -6,9 +6,50 @@ CustomScriptsListItem::CustomScriptsListItem(QWidget *parent) :
ui(new Ui::CustomScriptsListItem)
{
ui->setupUi(this);
connect(ui->b_Choose, &QAbstractButton::clicked, this, &CustomScriptsListItem::clickedChooseScript);
connect(ui->b_Edit, &QAbstractButton::clicked, this, &CustomScriptsListItem::clickedEditScript);
connect(ui->b_Delete, &QAbstractButton::clicked, this, &CustomScriptsListItem::clickedDeleteScript);
connect(ui->checkBox_Enable, &QCheckBox::toggled, this, &CustomScriptsListItem::toggledEnable);
connect(ui->lineEdit_filepath, &QLineEdit::textEdited, this, &CustomScriptsListItem::pathEdited);
}
CustomScriptsListItem::~CustomScriptsListItem()
CustomScriptsListItem::CustomScriptsListItem(const ScriptSettings& settings, QWidget *parent) :
CustomScriptsListItem(parent)
{
setSettings(settings);
}
CustomScriptsListItem::~CustomScriptsListItem() {
delete ui;
}
void CustomScriptsListItem::setPath(const QString& text) {
ui->lineEdit_filepath->setText(text);
}
QString CustomScriptsListItem::path() const {
return ui->lineEdit_filepath->text();
}
void CustomScriptsListItem::setScriptEnabled(bool enabled) {
ui->checkBox_Enable->setChecked(enabled);
}
bool CustomScriptsListItem::scriptEnabled() const {
return ui->checkBox_Enable->isChecked();
}
void CustomScriptsListItem::setSettings(const ScriptSettings& settings) {
setPath(settings.path);
setScriptEnabled(settings.enabled);
// TODO: Read userOnly
}
ScriptSettings CustomScriptsListItem::getSettings() const {
return {
.path = path(),
.enabled = scriptEnabled(),
.userOnly = true, // TODO
};
}

View File

@ -1,4 +1,6 @@
#include "eventfilters.h"
#include "config.h"
#include "log.h"
#include <QGraphicsSceneWheelEvent>
@ -16,10 +18,40 @@ bool MapSceneEventFilter::eventFilter(QObject*, QEvent *event) {
}
bool ActiveWindowFilter::eventFilter(QObject*, QEvent *event) {
if (event->type() == QEvent::WindowActivate) {
emit activated();
}
return false;
}
bool GeometrySaver::eventFilter(QObject *object, QEvent *event) {
if (event->spontaneous()) return false;
auto w = qobject_cast<QWidget*>(object);
if (!w) return false;
if (event->type() == QEvent::Polish) {
// Note: Restoring geometry in QEvent::Show would be too late,
// and the widget would briefly appear with the old geometry.
porymapConfig.restoreGeometry(w);
} else if (event->type() == QEvent::Show) {
if (m_loggingEnabled && !w->windowTitle().isEmpty()) {
logInfo(QString("Opening window: %1").arg(w->windowTitle()));
}
m_wasShown.insert(object);
} else if (event->type() == QEvent::Close && m_wasShown.contains(object)) {
// There are situations where a window might be 'closed' without
// ever actually having been opened (for example, the Shortcuts Editor
// will quietly construct windows to get their shortcuts, and those windows
// can later be closed without having been displayed).
// We don't want to save the geometry of these windows, or log that they closed,
// so we've checked to make sure the widget was displayed before proceeding.
porymapConfig.saveGeometry(w);
if (m_loggingEnabled && !w->windowTitle().isEmpty()) {
logInfo(QString("Closing window: %1").arg(w->windowTitle()));
}
}
return false;
}

View File

@ -74,6 +74,28 @@ QVector<qreal> GridSettings::getDashPattern(uint length) const {
}
}
QJsonObject GridSettings::toJson() const {
QJsonObject obj;
obj["width"] = static_cast<qint64>(this->width);
obj["height"] = static_cast<qint64>(this->height);
obj["offsetX"] = this->offsetX;
obj["offsetY"] = this->offsetY;
obj["style"] = getStyleName(this->style);
obj["color"] = this->color.name();
return obj;
}
GridSettings GridSettings::fromJson(const QJsonObject &obj) {
GridSettings settings;
settings.width = obj["width"].toInt();
settings.height = obj["height"].toInt();
settings.offsetX = obj["offsetX"].toInt();
settings.offsetY = obj["offsetY"].toInt();
settings.style = getStyleFromName(obj["style"].toString());
settings.color = QColor(obj["color"].toString());
return settings;
}
GridSettingsDialog::GridSettingsDialog(QWidget *parent) :

View File

@ -23,16 +23,17 @@ void MetatileLayersItem::setOrientation(Qt::Orientation orientation) {
// Generate a table of tile positions that allows us to map between
// the index of a tile in the metatile and its position in this layer view.
this->tilePositions.clear();
const int numLayers = Metatile::numLayers();
if (this->orientation == Qt::Horizontal) {
// Tiles are laid out horizontally, with the bottom layer on the left:
// 0 1 4 5 8 9
// 2 3 6 7 10 11
for (int layer = 0; layer < projectConfig.getNumLayersInMetatile(); layer++)
for (int layer = 0; layer < numLayers; layer++)
for (int y = 0; y < Metatile::tileHeight(); y++)
for (int x = 0; x < Metatile::tileWidth(); x++) {
this->tilePositions.append(QPoint(x + layer * Metatile::tileWidth(), y));
}
maxWidth *= projectConfig.getNumLayersInMetatile();
maxWidth *= numLayers;
} else if (this->orientation == Qt::Vertical) {
// Tiles are laid out vertically, with the bottom layer on the bottom:
// 8 9
@ -41,12 +42,12 @@ void MetatileLayersItem::setOrientation(Qt::Orientation orientation) {
// 6 7
// 0 1
// 2 3
for (int layer = projectConfig.getNumLayersInMetatile() - 1; layer >= 0; layer--)
for (int layer = numLayers - 1; layer >= 0; layer--)
for (int y = 0; y < Metatile::tileHeight(); y++)
for (int x = 0; x < Metatile::tileWidth(); x++) {
this->tilePositions.append(QPoint(x, y + layer * Metatile::tileHeight()));
}
maxHeight *= projectConfig.getNumLayersInMetatile();
maxHeight *= numLayers;
}
setMaxSelectionSize(maxWidth, maxHeight);
update();
@ -61,7 +62,7 @@ void MetatileLayersItem::draw() {
// Draw tile images
const Metatile* metatile = getMetatile();
int numTiles = qMin(projectConfig.getNumTilesInMetatile(), metatile ? metatile->tiles.length() : 0);
int numTiles = qMin(Metatile::maxTiles(), metatile ? metatile->tiles.length() : 0);
for (int i = 0; i < numTiles; i++) {
Tile tile = metatile->tiles.at(i);
QImage tileImage = getPalettedTileImage(tile.tileId,
@ -79,7 +80,7 @@ void MetatileLayersItem::draw() {
painter.setPen(Qt::white);
const int layerWidth = this->cellWidth * Metatile::tileWidth();
const int layerHeight = this->cellHeight * Metatile::tileHeight();
for (int i = 1; i < projectConfig.getNumLayersInMetatile(); i++) {
for (int i = 1; i < Metatile::numLayers(); i++) {
if (this->orientation == Qt::Vertical) {
int y = i * layerHeight;
painter.drawLine(0, y, layerWidth, y);

View File

@ -41,18 +41,16 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q
refresh();
if (porymapConfig.newLayoutDialogGeometry.isEmpty()){
if (!porymapConfig.restoreGeometry(this)) {
// On first display resize to fit contents a little better
adjustSize();
} else {
restoreGeometry(porymapConfig.newLayoutDialogGeometry);
}
ui->lineEdit_Name->setFocus();
}
NewLayoutDialog::~NewLayoutDialog()
{
porymapConfig.newLayoutDialogGeometry = saveGeometry();
porymapConfig.saveGeometry(this);
saveSettings();
delete ui;
}

View File

@ -4,6 +4,7 @@
#include "ui_newmapdialog.h"
#include "config.h"
#include "validator.h"
#include "eventfilters.h"
#include <QMap>
#include <QSet>
@ -61,7 +62,7 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked);
refresh();
restoreGeometry(porymapConfig.newMapDialogGeometry);
installEventFilter(new GeometrySaver(this));
ui->lineEdit_Name->setFocus();
}
@ -91,7 +92,6 @@ NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapL
NewMapDialog::~NewMapDialog()
{
porymapConfig.newMapDialogGeometry = saveGeometry();
saveSettings();
delete ui;
}

View File

@ -60,7 +60,7 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset
ui->actionRedo->setShortcuts({ui->actionRedo->shortcut(), QKeySequence("Ctrl+Shift+Z")});
refreshPaletteId();
restoreWindowState();
installEventFilter(new GeometrySaver(this));
}
PaletteEditor::~PaletteEditor() {
@ -159,13 +159,6 @@ void PaletteEditor::commitEditHistory(int paletteId) {
updateEditHistoryActions();
}
void PaletteEditor::restoreWindowState() {
logInfo("Restoring palette editor geometry from previous session.");
QMap<QString, QByteArray> geometry = porymapConfig.getPaletteEditorGeometry();
restoreGeometry(geometry.value("palette_editor_geometry"));
restoreState(geometry.value("palette_editor_state"));
}
void PaletteEditor::updateEditHistoryActions() {
int paletteId = currentPaletteId();
// We have an initial commit that shouldn't be available to Undo, so we ignore that.
@ -279,11 +272,6 @@ void PaletteEditor::setColorInputTitles(bool showUnused) {
}
void PaletteEditor::closeEvent(QCloseEvent*) {
porymapConfig.setPaletteEditorGeometry(
saveGeometry(),
saveState()
);
// Opening the color search window then closing the Palette Editor sets
// focus to the main editor window instead of the parent (Tileset Editor).
// Make sure the parent is active when we close.

View File

@ -20,7 +20,7 @@ const QString defaultFilepath = "prefabs.json";
void Prefab::loadPrefabs() {
this->items.clear();
QString filepath = projectConfig.prefabFilepath;
QString filepath = userConfig.prefabsFilepath;
if (filepath.isEmpty()) return;
ParseUtil parser;
@ -87,10 +87,10 @@ void Prefab::loadPrefabs() {
}
void Prefab::savePrefabs() {
if (projectConfig.prefabFilepath.isEmpty())
projectConfig.prefabFilepath = defaultFilepath;
if (userConfig.prefabsFilepath.isEmpty())
userConfig.prefabsFilepath = defaultFilepath;
QString filepath = projectConfig.prefabFilepath;
QString filepath = userConfig.prefabsFilepath;
QFileInfo info(filepath);
if (info.isRelative()) {
@ -283,9 +283,9 @@ void Prefab::addPrefab(MetatileSelection selection, Layout *layout, QString name
this->updatePrefabUi(layout);
}
bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version, QString filepath) {
bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGame::Version version, QString filepath) {
// Ensure we have default prefabs for the project's game version.
if (version != BaseGameVersion::pokeruby && version != BaseGameVersion::pokeemerald && version != BaseGameVersion::pokefirered)
if (version != BaseGame::Version::pokeruby && version != BaseGame::Version::pokeemerald && version != BaseGame::Version::pokefirered)
return false;
if (filepath.isEmpty())
@ -316,17 +316,17 @@ bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version,
QMessageBox::question(parent,
QApplication::applicationName(),
QString("Would you like to import the default prefabs for %1? %2.")
.arg(projectConfig.getBaseGameVersionString(version))
.arg(BaseGame::versionToString(version))
.arg(fileWarning),
QMessageBox::Yes | QMessageBox::No);
bool acceptedImport = (prompt == QMessageBox::Yes);
if (acceptedImport) {
// Sets up the default prefabs.json filepath.
projectConfig.prefabFilepath = filepath;
userConfig.prefabsFilepath = filepath;
QFile prefabsFile(absFilepath);
if (!prefabsFile.open(QIODevice::WriteOnly)) {
projectConfig.prefabFilepath = QString();
userConfig.prefabsFilepath = QString();
logError(QString("Error: Could not open %1 for writing").arg(absFilepath));
QMessageBox messageBox(parent);
@ -340,13 +340,13 @@ bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version,
ParseUtil parser;
QString content;
switch (version) {
case BaseGameVersion::pokeruby:
case BaseGame::Version::pokeruby:
content = parser.readTextFile(":/text/prefabs_default_ruby.json");
break;
case BaseGameVersion::pokefirered:
case BaseGame::Version::pokefirered:
content = parser.readTextFile(":/text/prefabs_default_firered.json");
break;
case BaseGameVersion::pokeemerald:
case BaseGame::Version::pokeemerald:
content = parser.readTextFile(":/text/prefabs_default_emerald.json");
break;
default:
@ -359,7 +359,7 @@ bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version,
this->loadPrefabs();
}
projectConfig.prefabImportPrompted = true;
userConfig.prefabsImportPrompted = true;
return acceptedImport;
}

View File

@ -103,7 +103,7 @@ void PreferenceEditor::updateFields() {
ui->checkBox_StatusWarnings->setChecked(porymapConfig.statusBarLogTypes.find(LogType::LOG_WARN) != logTypeEnd);
ui->checkBox_StatusInformation->setChecked(porymapConfig.statusBarLogTypes.find(LogType::LOG_INFO) != logTypeEnd);
if (/*porymapConfig.displayIdsHexadecimal*/ConfigDisplayIdsHexadecimal) {
if (porymapConfig.displayIdsHexadecimal) {
ui->radioButton_Hexadecimal->setChecked(true);
} else {
ui->radioButton_Decimal->setChecked(true);
@ -141,7 +141,7 @@ void PreferenceEditor::saveFields() {
porymapConfig.checkForUpdates = ui->checkBox_CheckForUpdates->isChecked();
porymapConfig.eventDeleteWarningDisabled = ui->checkBox_DisableEventWarning->isChecked();
porymapConfig.showProjectLoadingScreen = ui->checkBox_ShowProjectLoadingScreen->isChecked();
/*porymapConfig.displayIdsHexadecimal*/ConfigDisplayIdsHexadecimal = ui->radioButton_Hexadecimal->isChecked();
porymapConfig.displayIdsHexadecimal = ui->radioButton_Hexadecimal->isChecked();
porymapConfig.statusBarLogTypes.clear();
if (ui->checkBox_StatusErrors->isChecked()) porymapConfig.statusBarLogTypes.insert(LogType::LOG_ERROR);

View File

@ -4,6 +4,7 @@
#include "filedialog.h"
#include "newdefinedialog.h"
#include "utility.h"
#include "eventfilters.h"
#include <QAbstractButton>
#include <QFormLayout>
@ -27,9 +28,9 @@ ProjectSettingsEditor::ProjectSettingsEditor(QWidget *parent, Project *project)
this->initUi();
this->createProjectPathsTable();
this->createProjectIdentifiersTable();
this->installEventFilter(new GeometrySaver(this));
this->connectSignals();
this->refresh();
this->restoreWindowState();
}
ProjectSettingsEditor::~ProjectSettingsEditor()
@ -110,16 +111,21 @@ void ProjectSettingsEditor::initUi() {
ui->comboBox_IconSpecies->addItems(project->speciesNames);
ui->comboBox_WarpBehaviors->addItems(project->metatileBehaviorMap.keys());
}
ui->comboBox_BaseGameVersion->addItems(ProjectConfig::versionStrings);
// TODO: We don't need to keep converting these to/from strings, just include the value as data.
ui->comboBox_BaseGameVersion->addItems({
BaseGame::versionToString(BaseGame::Version::pokeruby),
BaseGame::versionToString(BaseGame::Version::pokefirered),
BaseGame::versionToString(BaseGame::Version::pokeemerald),
});
ui->comboBox_AttributesSize->addItems({"1", "2", "4"});
ui->comboBox_EventsTabIcon->addItem("Automatic", "");
ui->comboBox_EventsTabIcon->addItem("Brendan (Emerald)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeemerald, 0));
ui->comboBox_EventsTabIcon->addItem("Brendan (R/S)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeruby, 0));
ui->comboBox_EventsTabIcon->addItem("May (Emerald)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeemerald, 1));
ui->comboBox_EventsTabIcon->addItem("May (R/S)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeruby, 1));
ui->comboBox_EventsTabIcon->addItem("Red", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokefirered, 0));
ui->comboBox_EventsTabIcon->addItem("Green", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokefirered, 1));
ui->comboBox_EventsTabIcon->addItem("Brendan (Emerald)", BaseGame::getPlayerIconPath(BaseGame::Version::pokeemerald, 0));
ui->comboBox_EventsTabIcon->addItem("Brendan (R/S)", BaseGame::getPlayerIconPath(BaseGame::Version::pokeruby, 0));
ui->comboBox_EventsTabIcon->addItem("May (Emerald)", BaseGame::getPlayerIconPath(BaseGame::Version::pokeemerald, 1));
ui->comboBox_EventsTabIcon->addItem("May (R/S)", BaseGame::getPlayerIconPath(BaseGame::Version::pokeruby, 1));
ui->comboBox_EventsTabIcon->addItem("Red", BaseGame::getPlayerIconPath(BaseGame::Version::pokefirered, 0));
ui->comboBox_EventsTabIcon->addItem("Green", BaseGame::getPlayerIconPath(BaseGame::Version::pokefirered, 1));
ui->comboBox_EventsTabIcon->addItem("Custom", "Custom");
connect(ui->comboBox_EventsTabIcon, QOverload<int>::of(&NoScrollComboBox::currentIndexChanged), [this](int index) {
bool usingCustom = (index == ui->comboBox_EventsTabIcon->findText("Custom"));
@ -147,12 +153,12 @@ void ProjectSettingsEditor::initUi() {
ui->spinBox_Collision->setMaximum(Block::getMaxCollision());
ui->spinBox_MaxElevation->setMaximum(Block::getMaxElevation());
ui->spinBox_MaxCollision->setMaximum(Block::getMaxCollision());
ui->spinBox_MetatileIdMask->setMaximum(Block::maxValue);
ui->spinBox_CollisionMask->setMaximum(Block::maxValue);
ui->spinBox_ElevationMask->setMaximum(Block::maxValue);
ui->spinBox_UnusedTileNormal->setMaximum(Tile::maxValue);
ui->spinBox_UnusedTileCovered->setMaximum(Tile::maxValue);
ui->spinBox_UnusedTileSplit->setMaximum(Tile::maxValue);
ui->spinBox_MetatileIdMask->setMaximum(Block::MaxValue);
ui->spinBox_CollisionMask->setMaximum(Block::MaxValue);
ui->spinBox_ElevationMask->setMaximum(Block::MaxValue);
ui->spinBox_UnusedTileNormal->setMaximum(Tile::MaxValue);
ui->spinBox_UnusedTileCovered->setMaximum(Tile::MaxValue);
ui->spinBox_UnusedTileSplit->setMaximum(Tile::MaxValue);
ui->spinBox_MaxEvents->setMaximum(INT_MAX);
ui->spinBox_MapWidth->setMaximum(INT_MAX);
ui->spinBox_MapHeight->setMaximum(INT_MAX);
@ -434,13 +440,6 @@ QString ProjectSettingsEditor::chooseProjectFile(const QString &defaultFilepath)
return path.remove(0, this->baseDir.length());
}
void ProjectSettingsEditor::restoreWindowState() {
logInfo("Restoring project settings editor geometry from previous session.");
const QMap<QString, QByteArray> geometry = porymapConfig.getProjectSettingsEditorGeometry();
this->restoreGeometry(geometry.value("project_settings_editor_geometry"));
this->restoreState(geometry.value("project_settings_editor_state"));
}
// Set UI states using config data
void ProjectSettingsEditor::refresh() {
this->refreshing = true; // Block signals
@ -448,12 +447,12 @@ void ProjectSettingsEditor::refresh() {
// Set combo box texts
ui->comboBox_DefaultPrimaryTileset->setTextItem(projectConfig.defaultPrimaryTileset);
ui->comboBox_DefaultSecondaryTileset->setTextItem(projectConfig.defaultSecondaryTileset);
ui->comboBox_BaseGameVersion->setTextItem(projectConfig.getBaseGameVersionString());
ui->comboBox_BaseGameVersion->setTextItem(BaseGame::versionToString(projectConfig.baseGameVersion));
ui->comboBox_AttributesSize->setTextItem(QString::number(projectConfig.metatileAttributesSize));
this->updateAttributeLimits(ui->comboBox_AttributesSize->currentText());
this->prevIconSpecies = QString();
this->editedPokemonIconPaths = projectConfig.getPokemonIconPaths();
this->editedPokemonIconPaths = projectConfig.pokemonIconPaths;
this->updatePokemonIconPath(ui->comboBox_IconSpecies->currentText());
// Set check box states
@ -511,13 +510,13 @@ void ProjectSettingsEditor::refresh() {
this->setBorderMetatileIds(true, projectConfig.newMapBorderMetatileIds);
// Set line edit texts
ui->lineEdit_PrefabsPath->setText(projectConfig.prefabFilepath);
ui->lineEdit_PrefabsPath->setText(userConfig.prefabsFilepath);
ui->lineEdit_CollisionGraphics->setText(projectConfig.collisionSheetPath);
ui->lineEdit_ObjectsIcon->setText(projectConfig.getEventIconPath(Event::Group::Object));
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_HealLocationsIcon->setText(projectConfig.getEventIconPath(Event::Group::Heal));
ui->lineEdit_ObjectsIcon->setText(projectConfig.eventIconPaths.value(Event::Group::Object));
ui->lineEdit_WarpsIcon->setText(projectConfig.eventIconPaths.value(Event::Group::Warp));
ui->lineEdit_TriggersIcon->setText(projectConfig.eventIconPaths.value(Event::Group::Coord));
ui->lineEdit_BGsIcon->setText(projectConfig.eventIconPaths.value(Event::Group::Bg));
ui->lineEdit_HealLocationsIcon->setText(projectConfig.eventIconPaths.value(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*>())
@ -556,7 +555,7 @@ void ProjectSettingsEditor::save() {
// Save combo box settings
projectConfig.defaultPrimaryTileset = ui->comboBox_DefaultPrimaryTileset->currentText();
projectConfig.defaultSecondaryTileset = ui->comboBox_DefaultSecondaryTileset->currentText();
projectConfig.baseGameVersion = projectConfig.stringToBaseGameVersion(ui->comboBox_BaseGameVersion->currentText());
projectConfig.baseGameVersion = BaseGame::stringToVersion(ui->comboBox_BaseGameVersion->currentText());
projectConfig.metatileAttributesSize = ui->comboBox_AttributesSize->currentText().toInt();
// Save check box settings
@ -603,13 +602,13 @@ void ProjectSettingsEditor::save() {
projectConfig.metatileSelectorWidth = ui->spinBox_MetatileSelectorWidth->value();
// Save line edit settings
projectConfig.prefabFilepath = ui->lineEdit_PrefabsPath->text();
userConfig.prefabsFilepath = ui->lineEdit_PrefabsPath->text();
projectConfig.collisionSheetPath = ui->lineEdit_CollisionGraphics->text();
projectConfig.setEventIconPath(Event::Group::Object, ui->lineEdit_ObjectsIcon->text());
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_HealLocationsIcon->text());
projectConfig.eventIconPaths[Event::Group::Object] = ui->lineEdit_ObjectsIcon->text();
projectConfig.eventIconPaths[Event::Group::Warp] = ui->lineEdit_WarpsIcon->text();
projectConfig.eventIconPaths[Event::Group::Coord] = ui->lineEdit_TriggersIcon->text();
projectConfig.eventIconPaths[Event::Group::Bg] = ui->lineEdit_BGsIcon->text();
projectConfig.eventIconPaths[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*>())
@ -623,7 +622,7 @@ void ProjectSettingsEditor::save() {
projectConfig.warpBehaviors.clear();
const QStringList behaviorNames = this->getWarpBehaviorsList();
for (auto name : behaviorNames)
projectConfig.warpBehaviors.append(project->metatileBehaviorMap.value(name));
projectConfig.warpBehaviors.insert(project->metatileBehaviorMap.value(name));
// Save border metatile IDs
projectConfig.newMapBorderMetatileIds = this->getBorderMetatileIds(ui->checkBox_EnableCustomBorderSize->isChecked());
@ -633,7 +632,7 @@ void ProjectSettingsEditor::save() {
if (this->project->speciesNames.contains(species))
this->editedPokemonIconPaths.insert(species, ui->lineEdit_PokemonIcon->text());
for (auto i = this->editedPokemonIconPaths.cbegin(), end = this->editedPokemonIconPaths.cend(); i != end; i++)
projectConfig.setPokemonIconPath(i.key(), i.value());
projectConfig.pokemonIconPaths[i.key()] = i.value();
QString eventsTabIconPath;
QVariant data = ui->comboBox_EventsTabIcon->currentData();
@ -773,9 +772,9 @@ QString ProjectSettingsEditor::stripProjectDir(QString s) {
void ProjectSettingsEditor::importDefaultPrefabsClicked(bool) {
// If the prompt is accepted the prefabs file will be created and its filepath will be saved in the config.
BaseGameVersion version = projectConfig.stringToBaseGameVersion(ui->comboBox_BaseGameVersion->currentText());
BaseGame::Version version = BaseGame::stringToVersion(ui->comboBox_BaseGameVersion->currentText());
if (prefab.tryImportDefaultPrefabs(this, version, ui->lineEdit_PrefabsPath->text())) {
ui->lineEdit_PrefabsPath->setText(projectConfig.prefabFilepath); // Refresh with new filepath
ui->lineEdit_PrefabsPath->setText(userConfig.prefabsFilepath); // Refresh with new filepath
this->hasUnsavedChanges = true;
}
}
@ -815,8 +814,9 @@ bool ProjectSettingsEditor::promptRestoreDefaults() {
// Restore defaults by resetting config in memory, refreshing the UI, then restoring the config.
// Don't want to save changes until user accepts them.
// TODO: Maybe give the project settings editor it's own copy of the config then.
ProjectConfig tempProject = projectConfig;
projectConfig.reset(projectConfig.stringToBaseGameVersion(versionText));
projectConfig.setVersionSpecificDefaults(BaseGame::stringToVersion(versionText));
this->refresh();
projectConfig = tempProject;
@ -863,11 +863,6 @@ void ProjectSettingsEditor::closeEvent(QCloseEvent* event) {
return;
}
porymapConfig.setProjectSettingsEditorGeometry(
this->saveGeometry(),
this->saveState()
);
if (this->projectNeedsReload) {
// Note: Declining this prompt with changes that need a reload may cause problems
if (this->prompt("Settings saved, reload project to apply changes?") == QMessageBox::Yes)

View File

@ -7,6 +7,7 @@
#include "config.h"
#include "log.h"
#include "utility.h"
#include "eventfilters.h"
#include <QDir>
#include <QDialog>
@ -33,7 +34,7 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) :
this->configFilepath = QString("%1/%2").arg(this->project->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_porymap_cfg));
this->initShortcuts();
this->restoreWindowState();
this->installEventFilter(new GeometrySaver(this));
}
RegionMapEditor::~RegionMapEditor()
@ -56,13 +57,6 @@ RegionMapEditor::~RegionMapEditor()
delete ui;
}
void RegionMapEditor::restoreWindowState() {
logInfo("Restoring region map editor geometry from previous session.");
QMap<QString, QByteArray> geometry = porymapConfig.getRegionMapEditorGeometry();
this->restoreGeometry(geometry.value("region_map_editor_geometry"));
this->restoreState(geometry.value("region_map_editor_state"));
}
void RegionMapEditor::initShortcuts() {
auto *shortcut_RM_Options_delete = new Shortcut(
{QKeySequence("Del"), QKeySequence("Backspace")}, this, SLOT(on_pushButton_RM_Options_delete_clicked()));
@ -80,7 +74,6 @@ void RegionMapEditor::initShortcuts() {
ui->menuEdit->addAction(undoAction);
ui->menuEdit->addAction(redoAction);
shortcutsConfig.load();
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
applyUserShortcuts();
@ -144,13 +137,13 @@ void buildFireredDefaults(poryjson::Json &json) {
poryjson::Json RegionMapEditor::buildDefaultJson() {
poryjson::Json defaultJson;
switch (projectConfig.baseGameVersion) {
case BaseGameVersion::pokeemerald:
case BaseGame::Version::pokeemerald:
buildEmeraldDefaults(defaultJson);
break;
case BaseGameVersion::pokeruby:
case BaseGame::Version::pokeruby:
buildRubyDefaults(defaultJson);
break;
case BaseGameVersion::pokefirered:
case BaseGame::Version::pokefirered:
buildFireredDefaults(defaultJson);
break;
default:
@ -285,13 +278,13 @@ bool RegionMapEditor::buildConfigDialog() {
// for sake of convenience, option to just use defaults for each basegame version
QPushButton *config_useProjectDefault = nullptr;
switch (projectConfig.baseGameVersion) {
case BaseGameVersion::pokefirered:
case BaseGame::Version::pokefirered:
config_useProjectDefault = new QPushButton("\nUse pokefirered defaults\n");
break;
case BaseGameVersion::pokeemerald:
case BaseGame::Version::pokeemerald:
config_useProjectDefault = new QPushButton("\nUse pokeemerald defaults\n");
break;
case BaseGameVersion::pokeruby:
case BaseGame::Version::pokeruby:
config_useProjectDefault = new QPushButton("\nUse pokeruby defaults\n");
break;
default:
@ -1251,11 +1244,6 @@ void RegionMapEditor::closeEvent(QCloseEvent *event)
} else {
event->accept();
}
porymapConfig.setRegionMapEditorGeometry(
this->saveGeometry(),
this->saveState()
);
}
void RegionMapEditor::on_verticalSlider_Zoom_Map_Image_valueChanged(int val) {

View File

@ -4,6 +4,7 @@
#include "multikeyedit.h"
#include "message.h"
#include "log.h"
#include "eventfilters.h"
#include <QGroupBox>
#include <QFormLayout>
@ -22,6 +23,7 @@ ShortcutsEditor::ShortcutsEditor(QWidget *parent) :
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
installEventFilter(new GeometrySaver(this));
main_container = ui->scrollAreaWidgetContents_Shortcuts;
auto *main_layout = new QVBoxLayout(main_container);
main_layout->setSpacing(12);

View File

@ -37,7 +37,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
ActiveWindowFilter *filter = new ActiveWindowFilter(this);
connect(filter, &ActiveWindowFilter::activated, this, &TilesetEditor::onWindowActivated);
this->installEventFilter(filter);
installEventFilter(filter);
setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label);
@ -75,7 +75,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
initShortcuts();
setMetatileLayerOrientation(porymapConfig.tilesetEditorLayerOrientation);
this->metatileSelector->select(0);
restoreWindowState();
installEventFilter(new GeometrySaver(this));
}
TilesetEditor::~TilesetEditor()
@ -233,7 +233,7 @@ void TilesetEditor::setMetatileLayerOrientation(Qt::Orientation orientation) {
int numTilesWide = Metatile::tileWidth();
int numTilesTall = Metatile::tileHeight();
int numLayers = projectConfig.getNumLayersInMetatile();
int numLayers = Metatile::numLayers();
if (horizontal) {
numTilesWide *= numLayers;
} else {
@ -339,8 +339,6 @@ void TilesetEditor::initSelectedTileItem() {
void TilesetEditor::initShortcuts() {
initExtraShortcuts();
shortcutsConfig.load();
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
applyUserShortcuts();
}
@ -379,14 +377,6 @@ void TilesetEditor::applyUserShortcuts() {
shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut));
}
void TilesetEditor::restoreWindowState() {
logInfo("Restoring tileset editor geometry from previous session.");
QMap<QString, QByteArray> geometry = porymapConfig.getTilesetEditorGeometry();
this->restoreGeometry(geometry.value("tileset_editor_geometry"));
this->restoreState(geometry.value("tileset_editor_state"));
this->ui->splitter->restoreState(geometry.value("tileset_editor_splitter_state"));
}
void TilesetEditor::onWindowActivated() {
// User may have made layout edits since window was last focused, so update counts
if (this->metatileSelector) {
@ -853,11 +843,6 @@ void TilesetEditor::closeEvent(QCloseEvent *event)
if (event->isAccepted()) {
if (this->paletteEditor) this->paletteEditor->close();
porymapConfig.setTilesetEditorGeometry(
this->saveGeometry(),
this->saveState(),
this->ui->splitter->saveState()
);
}
}
@ -930,7 +915,7 @@ bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile &src, QS
// Update tile usage if any tiles changed
if (this->tileSelector && this->tileSelector->showUnused) {
int numTiles = projectConfig.getNumTilesInMetatile();
int numTiles = qMin(src.tiles.length(), dest->tiles.length());
for (int i = 0; i < numTiles; i++) {
if (src.tiles[i].tileId != dest->tiles[i].tileId) {
this->tileSelector->usedTiles[src.tiles[i].tileId] += 1;
@ -995,7 +980,7 @@ void TilesetEditor::on_actionRedo_triggered() {
void TilesetEditor::on_actionCut_triggered()
{
this->copyMetatile(true);
this->pasteMetatile(Metatile(projectConfig.getNumTilesInMetatile()), "");
this->pasteMetatile(Metatile(Metatile::maxTiles()), "");
}
void TilesetEditor::on_actionCopy_triggered()

View File

@ -4,6 +4,7 @@
#include "config.h"
#include "utility.h"
#include "message.h"
#include "eventfilters.h"
static const QString baseWindowTitle = QString("Wild Pokémon Summary Charts");
@ -25,6 +26,7 @@ WildMonChart::WildMonChart(QWidget *parent, const EncounterTableModel *table) :
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
setWindowFlags(Qt::Window);
installEventFilter(new GeometrySaver(this));
connect(ui->button_Help, &QAbstractButton::clicked, this, &WildMonChart::showHelpDialog);
@ -49,8 +51,6 @@ WildMonChart::WildMonChart(QWidget *parent, const EncounterTableModel *table) :
porymapConfig.wildMonChartTheme = ui->comboBox_Theme->currentText();
}
restoreGeometry(porymapConfig.wildMonChartGeometry);
setTable(table);
};
@ -461,9 +461,4 @@ void WildMonChart::showHelpDialog() {
InfoMessage::show(text, informativeText, this);
}
void WildMonChart::closeEvent(QCloseEvent *event) {
porymapConfig.wildMonChartGeometry = saveGeometry();
QWidget::closeEvent(event);
}
#endif // QT_CHARTS_LIB