Preserve unused metatile attributes
This commit is contained in:
parent
cca762ba94
commit
cf973710c8
7 changed files with 84 additions and 87 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue