Skip to content

Commit

Permalink
Preserve unused metatile attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
GriffinRichards authored and huderlem committed Feb 8, 2022
1 parent cca762b commit cf97371
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 87 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions include/core/metatile.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#define METATILE_H

#include "tile.h"
#include "config.h"
#include <QImage>
#include <QPoint>
#include <QString>
Expand Down Expand Up @@ -42,10 +43,15 @@ class Metatile
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
51 changes: 50 additions & 1 deletion src/core/metatile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Metatile::Metatile() :
behavior(0),
layerType(0),
encounterType(0),
terrainType(0)
terrainType(0),
unusedAttributes(0)
{ }

int Metatile::getIndexInTileset(int metatileId) {
Expand All @@ -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);
}
}
25 changes: 5 additions & 20 deletions src/core/metatileparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,25 @@ 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'
&& in.at(projIdOffset + 2) == 'E'
&& 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) |
Expand Down Expand Up @@ -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);
}
Expand Down
5 changes: 0 additions & 5 deletions src/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
71 changes: 20 additions & 51 deletions src/project.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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));
Expand Down
12 changes: 2 additions & 10 deletions src/ui/tileseteditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down

0 comments on commit cf97371

Please sign in to comment.