Allow custom metatile attribute layouts
This commit is contained in:
parent
3656c3082f
commit
fa2b4d3edb
8 changed files with 210 additions and 56 deletions
|
@ -36,6 +36,7 @@ protected:
|
|||
virtual void setUnreadKeys() = 0;
|
||||
bool getConfigBool(QString key, QString value);
|
||||
int getConfigInteger(QString key, QString value, int min, int max, int defaultValue);
|
||||
long getConfigLong(QString key, QString value, long min, long max, long defaultValue);
|
||||
};
|
||||
|
||||
class PorymapConfig: public KeyValueConfigBase
|
||||
|
@ -264,6 +265,11 @@ public:
|
|||
bool getTilesetsHaveCallback();
|
||||
void setTilesetsHaveIsCompressed(bool has);
|
||||
bool getTilesetsHaveIsCompressed();
|
||||
int getMetatileAttributesSize();
|
||||
uint32_t getMetatileBehaviorMask();
|
||||
uint32_t getMetatileTerrainTypeMask();
|
||||
uint32_t getMetatileEncounterTypeMask();
|
||||
uint32_t getMetatileLayerTypeMask();
|
||||
protected:
|
||||
virtual QString getConfigFilepath() override;
|
||||
virtual void parseConfigKeyValue(QString key, QString value) override;
|
||||
|
@ -295,6 +301,11 @@ private:
|
|||
bool prefabImportPrompted;
|
||||
bool tilesetsHaveCallback;
|
||||
bool tilesetsHaveIsCompressed;
|
||||
int metatileAttributesSize;
|
||||
uint32_t metatileBehaviorMask;
|
||||
uint32_t metatileTerrainTypeMask;
|
||||
uint32_t metatileEncounterTypeMask;
|
||||
uint32_t metatileLayerTypeMask;
|
||||
};
|
||||
|
||||
extern ProjectConfig projectConfig;
|
||||
|
|
|
@ -40,19 +40,34 @@ public:
|
|||
|
||||
public:
|
||||
QList<Tile> tiles;
|
||||
uint16_t behavior; // 8 bits RSE, 9 bits FRLG
|
||||
uint8_t layerType;
|
||||
uint8_t encounterType; // FRLG only
|
||||
uint8_t terrainType; // FRLG only
|
||||
uint32_t behavior;
|
||||
uint32_t layerType;
|
||||
uint32_t encounterType;
|
||||
uint32_t terrainType;
|
||||
uint32_t unusedAttributes;
|
||||
QString label;
|
||||
|
||||
uint32_t getAttributes();
|
||||
void setAttributes(uint32_t data);
|
||||
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);
|
||||
static void calculateAttributeLayout();
|
||||
|
||||
private:
|
||||
static uint32_t behaviorMask;
|
||||
static uint32_t terrainTypeMask;
|
||||
static uint32_t encounterTypeMask;
|
||||
static uint32_t layerTypeMask;
|
||||
static uint32_t unusedAttrMask;
|
||||
static int behaviorShift;
|
||||
static int terrainTypeShift;
|
||||
static int encounterTypeShift;
|
||||
static int layerTypeShift;
|
||||
|
||||
static int getShiftValue(uint32_t mask);
|
||||
};
|
||||
|
||||
inline bool operator==(const Metatile &a, const Metatile &b) {
|
||||
|
|
|
@ -145,7 +145,17 @@ bool KeyValueConfigBase::getConfigBool(QString key, QString value) {
|
|||
|
||||
int KeyValueConfigBase::getConfigInteger(QString key, QString value, int min, int max, int defaultValue) {
|
||||
bool ok;
|
||||
int result = value.toInt(&ok);
|
||||
int result = value.toInt(&ok, 0);
|
||||
if (!ok) {
|
||||
logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value));
|
||||
return defaultValue;
|
||||
}
|
||||
return qMin(max, qMax(min, result));
|
||||
}
|
||||
|
||||
long KeyValueConfigBase::getConfigLong(QString key, QString value, long min, long max, long defaultValue) {
|
||||
bool ok;
|
||||
long result = value.toLong(&ok, 0);
|
||||
if (!ok) {
|
||||
logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value));
|
||||
return defaultValue;
|
||||
|
@ -549,6 +559,21 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
this->defaultPrimaryTileset = value;
|
||||
} else if (key == "default_secondary_tileset") {
|
||||
this->defaultSecondaryTileset = value;
|
||||
} else if (key == "metatile_attributes_size") {
|
||||
int size = getConfigInteger(key, value, 1, 4, 2);
|
||||
if (size & (size - 1)) {
|
||||
logWarn(QString("Invalid config value for %1: must be 1, 2, or 4").arg(key));
|
||||
size = 2;
|
||||
}
|
||||
this->metatileAttributesSize = size;
|
||||
} else if (key == "metatile_behavior_mask") {
|
||||
this->metatileBehaviorMask = getConfigLong(key, value, 0, 0xFFFFFFFF, 0);
|
||||
} else if (key == "metatile_terrain_type_mask") {
|
||||
this->metatileTerrainTypeMask = getConfigLong(key, value, 0, 0xFFFFFFFF, 0);
|
||||
} else if (key == "metatile_encounter_type_mask") {
|
||||
this->metatileEncounterTypeMask = getConfigLong(key, value, 0, 0xFFFFFFFF, 0);
|
||||
} else if (key == "metatile_layer_type_mask") {
|
||||
this->metatileLayerTypeMask = getConfigLong(key, value, 0, 0xFFFFFFFF, 0);
|
||||
#ifdef CONFIG_BACKWARDS_COMPATABILITY
|
||||
} else if (key == "recent_map") {
|
||||
userConfig.setRecentMap(value);
|
||||
|
@ -599,6 +624,11 @@ void ProjectConfig::setUnreadKeys() {
|
|||
if (!readKeys.contains("create_map_text_file")) this->createMapTextFile = (this->baseGameVersion != BaseGameVersion::pokeemerald);
|
||||
if (!readKeys.contains("new_map_border_metatiles")) this->newMapBorderMetatileIds = isPokefirered ? DEFAULT_BORDER_FRLG : DEFAULT_BORDER_RSE;
|
||||
if (!readKeys.contains("default_secondary_tileset")) this->defaultSecondaryTileset = isPokefirered ? "gTileset_PalletTown" : "gTileset_Petalburg";
|
||||
if (!readKeys.contains("metatile_attributes_size")) this->metatileAttributesSize = isPokefirered ? 4 : 2;
|
||||
if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = isPokefirered ? 0x000001FF : 0x00FF;
|
||||
if (!readKeys.contains("metatile_terrain_type_mask")) this->metatileTerrainTypeMask = isPokefirered ? 0x00003E00 : 0;
|
||||
if (!readKeys.contains("metatile_encounter_type_mask")) this->metatileEncounterTypeMask = isPokefirered ? 0x07000000 : 0;
|
||||
if (!readKeys.contains("metatile_layer_type_mask")) this-> metatileLayerTypeMask = isPokefirered ? 0x60000000 : 0xF000;
|
||||
}
|
||||
|
||||
QMap<QString, QString> ProjectConfig::getKeyValueMap() {
|
||||
|
@ -630,6 +660,11 @@ QMap<QString, QString> ProjectConfig::getKeyValueMap() {
|
|||
}
|
||||
map.insert("tilesets_have_callback", QString::number(this->tilesetsHaveCallback));
|
||||
map.insert("tilesets_have_is_compressed", QString::number(this->tilesetsHaveIsCompressed));
|
||||
map.insert("metatile_attributes_size", QString::number(this->metatileAttributesSize));
|
||||
map.insert("metatile_behavior_mask", "0x" + QString::number(this->metatileBehaviorMask, 16).toUpper());
|
||||
map.insert("metatile_terrain_type_mask", "0x" + QString::number(this->metatileTerrainTypeMask, 16).toUpper());
|
||||
map.insert("metatile_encounter_type_mask", "0x" + QString::number(this->metatileEncounterTypeMask, 16).toUpper());
|
||||
map.insert("metatile_layer_type_mask", "0x" + QString::number(this->metatileLayerTypeMask, 16).toUpper());
|
||||
return map;
|
||||
}
|
||||
|
||||
|
@ -879,6 +914,26 @@ bool ProjectConfig::getTilesetsHaveIsCompressed() {
|
|||
return this->tilesetsHaveIsCompressed;
|
||||
}
|
||||
|
||||
int ProjectConfig::getMetatileAttributesSize() {
|
||||
return this->metatileAttributesSize;
|
||||
}
|
||||
|
||||
uint32_t ProjectConfig::getMetatileBehaviorMask() {
|
||||
return this->metatileBehaviorMask;
|
||||
}
|
||||
|
||||
uint32_t ProjectConfig::getMetatileTerrainTypeMask() {
|
||||
return this->metatileTerrainTypeMask;
|
||||
}
|
||||
|
||||
uint32_t ProjectConfig::getMetatileEncounterTypeMask() {
|
||||
return this->metatileEncounterTypeMask;
|
||||
}
|
||||
|
||||
uint32_t ProjectConfig::getMetatileLayerTypeMask() {
|
||||
return this->metatileLayerTypeMask;
|
||||
}
|
||||
|
||||
|
||||
UserConfig userConfig;
|
||||
|
||||
|
|
|
@ -2,6 +2,17 @@
|
|||
#include "tileset.h"
|
||||
#include "project.h"
|
||||
|
||||
uint32_t Metatile::behaviorMask = 0;
|
||||
uint32_t Metatile::terrainTypeMask = 0;
|
||||
uint32_t Metatile::encounterTypeMask = 0;
|
||||
uint32_t Metatile::layerTypeMask = 0;
|
||||
uint32_t Metatile::unusedAttrMask = 0;
|
||||
|
||||
int Metatile::behaviorShift = 0;
|
||||
int Metatile::terrainTypeShift = 0;
|
||||
int Metatile::encounterTypeShift = 0;
|
||||
int Metatile::layerTypeShift = 0;
|
||||
|
||||
Metatile::Metatile() :
|
||||
behavior(0),
|
||||
layerType(0),
|
||||
|
@ -37,50 +48,115 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
|
|||
return QPoint(x, y);
|
||||
}
|
||||
|
||||
// Returns the position of the rightmost set bit
|
||||
int Metatile::getShiftValue(uint32_t mask) {
|
||||
return log2(mask & ~(mask - 1));;
|
||||
}
|
||||
|
||||
|
||||
void Metatile::calculateAttributeLayout() {
|
||||
// Get the maximum size of any attribute mask
|
||||
const QHash<int, uint32_t> maxMasks = {
|
||||
{1, 0xFF},
|
||||
{2, 0xFFFF},
|
||||
{4, 0xFFFFFFFF},
|
||||
};
|
||||
const uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0);
|
||||
|
||||
// Behavior
|
||||
uint32_t mask = projectConfig.getMetatileBehaviorMask();
|
||||
if (mask > maxMask) {
|
||||
logWarn(QString("Metatile behavior mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask));
|
||||
mask &= maxMask;
|
||||
}
|
||||
Metatile::behaviorMask = mask;
|
||||
Metatile::behaviorShift = getShiftValue(mask);
|
||||
|
||||
// Terrain Type
|
||||
mask = projectConfig.getMetatileTerrainTypeMask();
|
||||
if (mask > maxMask) {
|
||||
logWarn(QString("Metatile terrain type mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask));
|
||||
mask &= maxMask;
|
||||
}
|
||||
Metatile::terrainTypeMask = mask;
|
||||
Metatile::terrainTypeShift = getShiftValue(mask);
|
||||
|
||||
// Encounter Type
|
||||
mask = projectConfig.getMetatileEncounterTypeMask();
|
||||
if (mask > maxMask) {
|
||||
logWarn(QString("Metatile encounter type mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask));
|
||||
mask &= maxMask;
|
||||
}
|
||||
Metatile::encounterTypeMask = mask;
|
||||
Metatile::encounterTypeShift = getShiftValue(mask);
|
||||
|
||||
// Layer Type
|
||||
mask = projectConfig.getMetatileLayerTypeMask();
|
||||
if (mask > maxMask) {
|
||||
logWarn(QString("Metatile layer type mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask));
|
||||
mask &= maxMask;
|
||||
}
|
||||
Metatile::layerTypeMask = mask;
|
||||
Metatile::layerTypeShift = getShiftValue(mask);
|
||||
|
||||
Metatile::unusedAttrMask = ~(Metatile::behaviorMask | Metatile::terrainTypeMask | Metatile::layerTypeMask | Metatile::encounterTypeMask);
|
||||
Metatile::unusedAttrMask &= maxMask;
|
||||
|
||||
// Warn user if any mask overlaps
|
||||
if (Metatile::behaviorMask & Metatile::terrainTypeMask
|
||||
|| Metatile::behaviorMask & Metatile::encounterTypeMask
|
||||
|| Metatile::behaviorMask & Metatile::layerTypeMask
|
||||
|| Metatile::terrainTypeMask & Metatile::encounterTypeMask
|
||||
|| Metatile::terrainTypeMask & Metatile::layerTypeMask
|
||||
|| Metatile::encounterTypeMask & Metatile::layerTypeMask) {
|
||||
logWarn("Metatile attribute masks are overlapping.");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Metatile::getAttributes() {
|
||||
uint32_t attributes = this->unusedAttributes & Metatile::unusedAttrMask;
|
||||
attributes |= (behavior << Metatile::behaviorShift) & Metatile::behaviorMask;
|
||||
attributes |= (terrainType << Metatile::terrainTypeShift) & Metatile::terrainTypeMask;
|
||||
attributes |= (encounterType << Metatile::encounterTypeShift) & Metatile::encounterTypeMask;
|
||||
attributes |= (layerType << Metatile::layerTypeShift) & Metatile::layerTypeMask;
|
||||
return attributes;
|
||||
}
|
||||
|
||||
void Metatile::setAttributes(uint32_t data) {
|
||||
this->behavior = (data & Metatile::behaviorMask) >> Metatile::behaviorShift;
|
||||
this->terrainType = (data & Metatile::terrainTypeMask) >> Metatile::terrainTypeShift;
|
||||
this->encounterType = (data & Metatile::encounterTypeMask) >> Metatile::encounterTypeShift;
|
||||
this->layerType = (data & Metatile::layerTypeMask) >> Metatile::layerTypeShift;
|
||||
this->unusedAttributes = data & Metatile::unusedAttrMask;
|
||||
}
|
||||
|
||||
// Get the vanilla attribute sizes based on version. For AdvanceMap import
|
||||
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;
|
||||
}
|
||||
|
||||
// Set the attributes using the vanilla layout based on version. For AdvanceMap import
|
||||
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);
|
||||
const uint32_t behaviorMask = 0x000001FF;
|
||||
const uint32_t terrainTypeMask = 0x00003E00;
|
||||
const uint32_t encounterTypeMask = 0x07000000;
|
||||
const uint32_t layerTypeMask = 0x60000000;
|
||||
|
||||
this->behavior = data & behaviorMask;
|
||||
this->terrainType = (data & terrainTypeMask) >> 9;
|
||||
this->encounterType = (data & encounterTypeMask) >> 24;
|
||||
this->layerType = (data & layerTypeMask) >> 29;
|
||||
this->unusedAttributes = data & ~(behaviorMask | terrainTypeMask | layerTypeMask | encounterTypeMask);
|
||||
} else {
|
||||
this->behavior = (data & behaviorMask_RSE) >> behaviorShift_RSE;
|
||||
this->layerType = (data & layerTypeMask_RSE) >> layerTypeShift_RSE;
|
||||
this->unusedAttributes = data & ~(behaviorMask_RSE | layerTypeMask_RSE);
|
||||
const uint16_t behaviorMask = 0x00FF;
|
||||
const uint16_t layerTypeMask = 0xF000;
|
||||
|
||||
this->behavior = data & behaviorMask;
|
||||
this->layerType = (data & layerTypeMask) >> 12;
|
||||
this->unusedAttributes = data & ~(behaviorMask | layerTypeMask);
|
||||
}
|
||||
|
||||
// Clean data to fit the user's custom masks
|
||||
this->setAttributes(this->getAttributes());
|
||||
}
|
||||
|
|
|
@ -233,8 +233,8 @@ bool Tileset::appendToMetatiles(QString root, QString friendlyName, bool usingAs
|
|||
} else {
|
||||
// Append to C file
|
||||
dataString.append(QString("const u16 gMetatiles_%1[] = INCBIN_U16(\"%2\");\n").arg(friendlyName, metatilesPath));
|
||||
QString attrSize = (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) ? "32" : "16";
|
||||
dataString.append(QString("const u%1 gMetatileAttributes_%2[] = INCBIN_U%1(\"%3\");\n").arg(attrSize, friendlyName, metatileAttrsPath));
|
||||
QString numBits = QString::number(projectConfig.getMetatileAttributesSize() * 8);
|
||||
dataString.append(QString("const u%1 gMetatileAttributes_%2[] = INCBIN_U%1(\"%3\");\n").arg(numBits, friendlyName, metatileAttrsPath));
|
||||
}
|
||||
file.write(dataString.toUtf8());
|
||||
file.flush();
|
||||
|
|
|
@ -528,6 +528,7 @@ bool MainWindow::openProject(QString dir) {
|
|||
userConfig.load();
|
||||
projectConfig.setProjectDir(dir);
|
||||
projectConfig.load();
|
||||
Metatile::calculateAttributeLayout();
|
||||
|
||||
this->closeSupplementaryWindows();
|
||||
this->setProjectSpecificUIVisibility();
|
||||
|
|
|
@ -980,10 +980,9 @@ void Project::saveTilesetMetatileAttributes(Tileset *tileset) {
|
|||
QFile attrs_file(tileset->metatile_attrs_path);
|
||||
if (attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
QByteArray data;
|
||||
BaseGameVersion version = projectConfig.getBaseGameVersion();
|
||||
int attrSize = Metatile::getAttributesSize(version);
|
||||
int attrSize = projectConfig.getMetatileAttributesSize();
|
||||
for (Metatile *metatile : tileset->metatiles) {
|
||||
uint32_t attributes = metatile->getAttributes(version);
|
||||
uint32_t attributes = metatile->getAttributes();
|
||||
for (int i = 0; i < attrSize; i++)
|
||||
data.append(static_cast<char>(attributes >> (8 * i)));
|
||||
}
|
||||
|
@ -1538,9 +1537,7 @@ void Project::loadTilesetMetatiles(Tileset* tileset) {
|
|||
if (attrs_file.open(QIODevice::ReadOnly)) {
|
||||
QByteArray data = attrs_file.readAll();
|
||||
int num_metatiles = tileset->metatiles.count();
|
||||
|
||||
BaseGameVersion version = projectConfig.getBaseGameVersion();
|
||||
int attrSize = Metatile::getAttributesSize(version);
|
||||
int attrSize = projectConfig.getMetatileAttributesSize();
|
||||
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));
|
||||
|
@ -1552,7 +1549,7 @@ void Project::loadTilesetMetatiles(Tileset* tileset) {
|
|||
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);
|
||||
tileset->metatiles.at(i)->setAttributes(attributes);
|
||||
}
|
||||
} else {
|
||||
logError(QString("Could not open tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path));
|
||||
|
|
|
@ -694,15 +694,14 @@ int MainWindow::getMetatileAttributes(int metatileId) {
|
|||
Metatile * metatile = this->getMetatile(metatileId);
|
||||
if (!metatile)
|
||||
return -1;
|
||||
return metatile->getAttributes(projectConfig.getBaseGameVersion());
|
||||
return metatile->getAttributes();
|
||||
}
|
||||
|
||||
void MainWindow::setMetatileAttributes(int metatileId, int attributes) {
|
||||
Metatile * metatile = this->getMetatile(metatileId);
|
||||
uint32_t u_attributes = static_cast<uint32_t>(attributes);
|
||||
if (!metatile)
|
||||
return;
|
||||
metatile->setAttributes(u_attributes, projectConfig.getBaseGameVersion());
|
||||
metatile->setAttributes(attributes);
|
||||
this->saveMetatileAttributesByMetatileId(metatileId);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue