From 5a3907bf5697c4076d4df24f88e005b8033af530 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 15 Dec 2023 14:33:36 -0500 Subject: [PATCH] Use Block masks to update value limits, parse fieldmap.c --- forms/projectsettingseditor.ui | 297 +++++++++++++++-------------- include/config.h | 37 ++-- include/core/block.h | 8 +- include/mainwindow.h | 4 +- include/project.h | 6 +- include/ui/projectsettingseditor.h | 1 + src/config.cpp | 71 +++---- src/core/block.cpp | 31 ++- src/core/events.cpp | 2 +- src/core/metatile.cpp | 1 + src/editor.cpp | 20 +- src/mainwindow.cpp | 69 +++---- src/project.cpp | 85 ++++++--- src/ui/projectsettingseditor.cpp | 99 +++++++--- src/ui/uintspinbox.cpp | 1 + 15 files changed, 431 insertions(+), 301 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index 72832b4b..02ecf34c 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -21,7 +21,7 @@ - 0 + 1 @@ -370,138 +370,17 @@ 0 0 531 - 566 + 545 - + - New Map Defaults + Map Data Defaults - - - - Fill Metatile - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Border Metatiles - - - - - - - The default metatile value that will be used for the top-left border metatile on new maps. - - - 0x - - - 16 - - - - - - - The default metatile value that will be used for the top-right border metatile on new maps. - - - 0x - - - 16 - - - - - - - The default metatile value that will be used for the bottom-left border metatile on new maps. - - - 0x - - - 16 - - - - - - - The default metatile value that will be used for the bottom-right border metatile on new maps. - - - 0x - - - 16 - - - - - - - - - - The default metatile value that will be used to fill new maps - - - 0x - - - 16 - - - - - - - The default elevation that will be used to fill new maps - - - - - - - Whether a separate text.inc or text.pory file will be created for new maps, alongside the scripts file - - - Create separate text file - - - - - - - Collision - - - - + @@ -516,34 +395,166 @@ 0 - - - - Border Metatiles - - - - - - - A comma-separated list of metatile values that will be used to fill new map borders - - - + + + + Border Metatiles + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + A comma-separated list of metatile values that will be used to fill new map borders + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + The default metatile value that will be used for the top-left border metatile on new maps. + + + 0x + + + 16 + + + + + + + The default metatile value that will be used for the top-right border metatile on new maps. + + + 0x + + + 16 + + + + + + + The default metatile value that will be used for the bottom-left border metatile on new maps. + + + 0x + + + 16 + + + + + + + The default metatile value that will be used for the bottom-right border metatile on new maps. + + + 0x + + + 16 + + + + + + + + + + + Fill Metatile + + + + + + + Elevation + + + + + + + Collision + + + + + + + The default metatile value that will be used to fill new maps + + + 0x + + + 16 + + + + + + The default elevation that will be used to fill new maps + + + + The default collision that will be used to fill new maps - - + + + + Whether a separate text.inc or text.pory file will be created for new maps, alongside the scripts file + - Elevation + Create separate text file diff --git a/include/config.h b/include/config.h index dd6c90c5..7605f0d6 100644 --- a/include/config.h +++ b/include/config.h @@ -214,6 +214,7 @@ enum ProjectFilePath { constants_species, constants_fieldmap, global_fieldmap, + fieldmap, initial_facing_table, pokemon_icon_table, pokemon_gfx, @@ -230,9 +231,9 @@ public: // Reset non-version-specific settings this->usePoryScript = false; this->enableTripleLayerMetatiles = false; - this->newMapMetatileId = 1; - this->newMapElevation = 3; - this->newMapCollision = 0; + this->defaultMetatileId = 1; + this->defaultElevation = 3; + this->defaultCollision = 0; this->defaultPrimaryTileset = "gTileset_General"; this->prefabFilepath = QString(); this->prefabImportPrompted = false; @@ -283,12 +284,12 @@ public: bool getTripleLayerMetatilesEnabled(); int getNumLayersInMetatile(); int getNumTilesInMetatile(); - void setNewMapMetatileId(uint16_t metatileId); - uint16_t getNewMapMetatileId(); - void setNewMapElevation(int elevation); - int getNewMapElevation(); - void setNewMapCollision(int collision); - int getNewMapCollision(); + void setDefaultMetatileId(uint16_t metatileId); + uint16_t getDefaultMetatileId(); + void setDefaultElevation(int elevation); + int getDefaultElevation(); + void setDefaultCollision(int collision); + int getDefaultCollision(); void setNewMapBorderMetatileIds(QList metatileIds); QList getNewMapBorderMetatileIds(); QString getDefaultPrimaryTileset(); @@ -337,6 +338,18 @@ public: void setCollisionSheetHeight(int height); int getCollisionSheetHeight(); + // TODO: Replace these once there's generic support for editing project names + static const QString metatileIdMaskName; + static const QString collisionMaskName; + static const QString elevationMaskName; + static const QString behaviorMaskName; + static const QString layerTypeMaskName; + static const QString behaviorTableName; + static const QString layerTypeTableName; + static const QString terrainTypeTableName; + static const QString encounterTypeTableName; + static const QString attrTableName; + protected: virtual QString getConfigFilepath() override; virtual void parseConfigKeyValue(QString key, QString value) override; @@ -358,9 +371,9 @@ private: bool enableFloorNumber; bool createMapTextFile; bool enableTripleLayerMetatiles; - uint16_t newMapMetatileId; - int newMapElevation; - int newMapCollision; + uint16_t defaultMetatileId; + uint16_t defaultElevation; + uint16_t defaultCollision; QList newMapBorderMetatileIds; QString defaultPrimaryTileset; QString defaultSecondaryTileset; diff --git a/include/core/block.h b/include/core/block.h index 827ac963..94c674c3 100644 --- a/include/core/block.h +++ b/include/core/block.h @@ -26,10 +26,12 @@ public: static uint16_t getMaxCollision(); static uint16_t getMaxElevation(); + static const uint16_t maxValue; + private: - uint16_t m_metatileId; // 10 - uint16_t m_collision; // 2 - uint16_t m_elevation; // 4 + uint16_t m_metatileId; + uint16_t m_collision; + uint16_t m_elevation; }; #endif // BLOCK_H diff --git a/include/mainwindow.h b/include/mainwindow.h index 7bbe5475..9c9d270b 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -350,7 +350,7 @@ private: void openSubWindow(QWidget * window); QString getExistingDirectory(QString); bool openProject(QString dir); - QString getDefaultMap(); + bool setInitialMap(); void setRecentMap(QString map_name); QStandardItem* createMapItem(QString mapName, int groupNum, int inGroupNum); @@ -372,7 +372,7 @@ private: void initMapSortOrder(); void initShortcuts(); void initExtraShortcuts(); - void setProjectSpecificUI(); + bool setProjectSpecificUI(); void setWildEncountersUIEnabled(bool enabled); void loadUserSettings(); void applyMapListFilter(QString filterText); diff --git a/include/project.h b/include/project.h index 39bd6fe7..ee727f07 100644 --- a/include/project.h +++ b/include/project.h @@ -85,11 +85,7 @@ public: QMap modifiedFileTimestamps; bool usingAsmTilesets; QString importExportPath; - bool parsedMetatileIdMask; - bool parsedCollisionMask; - bool parsedElevationMask; - bool parsedBehaviorMask; - bool parsedLayerTypeMask; + QSet disabledSettingsNames; void set_root(QString); diff --git a/include/ui/projectsettingseditor.h b/include/ui/projectsettingseditor.h index 4e383275..2510e788 100644 --- a/include/ui/projectsettingseditor.h +++ b/include/ui/projectsettingseditor.h @@ -54,6 +54,7 @@ private: void chooseImageFile(QLineEdit * filepathEdit); void chooseFile(QLineEdit * filepathEdit, const QString &description, const QString &extensions); QString stripProjectDir(QString s); + void disableParsedSetting(QWidget * widget, const QString &name, const QString &filepath); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/src/config.cpp b/src/config.cpp index 2517406e..ceb7ad01 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -16,6 +16,17 @@ #include #include +const QString ProjectConfig::metatileIdMaskName = "MAPGRID_METATILE_ID_MASK"; +const QString ProjectConfig::collisionMaskName = "MAPGRID_COLLISION_MASK"; +const QString ProjectConfig::elevationMaskName = "MAPGRID_ELEVATION_MASK"; +const QString ProjectConfig::behaviorMaskName = "METATILE_ATTR_BEHAVIOR_MASK"; +const QString ProjectConfig::layerTypeMaskName = "METATILE_ATTR_LAYER_MASK"; +const QString ProjectConfig::behaviorTableName = "METATILE_ATTRIBUTE_BEHAVIOR"; +const QString ProjectConfig::layerTypeTableName = "METATILE_ATTRIBUTE_LAYER_TYPE"; +const QString ProjectConfig::terrainTypeTableName = "METATILE_ATTRIBUTE_TERRAIN"; +const QString ProjectConfig::encounterTypeTableName = "METATILE_ATTRIBUTE_ENCOUNTER_TYPE"; +const QString ProjectConfig::attrTableName = "sMetatileAttrMasks"; + const QMap> ProjectConfig::defaultPaths = { {ProjectFilePath::data_map_folders, { "data_map_folders", "data/maps/"}}, {ProjectFilePath::data_scripts_folders, { "data_scripts_folders", "data/scripts/"}}, @@ -60,6 +71,7 @@ const QMap> ProjectConfig::defaultP {ProjectFilePath::constants_species, { "constants_species", "include/constants/species.h"}}, {ProjectFilePath::constants_fieldmap, { "constants_fieldmap", "include/fieldmap.h"}}, {ProjectFilePath::global_fieldmap, { "global_fieldmap", "include/global.fieldmap.h"}}, + {ProjectFilePath::fieldmap, { "fieldmap", "src/fieldmap.c"}}, {ProjectFilePath::pokemon_icon_table, { "pokemon_icon_table", "src/pokemon_icon.c"}}, {ProjectFilePath::initial_facing_table, { "initial_facing_table", "src/event_object_movement.c"}}, {ProjectFilePath::pokemon_gfx, { "pokemon_gfx", "graphics/pokemon/"}}, @@ -653,21 +665,17 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->createMapTextFile = getConfigBool(key, value); } else if (key == "enable_triple_layer_metatiles") { this->enableTripleLayerMetatiles = getConfigBool(key, value); - } else if (key == "new_map_metatile") { - // TODO: Update max once Block layout can be edited - this->newMapMetatileId = getConfigUint32(key, value, 0, 1023, 0); - } else if (key == "new_map_elevation") { - // TODO: Update max once Block layout can be edited - this->newMapElevation = getConfigInteger(key, value, 0, 15, 3); - } else if (key == "new_map_collision") { - // TODO: Update max once Block layout can be edited - this->newMapCollision = getConfigInteger(key, value, 0, 3, 0); + } else if (key == "default_metatile") { + this->defaultMetatileId = getConfigUint32(key, value, 0, Block::maxValue); + } else if (key == "default_elevation") { + this->defaultElevation = getConfigUint32(key, value, 0, Block::maxValue); + } else if (key == "default_collision") { + this->defaultCollision = getConfigUint32(key, value, 0, Block::maxValue); } else if (key == "new_map_border_metatiles") { this->newMapBorderMetatileIds.clear(); QList metatileIds = value.split(","); for (int i = 0; i < metatileIds.size(); i++) { - // TODO: Update max once Block layout can be edited - int metatileId = getConfigUint32(key, metatileIds.at(i), 0, 1023, 0); + int metatileId = getConfigUint32(key, metatileIds.at(i), 0, Block::maxValue); this->newMapBorderMetatileIds.append(metatileId); } } else if (key == "default_primary_tileset") { @@ -690,11 +698,11 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "metatile_layer_type_mask") { this->metatileLayerTypeMask = getConfigUint32(key, value); } else if (key == "block_metatile_id_mask") { - this->blockMetatileIdMask = getConfigUint32(key, value, 1, 0xFFFF); + this->blockMetatileIdMask = getConfigUint32(key, value, 0, Block::maxValue); } else if (key == "block_collision_mask") { - this->blockCollisionMask = getConfigUint32(key, value, 0, 0xFFFE); + this->blockCollisionMask = getConfigUint32(key, value, 0, Block::maxValue); } else if (key == "block_elevation_mask") { - this->blockElevationMask = getConfigUint32(key, value, 0, 0xFFFE); + this->blockElevationMask = getConfigUint32(key, value, 0, Block::maxValue); } else if (key == "enable_map_allow_flags") { this->enableMapAllowFlags = getConfigBool(key, value); #ifdef CONFIG_BACKWARDS_COMPATABILITY @@ -735,10 +743,9 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "collision_sheet_path") { this->collisionSheetPath = value; } else if (key == "collision_sheet_width") { - // TODO: Update max once Block layout can be edited (0x7FFF for 15 bits) - this->collisionSheetWidth = getConfigInteger(key, value, 1, 4, 2); + this->collisionSheetWidth = getConfigUint32(key, value, 1, Block::maxValue); } else if (key == "collision_sheet_height") { - this->collisionSheetHeight = getConfigInteger(key, value, 1, 16, 16); + this->collisionSheetHeight = getConfigUint32(key, value, 1, Block::maxValue); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -788,9 +795,9 @@ 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", Metatile::getMetatileIdString(this->newMapMetatileId)); - map.insert("new_map_elevation", QString::number(this->newMapElevation)); - map.insert("new_map_collision", QString::number(this->newMapCollision)); + map.insert("default_metatile", Metatile::getMetatileIdString(this->defaultMetatileId)); + map.insert("default_elevation", QString::number(this->defaultElevation)); + map.insert("default_collision", QString::number(this->defaultCollision)); map.insert("new_map_border_metatiles", Metatile::getMetatileIdStringList(this->newMapBorderMetatileIds)); map.insert("default_primary_tileset", this->defaultPrimaryTileset); map.insert("default_secondary_tileset", this->defaultSecondaryTileset); @@ -1027,31 +1034,31 @@ int ProjectConfig::getNumTilesInMetatile() { return this->enableTripleLayerMetatiles ? 12 : 8; } -void ProjectConfig::setNewMapMetatileId(uint16_t metatileId) { - this->newMapMetatileId = metatileId; +void ProjectConfig::setDefaultMetatileId(uint16_t metatileId) { + this->defaultMetatileId = metatileId; this->save(); } -uint16_t ProjectConfig::getNewMapMetatileId() { - return this->newMapMetatileId; +uint16_t ProjectConfig::getDefaultMetatileId() { + return this->defaultMetatileId; } -void ProjectConfig::setNewMapElevation(int elevation) { - this->newMapElevation = elevation; +void ProjectConfig::setDefaultElevation(int elevation) { + this->defaultElevation = elevation; this->save(); } -int ProjectConfig::getNewMapElevation() { - return this->newMapElevation; +int ProjectConfig::getDefaultElevation() { + return this->defaultElevation; } -void ProjectConfig::setNewMapCollision(int collision) { - this->newMapCollision = collision; +void ProjectConfig::setDefaultCollision(int collision) { + this->defaultCollision = collision; this->save(); } -int ProjectConfig::getNewMapCollision() { - return this->newMapCollision; +int ProjectConfig::getDefaultCollision() { + return this->defaultCollision; } void ProjectConfig::setNewMapBorderMetatileIds(QList metatileIds) { diff --git a/src/core/block.cpp b/src/core/block.cpp index 26eee906..1ef1c703 100644 --- a/src/core/block.cpp +++ b/src/core/block.cpp @@ -2,6 +2,9 @@ #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); @@ -43,16 +46,32 @@ uint16_t Block::rawValue() const { | bitsElevation.pack(m_elevation); } -// TODO: Resolve TODOs for max block limits, and disable collision tab if collision and elevation are 0 -// TODO: After parsing, recalc max collision/elevation for selector image (in Metatile::setLayout?) -// TODO: More generous config limits -// TODO: Settings editor -- disable UI & restore after refresh, red flag overlapping masks -// TODO: Generalize API tab disabling, i.e. check if disabled before allowing selection -// TODO: Metatile selector looks like it's having a fit during group block select +// TODO: After parsing, recalc config (or parsed!) values that depend on max collision/elevation +/* - newMapMetatileId + - newMapElevation + - newMapCollision + - newMapBorderMetatileIds + - collisionSheetWidth + - collisionSheetHeight + - NUM_METATILES_IN_PRIMARY + - event elevations + - metatile labels? + +*/ +// TODO: Settings editor -- disable UI & restore after refresh void Block::setLayout() { bitsMetatileId.setMask(projectConfig.getBlockMetatileIdMask()); bitsCollision.setMask(projectConfig.getBlockCollisionMask()); bitsElevation.setMask(projectConfig.getBlockElevationMask()); + + // Some settings may need to be reevaluated based on the layout + /*uint16_t metatileId = projectConfig.getNewMapMetatileId(); + if (bitsMetatileId.clamp(metatileId) != metatileId) + projectConfig.setNewMapMetatileId(bitsMetatileId.clamp(metatileId)); + uint16_t metatileId = projectConfig.getNewMapMetatileId(); + if (bitsMetatileId.clamp(metatileId) != metatileId) + projectConfig.setNewMapMetatileId(bitsMetatileId.clamp(metatileId));*/ + } bool Block::operator ==(Block other) const { diff --git a/src/core/events.cpp b/src/core/events.cpp index 05f6eb35..585c68e1 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -35,7 +35,7 @@ int Event::getEventIndex() { void Event::setDefaultValues(Project *) { this->setX(0); this->setY(0); - this->setElevation(3); + this->setElevation(projectConfig.getDefaultElevation()); } void Event::readCustomValues(QJsonObject values) { diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index ca706bda..937db58f 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -104,6 +104,7 @@ void Metatile::setLayout(Project * project) { uint32_t encounterTypeMask = projectConfig.getMetatileEncounterTypeMask() & maxMask; uint32_t layerTypeMask = projectConfig.getMetatileLayerTypeMask() & maxMask; + // TODO: Overlap handling to settings editor; set red text box with similar text warning if overlapping // Overlapping masks are technically ok, but probably not intended. // Additionally, Porymap will not properly reflect that the values are linked. if (doMasksOverlap({behaviorMask, terrainTypeMask, encounterTypeMask, layerTypeMask})) { diff --git a/src/editor.cpp b/src/editor.cpp index b21a7749..c4d314ff 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1080,16 +1080,16 @@ void Editor::onHoveredMapMovementPermissionCleared() { QString Editor::getMovementPermissionText(uint16_t collision, uint16_t elevation) { QString message; - if (collision == 0 && elevation == 0) { - message = "Collision: Transition between elevations"; - } else if (collision == 0 && elevation == 15) { - message = "Collision: Multi-Level (Bridge)"; - } else if (collision == 0 && elevation == 1) { - message = "Collision: Surf"; - } else if (collision == 0) { - message = QString("Collision: Passable, Elevation: %1").arg(elevation); - } else { + if (collision != 0) { message = QString("Collision: Impassable (%1), Elevation: %2").arg(collision).arg(elevation); + } else if (elevation == 0) { + message = "Collision: Transition between elevations"; + } else if (elevation == 15) { + message = "Collision: Multi-Level (Bridge)"; + } else if (elevation == 1) { + message = "Collision: Surf"; + } else { + message = QString("Collision: Passable, Elevation: %1").arg(elevation); } return message; } @@ -1497,7 +1497,7 @@ void Editor::displayMovementPermissionSelector() { connect(movement_permissions_selector_item, &SelectablePixmapItem::selectionChanged, [this](int x, int y, int, int) { this->setCollisionTabSpinBoxes(x, y); }); - movement_permissions_selector_item->select(projectConfig.getNewMapCollision(), projectConfig.getNewMapElevation()); + movement_permissions_selector_item->select(projectConfig.getDefaultCollision(), projectConfig.getDefaultElevation()); } scene_collision_metatiles->addItem(movement_permissions_selector_item); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f0d166a7..1fec8054 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -372,8 +372,8 @@ void MainWindow::setWildEncountersUIEnabled(bool enabled) { ui->mainTabBar->setTabEnabled(4, enabled); } -// Update the UI using information we've read from the user's project config file. -void MainWindow::setProjectSpecificUI() +// Update the UI using information we've read from the user's project files. +bool MainWindow::setProjectSpecificUI() { this->setWildEncountersUIEnabled(userConfig.getEncounterJsonActive()); @@ -397,6 +397,7 @@ void MainWindow::setProjectSpecificUI() editor->setCollisionGraphics(); ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation()); ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision()); + return true; } void MainWindow::mapSortOrder_changed(QAction *action) @@ -513,14 +514,12 @@ bool MainWindow::openProject(QString dir) { this->statusBar()->showMessage(QString("Opening project %1").arg(nativeDir)); - bool success = true; userConfig.setProjectDir(dir); userConfig.load(); projectConfig.setProjectDir(dir); projectConfig.load(); this->closeSupplementaryWindows(); - this->setProjectSpecificUI(); this->newMapDefaultsSet = false; Scripting::init(this); @@ -537,19 +536,18 @@ bool MainWindow::openProject(QString dir) { this->preferenceEditor->updateFields(); }); editor->project->set_root(dir); - success = loadDataStructures() - && populateMapList() - && setMap(getDefaultMap(), true); } else { - QString open_map = editor->map->name; editor->project->fileWatcher.removePaths(editor->project->fileWatcher.files()); editor->project->clearMapCache(); editor->project->clearTilesetCache(); - success = loadDataStructures() && populateMapList() && setMap(open_map, true); } - - projectOpenFailure = !success; - if (projectOpenFailure) { + + this->projectOpenFailure = !(loadDataStructures() + && setProjectSpecificUI() + && populateMapList() + && setInitialMap()); + + if (this->projectOpenFailure) { this->statusBar()->showMessage(QString("Failed to open project %1").arg(nativeDir)); QMessageBox msgBox(this); QString errorMsg = QString("There was an error opening the project %1. Please see %2 for full error details.\n\n%3") @@ -576,28 +574,31 @@ bool MainWindow::isProjectOpen() { return !projectOpenFailure && editor && editor->project; } -QString MainWindow::getDefaultMap() { - if (editor && editor->project) { - QList names = editor->project->groupedMapNames; - if (!names.isEmpty()) { - QString recentMap = userConfig.getRecentMap(); - if (!recentMap.isNull() && recentMap.length() > 0) { - for (int i = 0; i < names.length(); i++) { - if (names.value(i).contains(recentMap)) { - return recentMap; - } - } - } - // Failing that, just get the first map in the list. - for (int i = 0; i < names.length(); i++) { - QStringList list = names.value(i); - if (list.length()) { - return list.value(0); - } +bool MainWindow::setInitialMap() { + QList names; + if (editor && editor->project) + names = editor->project->groupedMapNames; + + QString recentMap = userConfig.getRecentMap(); + if (!recentMap.isEmpty()) { + // Make sure the recent map is still in the map list + for (int i = 0; i < names.length(); i++) { + if (names.value(i).contains(recentMap)) { + return setMap(recentMap, true); } } } - return QString(); + + // Failing that, just get the first map in the list. + for (int i = 0; i < names.length(); i++) { + QStringList list = names.value(i); + if (list.length()) { + return setMap(list.value(0), true); + } + } + + logError("Failed to load any map names."); + return false; } void MainWindow::openSubWindow(QWidget * window) { @@ -654,7 +655,7 @@ bool MainWindow::setMap(QString map_name, bool scrollTreeView) { return false; } - if (!editor->setMap(map_name)) { + if (!editor || !editor->setMap(map_name)) { logWarn(QString("Failed to set map to '%1'").arg(map_name)); return false; } @@ -1461,8 +1462,8 @@ void MainWindow::copy() { collisions.clear(); for (int i = 0; i < metatiles.length(); i++) { OrderedJson::object collision; - collision["collision"] = 0; - collision["elevation"] = 3; + collision["collision"] = projectConfig.getDefaultCollision(); + collision["elevation"] = projectConfig.getDefaultElevation(); collisions.append(collision); } } diff --git a/src/project.cpp b/src/project.cpp index 80168726..d2992f61 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -297,7 +297,7 @@ bool Project::loadMapData(Map* map) { heal->setMap(map); heal->setX(loc.x); heal->setY(loc.y); - heal->setElevation(3); + heal->setElevation(projectConfig.getDefaultElevation()); heal->setLocationName(loc.mapName); heal->setIdName(loc.idName); heal->setIndex(loc.index); @@ -1109,7 +1109,7 @@ void Project::setNewMapBlockdata(Map *map) { map->layout->blockdata.clear(); int width = map->getWidth(); int height = map->getHeight(); - Block block(projectConfig.getNewMapMetatileId(), projectConfig.getNewMapCollision(), projectConfig.getNewMapElevation()); + Block block(projectConfig.getDefaultMetatileId(), projectConfig.getDefaultCollision(), projectConfig.getDefaultElevation()); for (int i = 0; i < width * height; i++) { map->layout->blockdata.append(block); } @@ -1914,37 +1914,78 @@ bool Project::readTilesetProperties() { } // Read data masks for Blocks and metatile attributes. -// These settings are exposed in the settings window. If any are parsed from -// the project they'll be visible in the settings window but not editable. bool Project::readFieldmapMasks() { // We're looking for the suffix "_MASK". Technically our "prefix" is the whole define. static const QStringList definePrefixes{ "\\b\\w+_MASK" }; - QString filename = projectConfig.getFilePath(ProjectFilePath::global_fieldmap); - fileWatcher.addPath(root + "/" + filename); - QMap defines = parser.readCDefines(filename, definePrefixes); + QString globalFieldmap = projectConfig.getFilePath(ProjectFilePath::global_fieldmap); + fileWatcher.addPath(root + "/" + globalFieldmap); + QMap defines = parser.readCDefines(globalFieldmap, definePrefixes); - auto it = defines.find("MAPGRID_METATILE_ID_MASK"); - if ((parsedMetatileIdMask = (it != defines.end()))) + // These mask values are accessible via the settings editor for users who don't have these defines. + // If users do have the defines we disable them in the settings editor and direct them to their project files. + // Record the names we read so we know later which settings to disable. + const QStringList defineNames = defines.keys(); + this->disabledSettingsNames = QSet(defineNames.constBegin(), defineNames.constEnd()); + + // Avoid repeatedly writing the config file + projectConfig.setSaveDisabled(true); + + // Read Block masks + auto it = defines.find(ProjectConfig::metatileIdMaskName); + if (it != defines.end()) projectConfig.setBlockMetatileIdMask(static_cast(it.value())); - - it = defines.find("MAPGRID_COLLISION_MASK"); - if ((parsedCollisionMask = (it != defines.end()))) + it = defines.find(ProjectConfig::collisionMaskName); + if (it != defines.end()) projectConfig.setBlockCollisionMask(static_cast(it.value())); - - it = defines.find("MAPGRID_ELEVATION_MASK"); - if ((parsedElevationMask = (it != defines.end()))) + it = defines.find(ProjectConfig::elevationMaskName); + if (it != defines.end()) projectConfig.setBlockElevationMask(static_cast(it.value())); - // TODO: For FRLG, parse from fieldmap.c? - - it = defines.find("METATILE_ATTR_BEHAVIOR_MASK"); - if ((parsedBehaviorMask = (it != defines.end()))) + // Read RSE metatile attribute masks + it = defines.find(ProjectConfig::behaviorMaskName); + if (it != defines.end()) projectConfig.setMetatileBehaviorMask(static_cast(it.value())); - - it = defines.find("METATILE_ATTR_LAYER_MASK"); - if ((parsedLayerTypeMask = (it != defines.end()))) + it = defines.find(ProjectConfig::layerTypeMaskName); + if (it != defines.end()) projectConfig.setMetatileLayerTypeMask(static_cast(it.value())); + // pokefirered keeps its attribute masks in a separate table, parse this too. + QString srcFieldmap = projectConfig.getFilePath(ProjectFilePath::fieldmap); + const QMap attrTable = parser.readNamedIndexCArray(srcFieldmap, ProjectConfig::attrTableName); + if (!attrTable.isEmpty()) { + fileWatcher.addPath(root + "/" + srcFieldmap); + bool ok; + // Read terrain type mask + uint32_t mask = attrTable.value(ProjectConfig::terrainTypeTableName).toUInt(&ok, 0); + if (ok) { + projectConfig.setMetatileTerrainTypeMask(mask); + this->disabledSettingsNames.insert(ProjectConfig::terrainTypeTableName); + } + // Read encounter type mask + mask = attrTable.value(ProjectConfig::encounterTypeTableName).toUInt(&ok, 0); + if (ok) { + projectConfig.setMetatileEncounterTypeMask(mask); + this->disabledSettingsNames.insert(ProjectConfig::encounterTypeTableName); + } + // If we haven't already parsed behavior and layer type then try those too + if (!this->disabledSettingsNames.contains(ProjectConfig::behaviorMaskName)) { + // Read behavior mask + mask = attrTable.value(ProjectConfig::behaviorTableName).toUInt(&ok, 0); + if (ok) { + projectConfig.setMetatileBehaviorMask(mask); + this->disabledSettingsNames.insert(ProjectConfig::behaviorTableName); + } + } + if (!this->disabledSettingsNames.contains(ProjectConfig::layerTypeMaskName)) { + // Read layer type mask + mask = attrTable.value(ProjectConfig::layerTypeTableName).toUInt(&ok, 0); + if (ok) { + projectConfig.setMetatileLayerTypeMask(mask); + this->disabledSettingsNames.insert(ProjectConfig::layerTypeTableName); + } + } + } + projectConfig.setSaveDisabled(false); return true; } diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index e8d676a8..e2387fe8 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -87,7 +87,6 @@ void ProjectSettingsEditor::initUi() { ui->comboBox_DefaultPrimaryTileset->addItems(project->primaryTilesetLabels); ui->comboBox_DefaultSecondaryTileset->addItems(project->secondaryTilesetLabels); ui->comboBox_IconSpecies->addItems(project->speciesToIconPath.keys()); - ui->comboBox_IconSpecies->setEditable(false); } ui->comboBox_BaseGameVersion->addItems(ProjectConfig::versionStrings); ui->comboBox_AttributesSize->addItems({"1", "2", "4"}); @@ -96,14 +95,14 @@ void ProjectSettingsEditor::initUi() { ui->mainTabs->setCurrentIndex(porymapConfig.getProjectSettingsTab()); // Validate that the border metatiles text is a comma-separated list of metatile values - const QString regex_Hex = "(0[xX])?[A-Fa-f0-9]+"; + static const QString regex_Hex = "(0[xX])?[A-Fa-f0-9]+"; static const QRegularExpression expression(QString("^(%1,)*%1$").arg(regex_Hex)); // Comma-separated list of hex values QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression); ui->lineEdit_BorderMetatiles->setValidator(validator); this->setBorderMetatilesUi(projectConfig.getUseCustomBorderSize()); // Set spin box limits - int maxMetatileId = Block::getMaxMetatileId(); + uint16_t maxMetatileId = Block::getMaxMetatileId(); ui->spinBox_FillMetatile->setMaximum(maxMetatileId); ui->spinBox_BorderMetatile1->setMaximum(maxMetatileId); ui->spinBox_BorderMetatile2->setMaximum(maxMetatileId); @@ -113,16 +112,52 @@ void ProjectSettingsEditor::initUi() { ui->spinBox_Collision->setMaximum(Block::getMaxCollision()); ui->spinBox_MaxElevation->setMaximum(Block::getMaxElevation()); ui->spinBox_MaxCollision->setMaximum(Block::getMaxCollision()); - // TODO: Move to a global - ui->spinBox_MetatileIdMask->setMinimum(0x1); - ui->spinBox_MetatileIdMask->setMaximum(0xFFFF); // Metatile IDs can use all 16 bits of a block - ui->spinBox_CollisionMask->setMaximum(0xFFFE); // Collision/elevation can only use 15; metatile IDs must have at least 1 bit - ui->spinBox_ElevationMask->setMaximum(0xFFFE); + //ui->spinBox_MetatileIdMask->setMinimum(0x1); + ui->spinBox_MetatileIdMask->setMaximum(Block::maxValue); + ui->spinBox_CollisionMask->setMaximum(Block::maxValue); + ui->spinBox_ElevationMask->setMaximum(Block::maxValue); + + // Some settings can be determined by constants in the project. + // We reflect that here by disabling their UI elements. + if (project) { + const QString maskFilepath = projectConfig.getFilePath(ProjectFilePath::global_fieldmap); + const QString attrTableFilepath = projectConfig.getFilePath(ProjectFilePath::fieldmap); + + // Block masks + if (project->disabledSettingsNames.contains(ProjectConfig::metatileIdMaskName)) + this->disableParsedSetting(ui->spinBox_MetatileIdMask, ProjectConfig::metatileIdMaskName, maskFilepath); + if (project->disabledSettingsNames.contains(ProjectConfig::collisionMaskName)) + this->disableParsedSetting(ui->spinBox_CollisionMask, ProjectConfig::collisionMaskName, maskFilepath); + if (project->disabledSettingsNames.contains(ProjectConfig::elevationMaskName)) + this->disableParsedSetting(ui->spinBox_ElevationMask, ProjectConfig::elevationMaskName, maskFilepath); + + // Behavior mask + if (project->disabledSettingsNames.contains(ProjectConfig::behaviorMaskName)) + this->disableParsedSetting(ui->spinBox_BehaviorMask, ProjectConfig::behaviorMaskName, maskFilepath); + else if (project->disabledSettingsNames.contains(ProjectConfig::behaviorTableName)) + this->disableParsedSetting(ui->spinBox_BehaviorMask, ProjectConfig::attrTableName, attrTableFilepath); + + // Layer type mask + if (project->disabledSettingsNames.contains(ProjectConfig::layerTypeMaskName)) + this->disableParsedSetting(ui->spinBox_LayerTypeMask, ProjectConfig::layerTypeMaskName, maskFilepath); + else if (project->disabledSettingsNames.contains(ProjectConfig::layerTypeTableName)) + this->disableParsedSetting(ui->spinBox_LayerTypeMask, ProjectConfig::attrTableName, attrTableFilepath); + + // Encounter and terrain type masks + if (project->disabledSettingsNames.contains(ProjectConfig::terrainTypeTableName)) + this->disableParsedSetting(ui->spinBox_TerrainTypeMask, ProjectConfig::attrTableName, attrTableFilepath); + if (project->disabledSettingsNames.contains(ProjectConfig::encounterTypeTableName)) + this->disableParsedSetting(ui->spinBox_EncounterTypeMask, ProjectConfig::attrTableName, attrTableFilepath); + } +} + +void ProjectSettingsEditor::disableParsedSetting(QWidget * widget, const QString &name, const QString &filepath) { + widget->setEnabled(false); + widget->setToolTip(QString("This value has been read from '%1' in %2").arg(name).arg(filepath)); } void ProjectSettingsEditor::setBorderMetatilesUi(bool customSize) { - ui->widget_DefaultSizeBorderMetatiles->setVisible(!customSize); - ui->widget_CustomSizeBorderMetatiles->setVisible(customSize); + ui->stackedWidget_BorderMetatiles->setCurrentIndex(customSize ? 0 : 1); } void ProjectSettingsEditor::setBorderMetatileIds(bool customSize, QList metatileIds) { @@ -155,7 +190,7 @@ QList ProjectSettingsEditor::getBorderMetatileIds(bool customSize) { } void ProjectSettingsEditor::updateAttributeLimits(const QString &attrSize) { - QMap limits { + static const QMap limits { {"1", 0xFF}, {"2", 0xFFFF}, {"4", 0xFFFFFFFF}, @@ -170,20 +205,19 @@ void ProjectSettingsEditor::updateAttributeLimits(const QString &attrSize) { // Only one icon path is displayed at a time, so we need to keep track of the rest, // and update the path edit when the user changes the selected species. // The existing icon path map in ProjectConfig is left alone to allow unsaved changes. -void ProjectSettingsEditor::updatePokemonIconPath(const QString &species) { +void ProjectSettingsEditor::updatePokemonIconPath(const QString &newSpecies) { if (!project) return; // If user was editing a path for a valid species, record filepath text before we wipe it. - if (!this->prevIconSpecies.isEmpty() && this->project->speciesToIconPath.contains(species)) { + if (!this->prevIconSpecies.isEmpty() && this->project->speciesToIconPath.contains(this->prevIconSpecies)) this->editedPokemonIconPaths[this->prevIconSpecies] = ui->lineEdit_PokemonIcon->text(); - } - QString editedPath = this->editedPokemonIconPaths.value(species); - QString defaultPath = this->project->speciesToIconPath.value(species); + QString editedPath = this->editedPokemonIconPaths.value(newSpecies); + QString defaultPath = this->project->speciesToIconPath.value(newSpecies); ui->lineEdit_PokemonIcon->setText(this->stripProjectDir(editedPath)); ui->lineEdit_PokemonIcon->setPlaceholderText(this->stripProjectDir(defaultPath)); - this->prevIconSpecies = species; + this->prevIconSpecies = newSpecies; } void ProjectSettingsEditor::createProjectPathsTable() { @@ -282,18 +316,18 @@ void ProjectSettingsEditor::refresh() { ui->checkBox_OutputIsCompressed->setChecked(projectConfig.getTilesetsHaveIsCompressed()); // Set spin box values - ui->spinBox_Elevation->setValue(projectConfig.getNewMapElevation()); - ui->spinBox_Collision->setValue(projectConfig.getNewMapCollision()); - ui->spinBox_FillMetatile->setValue(projectConfig.getNewMapMetatileId()); + ui->spinBox_Elevation->setValue(projectConfig.getDefaultElevation()); + ui->spinBox_Collision->setValue(projectConfig.getDefaultCollision()); + ui->spinBox_FillMetatile->setValue(projectConfig.getDefaultMetatileId()); ui->spinBox_MaxElevation->setValue(projectConfig.getCollisionSheetHeight() - 1); ui->spinBox_MaxCollision->setValue(projectConfig.getCollisionSheetWidth() - 1); - ui->spinBox_BehaviorMask->setValue(projectConfig.getMetatileBehaviorMask()); - ui->spinBox_EncounterTypeMask->setValue(projectConfig.getMetatileEncounterTypeMask()); - ui->spinBox_LayerTypeMask->setValue(projectConfig.getMetatileLayerTypeMask()); - ui->spinBox_TerrainTypeMask->setValue(projectConfig.getMetatileTerrainTypeMask()); - ui->spinBox_MetatileIdMask->setValue(projectConfig.getBlockMetatileIdMask()); - ui->spinBox_CollisionMask->setValue(projectConfig.getBlockCollisionMask()); - ui->spinBox_ElevationMask->setValue(projectConfig.getBlockElevationMask()); + ui->spinBox_BehaviorMask->setValue(projectConfig.getMetatileBehaviorMask() & ui->spinBox_BehaviorMask->maximum()); + ui->spinBox_EncounterTypeMask->setValue(projectConfig.getMetatileEncounterTypeMask() & ui->spinBox_EncounterTypeMask->maximum()); + ui->spinBox_LayerTypeMask->setValue(projectConfig.getMetatileLayerTypeMask() & ui->spinBox_LayerTypeMask->maximum()); + ui->spinBox_TerrainTypeMask->setValue(projectConfig.getMetatileTerrainTypeMask() & ui->spinBox_TerrainTypeMask->maximum()); + ui->spinBox_MetatileIdMask->setValue(projectConfig.getBlockMetatileIdMask() & ui->spinBox_MetatileIdMask->maximum()); + ui->spinBox_CollisionMask->setValue(projectConfig.getBlockCollisionMask() & ui->spinBox_CollisionMask->maximum()); + ui->spinBox_ElevationMask->setValue(projectConfig.getBlockElevationMask() & ui->spinBox_ElevationMask->maximum()); // Set (and sync) border metatile IDs auto metatileIds = projectConfig.getNewMapBorderMetatileIds(); @@ -345,9 +379,9 @@ void ProjectSettingsEditor::save() { projectConfig.setTilesetsHaveIsCompressed(ui->checkBox_OutputIsCompressed->isChecked()); // Save spin box settings - projectConfig.setNewMapElevation(ui->spinBox_Elevation->value()); - projectConfig.setNewMapCollision(ui->spinBox_Collision->value()); - projectConfig.setNewMapMetatileId(ui->spinBox_FillMetatile->value()); + projectConfig.setDefaultElevation(ui->spinBox_Elevation->value()); + projectConfig.setDefaultCollision(ui->spinBox_Collision->value()); + projectConfig.setDefaultMetatileId(ui->spinBox_FillMetatile->value()); projectConfig.setCollisionSheetHeight(ui->spinBox_MaxElevation->value() + 1); projectConfig.setCollisionSheetWidth(ui->spinBox_MaxCollision->value() + 1); projectConfig.setMetatileBehaviorMask(ui->spinBox_BehaviorMask->value()); @@ -373,7 +407,9 @@ void ProjectSettingsEditor::save() { projectConfig.setNewMapBorderMetatileIds(this->getBorderMetatileIds(ui->checkBox_EnableCustomBorderSize->isChecked())); // Save pokemon icon paths - this->editedPokemonIconPaths.insert(ui->comboBox_IconSpecies->currentText(), ui->lineEdit_PokemonIcon->text()); + const QString species = ui->comboBox_IconSpecies->currentText(); + if (this->project->speciesToIconPath.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()); @@ -496,6 +532,7 @@ void ProjectSettingsEditor::closeEvent(QCloseEvent* event) { ); if (this->projectNeedsReload) { + // Note: Declining this prompt with changes that need a reload may cause problems if (this->prompt("Settings changed, reload project to apply changes?") == QMessageBox::Yes){ // Reloading the project will destroy this window, no other work should happen after this signal is emitted emit this->reloadProject(); diff --git a/src/ui/uintspinbox.cpp b/src/ui/uintspinbox.cpp index 31f66ba9..6579ed6b 100644 --- a/src/ui/uintspinbox.cpp +++ b/src/ui/uintspinbox.cpp @@ -18,6 +18,7 @@ UIntSpinBox::UIntSpinBox(QWidget *parent) }; void UIntSpinBox::setValue(uint32_t val) { + val = qMax(m_minimum, qMin(m_maximum, val)); if (m_value != val) { m_value = val; emit valueChanged(m_value);