From fa2b4d3edb1ad1c7ac30f9955bcff6fdd06edee3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 25 Oct 2022 17:51:32 -0400 Subject: [PATCH 01/10] Allow custom metatile attribute layouts --- include/config.h | 11 +++ include/core/metatile.h | 25 +++++-- src/config.cpp | 57 ++++++++++++++- src/core/metatile.cpp | 152 +++++++++++++++++++++++++++++---------- src/core/tileset.cpp | 4 +- src/mainwindow.cpp | 1 + src/project.cpp | 11 ++- src/scriptapi/apimap.cpp | 5 +- 8 files changed, 210 insertions(+), 56 deletions(-) diff --git a/include/config.h b/include/config.h index f7ff9a9d..5dd4f38e 100644 --- a/include/config.h +++ b/include/config.h @@ -36,6 +36,7 @@ protected: virtual void setUnreadKeys() = 0; bool getConfigBool(QString key, QString value); int getConfigInteger(QString key, QString value, int min, int max, int defaultValue); + long getConfigLong(QString key, QString value, long min, long max, long defaultValue); }; class PorymapConfig: public KeyValueConfigBase @@ -264,6 +265,11 @@ public: bool getTilesetsHaveCallback(); void setTilesetsHaveIsCompressed(bool has); bool getTilesetsHaveIsCompressed(); + int getMetatileAttributesSize(); + uint32_t getMetatileBehaviorMask(); + uint32_t getMetatileTerrainTypeMask(); + uint32_t getMetatileEncounterTypeMask(); + uint32_t getMetatileLayerTypeMask(); protected: virtual QString getConfigFilepath() override; virtual void parseConfigKeyValue(QString key, QString value) override; @@ -295,6 +301,11 @@ private: bool prefabImportPrompted; bool tilesetsHaveCallback; bool tilesetsHaveIsCompressed; + int metatileAttributesSize; + uint32_t metatileBehaviorMask; + uint32_t metatileTerrainTypeMask; + uint32_t metatileEncounterTypeMask; + uint32_t metatileLayerTypeMask; }; extern ProjectConfig projectConfig; diff --git a/include/core/metatile.h b/include/core/metatile.h index eb315ab8..2f8d6091 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -40,19 +40,34 @@ public: public: QList tiles; - uint16_t behavior; // 8 bits RSE, 9 bits FRLG - uint8_t layerType; - uint8_t encounterType; // FRLG only - uint8_t terrainType; // FRLG only + uint32_t behavior; + uint32_t layerType; + uint32_t encounterType; + uint32_t terrainType; uint32_t unusedAttributes; QString label; + uint32_t getAttributes(); + void setAttributes(uint32_t data); void setAttributes(uint32_t data, BaseGameVersion version); - uint32_t getAttributes(BaseGameVersion version); static int getIndexInTileset(int); static QPoint coordFromPixmapCoord(const QPointF &pixelCoord); static int getAttributesSize(BaseGameVersion version); + static void calculateAttributeLayout(); + +private: + static uint32_t behaviorMask; + static uint32_t terrainTypeMask; + static uint32_t encounterTypeMask; + static uint32_t layerTypeMask; + static uint32_t unusedAttrMask; + static int behaviorShift; + static int terrainTypeShift; + static int encounterTypeShift; + static int layerTypeShift; + + static int getShiftValue(uint32_t mask); }; inline bool operator==(const Metatile &a, const Metatile &b) { diff --git a/src/config.cpp b/src/config.cpp index a9ce994e..6ab6d633 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -145,7 +145,17 @@ bool KeyValueConfigBase::getConfigBool(QString key, QString value) { int KeyValueConfigBase::getConfigInteger(QString key, QString value, int min, int max, int defaultValue) { bool ok; - int result = value.toInt(&ok); + int result = value.toInt(&ok, 0); + if (!ok) { + logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value)); + return defaultValue; + } + return qMin(max, qMax(min, result)); +} + +long KeyValueConfigBase::getConfigLong(QString key, QString value, long min, long max, long defaultValue) { + bool ok; + long result = value.toLong(&ok, 0); if (!ok) { logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value)); return defaultValue; @@ -549,6 +559,21 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->defaultPrimaryTileset = value; } else if (key == "default_secondary_tileset") { this->defaultSecondaryTileset = value; + } else if (key == "metatile_attributes_size") { + int size = getConfigInteger(key, value, 1, 4, 2); + if (size & (size - 1)) { + logWarn(QString("Invalid config value for %1: must be 1, 2, or 4").arg(key)); + size = 2; + } + this->metatileAttributesSize = size; + } else if (key == "metatile_behavior_mask") { + this->metatileBehaviorMask = getConfigLong(key, value, 0, 0xFFFFFFFF, 0); + } else if (key == "metatile_terrain_type_mask") { + this->metatileTerrainTypeMask = getConfigLong(key, value, 0, 0xFFFFFFFF, 0); + } else if (key == "metatile_encounter_type_mask") { + this->metatileEncounterTypeMask = getConfigLong(key, value, 0, 0xFFFFFFFF, 0); + } else if (key == "metatile_layer_type_mask") { + this->metatileLayerTypeMask = getConfigLong(key, value, 0, 0xFFFFFFFF, 0); #ifdef CONFIG_BACKWARDS_COMPATABILITY } else if (key == "recent_map") { userConfig.setRecentMap(value); @@ -599,6 +624,11 @@ void ProjectConfig::setUnreadKeys() { if (!readKeys.contains("create_map_text_file")) this->createMapTextFile = (this->baseGameVersion != BaseGameVersion::pokeemerald); if (!readKeys.contains("new_map_border_metatiles")) this->newMapBorderMetatileIds = isPokefirered ? DEFAULT_BORDER_FRLG : DEFAULT_BORDER_RSE; if (!readKeys.contains("default_secondary_tileset")) this->defaultSecondaryTileset = isPokefirered ? "gTileset_PalletTown" : "gTileset_Petalburg"; + if (!readKeys.contains("metatile_attributes_size")) this->metatileAttributesSize = isPokefirered ? 4 : 2; + if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = isPokefirered ? 0x000001FF : 0x00FF; + if (!readKeys.contains("metatile_terrain_type_mask")) this->metatileTerrainTypeMask = isPokefirered ? 0x00003E00 : 0; + if (!readKeys.contains("metatile_encounter_type_mask")) this->metatileEncounterTypeMask = isPokefirered ? 0x07000000 : 0; + if (!readKeys.contains("metatile_layer_type_mask")) this-> metatileLayerTypeMask = isPokefirered ? 0x60000000 : 0xF000; } QMap ProjectConfig::getKeyValueMap() { @@ -630,6 +660,11 @@ 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", "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()); return map; } @@ -879,6 +914,26 @@ bool ProjectConfig::getTilesetsHaveIsCompressed() { return this->tilesetsHaveIsCompressed; } +int ProjectConfig::getMetatileAttributesSize() { + return this->metatileAttributesSize; +} + +uint32_t ProjectConfig::getMetatileBehaviorMask() { + return this->metatileBehaviorMask; +} + +uint32_t ProjectConfig::getMetatileTerrainTypeMask() { + return this->metatileTerrainTypeMask; +} + +uint32_t ProjectConfig::getMetatileEncounterTypeMask() { + return this->metatileEncounterTypeMask; +} + +uint32_t ProjectConfig::getMetatileLayerTypeMask() { + return this->metatileLayerTypeMask; +} + UserConfig userConfig; diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index 6264fda2..4f9f444b 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -2,6 +2,17 @@ #include "tileset.h" #include "project.h" +uint32_t Metatile::behaviorMask = 0; +uint32_t Metatile::terrainTypeMask = 0; +uint32_t Metatile::encounterTypeMask = 0; +uint32_t Metatile::layerTypeMask = 0; +uint32_t Metatile::unusedAttrMask = 0; + +int Metatile::behaviorShift = 0; +int Metatile::terrainTypeShift = 0; +int Metatile::encounterTypeShift = 0; +int Metatile::layerTypeShift = 0; + Metatile::Metatile() : behavior(0), layerType(0), @@ -37,50 +48,115 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) { return QPoint(x, y); } +// Returns the position of the rightmost set bit +int Metatile::getShiftValue(uint32_t mask) { + return log2(mask & ~(mask - 1));; +} + + +void Metatile::calculateAttributeLayout() { + // Get the maximum size of any attribute mask + const QHash maxMasks = { + {1, 0xFF}, + {2, 0xFFFF}, + {4, 0xFFFFFFFF}, + }; + const uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0); + + // Behavior + uint32_t mask = projectConfig.getMetatileBehaviorMask(); + if (mask > maxMask) { + logWarn(QString("Metatile behavior mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask)); + mask &= maxMask; + } + Metatile::behaviorMask = mask; + Metatile::behaviorShift = getShiftValue(mask); + + // Terrain Type + mask = projectConfig.getMetatileTerrainTypeMask(); + if (mask > maxMask) { + logWarn(QString("Metatile terrain type mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask)); + mask &= maxMask; + } + Metatile::terrainTypeMask = mask; + Metatile::terrainTypeShift = getShiftValue(mask); + + // Encounter Type + mask = projectConfig.getMetatileEncounterTypeMask(); + if (mask > maxMask) { + logWarn(QString("Metatile encounter type mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask)); + mask &= maxMask; + } + Metatile::encounterTypeMask = mask; + Metatile::encounterTypeShift = getShiftValue(mask); + + // Layer Type + mask = projectConfig.getMetatileLayerTypeMask(); + if (mask > maxMask) { + logWarn(QString("Metatile layer type mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask)); + mask &= maxMask; + } + Metatile::layerTypeMask = mask; + Metatile::layerTypeShift = getShiftValue(mask); + + Metatile::unusedAttrMask = ~(Metatile::behaviorMask | Metatile::terrainTypeMask | Metatile::layerTypeMask | Metatile::encounterTypeMask); + Metatile::unusedAttrMask &= maxMask; + + // Warn user if any mask overlaps + if (Metatile::behaviorMask & Metatile::terrainTypeMask + || Metatile::behaviorMask & Metatile::encounterTypeMask + || Metatile::behaviorMask & Metatile::layerTypeMask + || Metatile::terrainTypeMask & Metatile::encounterTypeMask + || Metatile::terrainTypeMask & Metatile::layerTypeMask + || Metatile::encounterTypeMask & Metatile::layerTypeMask) { + logWarn("Metatile attribute masks are overlapping."); + } +} + +uint32_t Metatile::getAttributes() { + uint32_t attributes = this->unusedAttributes & Metatile::unusedAttrMask; + attributes |= (behavior << Metatile::behaviorShift) & Metatile::behaviorMask; + attributes |= (terrainType << Metatile::terrainTypeShift) & Metatile::terrainTypeMask; + attributes |= (encounterType << Metatile::encounterTypeShift) & Metatile::encounterTypeMask; + attributes |= (layerType << Metatile::layerTypeShift) & Metatile::layerTypeMask; + return attributes; +} + +void Metatile::setAttributes(uint32_t data) { + this->behavior = (data & Metatile::behaviorMask) >> Metatile::behaviorShift; + this->terrainType = (data & Metatile::terrainTypeMask) >> Metatile::terrainTypeShift; + this->encounterType = (data & Metatile::encounterTypeMask) >> Metatile::encounterTypeShift; + this->layerType = (data & Metatile::layerTypeMask) >> Metatile::layerTypeShift; + this->unusedAttributes = data & Metatile::unusedAttrMask; +} + +// Get the vanilla attribute sizes based on version. For AdvanceMap import int Metatile::getAttributesSize(BaseGameVersion version) { return (version == BaseGameVersion::pokefirered) ? 4 : 2; } -// RSE attributes -const uint16_t behaviorMask_RSE = 0x00FF; -const uint16_t layerTypeMask_RSE = 0xF000; -const int behaviorShift_RSE = 0; -const int layerTypeShift_RSE = 12; - -// FRLG attributes -const uint32_t behaviorMask_FRLG = 0x000001FF; -const uint32_t terrainTypeMask = 0x00003E00; -const uint32_t encounterTypeMask = 0x07000000; -const uint32_t layerTypeMask_FRLG = 0x60000000; -const int behaviorShift_FRLG = 0; -const int terrainTypeShift = 9; -const int encounterTypeShift = 24; -const int layerTypeShift_FRLG = 29; - -uint32_t Metatile::getAttributes(BaseGameVersion version) { - uint32_t attributes = this->unusedAttributes; - if (version == BaseGameVersion::pokefirered) { - attributes |= (behavior << behaviorShift_FRLG) & behaviorMask_FRLG; - attributes |= (terrainType << terrainTypeShift) & terrainTypeMask; - attributes |= (encounterType << encounterTypeShift) & encounterTypeMask; - attributes |= (layerType << layerTypeShift_FRLG) & layerTypeMask_FRLG; - } else { - attributes |= (behavior << behaviorShift_RSE) & behaviorMask_RSE; - attributes |= (layerType << layerTypeShift_RSE) & layerTypeMask_RSE; - } - return attributes; -} - +// Set the attributes using the vanilla layout based on version. For AdvanceMap import void Metatile::setAttributes(uint32_t data, BaseGameVersion version) { if (version == BaseGameVersion::pokefirered) { - this->behavior = (data & behaviorMask_FRLG) >> behaviorShift_FRLG; - this->terrainType = (data & terrainTypeMask) >> terrainTypeShift; - this->encounterType = (data & encounterTypeMask) >> encounterTypeShift; - this->layerType = (data & layerTypeMask_FRLG) >> layerTypeShift_FRLG; - this->unusedAttributes = data & ~(behaviorMask_FRLG | terrainTypeMask | layerTypeMask_FRLG | encounterTypeMask); + const uint32_t behaviorMask = 0x000001FF; + const uint32_t terrainTypeMask = 0x00003E00; + const uint32_t encounterTypeMask = 0x07000000; + const uint32_t layerTypeMask = 0x60000000; + + this->behavior = data & behaviorMask; + this->terrainType = (data & terrainTypeMask) >> 9; + this->encounterType = (data & encounterTypeMask) >> 24; + this->layerType = (data & layerTypeMask) >> 29; + this->unusedAttributes = data & ~(behaviorMask | terrainTypeMask | layerTypeMask | encounterTypeMask); } else { - this->behavior = (data & behaviorMask_RSE) >> behaviorShift_RSE; - this->layerType = (data & layerTypeMask_RSE) >> layerTypeShift_RSE; - this->unusedAttributes = data & ~(behaviorMask_RSE | layerTypeMask_RSE); + const uint16_t behaviorMask = 0x00FF; + const uint16_t layerTypeMask = 0xF000; + + this->behavior = data & behaviorMask; + this->layerType = (data & layerTypeMask) >> 12; + this->unusedAttributes = data & ~(behaviorMask | layerTypeMask); } + + // Clean data to fit the user's custom masks + this->setAttributes(this->getAttributes()); } diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index adfb0a13..4b31e22e 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -233,8 +233,8 @@ bool Tileset::appendToMetatiles(QString root, QString friendlyName, bool usingAs } else { // Append to C file dataString.append(QString("const u16 gMetatiles_%1[] = INCBIN_U16(\"%2\");\n").arg(friendlyName, metatilesPath)); - QString attrSize = (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) ? "32" : "16"; - dataString.append(QString("const u%1 gMetatileAttributes_%2[] = INCBIN_U%1(\"%3\");\n").arg(attrSize, friendlyName, metatileAttrsPath)); + QString numBits = QString::number(projectConfig.getMetatileAttributesSize() * 8); + dataString.append(QString("const u%1 gMetatileAttributes_%2[] = INCBIN_U%1(\"%3\");\n").arg(numBits, friendlyName, metatileAttrsPath)); } file.write(dataString.toUtf8()); file.flush(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cdbb0734..d3f436e7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -528,6 +528,7 @@ bool MainWindow::openProject(QString dir) { userConfig.load(); projectConfig.setProjectDir(dir); projectConfig.load(); + Metatile::calculateAttributeLayout(); this->closeSupplementaryWindows(); this->setProjectSpecificUIVisibility(); diff --git a/src/project.cpp b/src/project.cpp index 4b68be4a..c6a1dd5c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -980,10 +980,9 @@ void Project::saveTilesetMetatileAttributes(Tileset *tileset) { QFile attrs_file(tileset->metatile_attrs_path); if (attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QByteArray data; - BaseGameVersion version = projectConfig.getBaseGameVersion(); - int attrSize = Metatile::getAttributesSize(version); + int attrSize = projectConfig.getMetatileAttributesSize(); for (Metatile *metatile : tileset->metatiles) { - uint32_t attributes = metatile->getAttributes(version); + uint32_t attributes = metatile->getAttributes(); for (int i = 0; i < attrSize; i++) data.append(static_cast(attributes >> (8 * i))); } @@ -1538,9 +1537,7 @@ void Project::loadTilesetMetatiles(Tileset* tileset) { if (attrs_file.open(QIODevice::ReadOnly)) { QByteArray data = attrs_file.readAll(); int num_metatiles = tileset->metatiles.count(); - - BaseGameVersion version = projectConfig.getBaseGameVersion(); - int attrSize = Metatile::getAttributesSize(version); + int attrSize = projectConfig.getMetatileAttributesSize(); int num_metatileAttrs = data.length() / attrSize; if (num_metatiles != num_metatileAttrs) { logWarn(QString("Metatile count %1 does not match metatile attribute count %2 in %3").arg(num_metatiles).arg(num_metatileAttrs).arg(tileset->name)); @@ -1552,7 +1549,7 @@ void Project::loadTilesetMetatiles(Tileset* tileset) { uint32_t attributes = 0; for (int j = 0; j < attrSize; j++) attributes |= static_cast(data.at(i * attrSize + j)) << (8 * j); - tileset->metatiles.at(i)->setAttributes(attributes, version); + tileset->metatiles.at(i)->setAttributes(attributes); } } else { logError(QString("Could not open tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path)); diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index b133a74b..ee352536 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -694,15 +694,14 @@ int MainWindow::getMetatileAttributes(int metatileId) { Metatile * metatile = this->getMetatile(metatileId); if (!metatile) return -1; - return metatile->getAttributes(projectConfig.getBaseGameVersion()); + return metatile->getAttributes(); } void MainWindow::setMetatileAttributes(int metatileId, int attributes) { Metatile * metatile = this->getMetatile(metatileId); - uint32_t u_attributes = static_cast(attributes); if (!metatile) return; - metatile->setAttributes(u_attributes, projectConfig.getBaseGameVersion()); + metatile->setAttributes(attributes); this->saveMetatileAttributesByMetatileId(metatileId); } From 1641ac00b0ab32db98647df8e8a939500a298dd3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 26 Oct 2022 00:14:55 -0400 Subject: [PATCH 02/10] Combine attribute shift/mask data --- include/core/metatile.h | 34 ++++--- src/config.cpp | 13 +-- src/core/metatile.cpp | 177 ++++++++++++++++++------------------ src/core/metatileparser.cpp | 4 +- src/mainwindow.cpp | 2 +- 5 files changed, 123 insertions(+), 107 deletions(-) diff --git a/include/core/metatile.h b/include/core/metatile.h index 2f8d6091..c5536016 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -47,27 +47,37 @@ public: uint32_t unusedAttributes; QString label; + enum Attr { + Behavior, + TerrainType, + EncounterType, + LayerType, + }; + + struct AttrLayout { + uint32_t mask; + int shift; + }; + + static const QHash defaultLayoutFRLG; + static const QHash defaultLayoutRSE; + uint32_t getAttributes(); void setAttributes(uint32_t data); - void setAttributes(uint32_t data, BaseGameVersion version); + void convertAttributes(uint32_t data, BaseGameVersion version); static int getIndexInTileset(int); static QPoint coordFromPixmapCoord(const QPointF &pixelCoord); - static int getAttributesSize(BaseGameVersion version); - static void calculateAttributeLayout(); + static int getDefaultAttributesSize(BaseGameVersion version); + static void setCustomLayout(); private: - static uint32_t behaviorMask; - static uint32_t terrainTypeMask; - static uint32_t encounterTypeMask; - static uint32_t layerTypeMask; + static QHash customLayout; static uint32_t unusedAttrMask; - static int behaviorShift; - static int terrainTypeShift; - static int encounterTypeShift; - static int layerTypeShift; - static int getShiftValue(uint32_t mask); + void setAttributes(uint32_t, const QHash*); + static void setCustomAttributeLayout(Metatile::AttrLayout *, uint32_t, uint32_t, QString); + static bool doMasksOverlap(QList); }; inline bool operator==(const Metatile &a, const Metatile &b) { diff --git a/src/config.cpp b/src/config.cpp index 6ab6d633..ae5abe92 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -563,7 +563,7 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { int size = getConfigInteger(key, value, 1, 4, 2); if (size & (size - 1)) { logWarn(QString("Invalid config value for %1: must be 1, 2, or 4").arg(key)); - size = 2; + return; // Don't set a default now, it will be set later based on the base game version } this->metatileAttributesSize = size; } else if (key == "metatile_behavior_mask") { @@ -624,11 +624,12 @@ void ProjectConfig::setUnreadKeys() { if (!readKeys.contains("create_map_text_file")) this->createMapTextFile = (this->baseGameVersion != BaseGameVersion::pokeemerald); if (!readKeys.contains("new_map_border_metatiles")) this->newMapBorderMetatileIds = isPokefirered ? DEFAULT_BORDER_FRLG : DEFAULT_BORDER_RSE; if (!readKeys.contains("default_secondary_tileset")) this->defaultSecondaryTileset = isPokefirered ? "gTileset_PalletTown" : "gTileset_Petalburg"; - if (!readKeys.contains("metatile_attributes_size")) this->metatileAttributesSize = isPokefirered ? 4 : 2; - if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = isPokefirered ? 0x000001FF : 0x00FF; - if (!readKeys.contains("metatile_terrain_type_mask")) this->metatileTerrainTypeMask = isPokefirered ? 0x00003E00 : 0; - if (!readKeys.contains("metatile_encounter_type_mask")) this->metatileEncounterTypeMask = isPokefirered ? 0x07000000 : 0; - if (!readKeys.contains("metatile_layer_type_mask")) this-> metatileLayerTypeMask = isPokefirered ? 0x60000000 : 0xF000; + if (!readKeys.contains("metatile_attributes_size")) this->metatileAttributesSize = Metatile::getDefaultAttributesSize(this->baseGameVersion); + const QHash layout = isPokefirered ? Metatile::defaultLayoutFRLG : Metatile::defaultLayoutRSE; + if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = layout[Metatile::Attr::Behavior].mask; + if (!readKeys.contains("metatile_terrain_type_mask")) this->metatileTerrainTypeMask = layout[Metatile::Attr::TerrainType].mask; + if (!readKeys.contains("metatile_encounter_type_mask")) this->metatileEncounterTypeMask = layout[Metatile::Attr::EncounterType].mask; + if (!readKeys.contains("metatile_layer_type_mask")) this-> metatileLayerTypeMask = layout[Metatile::Attr::LayerType].mask; } QMap ProjectConfig::getKeyValueMap() { diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index 4f9f444b..6fb8c413 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -2,16 +2,22 @@ #include "tileset.h" #include "project.h" -uint32_t Metatile::behaviorMask = 0; -uint32_t Metatile::terrainTypeMask = 0; -uint32_t Metatile::encounterTypeMask = 0; -uint32_t Metatile::layerTypeMask = 0; +QHash Metatile::customLayout = {}; uint32_t Metatile::unusedAttrMask = 0; -int Metatile::behaviorShift = 0; -int Metatile::terrainTypeShift = 0; -int Metatile::encounterTypeShift = 0; -int Metatile::layerTypeShift = 0; +const QHash Metatile::defaultLayoutFRLG = { + {Metatile::Attr::Behavior, { .mask = 0x000001FF, .shift = 0} }, + {Metatile::Attr::TerrainType, { .mask = 0x00003E00, .shift = 9} }, + {Metatile::Attr::EncounterType, { .mask = 0x07000000, .shift = 24} }, + {Metatile::Attr::LayerType, { .mask = 0x60000000, .shift = 29} }, +}; + +const QHash Metatile::defaultLayoutRSE = { + {Metatile::Attr::Behavior, { .mask = 0x000000FF, .shift = 0} }, + {Metatile::Attr::TerrainType, { .mask = 0x00000000, .shift = 0} }, + {Metatile::Attr::EncounterType, { .mask = 0x00000000, .shift = 0} }, + {Metatile::Attr::LayerType, { .mask = 0x0000F000, .shift = 12} }, +}; Metatile::Metatile() : behavior(0), @@ -42,19 +48,37 @@ int Metatile::getIndexInTileset(int metatileId) { } } +// Get the vanilla attribute sizes based on version. +// Used as a default in the config and for AdvanceMap import. +int Metatile::getDefaultAttributesSize(BaseGameVersion version) { + return (version == BaseGameVersion::pokefirered) ? 4 : 2; +} + QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) { int x = static_cast(pixelCoord.x()) / 16; int y = static_cast(pixelCoord.y()) / 16; return QPoint(x, y); } -// Returns the position of the rightmost set bit -int Metatile::getShiftValue(uint32_t mask) { - return log2(mask & ~(mask - 1));; +void Metatile::setCustomAttributeLayout(Metatile::AttrLayout * layout, uint32_t mask, uint32_t max, QString errName) { + if (mask > max) { + logWarn(QString("Metatile %1 mask '%2' exceeds maximum size '%3'").arg(errName).arg(mask).arg(max)); + mask &= max; + } + layout->mask = mask; + layout->shift = log2(mask & ~(mask - 1)); // Get the position of the rightmost set bit } +bool Metatile::doMasksOverlap(QList masks) { + for (int i = 0; i < masks.length(); i++) + for (int j = i + 1; j < masks.length(); j++) { + if (masks.at(i) & masks.at(j)) + return true; + } + return false; +} -void Metatile::calculateAttributeLayout() { +void Metatile::setCustomLayout() { // Get the maximum size of any attribute mask const QHash maxMasks = { {1, 0xFF}, @@ -63,100 +87,81 @@ void Metatile::calculateAttributeLayout() { }; const uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0); - // Behavior - uint32_t mask = projectConfig.getMetatileBehaviorMask(); - if (mask > maxMask) { - logWarn(QString("Metatile behavior mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask)); - mask &= maxMask; - } - Metatile::behaviorMask = mask; - Metatile::behaviorShift = getShiftValue(mask); + // Set custom attribute masks from the config file + setCustomAttributeLayout(&customLayout[Attr::Behavior], projectConfig.getMetatileBehaviorMask(), maxMask, "behavior"); + setCustomAttributeLayout(&customLayout[Attr::TerrainType], projectConfig.getMetatileTerrainTypeMask(), maxMask, "terrain type"); + setCustomAttributeLayout(&customLayout[Attr::EncounterType], projectConfig.getMetatileEncounterTypeMask(), maxMask, "encounter type"); + setCustomAttributeLayout(&customLayout[Attr::LayerType], projectConfig.getMetatileLayerTypeMask(), maxMask, "layer type"); - // Terrain Type - mask = projectConfig.getMetatileTerrainTypeMask(); - if (mask > maxMask) { - logWarn(QString("Metatile terrain type mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask)); - mask &= maxMask; - } - Metatile::terrainTypeMask = mask; - Metatile::terrainTypeShift = getShiftValue(mask); - - // Encounter Type - mask = projectConfig.getMetatileEncounterTypeMask(); - if (mask > maxMask) { - logWarn(QString("Metatile encounter type mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask)); - mask &= maxMask; - } - Metatile::encounterTypeMask = mask; - Metatile::encounterTypeShift = getShiftValue(mask); - - // Layer Type - mask = projectConfig.getMetatileLayerTypeMask(); - if (mask > maxMask) { - logWarn(QString("Metatile layer type mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask)); - mask &= maxMask; - } - Metatile::layerTypeMask = mask; - Metatile::layerTypeShift = getShiftValue(mask); - - Metatile::unusedAttrMask = ~(Metatile::behaviorMask | Metatile::terrainTypeMask | Metatile::layerTypeMask | Metatile::encounterTypeMask); + // Set mask for preserving any attribute bits not used by Porymap + Metatile::unusedAttrMask = ~(customLayout[Attr::Behavior].mask + | customLayout[Attr::TerrainType].mask + | customLayout[Attr::EncounterType].mask + | customLayout[Attr::LayerType].mask); Metatile::unusedAttrMask &= maxMask; - // Warn user if any mask overlaps - if (Metatile::behaviorMask & Metatile::terrainTypeMask - || Metatile::behaviorMask & Metatile::encounterTypeMask - || Metatile::behaviorMask & Metatile::layerTypeMask - || Metatile::terrainTypeMask & Metatile::encounterTypeMask - || Metatile::terrainTypeMask & Metatile::layerTypeMask - || Metatile::encounterTypeMask & Metatile::layerTypeMask) { + // Overlapping masks are legal, but probably not intended + if (doMasksOverlap({customLayout[Attr::Behavior].mask, + customLayout[Attr::TerrainType].mask, + customLayout[Attr::EncounterType].mask, + customLayout[Attr::LayerType].mask})) { logWarn("Metatile attribute masks are overlapping."); } } uint32_t Metatile::getAttributes() { uint32_t attributes = this->unusedAttributes & Metatile::unusedAttrMask; - attributes |= (behavior << Metatile::behaviorShift) & Metatile::behaviorMask; - attributes |= (terrainType << Metatile::terrainTypeShift) & Metatile::terrainTypeMask; - attributes |= (encounterType << Metatile::encounterTypeShift) & Metatile::encounterTypeMask; - attributes |= (layerType << Metatile::layerTypeShift) & Metatile::layerTypeMask; + + // Behavior + Metatile::AttrLayout attr = Metatile::customLayout[Attr::Behavior]; + attributes |= (this->behavior << attr.shift) & attr.mask; + + // Terrain Type + attr = Metatile::customLayout[Attr::TerrainType]; + attributes |= (this->terrainType << attr.shift) & attr.mask; + + // Encounter Type + attr = Metatile::customLayout[Attr::EncounterType]; + attributes |= (this->encounterType << attr.shift) & attr.mask; + + // Layer Type + attr = Metatile::customLayout[Attr::LayerType]; + attributes |= (this->layerType << attr.shift) & attr.mask; + return attributes; } -void Metatile::setAttributes(uint32_t data) { - this->behavior = (data & Metatile::behaviorMask) >> Metatile::behaviorShift; - this->terrainType = (data & Metatile::terrainTypeMask) >> Metatile::terrainTypeShift; - this->encounterType = (data & Metatile::encounterTypeMask) >> Metatile::encounterTypeShift; - this->layerType = (data & Metatile::layerTypeMask) >> Metatile::layerTypeShift; +void Metatile::setAttributes(uint32_t data, const QHash * layout) { + // Behavior + Metatile::AttrLayout attr = layout->value(Attr::Behavior); + this->behavior = (data & attr.mask) >> attr.shift; + + // Terrain Type + attr = layout->value(Attr::TerrainType); + this->terrainType = (data & attr.mask) >> attr.shift; + + // Encounter Type + attr = layout->value(Attr::EncounterType); + this->encounterType = (data & attr.mask) >> attr.shift; + + // Layer Type + attr = layout->value(Attr::LayerType); + this->layerType = (data & attr.mask) >> attr.shift; + this->unusedAttributes = data & Metatile::unusedAttrMask; } -// Get the vanilla attribute sizes based on version. For AdvanceMap import -int Metatile::getAttributesSize(BaseGameVersion version) { - return (version == BaseGameVersion::pokefirered) ? 4 : 2; +void Metatile::setAttributes(uint32_t data) { + this->setAttributes(data, &Metatile::customLayout); } -// Set the attributes using the vanilla layout based on version. For AdvanceMap import -void Metatile::setAttributes(uint32_t data, BaseGameVersion version) { +// Read attributes using a vanilla layout, then set them using the user's layout. For AdvanceMap import +void Metatile::convertAttributes(uint32_t data, BaseGameVersion version) { if (version == BaseGameVersion::pokefirered) { - const uint32_t behaviorMask = 0x000001FF; - const uint32_t terrainTypeMask = 0x00003E00; - const uint32_t encounterTypeMask = 0x07000000; - const uint32_t layerTypeMask = 0x60000000; - - this->behavior = data & behaviorMask; - this->terrainType = (data & terrainTypeMask) >> 9; - this->encounterType = (data & encounterTypeMask) >> 24; - this->layerType = (data & layerTypeMask) >> 29; - this->unusedAttributes = data & ~(behaviorMask | terrainTypeMask | layerTypeMask | encounterTypeMask); + this->setAttributes(data, &Metatile::defaultLayoutFRLG); } else { - const uint16_t behaviorMask = 0x00FF; - const uint16_t layerTypeMask = 0xF000; - - this->behavior = data & behaviorMask; - this->layerType = (data & layerTypeMask) >> 12; - this->unusedAttributes = data & ~(behaviorMask | layerTypeMask); + this->setAttributes(data, &Metatile::defaultLayoutRSE); } - // Clean data to fit the user's custom masks this->setAttributes(this->getAttributes()); } diff --git a/src/core/metatileparser.cpp b/src/core/metatileparser.cpp index 0bae75e0..3572010c 100644 --- a/src/core/metatileparser.cpp +++ b/src/core/metatileparser.cpp @@ -42,7 +42,7 @@ QList MetatileParser::parse(QString filepath, bool *error, bool prima return { }; } - int attrSize = Metatile::getAttributesSize(version); + int attrSize = Metatile::getDefaultAttributesSize(version); int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary(); int numMetatiles = static_cast(in.at(0)) | (static_cast(in.at(1)) << 8) | @@ -82,7 +82,7 @@ QList MetatileParser::parse(QString filepath, bool *error, bool prima uint32_t attributes = 0; for (int j = 0; j < attrSize; j++) attributes |= static_cast(in.at(attrOffset + j)) << (8 * j); - metatile->setAttributes(attributes, version); + metatile->convertAttributes(attributes, version); metatile->tiles = tiles; metatiles.append(metatile); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d3f436e7..e850118c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -528,7 +528,7 @@ bool MainWindow::openProject(QString dir) { userConfig.load(); projectConfig.setProjectDir(dir); projectConfig.load(); - Metatile::calculateAttributeLayout(); + Metatile::setCustomLayout(); this->closeSupplementaryWindows(); this->setProjectSpecificUIVisibility(); From 1283f5c19d0e50e727feabd9a7fe5df072e06619 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 26 Oct 2022 02:42:55 -0400 Subject: [PATCH 03/10] Use custom attribute masks in API and Tileset Editor --- include/core/metatile.h | 14 +++++-- include/ui/tileseteditor.h | 4 +- src/core/metatile.cpp | 59 +++++++++++++++++++++----- src/scriptapi/apimap.cpp | 20 ++++----- src/ui/tileseteditor.cpp | 86 ++++++++++++++++++++------------------ 5 files changed, 113 insertions(+), 70 deletions(-) diff --git a/include/core/metatile.h b/include/core/metatile.h index c5536016..db1f6e6f 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -41,9 +41,9 @@ public: public: QList tiles; uint32_t behavior; - uint32_t layerType; - uint32_t encounterType; uint32_t terrainType; + uint32_t encounterType; + uint32_t layerType; uint32_t unusedAttributes; QString label; @@ -61,22 +61,28 @@ public: static const QHash defaultLayoutFRLG; static const QHash defaultLayoutRSE; + static QHash customLayout; uint32_t getAttributes(); void setAttributes(uint32_t data); void convertAttributes(uint32_t data, BaseGameVersion version); + void setBehavior(uint32_t); + void setTerrainType(uint32_t); + void setEncounterType(uint32_t); + void setLayerType(uint32_t); + static int getIndexInTileset(int); static QPoint coordFromPixmapCoord(const QPointF &pixelCoord); static int getDefaultAttributesSize(BaseGameVersion version); static void setCustomLayout(); private: - static QHash customLayout; static uint32_t unusedAttrMask; void setAttributes(uint32_t, const QHash*); - static void setCustomAttributeLayout(Metatile::AttrLayout *, uint32_t, uint32_t, QString); + static void setCustomAttributeLayout(Metatile::AttrLayout *, uint32_t, uint32_t); + static bool isMaskTooSmall(Metatile::AttrLayout * layout, int n); static bool doMasksOverlap(QList); }; diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 29912e4d..e671564f 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -111,9 +111,7 @@ private slots: private: void initUi(); - void setMetatileBehaviors(); - void setMetatileLayersUi(); - void setVersionSpecificUi(); + void setAttributesUi(); void setMetatileLabelValidator(); void initMetatileSelector(); void initTileSelector(); diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index 6fb8c413..44b71fa3 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -21,17 +21,17 @@ const QHash Metatile::defaultLayoutRSE = { Metatile::Metatile() : behavior(0), - layerType(0), - encounterType(0), terrainType(0), + encounterType(0), + layerType(0), unusedAttributes(0) { } Metatile::Metatile(const int numTiles) : behavior(0), - layerType(0), - encounterType(0), terrainType(0), + encounterType(0), + layerType(0), unusedAttributes(0) { Tile tile = Tile(); @@ -60,15 +60,25 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) { return QPoint(x, y); } -void Metatile::setCustomAttributeLayout(Metatile::AttrLayout * layout, uint32_t mask, uint32_t max, QString errName) { +void Metatile::setCustomAttributeLayout(Metatile::AttrLayout * layout, uint32_t mask, uint32_t max) { if (mask > max) { - logWarn(QString("Metatile %1 mask '%2' exceeds maximum size '%3'").arg(errName).arg(mask).arg(max)); + uint32_t oldMask = mask; mask &= max; + logWarn(QString("Metatile attribute mask '0x%1' has been truncated to '0x%2'") + .arg(QString::number(oldMask, 16).toUpper()) + .arg(QString::number(mask, 16).toUpper())); } layout->mask = mask; layout->shift = log2(mask & ~(mask - 1)); // Get the position of the rightmost set bit } +// For checking whether a metatile attribute mask can contain all the hard-coded values +bool Metatile::isMaskTooSmall(Metatile::AttrLayout * layout, int n) { + if (!layout->mask) return false; + uint32_t maxValue = n - 1; + return (maxValue & (layout->mask >> layout->shift)) != maxValue; +} + bool Metatile::doMasksOverlap(QList masks) { for (int i = 0; i < masks.length(); i++) for (int j = i + 1; j < masks.length(); j++) { @@ -88,10 +98,10 @@ void Metatile::setCustomLayout() { const uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0); // Set custom attribute masks from the config file - setCustomAttributeLayout(&customLayout[Attr::Behavior], projectConfig.getMetatileBehaviorMask(), maxMask, "behavior"); - setCustomAttributeLayout(&customLayout[Attr::TerrainType], projectConfig.getMetatileTerrainTypeMask(), maxMask, "terrain type"); - setCustomAttributeLayout(&customLayout[Attr::EncounterType], projectConfig.getMetatileEncounterTypeMask(), maxMask, "encounter type"); - setCustomAttributeLayout(&customLayout[Attr::LayerType], projectConfig.getMetatileLayerTypeMask(), maxMask, "layer type"); + setCustomAttributeLayout(&customLayout[Attr::Behavior], projectConfig.getMetatileBehaviorMask(), maxMask); + setCustomAttributeLayout(&customLayout[Attr::TerrainType], projectConfig.getMetatileTerrainTypeMask(), maxMask); + setCustomAttributeLayout(&customLayout[Attr::EncounterType], projectConfig.getMetatileEncounterTypeMask(), maxMask); + setCustomAttributeLayout(&customLayout[Attr::LayerType], projectConfig.getMetatileLayerTypeMask(), maxMask); // Set mask for preserving any attribute bits not used by Porymap Metatile::unusedAttrMask = ~(customLayout[Attr::Behavior].mask @@ -100,13 +110,24 @@ void Metatile::setCustomLayout() { | customLayout[Attr::LayerType].mask); Metatile::unusedAttrMask &= maxMask; - // Overlapping masks are legal, but probably not intended + // Overlapping masks are technically ok, but probably not intended. + // Additionally, Porymap will not properly reflect that the values are linked. if (doMasksOverlap({customLayout[Attr::Behavior].mask, customLayout[Attr::TerrainType].mask, customLayout[Attr::EncounterType].mask, customLayout[Attr::LayerType].mask})) { logWarn("Metatile attribute masks are overlapping."); } + + // The available options in the Tileset Editor for Terrain Type, Encounter Type, and Layer Type are hard-coded. + // Warn the user if they have set a nonzero mask that is too small to contain these options. + // They'll be allowed to select them, but they'll be truncated to a different value when revisited. + if (isMaskTooSmall(&customLayout[Attr::TerrainType], NUM_METATILE_TERRAIN_TYPES)) + logWarn(QString("Metatile Terrain Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_TERRAIN_TYPES)); + if (isMaskTooSmall(&customLayout[Attr::EncounterType], NUM_METATILE_ENCOUNTER_TYPES)) + logWarn(QString("Metatile Encounter Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_ENCOUNTER_TYPES)); + if (isMaskTooSmall(&customLayout[Attr::LayerType], NUM_METATILE_LAYER_TYPES)) + logWarn(QString("Metatile Layer Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_LAYER_TYPES)); } uint32_t Metatile::getAttributes() { @@ -165,3 +186,19 @@ void Metatile::convertAttributes(uint32_t data, BaseGameVersion version) { // Clean data to fit the user's custom masks this->setAttributes(this->getAttributes()); } + +void Metatile::setBehavior(uint32_t value) { + this->behavior = value & Metatile::customLayout[Attr::Behavior].mask; +} + +void Metatile::setTerrainType(uint32_t value) { + this->terrainType = value & Metatile::customLayout[Attr::TerrainType].mask; +} + +void Metatile::setEncounterType(uint32_t value) { + this->encounterType = value & Metatile::customLayout[Attr::EncounterType].mask; +} + +void Metatile::setLayerType(uint32_t value) { + this->layerType = value & Metatile::customLayout[Attr::LayerType].mask; +} diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index ee352536..0adbded8 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -635,10 +635,9 @@ int MainWindow::getMetatileLayerType(int metatileId) { void MainWindow::setMetatileLayerType(int metatileId, int layerType) { Metatile * metatile = this->getMetatile(metatileId); - uint8_t u_layerType = static_cast(layerType); - if (!metatile || metatile->layerType == u_layerType || u_layerType >= NUM_METATILE_LAYER_TYPES) + if (!metatile) return; - metatile->layerType = u_layerType; + metatile->setLayerType(layerType); this->saveMetatileAttributesByMetatileId(metatileId); } @@ -651,10 +650,9 @@ int MainWindow::getMetatileEncounterType(int metatileId) { void MainWindow::setMetatileEncounterType(int metatileId, int encounterType) { Metatile * metatile = this->getMetatile(metatileId); - uint8_t u_encounterType = static_cast(encounterType); - if (!metatile || metatile->encounterType == u_encounterType || u_encounterType >= NUM_METATILE_ENCOUNTER_TYPES) + if (!metatile) return; - metatile->encounterType = u_encounterType; + metatile->setEncounterType(encounterType); this->saveMetatileAttributesByMetatileId(metatileId); } @@ -667,10 +665,9 @@ int MainWindow::getMetatileTerrainType(int metatileId) { void MainWindow::setMetatileTerrainType(int metatileId, int terrainType) { Metatile * metatile = this->getMetatile(metatileId); - uint8_t u_terrainType = static_cast(terrainType); - if (!metatile || metatile->terrainType == u_terrainType || u_terrainType >= NUM_METATILE_TERRAIN_TYPES) + if (!metatile) return; - metatile->terrainType = u_terrainType; + metatile->setTerrainType(terrainType); this->saveMetatileAttributesByMetatileId(metatileId); } @@ -683,10 +680,9 @@ int MainWindow::getMetatileBehavior(int metatileId) { void MainWindow::setMetatileBehavior(int metatileId, int behavior) { Metatile * metatile = this->getMetatile(metatileId); - uint16_t u_behavior = static_cast(behavior); - if (!metatile || metatile->behavior == u_behavior) + if (!metatile) return; - metatile->behavior = u_behavior; + metatile->setBehavior(behavior); this->saveMetatileAttributesByMetatileId(metatileId); } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 14e9d441..5532334a 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -99,9 +99,7 @@ void TilesetEditor::initUi() { this->ui->spinBox_paletteSelector->setMinimum(0); this->ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); - this->setMetatileBehaviors(); - this->setMetatileLayersUi(); - this->setVersionSpecificUi(); + this->setAttributesUi(); this->setMetatileLabelValidator(); this->initMetatileSelector(); @@ -113,43 +111,55 @@ void TilesetEditor::initUi() { this->restoreWindowState(); } -void TilesetEditor::setMetatileBehaviors() { - for (int num : project->metatileBehaviorMapInverse.keys()) { - this->ui->comboBox_metatileBehaviors->addItem(project->metatileBehaviorMapInverse[num], num); - } -} - -void TilesetEditor::setMetatileLayersUi() { - if (!projectConfig.getTripleLayerMetatilesEnabled()) { - this->ui->comboBox_layerType->addItem("Normal - Middle/Top", METATILE_LAYER_MIDDLE_TOP); - this->ui->comboBox_layerType->addItem("Covered - Bottom/Middle", METATILE_LAYER_BOTTOM_MIDDLE); - this->ui->comboBox_layerType->addItem("Split - Bottom/Top", METATILE_LAYER_BOTTOM_TOP); +void TilesetEditor::setAttributesUi() { + // Behavior + if (Metatile::customLayout[Metatile::Attr::Behavior].mask) { + for (int num : project->metatileBehaviorMapInverse.keys()) { + this->ui->comboBox_metatileBehaviors->addItem(project->metatileBehaviorMapInverse[num], num); + } } else { - this->ui->comboBox_layerType->setVisible(false); - this->ui->label_layerType->setVisible(false); - this->ui->label_BottomTop->setText("Bottom/Middle/Top"); + this->ui->comboBox_metatileBehaviors->setVisible(false); + this->ui->label_metatileBehavior->setVisible(false); } -} -void TilesetEditor::setVersionSpecificUi() { - if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { - this->ui->comboBox_encounterType->setVisible(true); - this->ui->label_encounterType->setVisible(true); - this->ui->comboBox_encounterType->addItem("None", ENCOUNTER_NONE); - this->ui->comboBox_encounterType->addItem("Land", ENCOUNTER_LAND); - this->ui->comboBox_encounterType->addItem("Water", ENCOUNTER_WATER); - this->ui->comboBox_terrainType->setVisible(true); - this->ui->label_terrainType->setVisible(true); + // Terrain Type + if (Metatile::customLayout[Metatile::Attr::TerrainType].mask) { this->ui->comboBox_terrainType->addItem("Normal", TERRAIN_NONE); this->ui->comboBox_terrainType->addItem("Grass", TERRAIN_GRASS); this->ui->comboBox_terrainType->addItem("Water", TERRAIN_WATER); this->ui->comboBox_terrainType->addItem("Waterfall", TERRAIN_WATERFALL); } else { - this->ui->comboBox_encounterType->setVisible(false); - this->ui->label_encounterType->setVisible(false); this->ui->comboBox_terrainType->setVisible(false); this->ui->label_terrainType->setVisible(false); } + + // Encounter Type + if (Metatile::customLayout[Metatile::Attr::EncounterType].mask) { + this->ui->comboBox_encounterType->addItem("None", ENCOUNTER_NONE); + this->ui->comboBox_encounterType->addItem("Land", ENCOUNTER_LAND); + this->ui->comboBox_encounterType->addItem("Water", ENCOUNTER_WATER); + } else { + this->ui->comboBox_encounterType->setVisible(false); + this->ui->label_encounterType->setVisible(false); + } + + // Layer Type + if (!projectConfig.getTripleLayerMetatilesEnabled()) { + this->ui->comboBox_layerType->addItem("Normal - Middle/Top", METATILE_LAYER_MIDDLE_TOP); + this->ui->comboBox_layerType->addItem("Covered - Bottom/Middle", METATILE_LAYER_BOTTOM_MIDDLE); + this->ui->comboBox_layerType->addItem("Split - Bottom/Top", METATILE_LAYER_BOTTOM_TOP); + if (!Metatile::customLayout[Metatile::Attr::LayerType].mask) { + // User doesn't have triple layer metatiles, but has no layer type attribute. + // Porymap is still using the layer type value to render metatiles, and with + // no mask set every metatile will be "Middle/Top", so just display the combo + // box but prevent the user from changing the value. + this->ui->comboBox_layerType->setEnabled(false); + } + } else { + this->ui->comboBox_layerType->setVisible(false); + this->ui->label_layerType->setVisible(false); + this->ui->label_BottomTop->setText("Bottom/Middle/Top"); + } } void TilesetEditor::setMetatileLabelValidator() { @@ -373,13 +383,9 @@ void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) { this->ui->graphicsView_metatileLayers->setFixedSize(this->metatileLayersItem->pixmap().width() + 2, this->metatileLayersItem->pixmap().height() + 2); this->ui->lineEdit_metatileLabel->setText(this->metatile->label); setComboValue(this->ui->comboBox_metatileBehaviors, this->metatile->behavior); - if (!projectConfig.getTripleLayerMetatilesEnabled()) { - setComboValue(this->ui->comboBox_layerType, this->metatile->layerType); - } - if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { - setComboValue(this->ui->comboBox_encounterType, this->metatile->encounterType); - setComboValue(this->ui->comboBox_terrainType, this->metatile->terrainType); - } + setComboValue(this->ui->comboBox_layerType, this->metatile->layerType); + setComboValue(this->ui->comboBox_encounterType, this->metatile->encounterType); + setComboValue(this->ui->comboBox_terrainType, this->metatile->terrainType); } void TilesetEditor::onHoveredTileChanged(uint16_t tile) { @@ -501,7 +507,7 @@ void TilesetEditor::on_comboBox_metatileBehaviors_textActivated(const QString &m { if (this->metatile) { Metatile *prevMetatile = new Metatile(*this->metatile); - this->metatile->behavior = static_cast(project->metatileBehaviorMap[metatileBehavior]); + this->metatile->setBehavior(project->metatileBehaviorMap[metatileBehavior]); MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(), prevMetatile, new Metatile(*this->metatile)); metatileHistory.push(commit); @@ -537,7 +543,7 @@ void TilesetEditor::on_comboBox_layerType_activated(int layerType) { if (this->metatile) { Metatile *prevMetatile = new Metatile(*this->metatile); - this->metatile->layerType = static_cast(layerType); + this->metatile->setLayerType(layerType); MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(), prevMetatile, new Metatile(*this->metatile)); metatileHistory.push(commit); @@ -550,7 +556,7 @@ void TilesetEditor::on_comboBox_encounterType_activated(int encounterType) { if (this->metatile) { Metatile *prevMetatile = new Metatile(*this->metatile); - this->metatile->encounterType = static_cast(encounterType); + this->metatile->setEncounterType(encounterType); MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(), prevMetatile, new Metatile(*this->metatile)); metatileHistory.push(commit); @@ -562,7 +568,7 @@ void TilesetEditor::on_comboBox_terrainType_activated(int terrainType) { if (this->metatile) { Metatile *prevMetatile = new Metatile(*this->metatile); - this->metatile->terrainType = static_cast(terrainType); + this->metatile->setTerrainType(terrainType); MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(), prevMetatile, new Metatile(*this->metatile)); metatileHistory.push(commit); From 8b884f38477fd775dac3ff43489baf4830700497 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 26 Oct 2022 03:37:09 -0400 Subject: [PATCH 04/10] Update manual/changelog --- CHANGELOG.md | 2 +- docsrc/manual/settings-and-options.rst | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7992a51b..5043ac9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add Cut/Copy/Paste for metatiles in the Tileset Editor. - Add button to copy the full metatile label to the clipboard in the Tileset Editor. - Add ability to export an image of the primary or secondary tileset's metatiles. -- Add new config options for customizing how new maps are filled, setting default tilesets, and whether the most recent project should be opened on launch. +- Add new config options for customizing metatile attributes, how new maps are filled, setting default tilesets, and whether the most recent project should be opened on launch. - Add color picker to palette editor for taking colors from the screen. - Add new features to the scripting API, including the ability to display messages and user input windows, set the overlay's opacity, rotation, scale, and clipping, interact with map header properties and the map border, read tile pixel data, and more. diff --git a/docsrc/manual/settings-and-options.rst b/docsrc/manual/settings-and-options.rst index 2d85ed0b..dd57dc27 100644 --- a/docsrc/manual/settings-and-options.rst +++ b/docsrc/manual/settings-and-options.rst @@ -59,5 +59,10 @@ your project root as ``porymap.user.cfg``. You should add this file to your giti ``prefabs_import_prompted``, 0, project, no, Keeps track of whether or not the project was prompted for importing default prefabs ``tilesets_have_callback``, 1, project, yes, Whether new tileset headers should have the ``callback`` field ``tilesets_have_is_compressed``, 1, project, yes, Whether new tileset headers should have the ``isCompressed`` field + ``metatile_attributes_size``, 2 or 4, project, yes, The number of attribute bytes each metatile has + ``metatile_behavior_mask``, ``0xFF`` or ``0x1FF``, project, yes, The mask for the metatile Behavior attribute + ``metatile_encounter_type_mask``, ``0x0`` or ``0x7000000``, project, yes, The mask for the metatile Encounter Type attribute + ``metatile_layer_type_mask``, ``0xF000`` or ``0x60000000``, project, yes, The mask for the metatile Layer Type attribute + ``metatile_terrain_type_mask``, ``0x0`` or ``0x3E00``, project, yes, The mask for the metatile Terrain Type attribute Some of these settings can be toggled manually in porymap via the *Options* menu. From 9cd8777246fdb62b4a094687480796c337590a5f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 26 Oct 2022 03:56:46 -0400 Subject: [PATCH 05/10] Remove last base game version difference --- docsrc/manual/settings-and-options.rst | 1 + include/config.h | 2 ++ src/config.cpp | 8 +++++ src/mainwindow.cpp | 45 ++++++-------------------- src/project.cpp | 19 ++++------- src/ui/newmappopup.cpp | 4 +-- 6 files changed, 30 insertions(+), 49 deletions(-) diff --git a/docsrc/manual/settings-and-options.rst b/docsrc/manual/settings-and-options.rst index dd57dc27..60328bdd 100644 --- a/docsrc/manual/settings-and-options.rst +++ b/docsrc/manual/settings-and-options.rst @@ -47,6 +47,7 @@ your project root as ``porymap.user.cfg``. You should add this file to your giti ``enable_hidden_item_requires_itemfinder``, 1 if ``pokefirered``, project, yes, Adds ``Requires Itemfinder`` to Hidden Item events ``enable_heal_location_respawn_data``, 1 if ``pokefirered``, project, yes, Adds ``Respawn Map`` and ``Respawn NPC`` to Heal Location events ``enable_floor_number``, 1 if ``pokefirered``, project, yes, Adds ``Floor Number`` to map headers + ``enable_map_allow_flags``, 1 if not ``pokeruby``, project, yes, "Adds ``Allow Running``, ``Allow Biking``, and ``Allow Dig & Escape Rope`` to map headers" ``create_map_text_file``, 1 if not ``pokeemerald``, project, yes, A ``text.inc`` or ``text.pory`` file will be created for any new map ``enable_triple_layer_metatiles``, 0, project, yes, Enables triple-layer metatiles (See https://github.com/pret/pokeemerald/wiki/Triple-layer-metatiles) ``new_map_metatile``, 1, project, yes, The metatile id that will be used to fill new maps diff --git a/include/config.h b/include/config.h index 5dd4f38e..9ceda88b 100644 --- a/include/config.h +++ b/include/config.h @@ -270,6 +270,7 @@ public: uint32_t getMetatileTerrainTypeMask(); uint32_t getMetatileEncounterTypeMask(); uint32_t getMetatileLayerTypeMask(); + bool getMapAllowFlagsEnabled(); protected: virtual QString getConfigFilepath() override; virtual void parseConfigKeyValue(QString key, QString value) override; @@ -306,6 +307,7 @@ private: uint32_t metatileTerrainTypeMask; uint32_t metatileEncounterTypeMask; uint32_t metatileLayerTypeMask; + bool enableMapAllowFlags; }; extern ProjectConfig projectConfig; diff --git a/src/config.cpp b/src/config.cpp index ae5abe92..c4acb43e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -574,6 +574,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->metatileEncounterTypeMask = getConfigLong(key, value, 0, 0xFFFFFFFF, 0); } else if (key == "metatile_layer_type_mask") { this->metatileLayerTypeMask = getConfigLong(key, value, 0, 0xFFFFFFFF, 0); + } else if (key == "enable_map_allow_flags") { + this->enableMapAllowFlags = getConfigBool(key, value); #ifdef CONFIG_BACKWARDS_COMPATABILITY } else if (key == "recent_map") { userConfig.setRecentMap(value); @@ -630,6 +632,7 @@ void ProjectConfig::setUnreadKeys() { if (!readKeys.contains("metatile_terrain_type_mask")) this->metatileTerrainTypeMask = layout[Metatile::Attr::TerrainType].mask; if (!readKeys.contains("metatile_encounter_type_mask")) this->metatileEncounterTypeMask = layout[Metatile::Attr::EncounterType].mask; if (!readKeys.contains("metatile_layer_type_mask")) this-> metatileLayerTypeMask = layout[Metatile::Attr::LayerType].mask; + if (!readKeys.contains("enable_map_allow_flags")) this->enableMapAllowFlags = (this->baseGameVersion != BaseGameVersion::pokeruby); } QMap ProjectConfig::getKeyValueMap() { @@ -666,6 +669,7 @@ QMap ProjectConfig::getKeyValueMap() { 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; } @@ -935,6 +939,10 @@ uint32_t ProjectConfig::getMetatileLayerTypeMask() { return this->metatileLayerTypeMask; } +bool ProjectConfig::getMapAllowFlagsEnabled() { + return this->enableMapAllowFlags; +} + UserConfig userConfig; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e850118c..2dc0dc0b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -376,36 +376,13 @@ void MainWindow::setProjectSpecificUIVisibility() ui->actionUse_Poryscript->setChecked(projectConfig.getUsePoryScript()); this->setWildEncountersUIEnabled(userConfig.getEncounterJsonActive()); - switch (projectConfig.getBaseGameVersion()) - { - case BaseGameVersion::pokeruby: - ui->checkBox_AllowRunning->setVisible(false); - ui->checkBox_AllowBiking->setVisible(false); - ui->checkBox_AllowEscaping->setVisible(false); - ui->label_AllowRunning->setVisible(false); - ui->label_AllowBiking->setVisible(false); - ui->label_AllowEscaping->setVisible(false); - ui->actionRegion_Map_Editor->setVisible(true); - break; - case BaseGameVersion::pokeemerald: - ui->checkBox_AllowRunning->setVisible(true); - ui->checkBox_AllowBiking->setVisible(true); - ui->checkBox_AllowEscaping->setVisible(true); - ui->label_AllowRunning->setVisible(true); - ui->label_AllowBiking->setVisible(true); - ui->label_AllowEscaping->setVisible(true); - ui->actionRegion_Map_Editor->setVisible(true); - break; - case BaseGameVersion::pokefirered: - ui->checkBox_AllowRunning->setVisible(true); - ui->checkBox_AllowBiking->setVisible(true); - ui->checkBox_AllowEscaping->setVisible(true); - ui->label_AllowRunning->setVisible(true); - ui->label_AllowBiking->setVisible(true); - ui->label_AllowEscaping->setVisible(true); - ui->actionRegion_Map_Editor->setVisible(true); - break; - } + bool hasFlags = projectConfig.getMapAllowFlagsEnabled(); + ui->checkBox_AllowRunning->setVisible(hasFlags); + ui->checkBox_AllowBiking->setVisible(hasFlags); + ui->checkBox_AllowEscaping->setVisible(hasFlags); + ui->label_AllowRunning->setVisible(hasFlags); + ui->label_AllowBiking->setVisible(hasFlags); + ui->label_AllowEscaping->setVisible(hasFlags); ui->newEventToolButton->newWeatherTriggerAction->setVisible(projectConfig.getEventWeatherTriggerEnabled()); ui->newEventToolButton->newSecretBaseAction->setVisible(projectConfig.getEventSecretBaseEnabled()); @@ -806,11 +783,9 @@ void MainWindow::displayMapProperties() { ui->comboBox_Type->setCurrentText(map->type); ui->comboBox_BattleScene->setCurrentText(map->battle_scene); ui->checkBox_ShowLocation->setChecked(map->show_location); - if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) { - ui->checkBox_AllowRunning->setChecked(map->allowRunning); - ui->checkBox_AllowBiking->setChecked(map->allowBiking); - ui->checkBox_AllowEscaping->setChecked(map->allowEscaping); - } + ui->checkBox_AllowRunning->setChecked(map->allowRunning); + ui->checkBox_AllowBiking->setChecked(map->allowBiking); + ui->checkBox_AllowEscaping->setChecked(map->allowEscaping); ui->spinBox_FloorNumber->setValue(map->floorNumber); // Custom fields table. diff --git a/src/project.cpp b/src/project.cpp index c6a1dd5c..dbf04f41 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -164,7 +164,7 @@ const QSet defaultTopLevelMapFields = { QSet Project::getTopLevelMapFields() { QSet topLevelMapFields = defaultTopLevelMapFields; - if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) { + if (projectConfig.getMapAllowFlagsEnabled()) { topLevelMapFields.insert("allow_cycling"); topLevelMapFields.insert("allow_escaping"); topLevelMapFields.insert("allow_running"); @@ -199,7 +199,7 @@ bool Project::loadMapData(Map* map) { map->show_location = ParseUtil::jsonToBool(mapObj["show_map_name"]); map->battle_scene = ParseUtil::jsonToQString(mapObj["battle_scene"]); - if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) { + if (projectConfig.getMapAllowFlagsEnabled()) { map->allowBiking = ParseUtil::jsonToBool(mapObj["allow_cycling"]); map->allowEscaping = ParseUtil::jsonToBool(mapObj["allow_escaping"]); map->allowRunning = ParseUtil::jsonToBool(mapObj["allow_running"]); @@ -382,15 +382,10 @@ void Project::setNewMapHeader(Map* map, int mapIndex) { map->type = mapTypes.value(0, "MAP_TYPE_NONE"); map->song = defaultSong; map->show_location = true; - if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) { - map->allowBiking = true; - map->allowEscaping = false; - map->allowRunning = true; - } - if (projectConfig.getFloorNumberEnabled()) { - map->floorNumber = 0; - } - + map->allowBiking = true; + map->allowEscaping = false; + map->allowRunning = true; + map->floorNumber = 0; map->battle_scene = mapBattleScenes.value(0, "MAP_BATTLE_SCENE_NORMAL"); } @@ -1263,7 +1258,7 @@ void Project::saveMap(Map *map) { mapObj["requires_flash"] = map->requiresFlash; mapObj["weather"] = map->weather; mapObj["map_type"] = map->type; - if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) { + if (projectConfig.getMapAllowFlagsEnabled()) { mapObj["allow_cycling"] = map->allowBiking; mapObj["allow_escaping"] = map->allowEscaping; mapObj["allow_running"] = map->allowRunning; diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index 31d85954..9965e7d3 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -46,7 +46,7 @@ void NewMapPopup::init() { ui->spinBox_NewMap_Floor_Number->setMaximum(127); // Hide config specific ui elements - bool hasFlags = (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby); + bool hasFlags = projectConfig.getMapAllowFlagsEnabled(); ui->checkBox_NewMap_Allow_Running->setVisible(hasFlags); ui->checkBox_NewMap_Allow_Biking->setVisible(hasFlags); ui->checkBox_NewMap_Allow_Escape_Rope->setVisible(hasFlags); @@ -291,7 +291,7 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { newMap->needsHealLocation = true; } - if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) { + if (projectConfig.getMapAllowFlagsEnabled()) { newMap->allowRunning = this->ui->checkBox_NewMap_Allow_Running->isChecked(); newMap->allowBiking = this->ui->checkBox_NewMap_Allow_Biking->isChecked(); newMap->allowEscaping = this->ui->checkBox_NewMap_Allow_Escape_Rope->isChecked(); From 577dc2fce263948bc5ce2b62497577c4b9762754 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 26 Oct 2022 04:13:53 -0400 Subject: [PATCH 06/10] Simplify metatile attribute layouts --- include/core/metatile.h | 75 ++++++++++----- src/config.cpp | 9 +- src/core/metatile.cpp | 185 +++++++++++++++++------------------- src/core/metatileparser.cpp | 2 +- src/ui/tileseteditor.cpp | 8 +- 5 files changed, 145 insertions(+), 134 deletions(-) diff --git a/include/core/metatile.h b/include/core/metatile.h index db1f6e6f..0b4b8a60 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -30,6 +30,29 @@ enum { NUM_METATILE_TERRAIN_TYPES }; +class MetatileAttr +{ +public: + MetatileAttr(); + MetatileAttr(uint32_t mask, int shift); + +public: + uint32_t mask; + int shift; + + // Given the raw value for all attributes of a metatile + // Returns the extracted value for this attribute + uint32_t fromRaw(uint32_t raw) const { return (raw & this->mask) >> this->shift; } + + // Given a value for this attribute + // Returns the raw value to OR together with the other attributes + uint32_t toRaw(uint32_t value) const { return (value << this->shift) & this->mask; } + + // Given an arbitrary value to set for an attribute + // Returns a bounded value for that attribute + uint32_t getClamped(int value) const { return static_cast(value) & (this->mask >> this->shift); } +}; + class Metatile { public: @@ -47,30 +70,23 @@ public: uint32_t unusedAttributes; QString label; - enum Attr { - Behavior, - TerrainType, - EncounterType, - LayerType, - }; - - struct AttrLayout { - uint32_t mask; - int shift; - }; - - static const QHash defaultLayoutFRLG; - static const QHash defaultLayoutRSE; - static QHash customLayout; - uint32_t getAttributes(); void setAttributes(uint32_t data); - void convertAttributes(uint32_t data, BaseGameVersion version); + void setAttributes(uint32_t data, BaseGameVersion version); - void setBehavior(uint32_t); - void setTerrainType(uint32_t); - void setEncounterType(uint32_t); - void setLayerType(uint32_t); + void setBehavior(int value) { this->behavior = behaviorAttr.getClamped(value); } + void setTerrainType(int value) { this->terrainType = terrainTypeAttr.getClamped(value); } + void setEncounterType(int value) { this->encounterType = encounterTypeAttr.getClamped(value); } + void setLayerType(int value) { this->layerType = layerTypeAttr.getClamped(value); } + + static uint32_t getBehaviorMask() { return behaviorAttr.mask; } + static uint32_t getTerrainTypeMask() { return terrainTypeAttr.mask; } + static uint32_t getEncounterTypeMask() { return encounterTypeAttr.mask; } + static uint32_t getLayerTypeMask() { return layerTypeAttr.mask; } + static uint32_t getBehaviorMask(BaseGameVersion version); + static uint32_t getTerrainTypeMask(BaseGameVersion version); + static uint32_t getEncounterTypeMask(BaseGameVersion version); + static uint32_t getLayerTypeMask(BaseGameVersion version); static int getIndexInTileset(int); static QPoint coordFromPixmapCoord(const QPointF &pixelCoord); @@ -78,11 +94,22 @@ public: static void setCustomLayout(); private: + // Stores how each attribute should be laid out for all metatiles, according to the user's config + static MetatileAttr behaviorAttr; + static MetatileAttr terrainTypeAttr; + static MetatileAttr encounterTypeAttr; + static MetatileAttr layerTypeAttr; + static uint32_t unusedAttrMask; - void setAttributes(uint32_t, const QHash*); - static void setCustomAttributeLayout(Metatile::AttrLayout *, uint32_t, uint32_t); - static bool isMaskTooSmall(Metatile::AttrLayout * layout, int n); + // Stores how each attribute should be laid out for all metatiles, according to the vanilla games + // Used to set default config values and import maps with AdvanceMap + static const QHash defaultLayoutFRLG; + static const QHash defaultLayoutRSE; + static const QHash*> defaultLayouts; + + static void setCustomAttributeLayout(MetatileAttr *, uint32_t, uint32_t); + static bool isMaskTooSmall(MetatileAttr *, int); static bool doMasksOverlap(QList); }; diff --git a/src/config.cpp b/src/config.cpp index c4acb43e..1c5663fd 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -627,11 +627,10 @@ void ProjectConfig::setUnreadKeys() { if (!readKeys.contains("new_map_border_metatiles")) this->newMapBorderMetatileIds = isPokefirered ? DEFAULT_BORDER_FRLG : DEFAULT_BORDER_RSE; if (!readKeys.contains("default_secondary_tileset")) this->defaultSecondaryTileset = isPokefirered ? "gTileset_PalletTown" : "gTileset_Petalburg"; if (!readKeys.contains("metatile_attributes_size")) this->metatileAttributesSize = Metatile::getDefaultAttributesSize(this->baseGameVersion); - const QHash layout = isPokefirered ? Metatile::defaultLayoutFRLG : Metatile::defaultLayoutRSE; - if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = layout[Metatile::Attr::Behavior].mask; - if (!readKeys.contains("metatile_terrain_type_mask")) this->metatileTerrainTypeMask = layout[Metatile::Attr::TerrainType].mask; - if (!readKeys.contains("metatile_encounter_type_mask")) this->metatileEncounterTypeMask = layout[Metatile::Attr::EncounterType].mask; - if (!readKeys.contains("metatile_layer_type_mask")) this-> metatileLayerTypeMask = layout[Metatile::Attr::LayerType].mask; + if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = Metatile::getBehaviorMask(this->baseGameVersion); + if (!readKeys.contains("metatile_terrain_type_mask")) this->metatileTerrainTypeMask = Metatile::getTerrainTypeMask(this->baseGameVersion); + if (!readKeys.contains("metatile_encounter_type_mask")) this->metatileEncounterTypeMask = Metatile::getEncounterTypeMask(this->baseGameVersion); + if (!readKeys.contains("metatile_layer_type_mask")) this-> metatileLayerTypeMask = Metatile::getLayerTypeMask(this->baseGameVersion); if (!readKeys.contains("enable_map_allow_flags")) this->enableMapAllowFlags = (this->baseGameVersion != BaseGameVersion::pokeruby); } diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index 44b71fa3..d48fbbac 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -2,22 +2,42 @@ #include "tileset.h" #include "project.h" -QHash Metatile::customLayout = {}; +const QHash Metatile::defaultLayoutFRLG = { + {"behavior", MetatileAttr(0x000001FF, 0) }, + {"terrainType", MetatileAttr(0x00003E00, 9) }, + {"encounterType", MetatileAttr(0x07000000, 24) }, + {"layerType", MetatileAttr(0x60000000, 29) }, +}; + +const QHash Metatile::defaultLayoutRSE = { + {"behavior", MetatileAttr(0x00FF, 0) }, + {"terrainType", MetatileAttr() }, + {"encounterType", MetatileAttr() }, + {"layerType", MetatileAttr(0xF000, 12) }, +}; + +const QHash*> Metatile::defaultLayouts = { + { BaseGameVersion::pokeruby, &defaultLayoutRSE }, + { BaseGameVersion::pokefirered, &defaultLayoutFRLG }, + { BaseGameVersion::pokeemerald, &defaultLayoutRSE }, +}; + +MetatileAttr Metatile::behaviorAttr; +MetatileAttr Metatile::terrainTypeAttr; +MetatileAttr Metatile::encounterTypeAttr; +MetatileAttr Metatile::layerTypeAttr; + uint32_t Metatile::unusedAttrMask = 0; -const QHash Metatile::defaultLayoutFRLG = { - {Metatile::Attr::Behavior, { .mask = 0x000001FF, .shift = 0} }, - {Metatile::Attr::TerrainType, { .mask = 0x00003E00, .shift = 9} }, - {Metatile::Attr::EncounterType, { .mask = 0x07000000, .shift = 24} }, - {Metatile::Attr::LayerType, { .mask = 0x60000000, .shift = 29} }, -}; +MetatileAttr::MetatileAttr() : + mask(0), + shift(0) +{ } -const QHash Metatile::defaultLayoutRSE = { - {Metatile::Attr::Behavior, { .mask = 0x000000FF, .shift = 0} }, - {Metatile::Attr::TerrainType, { .mask = 0x00000000, .shift = 0} }, - {Metatile::Attr::EncounterType, { .mask = 0x00000000, .shift = 0} }, - {Metatile::Attr::LayerType, { .mask = 0x0000F000, .shift = 12} }, -}; +MetatileAttr::MetatileAttr(uint32_t mask, int shift) : + mask(mask), + shift(shift) +{ } Metatile::Metatile() : behavior(0), @@ -48,19 +68,14 @@ int Metatile::getIndexInTileset(int metatileId) { } } -// Get the vanilla attribute sizes based on version. -// Used as a default in the config and for AdvanceMap import. -int Metatile::getDefaultAttributesSize(BaseGameVersion version) { - return (version == BaseGameVersion::pokefirered) ? 4 : 2; -} - QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) { int x = static_cast(pixelCoord.x()) / 16; int y = static_cast(pixelCoord.y()) / 16; return QPoint(x, y); } -void Metatile::setCustomAttributeLayout(Metatile::AttrLayout * layout, uint32_t mask, uint32_t max) { +// Set the layout of a metatile attribute using the mask read from the config file +void Metatile::setCustomAttributeLayout(MetatileAttr * attr, uint32_t mask, uint32_t max) { if (mask > max) { uint32_t oldMask = mask; mask &= max; @@ -68,15 +83,22 @@ void Metatile::setCustomAttributeLayout(Metatile::AttrLayout * layout, uint32_t .arg(QString::number(oldMask, 16).toUpper()) .arg(QString::number(mask, 16).toUpper())); } - layout->mask = mask; - layout->shift = log2(mask & ~(mask - 1)); // Get the position of the rightmost set bit + attr->mask = mask; + attr->shift = mask ? log2(mask & ~(mask - 1)) : 0; // Get position of the least significant set bit } -// For checking whether a metatile attribute mask can contain all the hard-coded values -bool Metatile::isMaskTooSmall(Metatile::AttrLayout * layout, int n) { - if (!layout->mask) return false; - uint32_t maxValue = n - 1; - return (maxValue & (layout->mask >> layout->shift)) != maxValue; +// For checking whether a metatile attribute mask can contain all the available hard-coded options +bool Metatile::isMaskTooSmall(MetatileAttr * attr, int max) { + if (attr->mask == 0 || max <= 0) return false; + + // Get position of the most significant set bit + uint32_t n = log2(max); + + // Get a mask for all values 0 to max. + // This may fail for n=31, but that's not a concern here. + uint32_t rangeMask = (1 << (n + 1)) - 1; + + return attr->getClamped(rangeMask) != rangeMask; } bool Metatile::doMasksOverlap(QList masks) { @@ -98,107 +120,70 @@ void Metatile::setCustomLayout() { const uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0); // Set custom attribute masks from the config file - setCustomAttributeLayout(&customLayout[Attr::Behavior], projectConfig.getMetatileBehaviorMask(), maxMask); - setCustomAttributeLayout(&customLayout[Attr::TerrainType], projectConfig.getMetatileTerrainTypeMask(), maxMask); - setCustomAttributeLayout(&customLayout[Attr::EncounterType], projectConfig.getMetatileEncounterTypeMask(), maxMask); - setCustomAttributeLayout(&customLayout[Attr::LayerType], projectConfig.getMetatileLayerTypeMask(), maxMask); + setCustomAttributeLayout(&Metatile::behaviorAttr, projectConfig.getMetatileBehaviorMask(), maxMask); + setCustomAttributeLayout(&Metatile::terrainTypeAttr, projectConfig.getMetatileTerrainTypeMask(), maxMask); + setCustomAttributeLayout(&Metatile::encounterTypeAttr, projectConfig.getMetatileEncounterTypeMask(), maxMask); + setCustomAttributeLayout(&Metatile::layerTypeAttr, projectConfig.getMetatileLayerTypeMask(), maxMask); // Set mask for preserving any attribute bits not used by Porymap - Metatile::unusedAttrMask = ~(customLayout[Attr::Behavior].mask - | customLayout[Attr::TerrainType].mask - | customLayout[Attr::EncounterType].mask - | customLayout[Attr::LayerType].mask); + Metatile::unusedAttrMask = ~(getBehaviorMask() | getTerrainTypeMask() | getEncounterTypeMask() | getLayerTypeMask()); Metatile::unusedAttrMask &= maxMask; // Overlapping masks are technically ok, but probably not intended. // Additionally, Porymap will not properly reflect that the values are linked. - if (doMasksOverlap({customLayout[Attr::Behavior].mask, - customLayout[Attr::TerrainType].mask, - customLayout[Attr::EncounterType].mask, - customLayout[Attr::LayerType].mask})) { - logWarn("Metatile attribute masks are overlapping."); + if (doMasksOverlap({getBehaviorMask(), getTerrainTypeMask(), getEncounterTypeMask(), getLayerTypeMask()})) { + logWarn("Metatile attribute masks are overlapping. This may result in unexpected attribute values."); } // The available options in the Tileset Editor for Terrain Type, Encounter Type, and Layer Type are hard-coded. // Warn the user if they have set a nonzero mask that is too small to contain these options. // They'll be allowed to select them, but they'll be truncated to a different value when revisited. - if (isMaskTooSmall(&customLayout[Attr::TerrainType], NUM_METATILE_TERRAIN_TYPES)) + if (isMaskTooSmall(&Metatile::terrainTypeAttr, NUM_METATILE_TERRAIN_TYPES - 1)) logWarn(QString("Metatile Terrain Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_TERRAIN_TYPES)); - if (isMaskTooSmall(&customLayout[Attr::EncounterType], NUM_METATILE_ENCOUNTER_TYPES)) + if (isMaskTooSmall(&Metatile::encounterTypeAttr, NUM_METATILE_ENCOUNTER_TYPES - 1)) logWarn(QString("Metatile Encounter Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_ENCOUNTER_TYPES)); - if (isMaskTooSmall(&customLayout[Attr::LayerType], NUM_METATILE_LAYER_TYPES)) + if (isMaskTooSmall(&Metatile::layerTypeAttr, NUM_METATILE_LAYER_TYPES - 1)) logWarn(QString("Metatile Layer Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_LAYER_TYPES)); } uint32_t Metatile::getAttributes() { uint32_t attributes = this->unusedAttributes & Metatile::unusedAttrMask; - - // Behavior - Metatile::AttrLayout attr = Metatile::customLayout[Attr::Behavior]; - attributes |= (this->behavior << attr.shift) & attr.mask; - - // Terrain Type - attr = Metatile::customLayout[Attr::TerrainType]; - attributes |= (this->terrainType << attr.shift) & attr.mask; - - // Encounter Type - attr = Metatile::customLayout[Attr::EncounterType]; - attributes |= (this->encounterType << attr.shift) & attr.mask; - - // Layer Type - attr = Metatile::customLayout[Attr::LayerType]; - attributes |= (this->layerType << attr.shift) & attr.mask; - + attributes |= Metatile::behaviorAttr.toRaw(this->behavior); + attributes |= Metatile::terrainTypeAttr.toRaw(this->terrainType); + attributes |= Metatile::encounterTypeAttr.toRaw(this->encounterType); + attributes |= Metatile::layerTypeAttr.toRaw(this->layerType); return attributes; } -void Metatile::setAttributes(uint32_t data, const QHash * layout) { - // Behavior - Metatile::AttrLayout attr = layout->value(Attr::Behavior); - this->behavior = (data & attr.mask) >> attr.shift; - - // Terrain Type - attr = layout->value(Attr::TerrainType); - this->terrainType = (data & attr.mask) >> attr.shift; - - // Encounter Type - attr = layout->value(Attr::EncounterType); - this->encounterType = (data & attr.mask) >> attr.shift; - - // Layer Type - attr = layout->value(Attr::LayerType); - this->layerType = (data & attr.mask) >> attr.shift; - +void Metatile::setAttributes(uint32_t data) { + this->behavior = Metatile::behaviorAttr.fromRaw(data); + this->terrainType = Metatile::terrainTypeAttr.fromRaw(data); + this->encounterType = Metatile::encounterTypeAttr.fromRaw(data); + this->layerType = Metatile::layerTypeAttr.fromRaw(data); this->unusedAttributes = data & Metatile::unusedAttrMask; } -void Metatile::setAttributes(uint32_t data) { - this->setAttributes(data, &Metatile::customLayout); -} - // Read attributes using a vanilla layout, then set them using the user's layout. For AdvanceMap import -void Metatile::convertAttributes(uint32_t data, BaseGameVersion version) { - if (version == BaseGameVersion::pokefirered) { - this->setAttributes(data, &Metatile::defaultLayoutFRLG); - } else { - this->setAttributes(data, &Metatile::defaultLayoutRSE); - } - // Clean data to fit the user's custom masks - this->setAttributes(this->getAttributes()); +void Metatile::setAttributes(uint32_t data, BaseGameVersion version) { + const auto defaultLayout = Metatile::defaultLayouts.value(version); + this->setBehavior(defaultLayout->value("behavior").fromRaw(data)); + this->setTerrainType(defaultLayout->value("terrainType").fromRaw(data)); + this->setEncounterType(defaultLayout->value("encounterType").fromRaw(data)); + this->setLayerType(defaultLayout->value("layerType").fromRaw(data)); } -void Metatile::setBehavior(uint32_t value) { - this->behavior = value & Metatile::customLayout[Attr::Behavior].mask; +int Metatile::getDefaultAttributesSize(BaseGameVersion version) { + return (version == BaseGameVersion::pokefirered) ? 4 : 2; } - -void Metatile::setTerrainType(uint32_t value) { - this->terrainType = value & Metatile::customLayout[Attr::TerrainType].mask; +uint32_t Metatile::getBehaviorMask(BaseGameVersion version) { + return Metatile::defaultLayouts.value(version)->value("behavior").mask; } - -void Metatile::setEncounterType(uint32_t value) { - this->encounterType = value & Metatile::customLayout[Attr::EncounterType].mask; +uint32_t Metatile::getTerrainTypeMask(BaseGameVersion version) { + return Metatile::defaultLayouts.value(version)->value("terrainType").mask; } - -void Metatile::setLayerType(uint32_t value) { - this->layerType = value & Metatile::customLayout[Attr::LayerType].mask; +uint32_t Metatile::getEncounterTypeMask(BaseGameVersion version) { + return Metatile::defaultLayouts.value(version)->value("encounterType").mask; +} +uint32_t Metatile::getLayerTypeMask(BaseGameVersion version) { + return Metatile::defaultLayouts.value(version)->value("layerType").mask; } diff --git a/src/core/metatileparser.cpp b/src/core/metatileparser.cpp index 3572010c..4fefd86e 100644 --- a/src/core/metatileparser.cpp +++ b/src/core/metatileparser.cpp @@ -82,7 +82,7 @@ QList MetatileParser::parse(QString filepath, bool *error, bool prima uint32_t attributes = 0; for (int j = 0; j < attrSize; j++) attributes |= static_cast(in.at(attrOffset + j)) << (8 * j); - metatile->convertAttributes(attributes, version); + metatile->setAttributes(attributes, version); metatile->tiles = tiles; metatiles.append(metatile); } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 5532334a..df130d5b 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -113,7 +113,7 @@ void TilesetEditor::initUi() { void TilesetEditor::setAttributesUi() { // Behavior - if (Metatile::customLayout[Metatile::Attr::Behavior].mask) { + if (Metatile::getBehaviorMask()) { for (int num : project->metatileBehaviorMapInverse.keys()) { this->ui->comboBox_metatileBehaviors->addItem(project->metatileBehaviorMapInverse[num], num); } @@ -123,7 +123,7 @@ void TilesetEditor::setAttributesUi() { } // Terrain Type - if (Metatile::customLayout[Metatile::Attr::TerrainType].mask) { + if (Metatile::getTerrainTypeMask()) { this->ui->comboBox_terrainType->addItem("Normal", TERRAIN_NONE); this->ui->comboBox_terrainType->addItem("Grass", TERRAIN_GRASS); this->ui->comboBox_terrainType->addItem("Water", TERRAIN_WATER); @@ -134,7 +134,7 @@ void TilesetEditor::setAttributesUi() { } // Encounter Type - if (Metatile::customLayout[Metatile::Attr::EncounterType].mask) { + if (Metatile::getEncounterTypeMask()) { this->ui->comboBox_encounterType->addItem("None", ENCOUNTER_NONE); this->ui->comboBox_encounterType->addItem("Land", ENCOUNTER_LAND); this->ui->comboBox_encounterType->addItem("Water", ENCOUNTER_WATER); @@ -148,7 +148,7 @@ void TilesetEditor::setAttributesUi() { this->ui->comboBox_layerType->addItem("Normal - Middle/Top", METATILE_LAYER_MIDDLE_TOP); this->ui->comboBox_layerType->addItem("Covered - Bottom/Middle", METATILE_LAYER_BOTTOM_MIDDLE); this->ui->comboBox_layerType->addItem("Split - Bottom/Top", METATILE_LAYER_BOTTOM_TOP); - if (!Metatile::customLayout[Metatile::Attr::LayerType].mask) { + if (!Metatile::getLayerTypeMask()) { // User doesn't have triple layer metatiles, but has no layer type attribute. // Porymap is still using the layer type value to render metatiles, and with // no mask set every metatile will be "Middle/Top", so just display the combo From d2fa68ba188e13db806ba8008b827c15b1ff5eff Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 27 Oct 2022 07:52:07 -0400 Subject: [PATCH 07/10] Allow number values for behavior in editor, add mask warning --- include/core/metatile.h | 4 +++- include/ui/tileseteditor.h | 2 +- src/core/metatile.cpp | 14 +++++++++----- src/mainwindow.cpp | 2 +- src/ui/tileseteditor.cpp | 19 +++++++++++++++++-- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/include/core/metatile.h b/include/core/metatile.h index 0b4b8a60..f606dcdb 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -8,6 +8,8 @@ #include #include +class Project; + enum { METATILE_LAYER_MIDDLE_TOP, METATILE_LAYER_BOTTOM_MIDDLE, @@ -91,7 +93,7 @@ public: static int getIndexInTileset(int); static QPoint coordFromPixmapCoord(const QPointF &pixelCoord); static int getDefaultAttributesSize(BaseGameVersion version); - static void setCustomLayout(); + static void setCustomLayout(Project*); private: // Stores how each attribute should be laid out for all metatiles, according to the user's config diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index e671564f..fcdd3bed 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -85,7 +85,7 @@ private slots: void on_actionRedo_triggered(); - void on_comboBox_metatileBehaviors_textActivated(const QString &arg1); + void on_comboBox_metatileBehaviors_currentTextChanged(const QString &arg1); void on_lineEdit_metatileLabel_editingFinished(); diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index d48fbbac..160b0c42 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -95,7 +95,7 @@ bool Metatile::isMaskTooSmall(MetatileAttr * attr, int max) { uint32_t n = log2(max); // Get a mask for all values 0 to max. - // This may fail for n=31, but that's not a concern here. + // This may fail for n > 30, but that's not possible here. uint32_t rangeMask = (1 << (n + 1)) - 1; return attr->getClamped(rangeMask) != rangeMask; @@ -110,7 +110,7 @@ bool Metatile::doMasksOverlap(QList masks) { return false; } -void Metatile::setCustomLayout() { +void Metatile::setCustomLayout(Project * project) { // Get the maximum size of any attribute mask const QHash maxMasks = { {1, 0xFF}, @@ -135,9 +135,13 @@ void Metatile::setCustomLayout() { logWarn("Metatile attribute masks are overlapping. This may result in unexpected attribute values."); } - // The available options in the Tileset Editor for Terrain Type, Encounter Type, and Layer Type are hard-coded. - // Warn the user if they have set a nonzero mask that is too small to contain these options. - // They'll be allowed to select them, but they'll be truncated to a different value when revisited. + // Warn the user if they have set a nonzero mask that is too small to contain its available options. + // They'll be allowed to select the options, but they'll be truncated to a different value when revisited. + if (!project->metatileBehaviorMapInverse.isEmpty()) { + int maxBehavior = project->metatileBehaviorMapInverse.lastKey(); + if (isMaskTooSmall(&Metatile::behaviorAttr, maxBehavior)) + logWarn(QString("Metatile Behavior mask is too small to contain all %1 available options.").arg(maxBehavior)); + } if (isMaskTooSmall(&Metatile::terrainTypeAttr, NUM_METATILE_TERRAIN_TYPES - 1)) logWarn(QString("Metatile Terrain Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_TERRAIN_TYPES)); if (isMaskTooSmall(&Metatile::encounterTypeAttr, NUM_METATILE_ENCOUNTER_TYPES - 1)) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2dc0dc0b..135b819f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -505,7 +505,6 @@ bool MainWindow::openProject(QString dir) { userConfig.load(); projectConfig.setProjectDir(dir); projectConfig.load(); - Metatile::setCustomLayout(); this->closeSupplementaryWindows(); this->setProjectSpecificUIVisibility(); @@ -914,6 +913,7 @@ bool MainWindow::loadDataStructures() { && project->readEventGraphics() && project->readSongNames(); + Metatile::setCustomLayout(project); Scripting::populateGlobalObject(this); return success && loadProjectCombos(); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index df130d5b..d45c65c7 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -503,11 +503,26 @@ void TilesetEditor::on_checkBox_yFlip_stateChanged(int checked) this->metatileLayersItem->clearLastModifiedCoords(); } -void TilesetEditor::on_comboBox_metatileBehaviors_textActivated(const QString &metatileBehavior) +void TilesetEditor::on_comboBox_metatileBehaviors_currentTextChanged(const QString &metatileBehavior) { if (this->metatile) { + int behavior; + if (project->metatileBehaviorMap.contains(metatileBehavior)) { + behavior = project->metatileBehaviorMap[metatileBehavior]; + } else { + // Check if user has entered a number value instead + bool ok; + behavior = metatileBehavior.toInt(&ok); + if (!ok) return; + } + + // This function can also be called when the user selects + // a different metatile. Stop this from being considered a change. + if (this->metatile->behavior == static_cast(behavior)) + return; + Metatile *prevMetatile = new Metatile(*this->metatile); - this->metatile->setBehavior(project->metatileBehaviorMap[metatileBehavior]); + this->metatile->setBehavior(behavior); MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(), prevMetatile, new Metatile(*this->metatile)); metatileHistory.push(commit); From c16a6d5d232e35cb8c7b4c4c0ff5452e96887c2e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 28 Oct 2022 12:40:36 -0400 Subject: [PATCH 08/10] Better support for parsing hex values --- src/config.cpp | 2 +- src/core/parseutil.cpp | 2 +- src/project.cpp | 14 +++++++------- src/ui/tileseteditor.cpp | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index 1c5663fd..69445c88 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -136,7 +136,7 @@ void KeyValueConfigBase::save() { bool KeyValueConfigBase::getConfigBool(QString key, QString value) { bool ok; - int result = value.toInt(&ok); + int result = value.toInt(&ok, 0); if (!ok || (result != 0 && result != 1)) { logWarn(QString("Invalid config value for %1: '%2'. Must be 0 or 1.").arg(key).arg(value)); } diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 1f933e7c..83954f8e 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -410,7 +410,7 @@ int ParseUtil::gameStringToInt(QString gameString, bool * ok) { return 1; if (QString::compare(gameString, "FALSE", Qt::CaseInsensitive) == 0) return 0; - return gameString.toInt(ok); + return gameString.toInt(ok, 0); } bool ParseUtil::gameStringToBool(QString gameString, bool * ok) { diff --git a/src/project.cpp b/src/project.cpp index dbf04f41..764340ab 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2070,8 +2070,8 @@ bool Project::readHealLocations() { HealLocation healLocation; if (match.hasMatch()) { QString mapName = match.captured("map"); - int x = match.captured("x").toInt(); - int y = match.captured("y").toInt(); + int x = match.captured("x").toInt(nullptr, 0); + int y = match.captured("y").toInt(nullptr, 0); healLocation = HealLocation(idName, mapName, this->healLocations.size() + 1, x, y); } else { // This heal location has data, but is missing from the location table and won't be displayed by Porymap. @@ -2091,7 +2091,7 @@ bool Project::readHealLocations() { QRegularExpression respawnNPCRegex(QString("%1(?[0-9]+)").arg(initializerPattern)); match = respawnNPCRegex.match(text); if (match.hasMatch()) - healLocation.respawnNPC = match.captured("npc").toInt(); + healLocation.respawnNPC = match.captured("npc").toInt(nullptr, 0); } this->healLocations.append(healLocation); @@ -2474,11 +2474,11 @@ bool Project::readEventGraphics() { QRegularExpressionMatch dimensionMatch = re.match(dimensions_label); QRegularExpressionMatch oamTablesMatch = re.match(subsprites_label); if (oamTablesMatch.hasMatch()) { - eventGraphics->spriteWidth = oamTablesMatch.captured(1).toInt(); - eventGraphics->spriteHeight = oamTablesMatch.captured(2).toInt(); + eventGraphics->spriteWidth = oamTablesMatch.captured(1).toInt(nullptr, 0); + eventGraphics->spriteHeight = oamTablesMatch.captured(2).toInt(nullptr, 0); } else if (dimensionMatch.hasMatch()) { - eventGraphics->spriteWidth = dimensionMatch.captured(1).toInt(); - eventGraphics->spriteHeight = dimensionMatch.captured(2).toInt(); + eventGraphics->spriteWidth = dimensionMatch.captured(1).toInt(nullptr, 0); + eventGraphics->spriteHeight = dimensionMatch.captured(2).toInt(nullptr, 0); } else { eventGraphics->spriteWidth = eventGraphics->spritesheet.width(); eventGraphics->spriteHeight = eventGraphics->spritesheet.height(); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index d45c65c7..6dcdea0e 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -512,7 +512,7 @@ void TilesetEditor::on_comboBox_metatileBehaviors_currentTextChanged(const QStri } else { // Check if user has entered a number value instead bool ok; - behavior = metatileBehavior.toInt(&ok); + behavior = metatileBehavior.toInt(&ok, 0); if (!ok) return; } From a1d6c76fba875140a096ddb880847a1e2bf9d39f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 30 Oct 2022 11:45:38 -0400 Subject: [PATCH 09/10] Move changelog entry --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c607daf3..051a9541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp The **"Breaking Changes"** listed below are changes that have been made in the decompilation projects (e.g. pokeemerald), which porymap requires in order to work properly. It also includes changes to the scripting API that may change the behavior of existing porymap scripts. If porymap is used with a project or API script that is not up-to-date with the breaking changes, then porymap will likely break or behave improperly. ## [Unreleased] -Nothing, yet. +### Added +- Added new config options for reorganizing metatile attributes. ## [5.0.0] - 2022-10-30 ### Breaking Changes @@ -22,7 +23,7 @@ Nothing, yet. - Add Cut/Copy/Paste for metatiles in the Tileset Editor. - Add button to copy the full metatile label to the clipboard in the Tileset Editor. - Add ability to export an image of the primary or secondary tileset's metatiles. -- Add new config options for customizing metatile attributes, how new maps are filled, setting default tilesets, and whether the most recent project should be opened on launch. +- Add new config options for customizing how new maps are filled, setting default tilesets, and whether the most recent project should be opened on launch. - Add color picker to palette editor for taking colors from the screen. - Add new features to the scripting API, including the ability to display messages and user input windows, set the overlay's opacity, rotation, scale, and clipping, interact with map header properties and the map border, read tile pixel data, and more. From 97b8bb96cb4e624eeddd0c836742601a8dd7777b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Nov 2022 01:02:33 -0500 Subject: [PATCH 10/10] Remove unused function --- include/project.h | 1 - src/project.cpp | 15 --------------- 2 files changed, 16 deletions(-) diff --git a/include/project.h b/include/project.h index c5599489..4e97a9bd 100644 --- a/include/project.h +++ b/include/project.h @@ -231,7 +231,6 @@ public: private: void updateMapLayout(Map*); - void setNewMapHeader(Map* map, int mapIndex); void setNewMapBlockdata(Map* map); void setNewMapBorder(Map *map); void setNewMapEvents(Map *map); diff --git a/src/project.cpp b/src/project.cpp index 76955169..b6db8f24 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -375,21 +375,6 @@ QString Project::readMapLocation(QString map_name) { return ParseUtil::jsonToQString(mapObj["region_map_section"]); } -void Project::setNewMapHeader(Map* map, int mapIndex) { - map->layoutId = QString("%1").arg(mapIndex); - map->location = mapSectionValueToName.value(0); - map->requiresFlash = false; - map->weather = weatherNames.value(0, "WEATHER_NONE"); - map->type = mapTypes.value(0, "MAP_TYPE_NONE"); - map->song = defaultSong; - map->show_location = true; - map->allowBiking = true; - map->allowEscaping = false; - map->allowRunning = true; - map->floorNumber = 0; - map->battle_scene = mapBattleScenes.value(0, "MAP_BATTLE_SCENE_NORMAL"); -} - bool Project::loadLayout(MapLayout *layout) { // Force these to run even if one fails bool loadedTilesets = loadLayoutTilesets(layout);