2018-09-25 01:12:29 +01:00
|
|
|
#include "metatile.h"
|
|
|
|
#include "tileset.h"
|
2018-09-23 00:47:28 +01:00
|
|
|
#include "project.h"
|
|
|
|
|
2022-10-26 05:14:55 +01:00
|
|
|
QHash<Metatile::Attr, Metatile::AttrLayout> Metatile::customLayout = {};
|
2022-10-25 22:51:32 +01:00
|
|
|
uint32_t Metatile::unusedAttrMask = 0;
|
|
|
|
|
2022-10-26 05:14:55 +01:00
|
|
|
const QHash<Metatile::Attr, Metatile::AttrLayout> Metatile::defaultLayoutFRLG = {
|
|
|
|
{Metatile::Attr::Behavior, { .mask = 0x000001FF, .shift = 0} },
|
|
|
|
{Metatile::Attr::TerrainType, { .mask = 0x00003E00, .shift = 9} },
|
|
|
|
{Metatile::Attr::EncounterType, { .mask = 0x07000000, .shift = 24} },
|
|
|
|
{Metatile::Attr::LayerType, { .mask = 0x60000000, .shift = 29} },
|
|
|
|
};
|
|
|
|
|
|
|
|
const QHash<Metatile::Attr, Metatile::AttrLayout> Metatile::defaultLayoutRSE = {
|
|
|
|
{Metatile::Attr::Behavior, { .mask = 0x000000FF, .shift = 0} },
|
|
|
|
{Metatile::Attr::TerrainType, { .mask = 0x00000000, .shift = 0} },
|
|
|
|
{Metatile::Attr::EncounterType, { .mask = 0x00000000, .shift = 0} },
|
|
|
|
{Metatile::Attr::LayerType, { .mask = 0x0000F000, .shift = 12} },
|
|
|
|
};
|
2022-10-25 22:51:32 +01:00
|
|
|
|
2021-02-16 17:14:27 +00:00
|
|
|
Metatile::Metatile() :
|
|
|
|
behavior(0),
|
|
|
|
layerType(0),
|
|
|
|
encounterType(0),
|
2022-02-03 23:10:50 +00:00
|
|
|
terrainType(0),
|
|
|
|
unusedAttributes(0)
|
2021-02-16 17:14:27 +00:00
|
|
|
{ }
|
|
|
|
|
2022-10-04 02:28:16 +01:00
|
|
|
Metatile::Metatile(const int numTiles) :
|
|
|
|
behavior(0),
|
|
|
|
layerType(0),
|
|
|
|
encounterType(0),
|
|
|
|
terrainType(0),
|
|
|
|
unusedAttributes(0)
|
|
|
|
{
|
|
|
|
Tile tile = Tile();
|
|
|
|
for (int i = 0; i < numTiles; i++) {
|
|
|
|
this->tiles.append(tile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 02:35:15 +00:00
|
|
|
int Metatile::getIndexInTileset(int metatileId) {
|
|
|
|
if (metatileId < Project::getNumMetatilesPrimary()) {
|
|
|
|
return metatileId;
|
2018-09-23 00:47:28 +01:00
|
|
|
} else {
|
2022-01-04 02:35:15 +00:00
|
|
|
return metatileId - Project::getNumMetatilesPrimary();
|
2018-09-23 00:47:28 +01:00
|
|
|
}
|
|
|
|
}
|
2020-09-27 17:17:12 +01:00
|
|
|
|
2022-10-26 05:14:55 +01:00
|
|
|
// Get the vanilla attribute sizes based on version.
|
|
|
|
// Used as a default in the config and for AdvanceMap import.
|
|
|
|
int Metatile::getDefaultAttributesSize(BaseGameVersion version) {
|
|
|
|
return (version == BaseGameVersion::pokefirered) ? 4 : 2;
|
|
|
|
}
|
|
|
|
|
2020-09-27 17:17:12 +01:00
|
|
|
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);
|
|
|
|
}
|
2022-02-03 23:10:50 +00:00
|
|
|
|
2022-10-26 05:14:55 +01:00
|
|
|
void Metatile::setCustomAttributeLayout(Metatile::AttrLayout * layout, uint32_t mask, uint32_t max, QString errName) {
|
|
|
|
if (mask > max) {
|
|
|
|
logWarn(QString("Metatile %1 mask '%2' exceeds maximum size '%3'").arg(errName).arg(mask).arg(max));
|
|
|
|
mask &= max;
|
|
|
|
}
|
|
|
|
layout->mask = mask;
|
|
|
|
layout->shift = log2(mask & ~(mask - 1)); // Get the position of the rightmost set bit
|
2022-02-03 23:10:50 +00:00
|
|
|
}
|
|
|
|
|
2022-10-26 05:14:55 +01:00
|
|
|
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;
|
|
|
|
}
|
2022-10-25 22:51:32 +01:00
|
|
|
|
2022-10-26 05:14:55 +01:00
|
|
|
void Metatile::setCustomLayout() {
|
2022-10-25 22:51:32 +01:00
|
|
|
// 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);
|
|
|
|
|
2022-10-26 05:14:55 +01:00
|
|
|
// 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");
|
|
|
|
|
|
|
|
// Set mask for preserving any attribute bits not used by Porymap
|
|
|
|
Metatile::unusedAttrMask = ~(customLayout[Attr::Behavior].mask
|
|
|
|
| customLayout[Attr::TerrainType].mask
|
|
|
|
| customLayout[Attr::EncounterType].mask
|
|
|
|
| customLayout[Attr::LayerType].mask);
|
2022-10-25 22:51:32 +01:00
|
|
|
Metatile::unusedAttrMask &= maxMask;
|
|
|
|
|
2022-10-26 05:14:55 +01:00
|
|
|
// Overlapping masks are legal, but probably not intended
|
|
|
|
if (doMasksOverlap({customLayout[Attr::Behavior].mask,
|
|
|
|
customLayout[Attr::TerrainType].mask,
|
|
|
|
customLayout[Attr::EncounterType].mask,
|
|
|
|
customLayout[Attr::LayerType].mask})) {
|
2022-10-25 22:51:32 +01:00
|
|
|
logWarn("Metatile attribute masks are overlapping.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Metatile::getAttributes() {
|
|
|
|
uint32_t attributes = this->unusedAttributes & Metatile::unusedAttrMask;
|
2022-10-26 05:14:55 +01:00
|
|
|
|
|
|
|
// Behavior
|
|
|
|
Metatile::AttrLayout attr = Metatile::customLayout[Attr::Behavior];
|
|
|
|
attributes |= (this->behavior << attr.shift) & attr.mask;
|
|
|
|
|
|
|
|
// Terrain Type
|
|
|
|
attr = Metatile::customLayout[Attr::TerrainType];
|
|
|
|
attributes |= (this->terrainType << attr.shift) & attr.mask;
|
|
|
|
|
|
|
|
// Encounter Type
|
|
|
|
attr = Metatile::customLayout[Attr::EncounterType];
|
|
|
|
attributes |= (this->encounterType << attr.shift) & attr.mask;
|
|
|
|
|
|
|
|
// Layer Type
|
|
|
|
attr = Metatile::customLayout[Attr::LayerType];
|
|
|
|
attributes |= (this->layerType << attr.shift) & attr.mask;
|
|
|
|
|
2022-02-03 23:10:50 +00:00
|
|
|
return attributes;
|
|
|
|
}
|
|
|
|
|
2022-10-26 05:14:55 +01:00
|
|
|
void Metatile::setAttributes(uint32_t data, const QHash<Metatile::Attr, Metatile::AttrLayout> * layout) {
|
|
|
|
// Behavior
|
|
|
|
Metatile::AttrLayout attr = layout->value(Attr::Behavior);
|
|
|
|
this->behavior = (data & attr.mask) >> attr.shift;
|
|
|
|
|
|
|
|
// Terrain Type
|
|
|
|
attr = layout->value(Attr::TerrainType);
|
|
|
|
this->terrainType = (data & attr.mask) >> attr.shift;
|
|
|
|
|
|
|
|
// Encounter Type
|
|
|
|
attr = layout->value(Attr::EncounterType);
|
|
|
|
this->encounterType = (data & attr.mask) >> attr.shift;
|
|
|
|
|
|
|
|
// Layer Type
|
|
|
|
attr = layout->value(Attr::LayerType);
|
|
|
|
this->layerType = (data & attr.mask) >> attr.shift;
|
|
|
|
|
2022-10-25 22:51:32 +01:00
|
|
|
this->unusedAttributes = data & Metatile::unusedAttrMask;
|
|
|
|
}
|
|
|
|
|
2022-10-26 05:14:55 +01:00
|
|
|
void Metatile::setAttributes(uint32_t data) {
|
|
|
|
this->setAttributes(data, &Metatile::customLayout);
|
2022-10-25 22:51:32 +01:00
|
|
|
}
|
|
|
|
|
2022-10-26 05:14:55 +01:00
|
|
|
// Read attributes using a vanilla layout, then set them using the user's layout. For AdvanceMap import
|
|
|
|
void Metatile::convertAttributes(uint32_t data, BaseGameVersion version) {
|
2022-02-03 23:10:50 +00:00
|
|
|
if (version == BaseGameVersion::pokefirered) {
|
2022-10-26 05:14:55 +01:00
|
|
|
this->setAttributes(data, &Metatile::defaultLayoutFRLG);
|
2022-02-03 23:10:50 +00:00
|
|
|
} else {
|
2022-10-26 05:14:55 +01:00
|
|
|
this->setAttributes(data, &Metatile::defaultLayoutRSE);
|
2022-02-03 23:10:50 +00:00
|
|
|
}
|
2022-10-25 22:51:32 +01:00
|
|
|
// Clean data to fit the user's custom masks
|
|
|
|
this->setAttributes(this->getAttributes());
|
2022-02-03 23:10:50 +00:00
|
|
|
}
|