Use custom attribute masks in API and Tileset Editor

This commit is contained in:
GriffinR
2022-10-26 02:42:55 -04:00
parent 1641ac00b0
commit 1283f5c19d
5 changed files with 113 additions and 70 deletions

View File

@@ -21,17 +21,17 @@ const QHash<Metatile::Attr, Metatile::AttrLayout> Metatile::defaultLayoutRSE = {
Metatile::Metatile() :
behavior(0),
layerType(0),
encounterType(0),
terrainType(0),
encounterType(0),
layerType(0),
unusedAttributes(0)
{ }
Metatile::Metatile(const int numTiles) :
behavior(0),
layerType(0),
encounterType(0),
terrainType(0),
encounterType(0),
layerType(0),
unusedAttributes(0)
{
Tile tile = Tile();
@@ -60,15 +60,25 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
return QPoint(x, y);
}
void Metatile::setCustomAttributeLayout(Metatile::AttrLayout * layout, uint32_t mask, uint32_t max, QString errName) {
void Metatile::setCustomAttributeLayout(Metatile::AttrLayout * layout, uint32_t mask, uint32_t max) {
if (mask > max) {
logWarn(QString("Metatile %1 mask '%2' exceeds maximum size '%3'").arg(errName).arg(mask).arg(max));
uint32_t oldMask = mask;
mask &= max;
logWarn(QString("Metatile attribute mask '0x%1' has been truncated to '0x%2'")
.arg(QString::number(oldMask, 16).toUpper())
.arg(QString::number(mask, 16).toUpper()));
}
layout->mask = mask;
layout->shift = log2(mask & ~(mask - 1)); // Get the position of the rightmost set bit
}
// For checking whether a metatile attribute mask can contain all the hard-coded values
bool Metatile::isMaskTooSmall(Metatile::AttrLayout * layout, int n) {
if (!layout->mask) return false;
uint32_t maxValue = n - 1;
return (maxValue & (layout->mask >> layout->shift)) != maxValue;
}
bool Metatile::doMasksOverlap(QList<uint32_t> masks) {
for (int i = 0; i < masks.length(); i++)
for (int j = i + 1; j < masks.length(); j++) {
@@ -88,10 +98,10 @@ void Metatile::setCustomLayout() {
const uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0);
// Set custom attribute masks from the config file
setCustomAttributeLayout(&customLayout[Attr::Behavior], projectConfig.getMetatileBehaviorMask(), maxMask, "behavior");
setCustomAttributeLayout(&customLayout[Attr::TerrainType], projectConfig.getMetatileTerrainTypeMask(), maxMask, "terrain type");
setCustomAttributeLayout(&customLayout[Attr::EncounterType], projectConfig.getMetatileEncounterTypeMask(), maxMask, "encounter type");
setCustomAttributeLayout(&customLayout[Attr::LayerType], projectConfig.getMetatileLayerTypeMask(), maxMask, "layer type");
setCustomAttributeLayout(&customLayout[Attr::Behavior], projectConfig.getMetatileBehaviorMask(), maxMask);
setCustomAttributeLayout(&customLayout[Attr::TerrainType], projectConfig.getMetatileTerrainTypeMask(), maxMask);
setCustomAttributeLayout(&customLayout[Attr::EncounterType], projectConfig.getMetatileEncounterTypeMask(), maxMask);
setCustomAttributeLayout(&customLayout[Attr::LayerType], projectConfig.getMetatileLayerTypeMask(), maxMask);
// Set mask for preserving any attribute bits not used by Porymap
Metatile::unusedAttrMask = ~(customLayout[Attr::Behavior].mask
@@ -100,13 +110,24 @@ void Metatile::setCustomLayout() {
| customLayout[Attr::LayerType].mask);
Metatile::unusedAttrMask &= maxMask;
// Overlapping masks are legal, but probably not intended
// Overlapping masks are technically ok, but probably not intended.
// Additionally, Porymap will not properly reflect that the values are linked.
if (doMasksOverlap({customLayout[Attr::Behavior].mask,
customLayout[Attr::TerrainType].mask,
customLayout[Attr::EncounterType].mask,
customLayout[Attr::LayerType].mask})) {
logWarn("Metatile attribute masks are overlapping.");
}
// The available options in the Tileset Editor for Terrain Type, Encounter Type, and Layer Type are hard-coded.
// Warn the user if they have set a nonzero mask that is too small to contain these options.
// They'll be allowed to select them, but they'll be truncated to a different value when revisited.
if (isMaskTooSmall(&customLayout[Attr::TerrainType], NUM_METATILE_TERRAIN_TYPES))
logWarn(QString("Metatile Terrain Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_TERRAIN_TYPES));
if (isMaskTooSmall(&customLayout[Attr::EncounterType], NUM_METATILE_ENCOUNTER_TYPES))
logWarn(QString("Metatile Encounter Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_ENCOUNTER_TYPES));
if (isMaskTooSmall(&customLayout[Attr::LayerType], NUM_METATILE_LAYER_TYPES))
logWarn(QString("Metatile Layer Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_LAYER_TYPES));
}
uint32_t Metatile::getAttributes() {
@@ -165,3 +186,19 @@ void Metatile::convertAttributes(uint32_t data, BaseGameVersion version) {
// Clean data to fit the user's custom masks
this->setAttributes(this->getAttributes());
}
void Metatile::setBehavior(uint32_t value) {
this->behavior = value & Metatile::customLayout[Attr::Behavior].mask;
}
void Metatile::setTerrainType(uint32_t value) {
this->terrainType = value & Metatile::customLayout[Attr::TerrainType].mask;
}
void Metatile::setEncounterType(uint32_t value) {
this->encounterType = value & Metatile::customLayout[Attr::EncounterType].mask;
}
void Metatile::setLayerType(uint32_t value) {
this->layerType = value & Metatile::customLayout[Attr::LayerType].mask;
}