Merge pull request #485 from GriffinRichards/metatile-attr

Allow reorganization of metatile attributes
This commit is contained in:
GriffinR 2023-01-15 18:55:42 -05:00 committed by GitHub
commit ab6d035ad8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 389 additions and 188 deletions

View file

@ -8,6 +8,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
## [Unreleased]
### Added
- Add new config options for reorganizing metatile attributes.
- Add `setScale` to the scripting API.
- Add option to turn off the checkerboard fill for new tilesets.

View file

@ -48,6 +48,7 @@ your project root as ``porymap.user.cfg``. You should add this file to your giti
``enable_hidden_item_requires_itemfinder``, 1 if ``pokefirered``, project, yes, Adds ``Requires Itemfinder`` to Hidden Item events
``enable_heal_location_respawn_data``, 1 if ``pokefirered``, project, yes, Adds ``Respawn Map`` and ``Respawn NPC`` to Heal Location events
``enable_floor_number``, 1 if ``pokefirered``, project, yes, Adds ``Floor Number`` to map headers
``enable_map_allow_flags``, 1 if not ``pokeruby``, project, yes, "Adds ``Allow Running``, ``Allow Biking``, and ``Allow Dig & Escape Rope`` to map headers"
``create_map_text_file``, 1 if not ``pokeemerald``, project, yes, A ``text.inc`` or ``text.pory`` file will be created for any new map
``enable_triple_layer_metatiles``, 0, project, yes, Enables triple-layer metatiles (See https://github.com/pret/pokeemerald/wiki/Triple-layer-metatiles)
``new_map_metatile``, 1, project, yes, The metatile id that will be used to fill new maps
@ -60,5 +61,10 @@ your project root as ``porymap.user.cfg``. You should add this file to your giti
``prefabs_import_prompted``, 0, project, no, Keeps track of whether or not the project was prompted for importing default prefabs
``tilesets_have_callback``, 1, project, yes, Whether new tileset headers should have the ``callback`` field
``tilesets_have_is_compressed``, 1, project, yes, Whether new tileset headers should have the ``isCompressed`` field
``metatile_attributes_size``, 2 or 4, project, yes, The number of attribute bytes each metatile has
``metatile_behavior_mask``, ``0xFF`` or ``0x1FF``, project, yes, The mask for the metatile Behavior attribute
``metatile_encounter_type_mask``, ``0x0`` or ``0x7000000``, project, yes, The mask for the metatile Encounter Type attribute
``metatile_layer_type_mask``, ``0xF000`` or ``0x60000000``, project, yes, The mask for the metatile Layer Type attribute
``metatile_terrain_type_mask``, ``0x0`` or ``0x3E00``, project, yes, The mask for the metatile Terrain Type attribute
Some of these settings can be toggled manually in porymap via the *Options* menu.

View file

@ -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
@ -268,6 +269,12 @@ public:
bool getTilesetsHaveCallback();
void setTilesetsHaveIsCompressed(bool has);
bool getTilesetsHaveIsCompressed();
int getMetatileAttributesSize();
uint32_t getMetatileBehaviorMask();
uint32_t getMetatileTerrainTypeMask();
uint32_t getMetatileEncounterTypeMask();
uint32_t getMetatileLayerTypeMask();
bool getMapAllowFlagsEnabled();
protected:
virtual QString getConfigFilepath() override;
virtual void parseConfigKeyValue(QString key, QString value) override;
@ -299,6 +306,12 @@ private:
bool prefabImportPrompted;
bool tilesetsHaveCallback;
bool tilesetsHaveIsCompressed;
int metatileAttributesSize;
uint32_t metatileBehaviorMask;
uint32_t metatileTerrainTypeMask;
uint32_t metatileEncounterTypeMask;
uint32_t metatileLayerTypeMask;
bool enableMapAllowFlags;
};
extern ProjectConfig projectConfig;

View file

@ -8,6 +8,8 @@
#include <QPoint>
#include <QString>
class Project;
enum {
METATILE_LAYER_MIDDLE_TOP,
METATILE_LAYER_BOTTOM_MIDDLE,
@ -30,6 +32,29 @@ enum {
NUM_METATILE_TERRAIN_TYPES
};
class MetatileAttr
{
public:
MetatileAttr();
MetatileAttr(uint32_t mask, int shift);
public:
uint32_t mask;
int shift;
// Given the raw value for all attributes of a metatile
// Returns the extracted value for this attribute
uint32_t fromRaw(uint32_t raw) const { return (raw & this->mask) >> this->shift; }
// Given a value for this attribute
// Returns the raw value to OR together with the other attributes
uint32_t toRaw(uint32_t value) const { return (value << this->shift) & this->mask; }
// Given an arbitrary value to set for an attribute
// Returns a bounded value for that attribute
uint32_t getClamped(int value) const { return static_cast<uint32_t>(value) & (this->mask >> this->shift); }
};
class Metatile
{
public:
@ -40,19 +65,54 @@ 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 terrainType;
uint32_t encounterType;
uint32_t layerType;
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);
void setBehavior(int value) { this->behavior = behaviorAttr.getClamped(value); }
void setTerrainType(int value) { this->terrainType = terrainTypeAttr.getClamped(value); }
void setEncounterType(int value) { this->encounterType = encounterTypeAttr.getClamped(value); }
void setLayerType(int value) { this->layerType = layerTypeAttr.getClamped(value); }
static uint32_t getBehaviorMask() { return behaviorAttr.mask; }
static uint32_t getTerrainTypeMask() { return terrainTypeAttr.mask; }
static uint32_t getEncounterTypeMask() { return encounterTypeAttr.mask; }
static uint32_t getLayerTypeMask() { return layerTypeAttr.mask; }
static uint32_t getBehaviorMask(BaseGameVersion version);
static uint32_t getTerrainTypeMask(BaseGameVersion version);
static uint32_t getEncounterTypeMask(BaseGameVersion version);
static uint32_t getLayerTypeMask(BaseGameVersion version);
static int getIndexInTileset(int);
static QPoint coordFromPixmapCoord(const QPointF &pixelCoord);
static int getAttributesSize(BaseGameVersion version);
static int getDefaultAttributesSize(BaseGameVersion version);
static void setCustomLayout(Project*);
private:
// Stores how each attribute should be laid out for all metatiles, according to the user's config
static MetatileAttr behaviorAttr;
static MetatileAttr terrainTypeAttr;
static MetatileAttr encounterTypeAttr;
static MetatileAttr layerTypeAttr;
static uint32_t unusedAttrMask;
// 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 QHash<QString, MetatileAttr> defaultLayoutFRLG;
static const QHash<QString, MetatileAttr> defaultLayoutRSE;
static const QHash<BaseGameVersion, const QHash<QString, MetatileAttr>*> defaultLayouts;
static void setCustomAttributeLayout(MetatileAttr *, uint32_t, uint32_t);
static bool isMaskTooSmall(MetatileAttr *, int);
static bool doMasksOverlap(QList<uint32_t>);
};
inline bool operator==(const Metatile &a, const Metatile &b) {

View file

@ -231,7 +231,6 @@ public:
private:
void updateMapLayout(Map*);
void setNewMapHeader(Map* map, int mapIndex);
void setNewMapBlockdata(Map* map);
void setNewMapBorder(Map *map);
void setNewMapEvents(Map *map);

View file

@ -111,9 +111,7 @@ private slots:
private:
void initUi();
void setMetatileBehaviors();
void setMetatileLayersUi();
void setVersionSpecificUi();
void setAttributesUi();
void setMetatileLabelValidator();
void initMetatileSelector();
void initTileSelector();

View file

@ -136,7 +136,7 @@ void KeyValueConfigBase::save() {
bool KeyValueConfigBase::getConfigBool(QString key, QString value) {
bool ok;
int result = value.toInt(&ok);
int result = value.toInt(&ok, 0);
if (!ok || (result != 0 && result != 1)) {
logWarn(QString("Invalid config value for %1: '%2'. Must be 0 or 1.").arg(key).arg(value));
}
@ -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;
@ -561,6 +571,23 @@ 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));
return; // Don't set a default now, it will be set later based on the base game version
}
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);
} else if (key == "enable_map_allow_flags") {
this->enableMapAllowFlags = getConfigBool(key, value);
#ifdef CONFIG_BACKWARDS_COMPATABILITY
} else if (key == "recent_map") {
userConfig.setRecentMap(value);
@ -611,6 +638,12 @@ 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 = Metatile::getDefaultAttributesSize(this->baseGameVersion);
if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = Metatile::getBehaviorMask(this->baseGameVersion);
if (!readKeys.contains("metatile_terrain_type_mask")) this->metatileTerrainTypeMask = Metatile::getTerrainTypeMask(this->baseGameVersion);
if (!readKeys.contains("metatile_encounter_type_mask")) this->metatileEncounterTypeMask = Metatile::getEncounterTypeMask(this->baseGameVersion);
if (!readKeys.contains("metatile_layer_type_mask")) this-> metatileLayerTypeMask = Metatile::getLayerTypeMask(this->baseGameVersion);
if (!readKeys.contains("enable_map_allow_flags")) this->enableMapAllowFlags = (this->baseGameVersion != BaseGameVersion::pokeruby);
}
QMap<QString, QString> ProjectConfig::getKeyValueMap() {
@ -642,6 +675,12 @@ 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());
map.insert("enable_map_allow_flags", QString::number(this->enableMapAllowFlags));
return map;
}
@ -891,6 +930,30 @@ 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;
}
bool ProjectConfig::getMapAllowFlagsEnabled() {
return this->enableMapAllowFlags;
}
UserConfig userConfig;

View file

@ -2,19 +2,56 @@
#include "tileset.h"
#include "project.h"
const QHash<QString, MetatileAttr> Metatile::defaultLayoutFRLG = {
{"behavior", MetatileAttr(0x000001FF, 0) },
{"terrainType", MetatileAttr(0x00003E00, 9) },
{"encounterType", MetatileAttr(0x07000000, 24) },
{"layerType", MetatileAttr(0x60000000, 29) },
};
const QHash<QString, MetatileAttr> Metatile::defaultLayoutRSE = {
{"behavior", MetatileAttr(0x00FF, 0) },
{"terrainType", MetatileAttr() },
{"encounterType", MetatileAttr() },
{"layerType", MetatileAttr(0xF000, 12) },
};
const QHash<BaseGameVersion, const QHash<QString, MetatileAttr>*> Metatile::defaultLayouts = {
{ BaseGameVersion::pokeruby, &defaultLayoutRSE },
{ BaseGameVersion::pokefirered, &defaultLayoutFRLG },
{ BaseGameVersion::pokeemerald, &defaultLayoutRSE },
};
MetatileAttr Metatile::behaviorAttr;
MetatileAttr Metatile::terrainTypeAttr;
MetatileAttr Metatile::encounterTypeAttr;
MetatileAttr Metatile::layerTypeAttr;
uint32_t Metatile::unusedAttrMask = 0;
MetatileAttr::MetatileAttr() :
mask(0),
shift(0)
{ }
MetatileAttr::MetatileAttr(uint32_t mask, int shift) :
mask(mask),
shift(shift)
{ }
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();
@ -37,50 +74,120 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
return QPoint(x, y);
}
int Metatile::getAttributesSize(BaseGameVersion version) {
return (version == BaseGameVersion::pokefirered) ? 4 : 2;
// Set the layout of a metatile attribute using the mask read from the config file
void Metatile::setCustomAttributeLayout(MetatileAttr * attr, uint32_t mask, uint32_t max) {
if (mask > 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()));
}
attr->mask = mask;
attr->shift = mask ? log2(mask & ~(mask - 1)) : 0; // Get position of the least significant set bit
}
// RSE attributes
const uint16_t behaviorMask_RSE = 0x00FF;
const uint16_t layerTypeMask_RSE = 0xF000;
const int behaviorShift_RSE = 0;
const int layerTypeShift_RSE = 12;
// For checking whether a metatile attribute mask can contain all the available hard-coded options
bool Metatile::isMaskTooSmall(MetatileAttr * attr, int max) {
if (attr->mask == 0 || max <= 0) return false;
// 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;
// Get position of the most significant set bit
uint32_t n = log2(max);
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;
// Get a mask for all values 0 to max.
// This may fail for n > 30, but that's not possible here.
uint32_t rangeMask = (1 << (n + 1)) - 1;
return attr->getClamped(rangeMask) != rangeMask;
}
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::setCustomLayout(Project * project) {
// 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);
// Set custom attribute masks from the config file
setCustomAttributeLayout(&Metatile::behaviorAttr, projectConfig.getMetatileBehaviorMask(), maxMask);
setCustomAttributeLayout(&Metatile::terrainTypeAttr, projectConfig.getMetatileTerrainTypeMask(), maxMask);
setCustomAttributeLayout(&Metatile::encounterTypeAttr, projectConfig.getMetatileEncounterTypeMask(), maxMask);
setCustomAttributeLayout(&Metatile::layerTypeAttr, projectConfig.getMetatileLayerTypeMask(), maxMask);
// Set mask for preserving any attribute bits not used by Porymap
Metatile::unusedAttrMask = ~(getBehaviorMask() | getTerrainTypeMask() | getEncounterTypeMask() | getLayerTypeMask());
Metatile::unusedAttrMask &= maxMask;
// Overlapping masks are technically ok, but probably not intended.
// Additionally, Porymap will not properly reflect that the values are linked.
if (doMasksOverlap({getBehaviorMask(), getTerrainTypeMask(), getEncounterTypeMask(), getLayerTypeMask()})) {
logWarn("Metatile attribute masks are overlapping. This may result in unexpected attribute values.");
}
// Warn the user if they have set a nonzero mask that is too small to contain its available options.
// They'll be allowed to select the options, but they'll be truncated to a different value when revisited.
if (!project->metatileBehaviorMapInverse.isEmpty()) {
int maxBehavior = project->metatileBehaviorMapInverse.lastKey();
if (isMaskTooSmall(&Metatile::behaviorAttr, maxBehavior))
logWarn(QString("Metatile Behavior mask is too small to contain all %1 available options.").arg(maxBehavior));
}
if (isMaskTooSmall(&Metatile::terrainTypeAttr, NUM_METATILE_TERRAIN_TYPES - 1))
logWarn(QString("Metatile Terrain Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_TERRAIN_TYPES));
if (isMaskTooSmall(&Metatile::encounterTypeAttr, NUM_METATILE_ENCOUNTER_TYPES - 1))
logWarn(QString("Metatile Encounter Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_ENCOUNTER_TYPES));
if (isMaskTooSmall(&Metatile::layerTypeAttr, NUM_METATILE_LAYER_TYPES - 1))
logWarn(QString("Metatile Layer Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_LAYER_TYPES));
}
uint32_t Metatile::getAttributes() {
uint32_t attributes = this->unusedAttributes & Metatile::unusedAttrMask;
attributes |= Metatile::behaviorAttr.toRaw(this->behavior);
attributes |= Metatile::terrainTypeAttr.toRaw(this->terrainType);
attributes |= Metatile::encounterTypeAttr.toRaw(this->encounterType);
attributes |= Metatile::layerTypeAttr.toRaw(this->layerType);
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);
}
void Metatile::setAttributes(uint32_t data) {
this->behavior = Metatile::behaviorAttr.fromRaw(data);
this->terrainType = Metatile::terrainTypeAttr.fromRaw(data);
this->encounterType = Metatile::encounterTypeAttr.fromRaw(data);
this->layerType = Metatile::layerTypeAttr.fromRaw(data);
this->unusedAttributes = data & Metatile::unusedAttrMask;
}
// Read attributes using a vanilla layout, then set them using the user's layout. For AdvanceMap import
void Metatile::setAttributes(uint32_t data, BaseGameVersion version) {
const auto defaultLayout = Metatile::defaultLayouts.value(version);
this->setBehavior(defaultLayout->value("behavior").fromRaw(data));
this->setTerrainType(defaultLayout->value("terrainType").fromRaw(data));
this->setEncounterType(defaultLayout->value("encounterType").fromRaw(data));
this->setLayerType(defaultLayout->value("layerType").fromRaw(data));
}
int Metatile::getDefaultAttributesSize(BaseGameVersion version) {
return (version == BaseGameVersion::pokefirered) ? 4 : 2;
}
uint32_t Metatile::getBehaviorMask(BaseGameVersion version) {
return Metatile::defaultLayouts.value(version)->value("behavior").mask;
}
uint32_t Metatile::getTerrainTypeMask(BaseGameVersion version) {
return Metatile::defaultLayouts.value(version)->value("terrainType").mask;
}
uint32_t Metatile::getEncounterTypeMask(BaseGameVersion version) {
return Metatile::defaultLayouts.value(version)->value("encounterType").mask;
}
uint32_t Metatile::getLayerTypeMask(BaseGameVersion version) {
return Metatile::defaultLayouts.value(version)->value("layerType").mask;
}

View file

@ -42,7 +42,7 @@ QList<Metatile*> MetatileParser::parse(QString filepath, bool *error, bool prima
return { };
}
int attrSize = Metatile::getAttributesSize(version);
int attrSize = Metatile::getDefaultAttributesSize(version);
int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary();
int numMetatiles = static_cast<unsigned char>(in.at(0)) |
(static_cast<unsigned char>(in.at(1)) << 8) |

View file

@ -410,7 +410,7 @@ int ParseUtil::gameStringToInt(QString gameString, bool * ok) {
return 1;
if (QString::compare(gameString, "FALSE", Qt::CaseInsensitive) == 0)
return 0;
return gameString.toInt(ok);
return gameString.toInt(ok, 0);
}
bool ParseUtil::gameStringToBool(QString gameString, bool * ok) {

View file

@ -240,8 +240,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();

View file

@ -376,36 +376,13 @@ void MainWindow::setProjectSpecificUIVisibility()
ui->actionUse_Poryscript->setChecked(projectConfig.getUsePoryScript());
this->setWildEncountersUIEnabled(userConfig.getEncounterJsonActive());
switch (projectConfig.getBaseGameVersion())
{
case BaseGameVersion::pokeruby:
ui->checkBox_AllowRunning->setVisible(false);
ui->checkBox_AllowBiking->setVisible(false);
ui->checkBox_AllowEscaping->setVisible(false);
ui->label_AllowRunning->setVisible(false);
ui->label_AllowBiking->setVisible(false);
ui->label_AllowEscaping->setVisible(false);
ui->actionRegion_Map_Editor->setVisible(true);
break;
case BaseGameVersion::pokeemerald:
ui->checkBox_AllowRunning->setVisible(true);
ui->checkBox_AllowBiking->setVisible(true);
ui->checkBox_AllowEscaping->setVisible(true);
ui->label_AllowRunning->setVisible(true);
ui->label_AllowBiking->setVisible(true);
ui->label_AllowEscaping->setVisible(true);
ui->actionRegion_Map_Editor->setVisible(true);
break;
case BaseGameVersion::pokefirered:
ui->checkBox_AllowRunning->setVisible(true);
ui->checkBox_AllowBiking->setVisible(true);
ui->checkBox_AllowEscaping->setVisible(true);
ui->label_AllowRunning->setVisible(true);
ui->label_AllowBiking->setVisible(true);
ui->label_AllowEscaping->setVisible(true);
ui->actionRegion_Map_Editor->setVisible(true);
break;
}
bool hasFlags = projectConfig.getMapAllowFlagsEnabled();
ui->checkBox_AllowRunning->setVisible(hasFlags);
ui->checkBox_AllowBiking->setVisible(hasFlags);
ui->checkBox_AllowEscaping->setVisible(hasFlags);
ui->label_AllowRunning->setVisible(hasFlags);
ui->label_AllowBiking->setVisible(hasFlags);
ui->label_AllowEscaping->setVisible(hasFlags);
ui->newEventToolButton->newWeatherTriggerAction->setVisible(projectConfig.getEventWeatherTriggerEnabled());
ui->newEventToolButton->newSecretBaseAction->setVisible(projectConfig.getEventSecretBaseEnabled());
@ -807,11 +784,9 @@ void MainWindow::displayMapProperties() {
ui->comboBox_Type->setCurrentText(map->type);
ui->comboBox_BattleScene->setCurrentText(map->battle_scene);
ui->checkBox_ShowLocation->setChecked(map->show_location);
if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) {
ui->checkBox_AllowRunning->setChecked(map->allowRunning);
ui->checkBox_AllowBiking->setChecked(map->allowBiking);
ui->checkBox_AllowEscaping->setChecked(map->allowEscaping);
}
ui->checkBox_AllowRunning->setChecked(map->allowRunning);
ui->checkBox_AllowBiking->setChecked(map->allowBiking);
ui->checkBox_AllowEscaping->setChecked(map->allowEscaping);
ui->spinBox_FloorNumber->setValue(map->floorNumber);
// Custom fields table.
@ -940,6 +915,7 @@ bool MainWindow::loadDataStructures() {
&& project->readEventGraphics()
&& project->readSongNames();
Metatile::setCustomLayout(project);
Scripting::populateGlobalObject(this);
return success && loadProjectCombos();

View file

@ -165,7 +165,7 @@ const QSet<QString> defaultTopLevelMapFields = {
QSet<QString> Project::getTopLevelMapFields() {
QSet<QString> topLevelMapFields = defaultTopLevelMapFields;
if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) {
if (projectConfig.getMapAllowFlagsEnabled()) {
topLevelMapFields.insert("allow_cycling");
topLevelMapFields.insert("allow_escaping");
topLevelMapFields.insert("allow_running");
@ -200,7 +200,7 @@ bool Project::loadMapData(Map* map) {
map->show_location = ParseUtil::jsonToBool(mapObj["show_map_name"]);
map->battle_scene = ParseUtil::jsonToQString(mapObj["battle_scene"]);
if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) {
if (projectConfig.getMapAllowFlagsEnabled()) {
map->allowBiking = ParseUtil::jsonToBool(mapObj["allow_cycling"]);
map->allowEscaping = ParseUtil::jsonToBool(mapObj["allow_escaping"]);
map->allowRunning = ParseUtil::jsonToBool(mapObj["allow_running"]);
@ -375,26 +375,6 @@ QString Project::readMapLocation(QString map_name) {
return ParseUtil::jsonToQString(mapObj["region_map_section"]);
}
void Project::setNewMapHeader(Map* map, int mapIndex) {
map->layoutId = QString("%1").arg(mapIndex);
map->location = mapSectionValueToName.value(0);
map->requiresFlash = false;
map->weather = weatherNames.value(0, "WEATHER_NONE");
map->type = mapTypes.value(0, "MAP_TYPE_NONE");
map->song = defaultSong;
map->show_location = true;
if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) {
map->allowBiking = true;
map->allowEscaping = false;
map->allowRunning = true;
}
if (projectConfig.getFloorNumberEnabled()) {
map->floorNumber = 0;
}
map->battle_scene = mapBattleScenes.value(0, "MAP_BATTLE_SCENE_NORMAL");
}
bool Project::loadLayout(MapLayout *layout) {
// Force these to run even if one fails
bool loadedTilesets = loadLayoutTilesets(layout);
@ -981,10 +961,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)));
}
@ -1265,7 +1244,7 @@ void Project::saveMap(Map *map) {
mapObj["requires_flash"] = map->requiresFlash;
mapObj["weather"] = map->weather;
mapObj["map_type"] = map->type;
if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) {
if (projectConfig.getMapAllowFlagsEnabled()) {
mapObj["allow_cycling"] = map->allowBiking;
mapObj["allow_escaping"] = map->allowEscaping;
mapObj["allow_running"] = map->allowRunning;
@ -1539,9 +1518,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));
@ -1553,7 +1530,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));
@ -2079,8 +2056,8 @@ bool Project::readHealLocations() {
HealLocation healLocation;
if (match.hasMatch()) {
QString mapName = match.captured("map");
int x = match.captured("x").toInt();
int y = match.captured("y").toInt();
int x = match.captured("x").toInt(nullptr, 0);
int y = match.captured("y").toInt(nullptr, 0);
healLocation = HealLocation(idName, mapName, this->healLocations.size() + 1, x, y);
} else {
// This heal location has data, but is missing from the location table and won't be displayed by Porymap.
@ -2100,7 +2077,7 @@ bool Project::readHealLocations() {
QRegularExpression respawnNPCRegex(QString("%1(?<npc>[0-9]+)").arg(initializerPattern));
match = respawnNPCRegex.match(text);
if (match.hasMatch())
healLocation.respawnNPC = match.captured("npc").toInt();
healLocation.respawnNPC = match.captured("npc").toInt(nullptr, 0);
}
this->healLocations.append(healLocation);
@ -2483,11 +2460,11 @@ bool Project::readEventGraphics() {
QRegularExpressionMatch dimensionMatch = re.match(dimensions_label);
QRegularExpressionMatch oamTablesMatch = re.match(subsprites_label);
if (oamTablesMatch.hasMatch()) {
eventGraphics->spriteWidth = oamTablesMatch.captured(1).toInt();
eventGraphics->spriteHeight = oamTablesMatch.captured(2).toInt();
eventGraphics->spriteWidth = oamTablesMatch.captured(1).toInt(nullptr, 0);
eventGraphics->spriteHeight = oamTablesMatch.captured(2).toInt(nullptr, 0);
} else if (dimensionMatch.hasMatch()) {
eventGraphics->spriteWidth = dimensionMatch.captured(1).toInt();
eventGraphics->spriteHeight = dimensionMatch.captured(2).toInt();
eventGraphics->spriteWidth = dimensionMatch.captured(1).toInt(nullptr, 0);
eventGraphics->spriteHeight = dimensionMatch.captured(2).toInt(nullptr, 0);
} else {
eventGraphics->spriteWidth = eventGraphics->spritesheet.width();
eventGraphics->spriteHeight = eventGraphics->spritesheet.height();

View file

@ -635,10 +635,9 @@ int MainWindow::getMetatileLayerType(int metatileId) {
void MainWindow::setMetatileLayerType(int metatileId, int layerType) {
Metatile * metatile = this->getMetatile(metatileId);
uint8_t u_layerType = static_cast<uint8_t>(layerType);
if (!metatile || metatile->layerType == u_layerType || u_layerType >= NUM_METATILE_LAYER_TYPES)
if (!metatile)
return;
metatile->layerType = u_layerType;
metatile->setLayerType(layerType);
this->saveMetatileAttributesByMetatileId(metatileId);
}
@ -651,10 +650,9 @@ int MainWindow::getMetatileEncounterType(int metatileId) {
void MainWindow::setMetatileEncounterType(int metatileId, int encounterType) {
Metatile * metatile = this->getMetatile(metatileId);
uint8_t u_encounterType = static_cast<uint8_t>(encounterType);
if (!metatile || metatile->encounterType == u_encounterType || u_encounterType >= NUM_METATILE_ENCOUNTER_TYPES)
if (!metatile)
return;
metatile->encounterType = u_encounterType;
metatile->setEncounterType(encounterType);
this->saveMetatileAttributesByMetatileId(metatileId);
}
@ -667,10 +665,9 @@ int MainWindow::getMetatileTerrainType(int metatileId) {
void MainWindow::setMetatileTerrainType(int metatileId, int terrainType) {
Metatile * metatile = this->getMetatile(metatileId);
uint8_t u_terrainType = static_cast<uint8_t>(terrainType);
if (!metatile || metatile->terrainType == u_terrainType || u_terrainType >= NUM_METATILE_TERRAIN_TYPES)
if (!metatile)
return;
metatile->terrainType = u_terrainType;
metatile->setTerrainType(terrainType);
this->saveMetatileAttributesByMetatileId(metatileId);
}
@ -683,10 +680,9 @@ int MainWindow::getMetatileBehavior(int metatileId) {
void MainWindow::setMetatileBehavior(int metatileId, int behavior) {
Metatile * metatile = this->getMetatile(metatileId);
uint16_t u_behavior = static_cast<uint16_t>(behavior);
if (!metatile || metatile->behavior == u_behavior)
if (!metatile)
return;
metatile->behavior = u_behavior;
metatile->setBehavior(behavior);
this->saveMetatileAttributesByMetatileId(metatileId);
}
@ -694,15 +690,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);
}

View file

@ -48,7 +48,7 @@ void NewMapPopup::init() {
ui->spinBox_NewMap_Floor_Number->setMaximum(127);
// Hide config specific ui elements
bool hasFlags = (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby);
bool hasFlags = projectConfig.getMapAllowFlagsEnabled();
ui->checkBox_NewMap_Allow_Running->setVisible(hasFlags);
ui->checkBox_NewMap_Allow_Biking->setVisible(hasFlags);
ui->checkBox_NewMap_Allow_Escape_Rope->setVisible(hasFlags);
@ -293,7 +293,7 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() {
newMap->needsHealLocation = true;
}
if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) {
if (projectConfig.getMapAllowFlagsEnabled()) {
newMap->allowRunning = this->ui->checkBox_NewMap_Allow_Running->isChecked();
newMap->allowBiking = this->ui->checkBox_NewMap_Allow_Biking->isChecked();
newMap->allowEscaping = this->ui->checkBox_NewMap_Allow_Escape_Rope->isChecked();

View file

@ -99,9 +99,7 @@ void TilesetEditor::initUi() {
this->ui->spinBox_paletteSelector->setMinimum(0);
this->ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1);
this->setMetatileBehaviors();
this->setMetatileLayersUi();
this->setVersionSpecificUi();
this->setAttributesUi();
this->setMetatileLabelValidator();
this->initMetatileSelector();
@ -113,43 +111,55 @@ void TilesetEditor::initUi() {
this->restoreWindowState();
}
void TilesetEditor::setMetatileBehaviors() {
for (int num : project->metatileBehaviorMapInverse.keys()) {
this->ui->comboBox_metatileBehaviors->addItem(project->metatileBehaviorMapInverse[num], num);
}
}
void TilesetEditor::setMetatileLayersUi() {
if (!projectConfig.getTripleLayerMetatilesEnabled()) {
this->ui->comboBox_layerType->addItem("Normal - Middle/Top", METATILE_LAYER_MIDDLE_TOP);
this->ui->comboBox_layerType->addItem("Covered - Bottom/Middle", METATILE_LAYER_BOTTOM_MIDDLE);
this->ui->comboBox_layerType->addItem("Split - Bottom/Top", METATILE_LAYER_BOTTOM_TOP);
void TilesetEditor::setAttributesUi() {
// Behavior
if (Metatile::getBehaviorMask()) {
for (int num : project->metatileBehaviorMapInverse.keys()) {
this->ui->comboBox_metatileBehaviors->addItem(project->metatileBehaviorMapInverse[num], num);
}
} else {
this->ui->comboBox_layerType->setVisible(false);
this->ui->label_layerType->setVisible(false);
this->ui->label_BottomTop->setText("Bottom/Middle/Top");
this->ui->comboBox_metatileBehaviors->setVisible(false);
this->ui->label_metatileBehavior->setVisible(false);
}
}
void TilesetEditor::setVersionSpecificUi() {
if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
this->ui->comboBox_encounterType->setVisible(true);
this->ui->label_encounterType->setVisible(true);
this->ui->comboBox_encounterType->addItem("None", ENCOUNTER_NONE);
this->ui->comboBox_encounterType->addItem("Land", ENCOUNTER_LAND);
this->ui->comboBox_encounterType->addItem("Water", ENCOUNTER_WATER);
this->ui->comboBox_terrainType->setVisible(true);
this->ui->label_terrainType->setVisible(true);
// Terrain Type
if (Metatile::getTerrainTypeMask()) {
this->ui->comboBox_terrainType->addItem("Normal", TERRAIN_NONE);
this->ui->comboBox_terrainType->addItem("Grass", TERRAIN_GRASS);
this->ui->comboBox_terrainType->addItem("Water", TERRAIN_WATER);
this->ui->comboBox_terrainType->addItem("Waterfall", TERRAIN_WATERFALL);
} else {
this->ui->comboBox_encounterType->setVisible(false);
this->ui->label_encounterType->setVisible(false);
this->ui->comboBox_terrainType->setVisible(false);
this->ui->label_terrainType->setVisible(false);
}
// Encounter Type
if (Metatile::getEncounterTypeMask()) {
this->ui->comboBox_encounterType->addItem("None", ENCOUNTER_NONE);
this->ui->comboBox_encounterType->addItem("Land", ENCOUNTER_LAND);
this->ui->comboBox_encounterType->addItem("Water", ENCOUNTER_WATER);
} else {
this->ui->comboBox_encounterType->setVisible(false);
this->ui->label_encounterType->setVisible(false);
}
// Layer Type
if (!projectConfig.getTripleLayerMetatilesEnabled()) {
this->ui->comboBox_layerType->addItem("Normal - Middle/Top", METATILE_LAYER_MIDDLE_TOP);
this->ui->comboBox_layerType->addItem("Covered - Bottom/Middle", METATILE_LAYER_BOTTOM_MIDDLE);
this->ui->comboBox_layerType->addItem("Split - Bottom/Top", METATILE_LAYER_BOTTOM_TOP);
if (!Metatile::getLayerTypeMask()) {
// User doesn't have triple layer metatiles, but has no layer type attribute.
// Porymap is still using the layer type value to render metatiles, and with
// no mask set every metatile will be "Middle/Top", so just display the combo
// box but prevent the user from changing the value.
this->ui->comboBox_layerType->setEnabled(false);
}
} else {
this->ui->comboBox_layerType->setVisible(false);
this->ui->label_layerType->setVisible(false);
this->ui->label_BottomTop->setText("Bottom/Middle/Top");
}
}
void TilesetEditor::setMetatileLabelValidator() {
@ -373,13 +383,9 @@ void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) {
this->ui->graphicsView_metatileLayers->setFixedSize(this->metatileLayersItem->pixmap().width() + 2, this->metatileLayersItem->pixmap().height() + 2);
this->ui->lineEdit_metatileLabel->setText(this->metatile->label);
setComboValue(this->ui->comboBox_metatileBehaviors, this->metatile->behavior);
if (!projectConfig.getTripleLayerMetatilesEnabled()) {
setComboValue(this->ui->comboBox_layerType, this->metatile->layerType);
}
if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
setComboValue(this->ui->comboBox_encounterType, this->metatile->encounterType);
setComboValue(this->ui->comboBox_terrainType, this->metatile->terrainType);
}
setComboValue(this->ui->comboBox_layerType, this->metatile->layerType);
setComboValue(this->ui->comboBox_encounterType, this->metatile->encounterType);
setComboValue(this->ui->comboBox_terrainType, this->metatile->terrainType);
}
void TilesetEditor::onHoveredTileChanged(uint16_t tile) {
@ -512,11 +518,11 @@ void TilesetEditor::on_comboBox_metatileBehaviors_currentTextChanged(const QStri
// This function can also be called when the user selects
// a different metatile. Stop this from being considered a change.
if (this->metatile->behavior == static_cast<uint16_t>(behavior))
if (this->metatile->behavior == static_cast<uint32_t>(behavior))
return;
Metatile *prevMetatile = new Metatile(*this->metatile);
this->metatile->behavior = behavior;
this->metatile->setBehavior(behavior);
MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(),
prevMetatile, new Metatile(*this->metatile));
metatileHistory.push(commit);
@ -552,7 +558,7 @@ void TilesetEditor::on_comboBox_layerType_activated(int layerType)
{
if (this->metatile) {
Metatile *prevMetatile = new Metatile(*this->metatile);
this->metatile->layerType = static_cast<uint8_t>(layerType);
this->metatile->setLayerType(layerType);
MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(),
prevMetatile, new Metatile(*this->metatile));
metatileHistory.push(commit);
@ -565,7 +571,7 @@ void TilesetEditor::on_comboBox_encounterType_activated(int encounterType)
{
if (this->metatile) {
Metatile *prevMetatile = new Metatile(*this->metatile);
this->metatile->encounterType = static_cast<uint8_t>(encounterType);
this->metatile->setEncounterType(encounterType);
MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(),
prevMetatile, new Metatile(*this->metatile));
metatileHistory.push(commit);
@ -577,7 +583,7 @@ void TilesetEditor::on_comboBox_terrainType_activated(int terrainType)
{
if (this->metatile) {
Metatile *prevMetatile = new Metatile(*this->metatile);
this->metatile->terrainType = static_cast<uint8_t>(terrainType);
this->metatile->setTerrainType(terrainType);
MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(),
prevMetatile, new Metatile(*this->metatile));
metatileHistory.push(commit);