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
|
### Changed
|
||||||
- If an object event is inanimate, it will always render using its first frame.
|
- 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.
|
- 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
|
### Fixed
|
||||||
- Fix cursor tile outline not updating at the end of a dragged selection.
|
- Fix cursor tile outline not updating at the end of a dragged selection.
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#define METATILE_H
|
#define METATILE_H
|
||||||
|
|
||||||
#include "tile.h"
|
#include "tile.h"
|
||||||
|
#include "config.h"
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
@ -42,10 +43,15 @@ public:
|
||||||
uint8_t layerType;
|
uint8_t layerType;
|
||||||
uint8_t encounterType; // FRLG only
|
uint8_t encounterType; // FRLG only
|
||||||
uint8_t terrainType; // FRLG only
|
uint8_t terrainType; // FRLG only
|
||||||
|
uint32_t unusedAttributes;
|
||||||
QString label;
|
QString label;
|
||||||
|
|
||||||
|
void setAttributes(uint32_t data, BaseGameVersion version);
|
||||||
|
uint32_t getAttributes(BaseGameVersion version);
|
||||||
|
|
||||||
static int getIndexInTileset(int);
|
static int getIndexInTileset(int);
|
||||||
static QPoint coordFromPixmapCoord(const QPointF &pixelCoord);
|
static QPoint coordFromPixmapCoord(const QPointF &pixelCoord);
|
||||||
|
static int getAttributesSize(BaseGameVersion version);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // METATILE_H
|
#endif // METATILE_H
|
||||||
|
|
|
@ -6,7 +6,8 @@ Metatile::Metatile() :
|
||||||
behavior(0),
|
behavior(0),
|
||||||
layerType(0),
|
layerType(0),
|
||||||
encounterType(0),
|
encounterType(0),
|
||||||
terrainType(0)
|
terrainType(0),
|
||||||
|
unusedAttributes(0)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
int Metatile::getIndexInTileset(int metatileId) {
|
int Metatile::getIndexInTileset(int metatileId) {
|
||||||
|
@ -22,3 +23,51 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
|
||||||
int y = static_cast<int>(pixelCoord.y()) / 16;
|
int y = static_cast<int>(pixelCoord.y()) / 16;
|
||||||
return QPoint(x, y);
|
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 projIdOffset = in.length() - 4;
|
||||||
int metatileSize = 16;
|
int metatileSize = 16;
|
||||||
int attrSize;
|
|
||||||
BaseGameVersion version;
|
BaseGameVersion version;
|
||||||
if (in.at(projIdOffset + 0) == 'R'
|
if (in.at(projIdOffset + 0) == 'R'
|
||||||
&& in.at(projIdOffset + 1) == 'S'
|
&& in.at(projIdOffset + 1) == 'S'
|
||||||
|
@ -32,19 +31,18 @@ QList<Metatile*> MetatileParser::parse(QString filepath, bool *error, bool prima
|
||||||
&& in.at(projIdOffset + 3) == ' ') {
|
&& in.at(projIdOffset + 3) == ' ') {
|
||||||
// ruby and emerald are handled equally here.
|
// ruby and emerald are handled equally here.
|
||||||
version = BaseGameVersion::pokeemerald;
|
version = BaseGameVersion::pokeemerald;
|
||||||
attrSize = 2;
|
|
||||||
} else if (in.at(projIdOffset + 0) == 'F'
|
} else if (in.at(projIdOffset + 0) == 'F'
|
||||||
&& in.at(projIdOffset + 1) == 'R'
|
&& in.at(projIdOffset + 1) == 'R'
|
||||||
&& in.at(projIdOffset + 2) == 'L'
|
&& in.at(projIdOffset + 2) == 'L'
|
||||||
&& in.at(projIdOffset + 3) == 'G') {
|
&& in.at(projIdOffset + 3) == 'G') {
|
||||||
version = BaseGameVersion::pokefirered;
|
version = BaseGameVersion::pokefirered;
|
||||||
attrSize = 4;
|
|
||||||
} else {
|
} else {
|
||||||
*error = true;
|
*error = true;
|
||||||
logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE ' or 'FRLG'."));
|
logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE ' or 'FRLG'."));
|
||||||
return { };
|
return { };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int attrSize = Metatile::getAttributesSize(version);
|
||||||
int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary();
|
int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary();
|
||||||
int numMetatiles = static_cast<unsigned char>(in.at(0)) |
|
int numMetatiles = static_cast<unsigned char>(in.at(0)) |
|
||||||
(static_cast<unsigned char>(in.at(1)) << 8) |
|
(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);
|
int attrOffset = 4 + (numMetatiles * metatileSize) + (i * attrSize);
|
||||||
if (version == BaseGameVersion::pokefirered) {
|
uint32_t attributes = 0;
|
||||||
int value = static_cast<unsigned char>(in.at(attrOffset)) |
|
for (int j = 0; j < attrSize; j++)
|
||||||
(static_cast<unsigned char>(in.at(attrOffset + 1)) << 8) |
|
attributes |= static_cast<unsigned char>(in.at(attrOffset + j)) << (8 * j);
|
||||||
(static_cast<unsigned char>(in.at(attrOffset + 2)) << 16) |
|
metatile->setAttributes(attributes, version);
|
||||||
(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;
|
|
||||||
}
|
|
||||||
metatile->tiles = tiles;
|
metatile->tiles = tiles;
|
||||||
metatiles.append(metatile);
|
metatiles.append(metatile);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1313,11 +1313,6 @@ void MainWindow::on_actionNew_Tileset_triggered() {
|
||||||
tile.tileId = ((i % 2) == 1) ? 1 : 2;
|
tile.tileId = ((i % 2) == 1) ? 1 : 2;
|
||||||
mt->tiles.append(tile);
|
mt->tiles.append(tile);
|
||||||
}
|
}
|
||||||
mt->behavior = 0;
|
|
||||||
mt->layerType = 0;
|
|
||||||
mt->encounterType = 0;
|
|
||||||
mt->terrainType = 0;
|
|
||||||
|
|
||||||
newSet.metatiles.append(mt);
|
newSet.metatiles.append(mt);
|
||||||
}
|
}
|
||||||
for(int i = 0; i < 16; ++i) {
|
for(int i = 0; i < 16; ++i) {
|
||||||
|
|
|
@ -1034,21 +1034,12 @@ void Project::saveTilesetMetatileAttributes(Tileset *tileset) {
|
||||||
QFile attrs_file(tileset->metatile_attrs_path);
|
QFile attrs_file(tileset->metatile_attrs_path);
|
||||||
if (attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
if (attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
|
BaseGameVersion version = projectConfig.getBaseGameVersion();
|
||||||
if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
|
int attrSize = Metatile::getAttributesSize(version);
|
||||||
for (Metatile *metatile : tileset->metatiles) {
|
for (Metatile *metatile : tileset->metatiles) {
|
||||||
data.append(static_cast<char>(metatile->behavior));
|
uint32_t attributes = metatile->getAttributes(version);
|
||||||
data.append(static_cast<char>(metatile->behavior >> 8) |
|
for (int i = 0; i < attrSize; i++)
|
||||||
static_cast<char>(metatile->terrainType << 1));
|
data.append(static_cast<char>(attributes >> (8 * i)));
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
attrs_file.write(data);
|
attrs_file.write(data);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1593,42 +1584,20 @@ void Project::loadTilesetMetatiles(Tileset* tileset) {
|
||||||
QByteArray data = attrs_file.readAll();
|
QByteArray data = attrs_file.readAll();
|
||||||
int num_metatiles = tileset->metatiles.count();
|
int num_metatiles = tileset->metatiles.count();
|
||||||
|
|
||||||
if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
|
BaseGameVersion version = projectConfig.getBaseGameVersion();
|
||||||
int num_metatileAttrs = data.length() / 4;
|
int attrSize = Metatile::getAttributesSize(version);
|
||||||
if (num_metatiles != num_metatileAttrs) {
|
int num_metatileAttrs = data.length() / attrSize;
|
||||||
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_metatiles != num_metatileAttrs) {
|
||||||
if (num_metatileAttrs > num_metatiles)
|
logWarn(QString("Metatile count %1 does not match metatile attribute count %2 in %3").arg(num_metatiles).arg(num_metatileAttrs).arg(tileset->name));
|
||||||
num_metatileAttrs = num_metatiles;
|
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) |
|
for (int i = 0; i < num_metatileAttrs; i++) {
|
||||||
(static_cast<unsigned char>(data.at(i * 4 + 2)) << 16) |
|
uint32_t attributes = 0;
|
||||||
(static_cast<unsigned char>(data.at(i * 4 + 1)) << 8) |
|
for (int j = 0; j < attrSize; j++)
|
||||||
(static_cast<unsigned char>(data.at(i * 4 + 0)));
|
attributes |= static_cast<unsigned char>(data.at(i * attrSize + j)) << (8 * j);
|
||||||
tileset->metatiles.at(i)->behavior = value & 0x1FF;
|
tileset->metatiles.at(i)->setAttributes(attributes, version);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logError(QString("Could not open tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path));
|
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) {
|
while (this->primaryTileset->metatiles.length() < numPrimaryMetatiles) {
|
||||||
Tile tile(0, false, false, 0);
|
Tile tile(0, false, false, 0);
|
||||||
Metatile *metatile = new Metatile;
|
Metatile *metatile = new Metatile();
|
||||||
metatile->behavior = 0;
|
|
||||||
metatile->layerType = 0;
|
|
||||||
metatile->encounterType = 0;
|
|
||||||
metatile->terrainType = 0;
|
|
||||||
for (int i = 0; i < numTiles; i++) {
|
for (int i = 0; i < numTiles; i++) {
|
||||||
metatile->tiles.append(tile);
|
metatile->tiles.append(tile);
|
||||||
}
|
}
|
||||||
|
@ -773,11 +769,7 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered()
|
||||||
}
|
}
|
||||||
while (this->secondaryTileset->metatiles.length() < numSecondaryMetatiles) {
|
while (this->secondaryTileset->metatiles.length() < numSecondaryMetatiles) {
|
||||||
Tile tile(0, false, false, 0);
|
Tile tile(0, false, false, 0);
|
||||||
Metatile *metatile = new Metatile;
|
Metatile *metatile = new Metatile();
|
||||||
metatile->behavior = 0;
|
|
||||||
metatile->layerType = 0;
|
|
||||||
metatile->encounterType = 0;
|
|
||||||
metatile->terrainType = 0;
|
|
||||||
for (int i = 0; i < numTiles; i++) {
|
for (int i = 0; i < numTiles; i++) {
|
||||||
metatile->tiles.append(tile);
|
metatile->tiles.append(tile);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue