mirror of
https://github.com/huderlem/porymap.git
synced 2026-03-27 12:34:50 -05:00
154 lines
6.6 KiB
C++
154 lines
6.6 KiB
C++
#include "metatile.h"
|
|
#include "tileset.h"
|
|
#include "project.h"
|
|
|
|
// Stores how each attribute should be laid out for all metatiles, according to the vanilla games.
|
|
// Used to set default config values and import maps with AdvanceMap.
|
|
static const QMap<Metatile::Attr, BitPacker> attributePackersFRLG = {
|
|
{Metatile::Attr::Behavior, BitPacker(0x000001FF) },
|
|
{Metatile::Attr::TerrainType, BitPacker(0x00003E00) },
|
|
{Metatile::Attr::EncounterType, BitPacker(0x07000000) },
|
|
{Metatile::Attr::LayerType, BitPacker(0x60000000) },
|
|
//{Metatile::Attr::Unused, BitPacker(0x98FFC000) },
|
|
};
|
|
static const QMap<Metatile::Attr, BitPacker> attributePackersRSE = {
|
|
{Metatile::Attr::Behavior, BitPacker(0x00FF) },
|
|
//{Metatile::Attr::Unused, BitPacker(0x0F00) },
|
|
{Metatile::Attr::LayerType, BitPacker(0xF000) },
|
|
};
|
|
|
|
static QMap<Metatile::Attr, BitPacker> attributePackers;
|
|
|
|
Metatile::Metatile(const int numTiles) {
|
|
Tile tile = Tile();
|
|
for (int i = 0; i < numTiles; i++) {
|
|
this->tiles.append(tile);
|
|
}
|
|
}
|
|
|
|
int Metatile::getIndexInTileset(int metatileId) {
|
|
if (metatileId < Project::getNumMetatilesPrimary()) {
|
|
return metatileId;
|
|
} else {
|
|
return metatileId - Project::getNumMetatilesPrimary();
|
|
}
|
|
}
|
|
|
|
QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
|
|
int x = static_cast<int>(pixelCoord.x()) / 16;
|
|
int y = static_cast<int>(pixelCoord.y()) / 16;
|
|
return QPoint(x, y);
|
|
}
|
|
|
|
// Read and pack together this metatile's attributes.
|
|
uint32_t Metatile::getAttributes() const {
|
|
uint32_t data = 0;
|
|
for (auto i = this->attributes.cbegin(), end = this->attributes.cend(); i != end; i++){
|
|
const auto packer = attributePackers.value(i.key());
|
|
data |= packer.pack(i.value());
|
|
}
|
|
return data;
|
|
}
|
|
|
|
// Unpack and insert metatile attributes from the given data.
|
|
void Metatile::setAttributes(uint32_t data) {
|
|
for (auto i = attributePackers.cbegin(), end = attributePackers.cend(); i != end; i++){
|
|
const auto packer = i.value();
|
|
this->setAttribute(i.key(), packer.unpack(data));
|
|
}
|
|
}
|
|
|
|
// Unpack and insert metatile attributes from the given data using a vanilla layout. For AdvanceMap import
|
|
void Metatile::setAttributes(uint32_t data, BaseGameVersion version) {
|
|
const auto vanillaPackers = (version == BaseGameVersion::pokefirered) ? attributePackersFRLG : attributePackersRSE;
|
|
for (auto i = vanillaPackers.cbegin(), end = vanillaPackers.cend(); i != end; i++){
|
|
const auto packer = i.value();
|
|
this->setAttribute(i.key(), packer.unpack(data));
|
|
}
|
|
}
|
|
|
|
// Set the value for a metatile attribute, and fit it within the valid value range.
|
|
void Metatile::setAttribute(Metatile::Attr attr, uint32_t value) {
|
|
const auto packer = attributePackers.value(attr);
|
|
this->attributes.insert(attr, packer.clamp(value));
|
|
}
|
|
|
|
int Metatile::getDefaultAttributesSize(BaseGameVersion version) {
|
|
return (version == BaseGameVersion::pokefirered) ? 4 : 2;
|
|
}
|
|
|
|
uint32_t Metatile::getDefaultAttributesMask(BaseGameVersion version, Metatile::Attr attr) {
|
|
const auto vanillaPackers = (version == BaseGameVersion::pokefirered) ? attributePackersFRLG : attributePackersRSE;
|
|
return vanillaPackers.value(attr).mask();
|
|
}
|
|
|
|
bool Metatile::doMasksOverlap(QList<uint32_t> masks) {
|
|
for (int i = 0; i < masks.length(); i++)
|
|
for (int j = i + 1; j < masks.length(); j++) {
|
|
if (masks.at(i) & masks.at(j))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Metatile::setLayout(Project * project) {
|
|
// Read masks from the config and limit them based on the specified attribute size.
|
|
const QHash<int, uint32_t> maxMasks = {
|
|
{1, 0xFF},
|
|
{2, 0xFFFF},
|
|
{4, 0xFFFFFFFF},
|
|
};
|
|
uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0);
|
|
uint32_t behaviorMask = projectConfig.getMetatileBehaviorMask() & maxMask;
|
|
uint32_t terrainTypeMask = projectConfig.getMetatileTerrainTypeMask() & maxMask;
|
|
uint32_t encounterTypeMask = projectConfig.getMetatileEncounterTypeMask() & maxMask;
|
|
uint32_t layerTypeMask = projectConfig.getMetatileLayerTypeMask() & maxMask;
|
|
|
|
// Overlapping masks are technically ok, but probably not intended.
|
|
// Additionally, Porymap will not properly reflect that the values are linked.
|
|
if (doMasksOverlap({behaviorMask, terrainTypeMask, encounterTypeMask, layerTypeMask})) {
|
|
logWarn("Metatile attribute masks are overlapping. This may result in unexpected attribute values.");
|
|
}
|
|
|
|
|
|
// Calculate mask of bits not used by standard behaviors so we can preserve this data.
|
|
uint32_t unusedMask = ~(behaviorMask | terrainTypeMask | encounterTypeMask | layerTypeMask);
|
|
unusedMask &= maxMask;
|
|
|
|
BitPacker packer = BitPacker(unusedMask);
|
|
attributePackers.clear();
|
|
attributePackers.insert(Metatile::Attr::Unused, unusedMask);
|
|
|
|
// TODO: Test displaying 32 bit behavior
|
|
// TODO: Logging masks to hex
|
|
// Validate metatile behavior mask
|
|
packer.setMask(behaviorMask);
|
|
if (behaviorMask && !project->metatileBehaviorMapInverse.isEmpty()) {
|
|
uint32_t maxBehavior = project->metatileBehaviorMapInverse.lastKey();
|
|
if (packer.clamp(maxBehavior) != maxBehavior)
|
|
logWarn(QString("Metatile Behavior mask '%1' is insufficient to contain all available options.").arg(behaviorMask));
|
|
}
|
|
attributePackers.insert(Metatile::Attr::Behavior, packer);
|
|
|
|
// Validate terrain type mask
|
|
packer.setMask(terrainTypeMask);
|
|
const uint32_t maxTerrainType = NUM_METATILE_TERRAIN_TYPES - 1;
|
|
if (terrainTypeMask && packer.clamp(maxTerrainType) != maxTerrainType)
|
|
logWarn(QString("Metatile Terrain Type mask '%1' is insufficient to contain all %2 available options.").arg(terrainTypeMask).arg(maxTerrainType + 1));
|
|
attributePackers.insert(Metatile::Attr::TerrainType, packer);
|
|
|
|
// Validate encounter type mask
|
|
packer.setMask(encounterTypeMask);
|
|
const uint32_t maxEncounterType = NUM_METATILE_ENCOUNTER_TYPES - 1;
|
|
if (encounterTypeMask && packer.clamp(maxEncounterType) != maxEncounterType)
|
|
logWarn(QString("Metatile Encounter Type mask '%1' is insufficient to contain all %2 available options.").arg(encounterTypeMask).arg(maxEncounterType + 1));
|
|
attributePackers.insert(Metatile::Attr::EncounterType, packer);
|
|
|
|
// Validate terrain type mask
|
|
packer.setMask(layerTypeMask);
|
|
const uint32_t maxLayerType = NUM_METATILE_LAYER_TYPES - 1;
|
|
if (layerTypeMask && packer.clamp(maxLayerType) != maxLayerType)
|
|
logWarn(QString("Metatile Layer Type mask '%1' is insufficient to contain all %2 available options.").arg(layerTypeMask).arg(maxLayerType + 1));
|
|
attributePackers.insert(Metatile::Attr::LayerType, packer);
|
|
}
|