Preserve unused metatile attributes

This commit is contained in:
GriffinR 2022-02-03 18:10:50 -05:00 committed by huderlem
parent cca762ba94
commit cf973710c8
7 changed files with 84 additions and 87 deletions

View file

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

View file

@ -3,6 +3,7 @@
#define METATILE_H
#include "tile.h"
#include "config.h"
#include <QImage>
#include <QPoint>
#include <QString>
@ -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

View file

@ -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<int>(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);
}
}

View file

@ -24,7 +24,6 @@ QList<Metatile*> 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<Metatile*> 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<unsigned char>(in.at(0)) |
(static_cast<unsigned char>(in.at(1)) << 8) |
@ -82,23 +80,10 @@ QList<Metatile*> MetatileParser::parse(QString filepath, bool *error, bool prima
}
int attrOffset = 4 + (numMetatiles * metatileSize) + (i * attrSize);
if (version == BaseGameVersion::pokefirered) {
int value = static_cast<unsigned char>(in.at(attrOffset)) |
(static_cast<unsigned char>(in.at(attrOffset + 1)) << 8) |
(static_cast<unsigned char>(in.at(attrOffset + 2)) << 16) |
(static_cast<unsigned char>(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<unsigned char>(in.at(attrOffset)) |
(static_cast<unsigned char>(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<unsigned char>(in.at(attrOffset + j)) << (8 * j);
metatile->setAttributes(attributes, version);
metatile->tiles = tiles;
metatiles.append(metatile);
}

View file

@ -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) {

View file

@ -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<char>(metatile->behavior));
data.append(static_cast<char>(metatile->behavior >> 8) |
static_cast<char>(metatile->terrainType << 1));
data.append(static_cast<char>(0));
data.append(static_cast<char>(metatile->encounterType) |
static_cast<char>(metatile->layerType << 5));
}
} else {
for (Metatile *metatile : tileset->metatiles) {
data.append(static_cast<char>(metatile->behavior));
data.append(static_cast<char>((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<char>(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<unsigned char>(data.at(i * 4 + 3)) << 24) |
(static_cast<unsigned char>(data.at(i * 4 + 2)) << 16) |
(static_cast<unsigned char>(data.at(i * 4 + 1)) << 8) |
(static_cast<unsigned char>(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<unsigned char>(data.at(i * 2 + 1)) << 8) | static_cast<unsigned char>(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<unsigned char>(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));

View file

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