From cf973710c837fd44f8c618932d0e65bfae43e936 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 3 Feb 2022 18:10:50 -0500 Subject: [PATCH] Preserve unused metatile attributes --- CHANGELOG.md | 1 + include/core/metatile.h | 6 ++++ src/core/metatile.cpp | 51 +++++++++++++++++++++++++- src/core/metatileparser.cpp | 25 +++---------- src/mainwindow.cpp | 5 --- src/project.cpp | 71 +++++++++++-------------------------- src/ui/tileseteditor.cpp | 12 ++----- 7 files changed, 84 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c261fc47..facc635f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d ### Changed - If an object event is inanimate, it will always render using its first frame. - Only log "Unknown custom script function" when a registered script function is not present in any script. +- Unused metatile attribute bits that are set are preserved instead of being cleared. ### Fixed - Fix cursor tile outline not updating at the end of a dragged selection. diff --git a/include/core/metatile.h b/include/core/metatile.h index e258087f..7c744dc4 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -3,6 +3,7 @@ #define METATILE_H #include "tile.h" +#include "config.h" #include #include #include @@ -42,10 +43,15 @@ public: uint8_t layerType; uint8_t encounterType; // FRLG only uint8_t terrainType; // FRLG only + uint32_t unusedAttributes; QString label; + 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); }; #endif // METATILE_H diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index b6f25905..99739a81 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -6,7 +6,8 @@ Metatile::Metatile() : behavior(0), layerType(0), encounterType(0), - terrainType(0) + terrainType(0), + unusedAttributes(0) { } int Metatile::getIndexInTileset(int metatileId) { @@ -22,3 +23,51 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) { int y = static_cast(pixelCoord.y()) / 16; return QPoint(x, y); } + +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; +} + +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); + } else { + this->behavior = (data & behaviorMask_RSE) >> behaviorShift_RSE; + this->layerType = (data & layerTypeMask_RSE) >> layerTypeShift_RSE; + this->unusedAttributes = data & ~(behaviorMask_RSE | layerTypeMask_RSE); + } +} diff --git a/src/core/metatileparser.cpp b/src/core/metatileparser.cpp index 852c26af..7d90ddfc 100644 --- a/src/core/metatileparser.cpp +++ b/src/core/metatileparser.cpp @@ -24,7 +24,6 @@ QList MetatileParser::parse(QString filepath, bool *error, bool prima int projIdOffset = in.length() - 4; int metatileSize = 16; - int attrSize; BaseGameVersion version; if (in.at(projIdOffset + 0) == 'R' && in.at(projIdOffset + 1) == 'S' @@ -32,19 +31,18 @@ QList MetatileParser::parse(QString filepath, bool *error, bool prima && in.at(projIdOffset + 3) == ' ') { // ruby and emerald are handled equally here. version = BaseGameVersion::pokeemerald; - attrSize = 2; } else if (in.at(projIdOffset + 0) == 'F' && in.at(projIdOffset + 1) == 'R' && in.at(projIdOffset + 2) == 'L' && in.at(projIdOffset + 3) == 'G') { version = BaseGameVersion::pokefirered; - attrSize = 4; } else { *error = true; logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE ' or 'FRLG'.")); return { }; } + int attrSize = Metatile::getAttributesSize(version); int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary(); int numMetatiles = static_cast(in.at(0)) | (static_cast(in.at(1)) << 8) | @@ -82,23 +80,10 @@ QList MetatileParser::parse(QString filepath, bool *error, bool prima } int attrOffset = 4 + (numMetatiles * metatileSize) + (i * attrSize); - if (version == BaseGameVersion::pokefirered) { - int value = static_cast(in.at(attrOffset)) | - (static_cast(in.at(attrOffset + 1)) << 8) | - (static_cast(in.at(attrOffset + 2)) << 16) | - (static_cast(in.at(attrOffset + 3)) << 24); - metatile->behavior = value & 0x1FF; - metatile->terrainType = (value & 0x3E00) >> 9; - metatile->encounterType = (value & 0x7000000) >> 24; - metatile->layerType = (value & 0x60000000) >> 29; - } else { - int value = static_cast(in.at(attrOffset)) | - (static_cast(in.at(attrOffset + 1)) << 8); - metatile->behavior = value & 0xFF; - metatile->layerType = (value & 0xF000) >> 12; - metatile->encounterType = 0; - metatile->terrainType = 0; - } + 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->tiles = tiles; metatiles.append(metatile); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6f5bcd8b..f30099cc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1313,11 +1313,6 @@ void MainWindow::on_actionNew_Tileset_triggered() { tile.tileId = ((i % 2) == 1) ? 1 : 2; mt->tiles.append(tile); } - mt->behavior = 0; - mt->layerType = 0; - mt->encounterType = 0; - mt->terrainType = 0; - newSet.metatiles.append(mt); } for(int i = 0; i < 16; ++i) { diff --git a/src/project.cpp b/src/project.cpp index 3f590aae..bb9a603f 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1034,21 +1034,12 @@ void Project::saveTilesetMetatileAttributes(Tileset *tileset) { QFile attrs_file(tileset->metatile_attrs_path); if (attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QByteArray data; - - if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { - for (Metatile *metatile : tileset->metatiles) { - data.append(static_cast(metatile->behavior)); - data.append(static_cast(metatile->behavior >> 8) | - static_cast(metatile->terrainType << 1)); - data.append(static_cast(0)); - data.append(static_cast(metatile->encounterType) | - static_cast(metatile->layerType << 5)); - } - } else { - for (Metatile *metatile : tileset->metatiles) { - data.append(static_cast(metatile->behavior)); - data.append(static_cast((metatile->layerType << 4) & 0xF0)); - } + BaseGameVersion version = projectConfig.getBaseGameVersion(); + int attrSize = Metatile::getAttributesSize(version); + for (Metatile *metatile : tileset->metatiles) { + uint32_t attributes = metatile->getAttributes(version); + for (int i = 0; i < attrSize; i++) + data.append(static_cast(attributes >> (8 * i))); } attrs_file.write(data); } else { @@ -1593,42 +1584,20 @@ void Project::loadTilesetMetatiles(Tileset* tileset) { QByteArray data = attrs_file.readAll(); int num_metatiles = tileset->metatiles.count(); - if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { - int num_metatileAttrs = data.length() / 4; - 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)); - if (num_metatileAttrs > num_metatiles) - num_metatileAttrs = num_metatiles; - } - bool unusedAttribute = false; - for (int i = 0; i < num_metatileAttrs; i++) { - int value = (static_cast(data.at(i * 4 + 3)) << 24) | - (static_cast(data.at(i * 4 + 2)) << 16) | - (static_cast(data.at(i * 4 + 1)) << 8) | - (static_cast(data.at(i * 4 + 0))); - tileset->metatiles.at(i)->behavior = value & 0x1FF; - tileset->metatiles.at(i)->terrainType = (value & 0x3E00) >> 9; - tileset->metatiles.at(i)->encounterType = (value & 0x7000000) >> 24; - tileset->metatiles.at(i)->layerType = (value & 0x60000000) >> 29; - if (value & ~(0x67003FFF)) - unusedAttribute = true; - } - if (unusedAttribute) - logWarn(QString("Unrecognized metatile attributes in %1 will not be saved.").arg(tileset->metatile_attrs_path)); - } else { - int num_metatileAttrs = data.length() / 2; - 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)); - if (num_metatileAttrs > num_metatiles) - num_metatileAttrs = num_metatiles; - } - for (int i = 0; i < num_metatileAttrs; i++) { - int value = (static_cast(data.at(i * 2 + 1)) << 8) | static_cast(data.at(i * 2)); - tileset->metatiles.at(i)->behavior = value & 0xFF; - tileset->metatiles.at(i)->layerType = (value & 0xF000) >> 12; - tileset->metatiles.at(i)->encounterType = 0; - tileset->metatiles.at(i)->terrainType = 0; - } + BaseGameVersion version = projectConfig.getBaseGameVersion(); + int attrSize = Metatile::getAttributesSize(version); + 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)); + if (num_metatileAttrs > num_metatiles) + num_metatileAttrs = num_metatiles; + } + + for (int i = 0; i < num_metatileAttrs; i++) { + 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); } } else { logError(QString("Could not open tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path)); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index f2499b8d..540ef66c 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -758,11 +758,7 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered() } while (this->primaryTileset->metatiles.length() < numPrimaryMetatiles) { Tile tile(0, false, false, 0); - Metatile *metatile = new Metatile; - metatile->behavior = 0; - metatile->layerType = 0; - metatile->encounterType = 0; - metatile->terrainType = 0; + Metatile *metatile = new Metatile(); for (int i = 0; i < numTiles; i++) { metatile->tiles.append(tile); } @@ -773,11 +769,7 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered() } while (this->secondaryTileset->metatiles.length() < numSecondaryMetatiles) { Tile tile(0, false, false, 0); - Metatile *metatile = new Metatile; - metatile->behavior = 0; - metatile->layerType = 0; - metatile->encounterType = 0; - metatile->terrainType = 0; + Metatile *metatile = new Metatile(); for (int i = 0; i < numTiles; i++) { metatile->tiles.append(tile); }