diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index d9e19dcc..f153538c 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -3012,9 +3012,7 @@ Options - - @@ -3065,14 +3063,6 @@ Ctrl+S - - - true - - - Show Wild Encounter Tables - - true @@ -3081,14 +3071,6 @@ Monitor Project Files - - - true - - - Use Poryscript - - true diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index 442291dc..529992ea 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -72,15 +72,8 @@ New Map Defaults - - - - The default metatile value that will be used to fill new maps - - - Fill Metatile - - + + @@ -92,19 +85,6 @@ - - - - A comma-separated list of metatile values that will be used to fill new map borders - - - Border Metatiles - - - - - - @@ -118,12 +98,6 @@ - - - - - - @@ -134,6 +108,52 @@ + + + + + + + The default metatile value that will be used to fill new maps + + + Fill Metatile + + + + + + + A comma-separated list of metatile values that will be used to fill new map borders + + + Border Metatiles + + + + + + + 0x + + + 16 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -217,16 +237,6 @@ Tilesets / Metatiles - - - - The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled. - - - Behavior mask - - - @@ -237,15 +247,18 @@ - - - - The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled. + + + + Qt::Vertical - - Layer Type mask + + + 20 + 15 + - + @@ -264,32 +277,16 @@ - - - - Qt::Vertical - - - - 20 - 15 - - - + + - - - - - - - - + + - The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled. + Whether the C data outputted for new tilesets will include the "isCompressed" field - Encounter Type mask + Output 'isCompressed' field @@ -306,27 +303,35 @@ - - - - - - - - - - - - - Qt::Horizontal + + + + 0x - - - 40 - 20 - + + 16 - + + + + + + 0x + + + 16 + + + + + + + The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled. + + + Encounter Type mask + + @@ -338,13 +343,43 @@ - - + + - Whether the C data outputted for new tilesets will include the "isCompressed" field + The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled. - Output 'isCompressed' field + Behavior mask + + + + + + + The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled. + + + Layer Type mask + + + + + + + 0x + + + 16 + + + + + + + 0x + + + 16 diff --git a/include/config.h b/include/config.h index cb927caa..72d42bbf 100644 --- a/include/config.h +++ b/include/config.h @@ -10,8 +10,8 @@ #include // In both versions the default new map border is a generic tree -#define DEFAULT_BORDER_RSE (QList{468, 469, 476, 477}) -#define DEFAULT_BORDER_FRLG (QList{20, 21, 28, 29}) +#define DEFAULT_BORDER_RSE (QList{0x1D4, 0x1D5, 0x1DC, 0x1DD}) +#define DEFAULT_BORDER_FRLG (QList{0x14, 0x15, 0x1C, 0x1D}) #define CONFIG_BACKWARDS_COMPATABILITY @@ -26,6 +26,7 @@ class KeyValueConfigBase public: void save(); void load(); + void setSaveDisabled(bool disabled); virtual ~KeyValueConfigBase(); virtual void reset() = 0; protected: @@ -37,6 +38,8 @@ protected: bool getConfigBool(QString key, QString value); int getConfigInteger(QString key, QString value, int min, int max, int defaultValue); uint32_t getConfigUint32(QString key, QString value, uint32_t min, uint32_t max, uint32_t defaultValue); +private: + bool saveDisabled = false; }; class PorymapConfig: public KeyValueConfigBase @@ -203,20 +206,11 @@ public: } virtual void reset() override { this->baseGameVersion = BaseGameVersion::pokeemerald; - this->useCustomBorderSize = false; - this->enableEventWeatherTrigger = true; - this->enableEventSecretBase = true; - this->enableHiddenItemQuantity = false; - this->enableHiddenItemRequiresItemfinder = false; - this->enableHealLocationRespawnData = false; - this->enableEventCloneObject = false; - this->enableFloorNumber = false; - this->createMapTextFile = false; + // Reset non-version-specific settings this->usePoryScript = false; this->enableTripleLayerMetatiles = false; this->newMapMetatileId = 1; this->newMapElevation = 3; - this->newMapBorderMetatileIds = DEFAULT_BORDER_RSE; this->defaultPrimaryTileset = "gTileset_General"; this->prefabFilepath = QString(); this->prefabImportPrompted = false; @@ -225,10 +219,12 @@ public: this->filePaths.clear(); this->readKeys.clear(); } - static const QStringList baseGameVersions; + static const QStringList versionStrings; + void reset(BaseGameVersion baseGameVersion); void setBaseGameVersion(BaseGameVersion baseGameVersion); BaseGameVersion getBaseGameVersion(); QString getBaseGameVersionString(); + BaseGameVersion stringToBaseGameVersion(QString string, bool * ok = nullptr); void setUsePoryScript(bool usePoryScript); bool getUsePoryScript(); void setProjectDir(QString projectDir); @@ -255,16 +251,17 @@ public: bool getTripleLayerMetatilesEnabled(); int getNumLayersInMetatile(); int getNumTilesInMetatile(); - void setNewMapMetatileId(int metatileId); - int getNewMapMetatileId(); - QString getNewMapMetatileIdString(); + void setNewMapMetatileId(uint16_t metatileId); + uint16_t getNewMapMetatileId(); void setNewMapElevation(int elevation); int getNewMapElevation(); - void setNewMapBorderMetatileIds(QList metatileIds); - QList getNewMapBorderMetatileIds(); + void setNewMapBorderMetatileIds(QList metatileIds); + QList getNewMapBorderMetatileIds(); QString getNewMapBorderMetatileIdsString(); QString getDefaultPrimaryTileset(); QString getDefaultSecondaryTileset(); + void setDefaultPrimaryTileset(QString tilesetName); + void setDefaultSecondaryTileset(QString tilesetName); void setFilePath(ProjectFilePath pathId, QString path); QString getFilePath(ProjectFilePath pathId); void setPrefabFilepath(QString filepath); @@ -276,12 +273,17 @@ public: void setTilesetsHaveIsCompressed(bool has); bool getTilesetsHaveIsCompressed(); int getMetatileAttributesSize(); + void setMetatileAttributesSize(int size); uint32_t getMetatileBehaviorMask(); uint32_t getMetatileTerrainTypeMask(); uint32_t getMetatileEncounterTypeMask(); uint32_t getMetatileLayerTypeMask(); - static QString getMaskString(uint32_t mask); + void setMetatileBehaviorMask(uint32_t mask); + void setMetatileTerrainTypeMask(uint32_t mask); + void setMetatileEncounterTypeMask(uint32_t mask); + void setMetatileLayerTypeMask(uint32_t mask); bool getMapAllowFlagsEnabled(); + void setMapAllowFlagsEnabled(bool enabled); protected: virtual QString getConfigFilepath() override; virtual void parseConfigKeyValue(QString key, QString value) override; @@ -303,9 +305,9 @@ private: bool enableFloorNumber; bool createMapTextFile; bool enableTripleLayerMetatiles; - int newMapMetatileId; + uint16_t newMapMetatileId; int newMapElevation; - QList newMapBorderMetatileIds; + QList newMapBorderMetatileIds; QString defaultPrimaryTileset; QString defaultSecondaryTileset; QStringList readKeys; diff --git a/include/core/metatile.h b/include/core/metatile.h index 49fb5c7b..c3bda046 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -93,6 +93,7 @@ public: static QPoint coordFromPixmapCoord(const QPointF &pixelCoord); static int getDefaultAttributesSize(BaseGameVersion version); static void setCustomLayout(Project*); + static QString getMetatileIdString(uint16_t metatileId) { return "0x" + QString("%1").arg(metatileId, 3, 16, QChar('0')).toUpper(); }; private: // Stores how each attribute should be laid out for all metatiles, according to the user's config diff --git a/include/mainwindow.h b/include/mainwindow.h index c6c30b42..32bb03eb 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -198,9 +198,7 @@ private slots: void on_checkBox_AllowBiking_stateChanged(int selected); void on_checkBox_AllowEscaping_stateChanged(int selected); void on_spinBox_FloorNumber_valueChanged(int offset); - void on_actionUse_Encounter_Json_triggered(bool checked); void on_actionMonitor_Project_Files_triggered(bool checked); - void on_actionUse_Poryscript_triggered(bool checked); void on_actionOpen_Recent_Project_On_Launch_triggered(bool checked); void on_actionEdit_Shortcuts_triggered(); diff --git a/include/ui/projectsettingseditor.h b/include/ui/projectsettingseditor.h index 16b28b0b..6e1f7ed5 100644 --- a/include/ui/projectsettingseditor.h +++ b/include/ui/projectsettingseditor.h @@ -31,11 +31,17 @@ private: NoScrollComboBox *combo_baseGameVersion; NoScrollComboBox *combo_attributesSize; + bool hasUnsavedChanges = false; + void initUi(); void saveFields(); + void connectSignals(); + void refresh(); + bool prompt(const QString &text); private slots: void dialogButtonClicked(QAbstractButton *button); + void markEdited(); }; #endif // PROJECTSETTINGSEDITOR_H diff --git a/src/config.cpp b/src/config.cpp index 9419a7b4..037511ee 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -119,6 +119,9 @@ void KeyValueConfigBase::load() { } void KeyValueConfigBase::save() { + if (this->saveDisabled) + return; + QString text = ""; QMap map = this->getKeyValueMap(); for (QMap::iterator it = map.begin(); it != map.end(); it++) { @@ -163,6 +166,11 @@ uint32_t KeyValueConfigBase::getConfigUint32(QString key, QString value, uint32_ return qMin(max, qMax(min, result)); } +// For temporarily disabling saving during frequent config changes. +void KeyValueConfigBase::setSaveDisabled(bool disabled) { + this->saveDisabled = disabled; +} + const QMap mapSortOrderMap = { {MapSortOrder::Group, "group"}, {MapSortOrder::Layout, "layout"}, @@ -513,24 +521,34 @@ int PorymapConfig::getPaletteEditorBitDepth() { return this->paletteEditorBitDepth; } -const QStringList ProjectConfig::baseGameVersions = { +const QStringList ProjectConfig::versionStrings = { "pokeruby", "pokefirered", "pokeemerald", }; const QMap baseGameVersionMap = { - {BaseGameVersion::pokeruby, ProjectConfig::baseGameVersions[0]}, - {BaseGameVersion::pokefirered, ProjectConfig::baseGameVersions[1]}, - {BaseGameVersion::pokeemerald, ProjectConfig::baseGameVersions[2]}, + {BaseGameVersion::pokeruby, ProjectConfig::versionStrings[0]}, + {BaseGameVersion::pokefirered, ProjectConfig::versionStrings[1]}, + {BaseGameVersion::pokeemerald, ProjectConfig::versionStrings[2]}, }; const QMap baseGameVersionReverseMap = { - {ProjectConfig::baseGameVersions[0], BaseGameVersion::pokeruby}, - {ProjectConfig::baseGameVersions[1], BaseGameVersion::pokefirered}, - {ProjectConfig::baseGameVersions[2], BaseGameVersion::pokeemerald}, + {ProjectConfig::versionStrings[0], BaseGameVersion::pokeruby}, + {ProjectConfig::versionStrings[1], BaseGameVersion::pokefirered}, + {ProjectConfig::versionStrings[2], BaseGameVersion::pokeemerald}, }; +BaseGameVersion ProjectConfig::stringToBaseGameVersion(QString string, bool * ok) { + if (baseGameVersionReverseMap.contains(string)) { + if (ok) *ok = true; + return baseGameVersionReverseMap.value(string); + } else { + if (ok) *ok = false; + return BaseGameVersion::pokeemerald; + } +} + ProjectConfig projectConfig; QString ProjectConfig::getConfigFilepath() { @@ -540,13 +558,10 @@ QString ProjectConfig::getConfigFilepath() { void ProjectConfig::parseConfigKeyValue(QString key, QString value) { if (key == "base_game_version") { - QString baseGameVersion = value.toLower(); - if (baseGameVersionReverseMap.contains(baseGameVersion)) { - this->baseGameVersion = baseGameVersionReverseMap.value(baseGameVersion); - } else { - this->baseGameVersion = BaseGameVersion::pokeemerald; + bool ok; + this->baseGameVersion = this->stringToBaseGameVersion(value.toLower(), &ok); + if (!ok) logWarn(QString("Invalid config value for base_game_version: '%1'. Must be 'pokeruby', 'pokefirered' or 'pokeemerald'.").arg(value)); - } } else if (key == "use_poryscript") { this->usePoryScript = getConfigBool(key, value); } else if (key == "use_custom_border_size") { @@ -571,24 +586,18 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "enable_triple_layer_metatiles") { this->enableTripleLayerMetatiles = getConfigBool(key, value); } else if (key == "new_map_metatile") { - this->newMapMetatileId = getConfigInteger(key, value, 0, 1023, 0); + this->newMapMetatileId = getConfigUint32(key, value, 0, 1023, 0); } else if (key == "new_map_elevation") { this->newMapElevation = getConfigInteger(key, value, 0, 15, 3); } else if (key == "new_map_border_metatiles") { this->newMapBorderMetatileIds.clear(); QList metatileIds = value.split(","); - const int maxSize = DEFAULT_BORDER_WIDTH * DEFAULT_BORDER_HEIGHT; - const int size = qMin(metatileIds.size(), maxSize); - int i; - for (i = 0; i < size; i++) { - int metatileId = getConfigInteger(key, metatileIds.at(i), 0, 1023, 0); + for (int i = 0; i < metatileIds.size(); i++) { + // TODO: The max of 1023 here should eventually reflect Project::num_metatiles_total-1, + // but the config is parsed well before that constant is. + int metatileId = getConfigUint32(key, metatileIds.at(i), 0, 1023, 0); this->newMapBorderMetatileIds.append(metatileId); } - // TODO: If insufficient metatiles are provided, it should loop the provided metatiles instead. - for (; i < maxSize; i++) { - // Set any metatiles not provided to 0 - this->newMapBorderMetatileIds.append(0); - } } else if (key == "default_primary_tileset") { this->defaultPrimaryTileset = value; } else if (key == "default_secondary_tileset") { @@ -646,6 +655,13 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { readKeys.append(key); } +// Restore config to version-specific defaults +void::ProjectConfig::reset(BaseGameVersion baseGameVersion) { + this->reset(); + this->setBaseGameVersion(baseGameVersion); + this->setUnreadKeys(); +} + void ProjectConfig::setUnreadKeys() { // Set game-version specific defaults for any config field that wasn't read bool isPokefirered = this->baseGameVersion == BaseGameVersion::pokefirered; @@ -682,7 +698,7 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("enable_floor_number", QString::number(this->enableFloorNumber)); map.insert("create_map_text_file", QString::number(this->createMapTextFile)); map.insert("enable_triple_layer_metatiles", QString::number(this->enableTripleLayerMetatiles)); - map.insert("new_map_metatile", this->getNewMapMetatileIdString()); + map.insert("new_map_metatile", Metatile::getMetatileIdString(this->newMapMetatileId)); map.insert("new_map_elevation", QString::number(this->newMapElevation)); map.insert("new_map_border_metatiles", this->getNewMapBorderMetatileIdsString()); map.insert("default_primary_tileset", this->defaultPrimaryTileset); @@ -695,10 +711,10 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("tilesets_have_callback", QString::number(this->tilesetsHaveCallback)); map.insert("tilesets_have_is_compressed", QString::number(this->tilesetsHaveIsCompressed)); map.insert("metatile_attributes_size", QString::number(this->metatileAttributesSize)); - map.insert("metatile_behavior_mask", getMaskString(this->metatileBehaviorMask)); - map.insert("metatile_terrain_type_mask", getMaskString(this->metatileTerrainTypeMask)); - map.insert("metatile_encounter_type_mask", getMaskString(this->metatileEncounterTypeMask)); - map.insert("metatile_layer_type_mask", getMaskString(this->metatileLayerTypeMask)); + map.insert("metatile_behavior_mask", "0x" + QString::number(this->metatileBehaviorMask, 16).toUpper()); + map.insert("metatile_terrain_type_mask", "0x" + QString::number(this->metatileTerrainTypeMask, 16).toUpper()); + map.insert("metatile_encounter_type_mask", "0x" + QString::number(this->metatileEncounterTypeMask, 16).toUpper()); + map.insert("metatile_layer_type_mask", "0x" + QString::number(this->metatileLayerTypeMask, 16).toUpper()); map.insert("enable_map_allow_flags", QString::number(this->enableMapAllowFlags)); return map; } @@ -875,19 +891,15 @@ int ProjectConfig::getNumTilesInMetatile() { return this->enableTripleLayerMetatiles ? 12 : 8; } -void ProjectConfig::setNewMapMetatileId(int metatileId) { +void ProjectConfig::setNewMapMetatileId(uint16_t metatileId) { this->newMapMetatileId = metatileId; this->save(); } -int ProjectConfig::getNewMapMetatileId() { +uint16_t ProjectConfig::getNewMapMetatileId() { return this->newMapMetatileId; } -QString ProjectConfig::getNewMapMetatileIdString() { - return "0x" + QString::number(this->newMapMetatileId, 16).toUpper(); -} - void ProjectConfig::setNewMapElevation(int elevation) { this->newMapElevation = elevation; this->save(); @@ -897,19 +909,19 @@ int ProjectConfig::getNewMapElevation() { return this->newMapElevation; } -void ProjectConfig::setNewMapBorderMetatileIds(QList metatileIds) { +void ProjectConfig::setNewMapBorderMetatileIds(QList metatileIds) { this->newMapBorderMetatileIds = metatileIds; this->save(); } -QList ProjectConfig::getNewMapBorderMetatileIds() { +QList ProjectConfig::getNewMapBorderMetatileIds() { return this->newMapBorderMetatileIds; } QString ProjectConfig::getNewMapBorderMetatileIdsString() { QStringList metatiles; for (auto metatileId : this->newMapBorderMetatileIds){ - metatiles << ("0x" + QString::number(metatileId, 16).toUpper()); + metatiles << Metatile::getMetatileIdString(metatileId); } return metatiles.join(","); } @@ -922,6 +934,16 @@ QString ProjectConfig::getDefaultSecondaryTileset() { return this->defaultSecondaryTileset; } +void ProjectConfig::setDefaultPrimaryTileset(QString tilesetName) { + this->defaultPrimaryTileset = tilesetName; + this->save(); +} + +void ProjectConfig::setDefaultSecondaryTileset(QString tilesetName) { + this->defaultSecondaryTileset = tilesetName; + this->save(); +} + void ProjectConfig::setPrefabFilepath(QString filepath) { this->prefabFilepath = filepath; this->save(); @@ -965,6 +987,11 @@ int ProjectConfig::getMetatileAttributesSize() { return this->metatileAttributesSize; } +void ProjectConfig::setMetatileAttributesSize(int size) { + this->metatileAttributesSize = size; + this->save(); +} + uint32_t ProjectConfig::getMetatileBehaviorMask() { return this->metatileBehaviorMask; } @@ -981,14 +1008,35 @@ uint32_t ProjectConfig::getMetatileLayerTypeMask() { return this->metatileLayerTypeMask; } -QString ProjectConfig::getMaskString(uint32_t mask) { - return "0x" + QString::number(mask, 16).toUpper(); +void ProjectConfig::setMetatileBehaviorMask(uint32_t mask) { + this->metatileBehaviorMask = mask; + this->save(); +} + +void ProjectConfig::setMetatileTerrainTypeMask(uint32_t mask) { + this->metatileTerrainTypeMask = mask; + this->save(); +} + +void ProjectConfig::setMetatileEncounterTypeMask(uint32_t mask) { + this->metatileEncounterTypeMask = mask; + this->save(); +} + +void ProjectConfig::setMetatileLayerTypeMask(uint32_t mask) { + this->metatileLayerTypeMask = mask; + this->save(); } bool ProjectConfig::getMapAllowFlagsEnabled() { return this->enableMapAllowFlags; } +void ProjectConfig::setMapAllowFlagsEnabled(bool enabled) { + this->enableMapAllowFlags = enabled; + this->save(); +} + UserConfig userConfig; diff --git a/src/editor.cpp b/src/editor.cpp index 23234b87..9996460e 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -933,8 +933,7 @@ void Editor::onHoveredMovementPermissionCleared() { QString Editor::getMetatileDisplayMessage(uint16_t metatileId) { Metatile *metatile = Tileset::getMetatile(metatileId, map->layout->tileset_primary, map->layout->tileset_secondary); QString label = Tileset::getMetatileLabel(metatileId, map->layout->tileset_primary, map->layout->tileset_secondary); - QString hexString = QString("%1").arg(metatileId, 3, 16, QChar('0')).toUpper(); - QString message = QString("Metatile: 0x%1").arg(hexString); + QString message = QString("Metatile: %1").arg(Metatile::getMetatileIdString(metatileId)); if (label.size()) message += QString(" \"%1\"").arg(label); if (metatile && metatile->behavior) // Skip MB_NORMAL diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9ceb9b94..b40f5ef0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -369,13 +369,11 @@ void MainWindow::markMapEdited() { } void MainWindow::setWildEncountersUIEnabled(bool enabled) { - ui->actionUse_Encounter_Json->setChecked(enabled); ui->mainTabBar->setTabEnabled(4, enabled); } void MainWindow::setProjectSpecificUIVisibility() { - ui->actionUse_Poryscript->setChecked(projectConfig.getUsePoryScript()); this->setWildEncountersUIEnabled(userConfig.getEncounterJsonActive()); bool hasFlags = projectConfig.getMapAllowFlagsEnabled(); @@ -1757,25 +1755,11 @@ void MainWindow::on_actionCursor_Tile_Outline_triggered() } } -void MainWindow::on_actionUse_Encounter_Json_triggered(bool checked) -{ - QMessageBox warning(this); - warning.setText("You must reload the project for this setting to take effect."); - warning.setIcon(QMessageBox::Information); - warning.exec(); - userConfig.setEncounterJsonActive(checked); -} - void MainWindow::on_actionMonitor_Project_Files_triggered(bool checked) { porymapConfig.setMonitorFiles(checked); } -void MainWindow::on_actionUse_Poryscript_triggered(bool checked) -{ - projectConfig.setUsePoryScript(checked); -} - void MainWindow::on_actionOpen_Recent_Project_On_Launch_triggered(bool checked) { porymapConfig.setReopenOnLaunch(checked); diff --git a/src/project.cpp b/src/project.cpp index 92a71e2f..66e5aba8 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -957,9 +957,9 @@ void Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *second } for (QString defineName : definesOut.keys()) { int value = defines[defineName]; - QString line = QString("#define %1 0x%2\n") + QString line = QString("#define %1 %2\n") .arg(defineName, -1 * longestLength) - .arg(QString("%1").arg(value, 3, 16, QChar('0')).toUpper()); + .arg(Metatile::getMetatileIdString(value)); outputText += line; } i += j; @@ -1142,16 +1142,21 @@ void Project::setNewMapBorder(Map *map) { map->layout->border.clear(); int width = map->getBorderWidth(); int height = map->getBorderHeight(); - if (width != DEFAULT_BORDER_WIDTH || height != DEFAULT_BORDER_HEIGHT) { + + const QList configMetatileIds = projectConfig.getNewMapBorderMetatileIds(); + if (configMetatileIds.length() != width * height) { + // Border size doesn't match the number of default border metatiles. + // Fill the border with empty metatiles. for (int i = 0; i < width * height; i++) { map->layout->border.append(0); } } else { - QList metatileIds = projectConfig.getNewMapBorderMetatileIds(); - for (int i = 0; i < DEFAULT_BORDER_WIDTH * DEFAULT_BORDER_HEIGHT; i++) { - map->layout->border.append(qint16(metatileIds.at(i))); + // Fill the border with the default metatiles from the config. + for (int i = 0; i < width * height; i++) { + map->layout->border.append(configMetatileIds.at(i)); } } + map->layout->lastCommitBlocks.border = map->layout->border; map->layout->lastCommitBlocks.borderDimensions = QSize(width, height); } @@ -2174,8 +2179,8 @@ bool Project::readCoordEventWeatherNames() { fileWatcher.addPath(root + "/" + filename); coordEventWeatherNames = parser.readCDefinesSorted(filename, prefixes); if (coordEventWeatherNames.isEmpty()) { - logError(QString("Failed to read coord event weather constants from %1").arg(filename)); - return false; + logWarn(QString("Failed to read coord event weather constants from %1. Disabling Weather Trigger events.").arg(filename)); + projectConfig.setEventWeatherTriggerEnabled(false); } return true; } @@ -2189,8 +2194,8 @@ bool Project::readSecretBaseIds() { fileWatcher.addPath(root + "/" + filename); secretBaseIds = parser.readCDefinesSorted(filename, prefixes); if (secretBaseIds.isEmpty()) { - logError(QString("Failed to read secret base id constants from %1").arg(filename)); - return false; + logWarn(QString("Failed to read secret base id constants from '%1'. Disabling Secret Base events.").arg(filename)); + projectConfig.setEventSecretBaseEnabled(false); } return true; } diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index c6f2ef6a..5aeb9deb 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -22,8 +22,8 @@ ProjectSettingsEditor::ProjectSettingsEditor(QWidget *parent, Project *project) ui->setupUi(this); initUi(); setAttribute(Qt::WA_DeleteOnClose); - connect(ui->buttonBox, &QDialogButtonBox::clicked, - this, &ProjectSettingsEditor::dialogButtonClicked); + connectSignals(); + refresh(); } ProjectSettingsEditor::~ProjectSettingsEditor() @@ -31,8 +31,76 @@ ProjectSettingsEditor::~ProjectSettingsEditor() delete ui; } +void ProjectSettingsEditor::connectSignals() { + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &ProjectSettingsEditor::dialogButtonClicked); + + // Connect combo boxes + QList combos = ui->centralwidget->findChildren(); + foreach(auto i, combos) + connect(i, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::markEdited); + + // Connect check boxes + QList checkboxes = ui->centralwidget->findChildren(); + foreach(auto i, checkboxes) + connect(i, &QCheckBox::stateChanged, this, &ProjectSettingsEditor::markEdited); + + // Connect spin boxes + QList spinBoxes = ui->centralwidget->findChildren(); + foreach(auto i, spinBoxes) + connect(i, QOverload::of(&QSpinBox::valueChanged), [this](int) { this->markEdited(); }); + + // Connect line edits + QList lineEdits = ui->centralwidget->findChildren(); + foreach(auto i, lineEdits) + connect(i, &QLineEdit::textEdited, this, &ProjectSettingsEditor::markEdited); +} + +void ProjectSettingsEditor::markEdited() { + this->hasUnsavedChanges = true; +} + void ProjectSettingsEditor::initUi() { - // Block signals while setting initial UI states + // Create Default Tilesets combo boxes + auto *defaultTilesetsLayout = new QFormLayout(ui->groupBox_DefaultTilesets); + combo_defaultPrimaryTileset = new NoScrollComboBox(ui->groupBox_DefaultTilesets); + combo_defaultSecondaryTileset = new NoScrollComboBox(ui->groupBox_DefaultTilesets); + if (project) combo_defaultPrimaryTileset->addItems(project->primaryTilesetLabels); + if (project) combo_defaultSecondaryTileset->addItems(project->secondaryTilesetLabels); + defaultTilesetsLayout->addRow("Primary Tileset", combo_defaultPrimaryTileset); + defaultTilesetsLayout->addRow("Secondary Tileset", combo_defaultSecondaryTileset); + + // Create Base game version combo box + combo_baseGameVersion = new NoScrollComboBox(ui->widget_BaseGameVersion); + combo_baseGameVersion->addItems(ProjectConfig::versionStrings); + combo_baseGameVersion->setEditable(false); + ui->layout_BaseGameVersion->insertRow(0, "Base game version", combo_baseGameVersion); + + // Create Attributes size combo box + auto *attributesSizeLayout = new QFormLayout(ui->widget_SizeDropdown); + combo_attributesSize = new NoScrollComboBox(ui->widget_SizeDropdown); + combo_attributesSize->addItems({"1", "2", "4"}); + combo_attributesSize->setEditable(false); + attributesSizeLayout->addRow("", combo_attributesSize); + + // Validate that the border metatiles text is a comma-separated list of hex values + static const QRegularExpression expression("^((0[xX])?[A-Fa-f0-9]+,)*(0[xX])?[A-Fa-f0-9]$"); + QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression); + ui->lineEdit_BorderMetatiles->setValidator(validator); + + ui->spinBox_Elevation->setMaximum(15); + ui->spinBox_FillMetatile->setMaximum(Project::getNumMetatilesTotal() - 1); + + // TODO: These need to be subclassed to handle larger values + ui->spinBox_BehaviorMask->setMaximum(INT_MAX); + ui->spinBox_EncounterTypeMask->setMaximum(INT_MAX); + ui->spinBox_LayerTypeMask->setMaximum(INT_MAX); + ui->spinBox_TerrainTypeMask->setMaximum(INT_MAX); + + // TODO: File picker for prefabs? +} + +// Set UI states using config data +void ProjectSettingsEditor::refresh() { const QSignalBlocker blocker0(combo_defaultPrimaryTileset); const QSignalBlocker blocker1(combo_defaultSecondaryTileset); const QSignalBlocker blocker2(combo_baseGameVersion); @@ -54,41 +122,21 @@ void ProjectSettingsEditor::initUi() { const QSignalBlocker blocker12(ui->checkBox_OutputCallback); const QSignalBlocker blocker13(ui->checkBox_OutputIsCompressed); const QSignalBlocker blocker14(ui->spinBox_Elevation); - const QSignalBlocker blocker15(ui->lineEdit_BorderMetatiles); - const QSignalBlocker blocker16(ui->lineEdit_FillMetatile); - const QSignalBlocker blocker17(ui->lineEdit_PrefabsPath); - const QSignalBlocker blocker18(ui->lineEdit_BehaviorMask); - const QSignalBlocker blocker19(ui->lineEdit_EncounterTypeMask); - const QSignalBlocker blocker1A(ui->lineEdit_LayerTypeMask); - const QSignalBlocker blocker1B(ui->lineEdit_TerrainTypeMask); + const QSignalBlocker blocker15(ui->spinBox_FillMetatile); + const QSignalBlocker blocker16(ui->spinBox_BehaviorMask); + const QSignalBlocker blocker17(ui->spinBox_EncounterTypeMask); + const QSignalBlocker blocker18(ui->spinBox_LayerTypeMask); + const QSignalBlocker blocker19(ui->spinBox_TerrainTypeMask); + const QSignalBlocker blocker1A(ui->lineEdit_BorderMetatiles); + const QSignalBlocker blocker1B(ui->lineEdit_PrefabsPath); - // Create Default Tilesets combo boxes - auto *defaultTilesetsLayout = new QFormLayout(ui->groupBox_DefaultTilesets); - combo_defaultPrimaryTileset = new NoScrollComboBox(ui->groupBox_DefaultTilesets); - combo_defaultSecondaryTileset = new NoScrollComboBox(ui->groupBox_DefaultTilesets); - if (project) combo_defaultPrimaryTileset->addItems(project->primaryTilesetLabels); - if (project) combo_defaultSecondaryTileset->addItems(project->secondaryTilesetLabels); + // Set combo box texts combo_defaultPrimaryTileset->setTextItem(projectConfig.getDefaultPrimaryTileset()); combo_defaultSecondaryTileset->setTextItem(projectConfig.getDefaultSecondaryTileset()); - defaultTilesetsLayout->addRow("Primary Tileset", combo_defaultPrimaryTileset); - defaultTilesetsLayout->addRow("Secondary Tileset", combo_defaultSecondaryTileset); - - // Create Base game version combo box - combo_baseGameVersion = new NoScrollComboBox(ui->widget_BaseGameVersion); - combo_baseGameVersion->addItems(ProjectConfig::baseGameVersions); combo_baseGameVersion->setTextItem(projectConfig.getBaseGameVersionString()); - combo_baseGameVersion->setEditable(false); - ui->layout_BaseGameVersion->insertRow(0, "Base game version", combo_baseGameVersion); - - // Create Attributes size combo box - auto *attributesSizeLayout = new QFormLayout(ui->widget_SizeDropdown); - combo_attributesSize = new NoScrollComboBox(ui->widget_SizeDropdown); - combo_attributesSize->addItems({"1", "2", "4"}); combo_attributesSize->setTextItem(QString::number(projectConfig.getMetatileAttributesSize())); - combo_attributesSize->setEditable(false); - attributesSizeLayout->addRow("", combo_attributesSize); - // Init check boxes + // Set check box states ui->checkBox_UsePoryscript->setChecked(projectConfig.getUsePoryScript()); ui->checkBox_ShowWildEncounterTables->setChecked(userConfig.getEncounterJsonActive()); ui->checkBox_CreateTextFile->setChecked(projectConfig.getCreateMapTextFileEnabled()); @@ -106,71 +154,107 @@ void ProjectSettingsEditor::initUi() { ui->checkBox_OutputCallback->setChecked(projectConfig.getTilesetsHaveCallback()); ui->checkBox_OutputIsCompressed->setChecked(projectConfig.getTilesetsHaveIsCompressed()); - // Init spinners - ui->spinBox_Elevation->setRange(0, 15); + // Set spin box values ui->spinBox_Elevation->setValue(projectConfig.getNewMapElevation()); + ui->spinBox_FillMetatile->setValue(projectConfig.getNewMapMetatileId()); + ui->spinBox_BehaviorMask->setValue(projectConfig.getMetatileBehaviorMask()); + ui->spinBox_EncounterTypeMask->setValue(projectConfig.getMetatileEncounterTypeMask()); + ui->spinBox_LayerTypeMask->setValue(projectConfig.getMetatileLayerTypeMask()); + ui->spinBox_TerrainTypeMask->setValue(projectConfig.getMetatileTerrainTypeMask()); - // Init text boxes - // TODO: Validator for Border Metatiles and Fill Metatile + // Set line edit texts ui->lineEdit_BorderMetatiles->setText(projectConfig.getNewMapBorderMetatileIdsString()); - ui->lineEdit_FillMetatile->setText(projectConfig.getNewMapMetatileIdString()); ui->lineEdit_PrefabsPath->setText(projectConfig.getPrefabFilepath(false)); - QString mask = ProjectConfig::getMaskString(projectConfig.getMetatileBehaviorMask()); - ui->lineEdit_BehaviorMask->setText(mask); - mask = ProjectConfig::getMaskString(projectConfig.getMetatileEncounterTypeMask()); - ui->lineEdit_EncounterTypeMask->setText(mask); - mask = ProjectConfig::getMaskString(projectConfig.getMetatileLayerTypeMask()); - ui->lineEdit_LayerTypeMask->setText(mask); - mask = ProjectConfig::getMaskString(projectConfig.getMetatileTerrainTypeMask()); - ui->lineEdit_TerrainTypeMask->setText(mask); } +// TODO: Certain setting changes may require project reload + void ProjectSettingsEditor::saveFields() { - // TODO - /* - TODO combo_defaultPrimaryTileset - TODO combo_defaultSecondaryTileset - setBaseGameVersion combo_baseGameVersion - TODO combo_attributesSize - setUsePoryScript ui->checkBox_UsePoryscript - userConfig.setEncounterJsonActive ui->checkBox_ShowWildEncounterTables - setCreateMapTextFileEnabled ui->checkBox_CreateTextFile - setPrefabImportPrompted ui->checkBox_PrefabImportPrompted - setTripleLayerMetatilesEnabled ui->checkBox_EnableTripleLayerMetatiles - setHiddenItemRequiresItemfinderEnabled ui->checkBox_EnableRequiresItemfinder - setHiddenItemQuantityEnabled ui->checkBox_EnableQuantity - setEventCloneObjectEnabled ui->checkBox_EnableCloneObjects - setEventWeatherTriggerEnabled ui->checkBox_EnableWeatherTriggers - setEventSecretBaseEnabled ui->checkBox_EnableSecretBases - setHealLocationRespawnDataEnabled ui->checkBox_EnableRespawn - TODO ui->checkBox_EnableAllowFlags - setFloorNumberEnabled ui->checkBox_EnableFloorNumber - setUseCustomBorderSize ui->checkBox_EnableCustomBorderSize - setTilesetsHaveCallback ui->checkBox_OutputCallback - setTilesetsHaveIsCompressed ui->checkBox_OutputIsCompressed - setNewMapElevation ui->spinBox_Elevation - setPrefabFilepath ui->lineEdit_PrefabsPath - TODO ui->lineEdit_BehaviorMask - TODO ui->lineEdit_EncounterTypeMask - TODO ui->lineEdit_LayerTypeMask - TODO ui->lineEdit_TerrainTypeMask - setNewMapMetatileId ui->lineEdit_FillMetatile - setNewMapBorderMetatileIds ui->lineEdit_BorderMetatiles - */ + if (!this->hasUnsavedChanges) + return; + + // Prevent a call to save() for each of the config settings + projectConfig.setSaveDisabled(true); + userConfig.setSaveDisabled(true); + + projectConfig.setDefaultPrimaryTileset(combo_defaultPrimaryTileset->currentText()); + projectConfig.setDefaultSecondaryTileset(combo_defaultSecondaryTileset->currentText()); + projectConfig.setBaseGameVersion(projectConfig.stringToBaseGameVersion(combo_baseGameVersion->currentText())); + projectConfig.setMetatileAttributesSize(combo_attributesSize->currentText().toInt()); + projectConfig.setUsePoryScript(ui->checkBox_UsePoryscript->isChecked()); + userConfig.setEncounterJsonActive(ui->checkBox_ShowWildEncounterTables->isChecked()); + projectConfig.setCreateMapTextFileEnabled(ui->checkBox_CreateTextFile->isChecked()); + projectConfig.setPrefabImportPrompted(ui->checkBox_PrefabImportPrompted->isChecked()); + projectConfig.setTripleLayerMetatilesEnabled(ui->checkBox_EnableTripleLayerMetatiles->isChecked()); + projectConfig.setHiddenItemRequiresItemfinderEnabled(ui->checkBox_EnableRequiresItemfinder->isChecked()); + projectConfig.setHiddenItemQuantityEnabled(ui->checkBox_EnableQuantity->isChecked()); + projectConfig.setEventCloneObjectEnabled(ui->checkBox_EnableCloneObjects->isChecked()); + projectConfig.setEventWeatherTriggerEnabled(ui->checkBox_EnableWeatherTriggers->isChecked()); + projectConfig.setEventSecretBaseEnabled(ui->checkBox_EnableSecretBases->isChecked()); + projectConfig.setHealLocationRespawnDataEnabled(ui->checkBox_EnableRespawn->isChecked()); + projectConfig.setMapAllowFlagsEnabled(ui->checkBox_EnableAllowFlags->isChecked()); + projectConfig.setFloorNumberEnabled(ui->checkBox_EnableFloorNumber->isChecked()); + projectConfig.setUseCustomBorderSize(ui->checkBox_EnableCustomBorderSize->isChecked()); + projectConfig.setTilesetsHaveCallback(ui->checkBox_OutputCallback->isChecked()); + projectConfig.setTilesetsHaveIsCompressed(ui->checkBox_OutputIsCompressed->isChecked()); + projectConfig.setNewMapElevation(ui->spinBox_Elevation->value()); + projectConfig.setNewMapMetatileId(ui->spinBox_FillMetatile->value()); + projectConfig.setMetatileBehaviorMask(ui->spinBox_BehaviorMask->value()); + projectConfig.setMetatileTerrainTypeMask(ui->spinBox_EncounterTypeMask->value()); + projectConfig.setMetatileEncounterTypeMask(ui->spinBox_LayerTypeMask->value()); + projectConfig.setMetatileLayerTypeMask(ui->spinBox_TerrainTypeMask->value()); + projectConfig.setPrefabFilepath(ui->lineEdit_PrefabsPath->text()); + + // Parse border metatile list + QList metatileIdStrings = ui->lineEdit_BorderMetatiles->text().split(","); + QList metatileIds; + for (auto s : metatileIdStrings) { + uint16_t metatileId = s.toUInt(nullptr, 0); + metatileIds.append(qMin(metatileId, static_cast(Project::getNumMetatilesTotal() - 1))); + } + projectConfig.setNewMapBorderMetatileIds(metatileIds); + + projectConfig.setSaveDisabled(false); + projectConfig.save(); + userConfig.setSaveDisabled(false); + userConfig.save(); + + this->hasUnsavedChanges = false; emit saved(); } +// TODO: Standard prompt replacement? +bool ProjectSettingsEditor::prompt(const QString &text) { + QMessageBox messageBox(this); + messageBox.setText(text); + messageBox.setIcon(QMessageBox::Question); + messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); // TODO: Cancel + return messageBox.exec() == QMessageBox::Yes; +} + void ProjectSettingsEditor::dialogButtonClicked(QAbstractButton *button) { auto buttonRole = ui->buttonBox->buttonRole(button); if (buttonRole == QDialogButtonBox::AcceptRole) { - // TODO: Prompt for unsaved changes saveFields(); close(); - } else if (buttonRole == QDialogButtonBox::ResetRole) { - // TODO } else if (buttonRole == QDialogButtonBox::ApplyRole) { saveFields(); + } else if (buttonRole == QDialogButtonBox::ResetRole) { + // Restore Defaults + // TODO: Confirm dialogue? + const QString versionText = combo_baseGameVersion->currentText(); + if (!prompt(QString("Restore default config settings for %1?").arg(versionText))) + return; + projectConfig.reset(projectConfig.stringToBaseGameVersion(versionText)); + projectConfig.save(); + userConfig.reset(); + userConfig.save(); + refresh(); } else if (buttonRole == QDialogButtonBox::RejectRole) { + if (this->hasUnsavedChanges && !prompt(QString("Discard unsaved changes?"))) { + // TODO: + // Unsaved changes prompt + } close(); } // TODO: Save geometry on close diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index e8fc6be5..8c4d67e1 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -348,8 +348,7 @@ void TilesetEditor::drawSelectedTiles() { void TilesetEditor::onHoveredMetatileChanged(uint16_t metatileId) { QString label = Tileset::getMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset); - QString hexString = QString("%1").arg(metatileId, 3, 16, QChar('0')).toUpper(); - QString message = QString("Metatile: 0x%1").arg(hexString); + QString message = QString("Metatile: %1").arg(Metatile::getMetatileIdString(metatileId)); if (label.size() != 0) { message += QString(" \"%1\"").arg(label); }