Generalize bit packing utility for Block
This commit is contained in:
parent
19e5e681e5
commit
f0310d4a63
17 changed files with 347 additions and 282 deletions
|
@ -213,6 +213,7 @@ enum ProjectFilePath {
|
||||||
constants_metatile_behaviors,
|
constants_metatile_behaviors,
|
||||||
constants_species,
|
constants_species,
|
||||||
constants_fieldmap,
|
constants_fieldmap,
|
||||||
|
global_fieldmap,
|
||||||
initial_facing_table,
|
initial_facing_table,
|
||||||
pokemon_icon_table,
|
pokemon_icon_table,
|
||||||
pokemon_gfx,
|
pokemon_gfx,
|
||||||
|
|
28
include/core/bitpacker.h
Normal file
28
include/core/bitpacker.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#ifndef BITPACKER_H
|
||||||
|
#define BITPACKER_H
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
//#include <cstdint>
|
||||||
|
|
||||||
|
class BitPacker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BitPacker() = default;
|
||||||
|
BitPacker(uint32_t mask);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setMask(uint32_t mask);
|
||||||
|
uint32_t mask() const { return m_mask; }
|
||||||
|
uint32_t maxValue() const { return m_maxValue; }
|
||||||
|
|
||||||
|
uint32_t unpack(uint32_t data) const;
|
||||||
|
uint32_t pack(uint32_t value) const;
|
||||||
|
uint32_t clamp(uint32_t value) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t m_mask = 0;
|
||||||
|
uint32_t m_maxValue = 0;
|
||||||
|
QList<uint32_t> m_setBits;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BITPACKER_H
|
|
@ -14,13 +14,17 @@ public:
|
||||||
Block &operator=(const Block &);
|
Block &operator=(const Block &);
|
||||||
bool operator ==(Block) const;
|
bool operator ==(Block) const;
|
||||||
bool operator !=(Block) const;
|
bool operator !=(Block) const;
|
||||||
void setMetatileId(uint16_t metatileId) { m_metatileId = metatileId; }
|
void setMetatileId(uint16_t metatileId);
|
||||||
void setCollision(uint16_t collision) { m_collision = collision; }
|
void setCollision(uint16_t collision);
|
||||||
void setElevation(uint16_t elevation) { m_elevation = elevation; }
|
void setElevation(uint16_t elevation);
|
||||||
uint16_t metatileId() const { return m_metatileId; }
|
uint16_t metatileId() const { return m_metatileId; }
|
||||||
uint16_t collision() const { return m_collision; }
|
uint16_t collision() const { return m_collision; }
|
||||||
uint16_t elevation() const { return m_elevation; }
|
uint16_t elevation() const { return m_elevation; }
|
||||||
uint16_t rawValue() const;
|
uint16_t rawValue() const;
|
||||||
|
static void setLayout();
|
||||||
|
static uint16_t getMaxMetatileId();
|
||||||
|
static uint16_t getMaxCollision();
|
||||||
|
static uint16_t getMaxElevation();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint16_t m_metatileId; // 10
|
uint16_t m_metatileId; // 10
|
||||||
|
|
|
@ -4,12 +4,14 @@
|
||||||
|
|
||||||
#include "tile.h"
|
#include "tile.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "bitpacker.h"
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
class Project;
|
class Project;
|
||||||
|
|
||||||
|
// TODO: Reevaluate enums
|
||||||
enum {
|
enum {
|
||||||
METATILE_LAYER_MIDDLE_TOP,
|
METATILE_LAYER_MIDDLE_TOP,
|
||||||
METATILE_LAYER_BOTTOM_MIDDLE,
|
METATILE_LAYER_BOTTOM_MIDDLE,
|
||||||
|
@ -32,67 +34,46 @@ enum {
|
||||||
NUM_METATILE_TERRAIN_TYPES
|
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
|
class Metatile
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Metatile();
|
Metatile() = default;
|
||||||
Metatile(const Metatile &other) = default;
|
Metatile(const Metatile &other) = default;
|
||||||
Metatile &operator=(const Metatile &other) = default;
|
Metatile &operator=(const Metatile &other) = default;
|
||||||
Metatile(const int numTiles);
|
Metatile(const int numTiles);
|
||||||
|
|
||||||
|
enum Attr {
|
||||||
|
Behavior,
|
||||||
|
TerrainType,
|
||||||
|
EncounterType,
|
||||||
|
LayerType,
|
||||||
|
Unused, // Preserve bits not used by the other attributes
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QList<Tile> tiles;
|
QList<Tile> tiles;
|
||||||
uint32_t behavior;
|
|
||||||
uint32_t terrainType;
|
|
||||||
uint32_t encounterType;
|
|
||||||
uint32_t layerType;
|
|
||||||
uint32_t unusedAttributes;
|
|
||||||
|
|
||||||
uint32_t getAttributes();
|
uint32_t getAttributes() const;
|
||||||
|
uint32_t getAttribute(Metatile::Attr attr) const { return this->attributes.value(attr, 0); }
|
||||||
void setAttributes(uint32_t data);
|
void setAttributes(uint32_t data);
|
||||||
void setAttributes(uint32_t data, BaseGameVersion version);
|
void setAttributes(uint32_t data, BaseGameVersion version);
|
||||||
|
void setAttribute(Metatile::Attr attr, uint32_t value);
|
||||||
|
|
||||||
void setBehavior(int value) { this->behavior = behaviorAttr.getClamped(value); }
|
// For convenience
|
||||||
void setTerrainType(int value) { this->terrainType = terrainTypeAttr.getClamped(value); }
|
uint32_t behavior() const { return this->getAttribute(Attr::Behavior); }
|
||||||
void setEncounterType(int value) { this->encounterType = encounterTypeAttr.getClamped(value); }
|
uint32_t terrainType() const { return this->getAttribute(Attr::TerrainType); }
|
||||||
void setLayerType(int value) { this->layerType = layerTypeAttr.getClamped(value); }
|
uint32_t encounterType() const { return this->getAttribute(Attr::EncounterType); }
|
||||||
|
uint32_t layerType() const { return this->getAttribute(Attr::LayerType); }
|
||||||
static uint32_t getBehaviorMask() { return behaviorAttr.mask; }
|
void setBehavior(int value) { this->setAttribute(Attr::Behavior, static_cast<uint32_t>(value)); }
|
||||||
static uint32_t getTerrainTypeMask() { return terrainTypeAttr.mask; }
|
void setTerrainType(int value) { this->setAttribute(Attr::TerrainType, static_cast<uint32_t>(value)); }
|
||||||
static uint32_t getEncounterTypeMask() { return encounterTypeAttr.mask; }
|
void setEncounterType(int value) { this->setAttribute(Attr::EncounterType, static_cast<uint32_t>(value)); }
|
||||||
static uint32_t getLayerTypeMask() { return layerTypeAttr.mask; }
|
void setLayerType(int value) { this->setAttribute(Attr::LayerType, static_cast<uint32_t>(value)); }
|
||||||
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 int getIndexInTileset(int);
|
||||||
static QPoint coordFromPixmapCoord(const QPointF &pixelCoord);
|
static QPoint coordFromPixmapCoord(const QPointF &pixelCoord);
|
||||||
|
static uint32_t getDefaultAttributesMask(BaseGameVersion version, Metatile::Attr attr);
|
||||||
static int getDefaultAttributesSize(BaseGameVersion version);
|
static int getDefaultAttributesSize(BaseGameVersion version);
|
||||||
static void setCustomLayout(Project*);
|
static void setLayout(Project*);
|
||||||
static QString getMetatileIdString(uint16_t metatileId) {
|
static QString getMetatileIdString(uint16_t metatileId) {
|
||||||
return "0x" + QString("%1").arg(metatileId, 3, 16, QChar('0')).toUpper();
|
return "0x" + QString("%1").arg(metatileId, 3, 16, QChar('0')).toUpper();
|
||||||
};
|
};
|
||||||
|
@ -103,37 +84,18 @@ public:
|
||||||
return metatiles.join(",");
|
return metatiles.join(",");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline bool operator==(const Metatile &other) {
|
||||||
|
return this->tiles == other.tiles && this->attributes == other.attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator!=(const Metatile &other) {
|
||||||
|
return !(operator==(other));
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Stores how each attribute should be laid out for all metatiles, according to the user's config
|
QMap<Metatile::Attr, uint32_t> attributes;
|
||||||
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>);
|
static bool doMasksOverlap(QList<uint32_t>);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool operator==(const Metatile &a, const Metatile &b) {
|
|
||||||
return a.behavior == b.behavior &&
|
|
||||||
a.layerType == b.layerType &&
|
|
||||||
a.encounterType == b.encounterType &&
|
|
||||||
a.terrainType == b.terrainType &&
|
|
||||||
a.unusedAttributes == b.unusedAttributes &&
|
|
||||||
a.tiles == b.tiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool operator!=(const Metatile &a, const Metatile &b) {
|
|
||||||
return !(operator==(a, b));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // METATILE_H
|
#endif // METATILE_H
|
||||||
|
|
|
@ -85,6 +85,11 @@ public:
|
||||||
QMap<QString, qint64> modifiedFileTimestamps;
|
QMap<QString, qint64> modifiedFileTimestamps;
|
||||||
bool usingAsmTilesets;
|
bool usingAsmTilesets;
|
||||||
QString importExportPath;
|
QString importExportPath;
|
||||||
|
bool parsedMetatileIdMask;
|
||||||
|
bool parsedCollisionMask;
|
||||||
|
bool parsedElevationMask;
|
||||||
|
bool parsedBehaviorMask;
|
||||||
|
bool parsedLayerTypeMask;
|
||||||
|
|
||||||
void set_root(QString);
|
void set_root(QString);
|
||||||
|
|
||||||
|
@ -196,6 +201,7 @@ public:
|
||||||
bool readObjEventGfxConstants();
|
bool readObjEventGfxConstants();
|
||||||
bool readSongNames();
|
bool readSongNames();
|
||||||
bool readEventGraphics();
|
bool readEventGraphics();
|
||||||
|
bool readFieldmapMasks();
|
||||||
QMap<QString, QMap<QString, QString>> readObjEventGfxInfo();
|
QMap<QString, QMap<QString, QString>> readObjEventGfxInfo();
|
||||||
|
|
||||||
void setEventPixmap(Event *event, bool forceLoad = false);
|
void setEventPixmap(Event *event, bool forceLoad = false);
|
||||||
|
@ -229,8 +235,6 @@ public:
|
||||||
static bool mapDimensionsValid(int width, int height);
|
static bool mapDimensionsValid(int width, int height);
|
||||||
bool calculateDefaultMapSize();
|
bool calculateDefaultMapSize();
|
||||||
static int getMaxObjectEvents();
|
static int getMaxObjectEvents();
|
||||||
static int getMaxCollision();
|
|
||||||
static int getMaxElevation();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateMapLayout(Map*);
|
void updateMapLayout(Map*);
|
||||||
|
@ -248,14 +252,11 @@ private:
|
||||||
static int num_tiles_primary;
|
static int num_tiles_primary;
|
||||||
static int num_tiles_total;
|
static int num_tiles_total;
|
||||||
static int num_metatiles_primary;
|
static int num_metatiles_primary;
|
||||||
static int num_metatiles_total;
|
|
||||||
static int num_pals_primary;
|
static int num_pals_primary;
|
||||||
static int num_pals_total;
|
static int num_pals_total;
|
||||||
static int max_map_data_size;
|
static int max_map_data_size;
|
||||||
static int default_map_size;
|
static int default_map_size;
|
||||||
static int max_object_events;
|
static int max_object_events;
|
||||||
static int max_collision;
|
|
||||||
static int max_elevation;
|
|
||||||
|
|
||||||
QStringListModel eventScriptLabelModel;
|
QStringListModel eventScriptLabelModel;
|
||||||
QCompleter eventScriptLabelCompleter;
|
QCompleter eventScriptLabelCompleter;
|
||||||
|
|
|
@ -16,6 +16,7 @@ QMAKE_CXXFLAGS += -std=c++17 -Wall
|
||||||
QMAKE_TARGET_BUNDLE_PREFIX = com.pret
|
QMAKE_TARGET_BUNDLE_PREFIX = com.pret
|
||||||
|
|
||||||
SOURCES += src/core/block.cpp \
|
SOURCES += src/core/block.cpp \
|
||||||
|
src/core/bitpacker.cpp \
|
||||||
src/core/blockdata.cpp \
|
src/core/blockdata.cpp \
|
||||||
src/core/events.cpp \
|
src/core/events.cpp \
|
||||||
src/core/heallocation.cpp \
|
src/core/heallocation.cpp \
|
||||||
|
@ -104,6 +105,7 @@ SOURCES += src/core/block.cpp \
|
||||||
src/ui/uintspinbox.cpp
|
src/ui/uintspinbox.cpp
|
||||||
|
|
||||||
HEADERS += include/core/block.h \
|
HEADERS += include/core/block.h \
|
||||||
|
include/core/bitpacker.h \
|
||||||
include/core/blockdata.h \
|
include/core/blockdata.h \
|
||||||
include/core/events.h \
|
include/core/events.h \
|
||||||
include/core/heallocation.h \
|
include/core/heallocation.h \
|
||||||
|
|
|
@ -59,6 +59,7 @@ const QMap<ProjectFilePath, std::pair<QString, QString>> ProjectConfig::defaultP
|
||||||
{ProjectFilePath::constants_metatile_behaviors, { "constants_metatile_behaviors", "include/constants/metatile_behaviors.h"}},
|
{ProjectFilePath::constants_metatile_behaviors, { "constants_metatile_behaviors", "include/constants/metatile_behaviors.h"}},
|
||||||
{ProjectFilePath::constants_species, { "constants_species", "include/constants/species.h"}},
|
{ProjectFilePath::constants_species, { "constants_species", "include/constants/species.h"}},
|
||||||
{ProjectFilePath::constants_fieldmap, { "constants_fieldmap", "include/fieldmap.h"}},
|
{ProjectFilePath::constants_fieldmap, { "constants_fieldmap", "include/fieldmap.h"}},
|
||||||
|
{ProjectFilePath::global_fieldmap, { "global_fieldmap", "include/global.fieldmap.h"}},
|
||||||
{ProjectFilePath::pokemon_icon_table, { "pokemon_icon_table", "src/pokemon_icon.c"}},
|
{ProjectFilePath::pokemon_icon_table, { "pokemon_icon_table", "src/pokemon_icon.c"}},
|
||||||
{ProjectFilePath::initial_facing_table, { "initial_facing_table", "src/event_object_movement.c"}},
|
{ProjectFilePath::initial_facing_table, { "initial_facing_table", "src/event_object_movement.c"}},
|
||||||
{ProjectFilePath::pokemon_gfx, { "pokemon_gfx", "graphics/pokemon/"}},
|
{ProjectFilePath::pokemon_gfx, { "pokemon_gfx", "graphics/pokemon/"}},
|
||||||
|
@ -766,10 +767,10 @@ void ProjectConfig::setUnreadKeys() {
|
||||||
if (!readKeys.contains("new_map_border_metatiles")) this->newMapBorderMetatileIds = isPokefirered ? DEFAULT_BORDER_FRLG : DEFAULT_BORDER_RSE;
|
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("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_attributes_size")) this->metatileAttributesSize = Metatile::getDefaultAttributesSize(this->baseGameVersion);
|
||||||
if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = Metatile::getBehaviorMask(this->baseGameVersion);
|
if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = Metatile::getDefaultAttributesMask(this->baseGameVersion, Metatile::Attr::Behavior);
|
||||||
if (!readKeys.contains("metatile_terrain_type_mask")) this->metatileTerrainTypeMask = Metatile::getTerrainTypeMask(this->baseGameVersion);
|
if (!readKeys.contains("metatile_terrain_type_mask")) this->metatileTerrainTypeMask = Metatile::getDefaultAttributesMask(this->baseGameVersion, Metatile::Attr::TerrainType);
|
||||||
if (!readKeys.contains("metatile_encounter_type_mask")) this->metatileEncounterTypeMask = Metatile::getEncounterTypeMask(this->baseGameVersion);
|
if (!readKeys.contains("metatile_encounter_type_mask")) this->metatileEncounterTypeMask = Metatile::getDefaultAttributesMask(this->baseGameVersion, Metatile::Attr::EncounterType);
|
||||||
if (!readKeys.contains("metatile_layer_type_mask")) this->metatileLayerTypeMask = Metatile::getLayerTypeMask(this->baseGameVersion);
|
if (!readKeys.contains("metatile_layer_type_mask")) this->metatileLayerTypeMask = Metatile::getDefaultAttributesMask(this->baseGameVersion, Metatile::Attr::LayerType);
|
||||||
if (!readKeys.contains("enable_map_allow_flags")) this->enableMapAllowFlags = (this->baseGameVersion != BaseGameVersion::pokeruby);
|
if (!readKeys.contains("enable_map_allow_flags")) this->enableMapAllowFlags = (this->baseGameVersion != BaseGameVersion::pokeruby);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
51
src/core/bitpacker.cpp
Normal file
51
src/core/bitpacker.cpp
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#include "bitpacker.h"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
// Sometimes we can't explicitly define bitfields because we need to allow users to
|
||||||
|
// change the size and arrangement of its members. In those cases we use this
|
||||||
|
// convenience class to handle packing and unpacking each member.
|
||||||
|
|
||||||
|
BitPacker::BitPacker(uint32_t mask) {
|
||||||
|
this->setMask(mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BitPacker::setMask(uint32_t mask) {
|
||||||
|
m_mask = mask;
|
||||||
|
|
||||||
|
// Precalculate the number and positions of the mask bits
|
||||||
|
m_setBits.clear();
|
||||||
|
for (int i = 0; mask != 0; mask >>= 1, i++)
|
||||||
|
if (mask & 1) m_setBits.append(1 << i);
|
||||||
|
|
||||||
|
// For masks with only contiguous bits m_maxValue is equivalent to (m_mask >> n), where n is the number of trailing 0's in m_mask.
|
||||||
|
m_maxValue = (m_setBits.length() >= 32) ? UINT_MAX : ((1 << m_setBits.length()) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given an arbitrary value to set for this bitfield member, returns a bounded value that can later be packed losslessly.
|
||||||
|
uint32_t BitPacker::clamp(uint32_t value) const {
|
||||||
|
return (m_maxValue == UINT_MAX) ? value : (value % (m_maxValue + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given packed data, returns the extracted value for the bitfield member.
|
||||||
|
// For masks with only contiguous bits this is equivalent to ((data & m_mask) >> n), where n is the number of trailing 0's in m_mask.
|
||||||
|
uint32_t BitPacker::unpack(uint32_t data) const {
|
||||||
|
uint32_t value = 0;
|
||||||
|
data &= m_mask;
|
||||||
|
for (int i = 0; i < m_setBits.length(); i++) {
|
||||||
|
if (data & m_setBits.at(i))
|
||||||
|
value |= (1 << i);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a value for the bitfield member, returns the value to OR together with the other members.
|
||||||
|
// For masks with only contiguous bits this is equivalent to ((value << n) & m_mask), where n is the number of trailing 0's in m_mask.
|
||||||
|
uint32_t BitPacker::pack(uint32_t value) const {
|
||||||
|
uint32_t data = 0;
|
||||||
|
for (int i = 0; i < m_setBits.length(); i++) {
|
||||||
|
if (value == 0) return data;
|
||||||
|
if (value & 1) data |= m_setBits.at(i);
|
||||||
|
value >>= 1;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
|
@ -1,4 +1,10 @@
|
||||||
#include "block.h"
|
#include "block.h"
|
||||||
|
#include "bitpacker.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
static BitPacker bitsMetatileId = BitPacker(0x3FF);
|
||||||
|
static BitPacker bitsCollision = BitPacker(0xC00);
|
||||||
|
static BitPacker bitsElevation = BitPacker(0xF000);
|
||||||
|
|
||||||
Block::Block() :
|
Block::Block() :
|
||||||
m_metatileId(0),
|
m_metatileId(0),
|
||||||
|
@ -12,10 +18,10 @@ Block::Block(uint16_t metatileId, uint16_t collision, uint16_t elevation) :
|
||||||
m_elevation(elevation)
|
m_elevation(elevation)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
Block::Block(uint16_t word) :
|
Block::Block(uint16_t data) :
|
||||||
m_metatileId(word & 0x3ff),
|
m_metatileId(bitsMetatileId.unpack(data)),
|
||||||
m_collision((word >> 10) & 0x3),
|
m_collision(bitsCollision.unpack(data)),
|
||||||
m_elevation((word >> 12) & 0xf)
|
m_elevation(bitsElevation.unpack(data))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
Block::Block(const Block &other) :
|
Block::Block(const Block &other) :
|
||||||
|
@ -32,16 +38,53 @@ Block &Block::operator=(const Block &other) {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t Block::rawValue() const {
|
uint16_t Block::rawValue() const {
|
||||||
return static_cast<uint16_t>(
|
return bitsMetatileId.pack(m_metatileId)
|
||||||
(m_metatileId & 0x3ff) +
|
| bitsCollision.pack(m_collision)
|
||||||
((m_collision & 0x3) << 10) +
|
| bitsElevation.pack(m_elevation);
|
||||||
((m_elevation & 0xf) << 12));
|
}
|
||||||
|
|
||||||
|
// TODO: Resolve TODOs for max block limits, and disable collision tab if collision and elevation are 0
|
||||||
|
// TODO: After parsing, recalc max collision/elevation for selector image (in Metatile::setLayout?)
|
||||||
|
// TODO: More generous config limits
|
||||||
|
// TODO: Settings editor -- disable UI & restore after refresh, red flag overlapping masks
|
||||||
|
// TODO: Generalize API tab disabling, i.e. check if disabled before allowing selection
|
||||||
|
// TODO: Metatile selector looks like it's having a fit during group block select
|
||||||
|
void Block::setLayout() {
|
||||||
|
bitsMetatileId.setMask(projectConfig.getBlockMetatileIdMask());
|
||||||
|
bitsCollision.setMask(projectConfig.getBlockCollisionMask());
|
||||||
|
bitsElevation.setMask(projectConfig.getBlockElevationMask());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Block::operator ==(Block other) const {
|
bool Block::operator ==(Block other) const {
|
||||||
return (m_metatileId == other.m_metatileId) && (m_collision == other.m_collision) && (m_elevation == other.m_elevation);
|
return (m_metatileId == other.m_metatileId)
|
||||||
|
&& (m_collision == other.m_collision)
|
||||||
|
&& (m_elevation == other.m_elevation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Block::operator !=(Block other) const {
|
bool Block::operator !=(Block other) const {
|
||||||
return !(operator ==(other));
|
return !(operator ==(other));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Block::setMetatileId(uint16_t metatileId) {
|
||||||
|
m_metatileId = bitsMetatileId.clamp(metatileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Block::setCollision(uint16_t collision) {
|
||||||
|
m_collision = bitsCollision.clamp(collision);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Block::setElevation(uint16_t elevation) {
|
||||||
|
m_elevation = bitsElevation.clamp(elevation);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Block::getMaxMetatileId() {
|
||||||
|
return bitsMetatileId.maxValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Block::getMaxCollision() {
|
||||||
|
return bitsCollision.maxValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Block::getMaxElevation() {
|
||||||
|
return bitsElevation.maxValue();
|
||||||
|
}
|
||||||
|
|
|
@ -2,58 +2,24 @@
|
||||||
#include "tileset.h"
|
#include "tileset.h"
|
||||||
#include "project.h"
|
#include "project.h"
|
||||||
|
|
||||||
const QHash<QString, MetatileAttr> Metatile::defaultLayoutFRLG = {
|
// Stores how each attribute should be laid out for all metatiles, according to the vanilla games.
|
||||||
{"behavior", MetatileAttr(0x000001FF, 0) },
|
// Used to set default config values and import maps with AdvanceMap.
|
||||||
{"terrainType", MetatileAttr(0x00003E00, 9) },
|
static const QMap<Metatile::Attr, BitPacker> attributePackersFRLG = {
|
||||||
{"encounterType", MetatileAttr(0x07000000, 24) },
|
{Metatile::Attr::Behavior, BitPacker(0x000001FF) },
|
||||||
{"layerType", MetatileAttr(0x60000000, 29) },
|
{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) },
|
||||||
};
|
};
|
||||||
|
|
||||||
const QHash<QString, MetatileAttr> Metatile::defaultLayoutRSE = {
|
static QMap<Metatile::Attr, BitPacker> attributePackers;
|
||||||
{"behavior", MetatileAttr(0x00FF, 0) },
|
|
||||||
{"terrainType", MetatileAttr() },
|
|
||||||
{"encounterType", MetatileAttr() },
|
|
||||||
{"layerType", MetatileAttr(0xF000, 12) },
|
|
||||||
};
|
|
||||||
|
|
||||||
const QHash<BaseGameVersion, const QHash<QString, MetatileAttr>*> Metatile::defaultLayouts = {
|
Metatile::Metatile(const int numTiles) {
|
||||||
{ 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),
|
|
||||||
terrainType(0),
|
|
||||||
encounterType(0),
|
|
||||||
layerType(0),
|
|
||||||
unusedAttributes(0)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
Metatile::Metatile(const int numTiles) :
|
|
||||||
behavior(0),
|
|
||||||
terrainType(0),
|
|
||||||
encounterType(0),
|
|
||||||
layerType(0),
|
|
||||||
unusedAttributes(0)
|
|
||||||
{
|
|
||||||
Tile tile = Tile();
|
Tile tile = Tile();
|
||||||
for (int i = 0; i < numTiles; i++) {
|
for (int i = 0; i < numTiles; i++) {
|
||||||
this->tiles.append(tile);
|
this->tiles.append(tile);
|
||||||
|
@ -74,31 +40,46 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
|
||||||
return QPoint(x, y);
|
return QPoint(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the layout of a metatile attribute using the mask read from the config file
|
// Read and pack together this metatile's attributes.
|
||||||
void Metatile::setCustomAttributeLayout(MetatileAttr * attr, uint32_t mask, uint32_t max) {
|
uint32_t Metatile::getAttributes() const {
|
||||||
if (mask > max) {
|
uint32_t data = 0;
|
||||||
uint32_t oldMask = mask;
|
for (auto i = this->attributes.cbegin(), end = this->attributes.cend(); i != end; i++){
|
||||||
mask &= max;
|
const auto packer = attributePackers.value(i.key());
|
||||||
logWarn(QString("Metatile attribute mask '0x%1' has been truncated to '0x%2'")
|
data |= packer.pack(i.value());
|
||||||
.arg(QString::number(oldMask, 16).toUpper())
|
|
||||||
.arg(QString::number(mask, 16).toUpper()));
|
|
||||||
}
|
}
|
||||||
attr->mask = mask;
|
return data;
|
||||||
attr->shift = mask ? log2(mask & ~(mask - 1)) : 0; // Get position of the least significant set bit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For checking whether a metatile attribute mask can contain all the available hard-coded options
|
// Unpack and insert metatile attributes from the given data.
|
||||||
bool Metatile::isMaskTooSmall(MetatileAttr * attr, int max) {
|
void Metatile::setAttributes(uint32_t data) {
|
||||||
if (attr->mask == 0 || max <= 0) return false;
|
for (auto i = attributePackers.cbegin(), end = attributePackers.cend(); i != end; i++){
|
||||||
|
const auto packer = i.value();
|
||||||
|
this->setAttribute(i.key(), packer.unpack(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get position of the most significant set bit
|
// Unpack and insert metatile attributes from the given data using a vanilla layout. For AdvanceMap import
|
||||||
uint32_t n = log2(max);
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get a mask for all values 0 to max.
|
// Set the value for a metatile attribute, and fit it within the valid value range.
|
||||||
// This may fail for n > 30, but that's not possible here.
|
void Metatile::setAttribute(Metatile::Attr attr, uint32_t value) {
|
||||||
uint32_t rangeMask = (1 << (n + 1)) - 1;
|
const auto packer = attributePackers.value(attr);
|
||||||
|
this->attributes.insert(attr, packer.clamp(value));
|
||||||
|
}
|
||||||
|
|
||||||
return attr->getClamped(rangeMask) != rangeMask;
|
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) {
|
bool Metatile::doMasksOverlap(QList<uint32_t> masks) {
|
||||||
|
@ -110,84 +91,63 @@ bool Metatile::doMasksOverlap(QList<uint32_t> masks) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Metatile::setCustomLayout(Project * project) {
|
void Metatile::setLayout(Project * project) {
|
||||||
// Get the maximum size of any attribute mask
|
// Read masks from the config and limit them based on the specified attribute size.
|
||||||
const QHash<int, uint32_t> maxMasks = {
|
const QHash<int, uint32_t> maxMasks = {
|
||||||
{1, 0xFF},
|
{1, 0xFF},
|
||||||
{2, 0xFFFF},
|
{2, 0xFFFF},
|
||||||
{4, 0xFFFFFFFF},
|
{4, 0xFFFFFFFF},
|
||||||
};
|
};
|
||||||
const uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0);
|
uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0);
|
||||||
|
uint32_t behaviorMask = projectConfig.getMetatileBehaviorMask() & maxMask;
|
||||||
// Set custom attribute masks from the config file
|
uint32_t terrainTypeMask = projectConfig.getMetatileTerrainTypeMask() & maxMask;
|
||||||
setCustomAttributeLayout(&Metatile::behaviorAttr, projectConfig.getMetatileBehaviorMask(), maxMask);
|
uint32_t encounterTypeMask = projectConfig.getMetatileEncounterTypeMask() & maxMask;
|
||||||
setCustomAttributeLayout(&Metatile::terrainTypeAttr, projectConfig.getMetatileTerrainTypeMask(), maxMask);
|
uint32_t layerTypeMask = projectConfig.getMetatileLayerTypeMask() & 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.
|
// Overlapping masks are technically ok, but probably not intended.
|
||||||
// Additionally, Porymap will not properly reflect that the values are linked.
|
// Additionally, Porymap will not properly reflect that the values are linked.
|
||||||
if (doMasksOverlap({getBehaviorMask(), getTerrainTypeMask(), getEncounterTypeMask(), getLayerTypeMask()})) {
|
if (doMasksOverlap({behaviorMask, terrainTypeMask, encounterTypeMask, layerTypeMask})) {
|
||||||
logWarn("Metatile attribute masks are overlapping. This may result in unexpected attribute values.");
|
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.
|
// Calculate mask of bits not used by standard behaviors so we can preserve this data.
|
||||||
if (!project->metatileBehaviorMapInverse.isEmpty()) {
|
uint32_t unusedMask = ~(behaviorMask | terrainTypeMask | encounterTypeMask | layerTypeMask);
|
||||||
int maxBehavior = project->metatileBehaviorMapInverse.lastKey();
|
unusedMask &= maxMask;
|
||||||
if (isMaskTooSmall(&Metatile::behaviorAttr, maxBehavior))
|
|
||||||
logWarn(QString("Metatile Behavior mask is too small to contain all %1 available options.").arg(maxBehavior));
|
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));
|
||||||
}
|
}
|
||||||
if (isMaskTooSmall(&Metatile::terrainTypeAttr, NUM_METATILE_TERRAIN_TYPES - 1))
|
attributePackers.insert(Metatile::Attr::Behavior, packer);
|
||||||
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() {
|
// Validate terrain type mask
|
||||||
uint32_t attributes = this->unusedAttributes & Metatile::unusedAttrMask;
|
packer.setMask(terrainTypeMask);
|
||||||
attributes |= Metatile::behaviorAttr.toRaw(this->behavior);
|
const uint32_t maxTerrainType = NUM_METATILE_TERRAIN_TYPES - 1;
|
||||||
attributes |= Metatile::terrainTypeAttr.toRaw(this->terrainType);
|
if (terrainTypeMask && packer.clamp(maxTerrainType) != maxTerrainType)
|
||||||
attributes |= Metatile::encounterTypeAttr.toRaw(this->encounterType);
|
logWarn(QString("Metatile Terrain Type mask '%1' is insufficient to contain all %2 available options.").arg(terrainTypeMask).arg(maxTerrainType + 1));
|
||||||
attributes |= Metatile::layerTypeAttr.toRaw(this->layerType);
|
attributePackers.insert(Metatile::Attr::TerrainType, packer);
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Metatile::setAttributes(uint32_t data) {
|
// Validate encounter type mask
|
||||||
this->behavior = Metatile::behaviorAttr.fromRaw(data);
|
packer.setMask(encounterTypeMask);
|
||||||
this->terrainType = Metatile::terrainTypeAttr.fromRaw(data);
|
const uint32_t maxEncounterType = NUM_METATILE_ENCOUNTER_TYPES - 1;
|
||||||
this->encounterType = Metatile::encounterTypeAttr.fromRaw(data);
|
if (encounterTypeMask && packer.clamp(maxEncounterType) != maxEncounterType)
|
||||||
this->layerType = Metatile::layerTypeAttr.fromRaw(data);
|
logWarn(QString("Metatile Encounter Type mask '%1' is insufficient to contain all %2 available options.").arg(encounterTypeMask).arg(maxEncounterType + 1));
|
||||||
this->unusedAttributes = data & Metatile::unusedAttrMask;
|
attributePackers.insert(Metatile::Attr::EncounterType, packer);
|
||||||
}
|
|
||||||
|
|
||||||
// Read attributes using a vanilla layout, then set them using the user's layout. For AdvanceMap import
|
// Validate terrain type mask
|
||||||
void Metatile::setAttributes(uint32_t data, BaseGameVersion version) {
|
packer.setMask(layerTypeMask);
|
||||||
const auto defaultLayout = Metatile::defaultLayouts.value(version);
|
const uint32_t maxLayerType = NUM_METATILE_LAYER_TYPES - 1;
|
||||||
this->setBehavior(defaultLayout->value("behavior").fromRaw(data));
|
if (layerTypeMask && packer.clamp(maxLayerType) != maxLayerType)
|
||||||
this->setTerrainType(defaultLayout->value("terrainType").fromRaw(data));
|
logWarn(QString("Metatile Layer Type mask '%1' is insufficient to contain all %2 available options.").arg(layerTypeMask).arg(maxLayerType + 1));
|
||||||
this->setEncounterType(defaultLayout->value("encounterType").fromRaw(data));
|
attributePackers.insert(Metatile::Attr::LayerType, packer);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -941,8 +941,8 @@ QString Editor::getMetatileDisplayMessage(uint16_t metatileId) {
|
||||||
QString message = QString("Metatile: %1").arg(Metatile::getMetatileIdString(metatileId));
|
QString message = QString("Metatile: %1").arg(Metatile::getMetatileIdString(metatileId));
|
||||||
if (label.size())
|
if (label.size())
|
||||||
message += QString(" \"%1\"").arg(label);
|
message += QString(" \"%1\"").arg(label);
|
||||||
if (metatile && metatile->behavior) // Skip MB_NORMAL
|
if (metatile && metatile->behavior()) // Skip MB_NORMAL
|
||||||
message += QString(", Behavior: %1").arg(this->project->metatileBehaviorMapInverse.value(metatile->behavior, QString::number(metatile->behavior)));
|
message += QString(", Behavior: %1").arg(this->project->metatileBehaviorMapInverse.value(metatile->behavior(), QString::number(metatile->behavior())));
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2284,14 +2284,14 @@ void Editor::setCollisionGraphics() {
|
||||||
// Any icons for combinations that aren't provided by the image sheet are also created now using default graphics.
|
// Any icons for combinations that aren't provided by the image sheet are also created now using default graphics.
|
||||||
const int w = 16, h = 16;
|
const int w = 16, h = 16;
|
||||||
imgSheet = imgSheet.scaled(w * imgColumns, h * imgRows);
|
imgSheet = imgSheet.scaled(w * imgColumns, h * imgRows);
|
||||||
for (int collision = 0; collision <= Project::getMaxCollision(); collision++) {
|
for (int collision = 0; collision <= Block::getMaxCollision(); collision++) {
|
||||||
// If (collision >= imgColumns) here, it's a valid collision value, but it is not represented with an icon on the image sheet.
|
// If (collision >= imgColumns) here, it's a valid collision value, but it is not represented with an icon on the image sheet.
|
||||||
// In this case we just use the rightmost collision icon. This is mostly to support the vanilla case, where technically 0-3
|
// In this case we just use the rightmost collision icon. This is mostly to support the vanilla case, where technically 0-3
|
||||||
// are valid collision values, but 1-3 have the same meaning, so the vanilla collision selector image only has 2 columns.
|
// are valid collision values, but 1-3 have the same meaning, so the vanilla collision selector image only has 2 columns.
|
||||||
int x = ((collision < imgColumns) ? collision : (imgColumns - 1)) * w;
|
int x = ((collision < imgColumns) ? collision : (imgColumns - 1)) * w;
|
||||||
|
|
||||||
QList<const QImage*> sublist;
|
QList<const QImage*> sublist;
|
||||||
for (int elevation = 0; elevation <= Project::getMaxElevation(); elevation++) {
|
for (int elevation = 0; elevation <= Block::getMaxElevation(); elevation++) {
|
||||||
if (elevation < imgRows) {
|
if (elevation < imgRows) {
|
||||||
// This elevation has an icon on the image sheet, add it to the list
|
// This elevation has an icon on the image sheet, add it to the list
|
||||||
int y = elevation * h;
|
int y = elevation * h;
|
||||||
|
|
|
@ -395,8 +395,8 @@ void MainWindow::setProjectSpecificUI()
|
||||||
|
|
||||||
Event::setIcons();
|
Event::setIcons();
|
||||||
editor->setCollisionGraphics();
|
editor->setCollisionGraphics();
|
||||||
ui->spinBox_SelectedElevation->setMaximum(Project::getMaxElevation());
|
ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation());
|
||||||
ui->spinBox_SelectedCollision->setMaximum(Project::getMaxCollision());
|
ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::mapSortOrder_changed(QAction *action)
|
void MainWindow::mapSortOrder_changed(QAction *action)
|
||||||
|
@ -936,6 +936,7 @@ bool MainWindow::loadDataStructures() {
|
||||||
&& project->readTilesetProperties()
|
&& project->readTilesetProperties()
|
||||||
&& project->readTilesetLabels()
|
&& project->readTilesetLabels()
|
||||||
&& project->readTilesetMetatileLabels()
|
&& project->readTilesetMetatileLabels()
|
||||||
|
&& project->readFieldmapMasks()
|
||||||
&& project->readMaxMapDataSize()
|
&& project->readMaxMapDataSize()
|
||||||
&& project->readHealLocations()
|
&& project->readHealLocations()
|
||||||
&& project->readMiscellaneousConstants()
|
&& project->readMiscellaneousConstants()
|
||||||
|
@ -946,7 +947,8 @@ bool MainWindow::loadDataStructures() {
|
||||||
&& project->readEventGraphics()
|
&& project->readEventGraphics()
|
||||||
&& project->readSongNames();
|
&& project->readSongNames();
|
||||||
|
|
||||||
Metatile::setCustomLayout(project);
|
Block::setLayout();
|
||||||
|
Metatile::setLayout(project);
|
||||||
Scripting::populateGlobalObject(this);
|
Scripting::populateGlobalObject(this);
|
||||||
|
|
||||||
return success && loadProjectCombos();
|
return success && loadProjectCombos();
|
||||||
|
|
|
@ -27,18 +27,13 @@ using OrderedJsonDoc = poryjson::JsonDoc;
|
||||||
|
|
||||||
int Project::num_tiles_primary = 512;
|
int Project::num_tiles_primary = 512;
|
||||||
int Project::num_tiles_total = 1024;
|
int Project::num_tiles_total = 1024;
|
||||||
int Project::num_metatiles_primary = 512;
|
int Project::num_metatiles_primary = 512; // TODO: Verify fits within max
|
||||||
int Project::num_metatiles_total = 1024;
|
|
||||||
int Project::num_pals_primary = 6;
|
int Project::num_pals_primary = 6;
|
||||||
int Project::num_pals_total = 13;
|
int Project::num_pals_total = 13;
|
||||||
int Project::max_map_data_size = 10240; // 0x2800
|
int Project::max_map_data_size = 10240; // 0x2800
|
||||||
int Project::default_map_size = 20;
|
int Project::default_map_size = 20;
|
||||||
int Project::max_object_events = 64;
|
int Project::max_object_events = 64;
|
||||||
|
|
||||||
// TODO: Replace once Block layout can be edited
|
|
||||||
int Project::max_collision = 3;
|
|
||||||
int Project::max_elevation = 15;
|
|
||||||
|
|
||||||
Project::Project(QWidget *parent) :
|
Project::Project(QWidget *parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
eventScriptLabelModel(this),
|
eventScriptLabelModel(this),
|
||||||
|
@ -1870,8 +1865,7 @@ bool Project::readTilesetLabels() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Project::readTilesetProperties() {
|
bool Project::readTilesetProperties() {
|
||||||
QStringList definePrefixes;
|
static const QStringList definePrefixes{ "\\bNUM_" };
|
||||||
definePrefixes << "\\bNUM_";
|
|
||||||
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap);
|
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap);
|
||||||
fileWatcher.addPath(root + "/" + filename);
|
fileWatcher.addPath(root + "/" + filename);
|
||||||
QMap<QString, int> defines = parser.readCDefines(filename, definePrefixes);
|
QMap<QString, int> defines = parser.readCDefines(filename, definePrefixes);
|
||||||
|
@ -1900,14 +1894,6 @@ bool Project::readTilesetProperties() {
|
||||||
logWarn(QString("Value for tileset property 'NUM_METATILES_IN_PRIMARY' not found. Using default (%1) instead.")
|
logWarn(QString("Value for tileset property 'NUM_METATILES_IN_PRIMARY' not found. Using default (%1) instead.")
|
||||||
.arg(Project::num_metatiles_primary));
|
.arg(Project::num_metatiles_primary));
|
||||||
}
|
}
|
||||||
it = defines.find("NUM_METATILES_TOTAL");
|
|
||||||
if (it != defines.end()) {
|
|
||||||
Project::num_metatiles_total = it.value();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logWarn(QString("Value for tileset property 'NUM_METATILES_TOTAL' not found. Using default (%1) instead.")
|
|
||||||
.arg(Project::num_metatiles_total));
|
|
||||||
}
|
|
||||||
it = defines.find("NUM_PALS_IN_PRIMARY");
|
it = defines.find("NUM_PALS_IN_PRIMARY");
|
||||||
if (it != defines.end()) {
|
if (it != defines.end()) {
|
||||||
Project::num_pals_primary = it.value();
|
Project::num_pals_primary = it.value();
|
||||||
|
@ -1927,9 +1913,43 @@ bool Project::readTilesetProperties() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read data masks for Blocks and metatile attributes.
|
||||||
|
// These settings are exposed in the settings window. If any are parsed from
|
||||||
|
// the project they'll be visible in the settings window but not editable.
|
||||||
|
bool Project::readFieldmapMasks() {
|
||||||
|
// We're looking for the suffix "_MASK". Technically our "prefix" is the whole define.
|
||||||
|
static const QStringList definePrefixes{ "\\b\\w+_MASK" };
|
||||||
|
QString filename = projectConfig.getFilePath(ProjectFilePath::global_fieldmap);
|
||||||
|
fileWatcher.addPath(root + "/" + filename);
|
||||||
|
QMap<QString, int> defines = parser.readCDefines(filename, definePrefixes);
|
||||||
|
|
||||||
|
auto it = defines.find("MAPGRID_METATILE_ID_MASK");
|
||||||
|
if ((parsedMetatileIdMask = (it != defines.end())))
|
||||||
|
projectConfig.setBlockMetatileIdMask(static_cast<uint16_t>(it.value()));
|
||||||
|
|
||||||
|
it = defines.find("MAPGRID_COLLISION_MASK");
|
||||||
|
if ((parsedCollisionMask = (it != defines.end())))
|
||||||
|
projectConfig.setBlockCollisionMask(static_cast<uint16_t>(it.value()));
|
||||||
|
|
||||||
|
it = defines.find("MAPGRID_ELEVATION_MASK");
|
||||||
|
if ((parsedElevationMask = (it != defines.end())))
|
||||||
|
projectConfig.setBlockElevationMask(static_cast<uint16_t>(it.value()));
|
||||||
|
|
||||||
|
// TODO: For FRLG, parse from fieldmap.c?
|
||||||
|
|
||||||
|
it = defines.find("METATILE_ATTR_BEHAVIOR_MASK");
|
||||||
|
if ((parsedBehaviorMask = (it != defines.end())))
|
||||||
|
projectConfig.setMetatileBehaviorMask(static_cast<uint32_t>(it.value()));
|
||||||
|
|
||||||
|
it = defines.find("METATILE_ATTR_LAYER_MASK");
|
||||||
|
if ((parsedLayerTypeMask = (it != defines.end())))
|
||||||
|
projectConfig.setMetatileLayerTypeMask(static_cast<uint32_t>(it.value()));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool Project::readMaxMapDataSize() {
|
bool Project::readMaxMapDataSize() {
|
||||||
QStringList definePrefixes;
|
static const QStringList definePrefixes{ "\\bMAX_" };
|
||||||
definePrefixes << "\\bMAX_";
|
|
||||||
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); // already in fileWatcher from readTilesetProperties
|
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); // already in fileWatcher from readTilesetProperties
|
||||||
QMap<QString, int> defines = parser.readCDefines(filename, definePrefixes);
|
QMap<QString, int> defines = parser.readCDefines(filename, definePrefixes);
|
||||||
|
|
||||||
|
@ -2272,7 +2292,7 @@ bool Project::readMiscellaneousConstants() {
|
||||||
|
|
||||||
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_global);
|
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_global);
|
||||||
fileWatcher.addPath(root + "/" + filename);
|
fileWatcher.addPath(root + "/" + filename);
|
||||||
QStringList definePrefixes("\\bOBJECT_");
|
static const QStringList definePrefixes("\\bOBJECT_");
|
||||||
QMap<QString, int> defines = parser.readCDefines(filename, definePrefixes);
|
QMap<QString, int> defines = parser.readCDefines(filename, definePrefixes);
|
||||||
|
|
||||||
auto it = defines.find("OBJECT_EVENT_TEMPLATES_COUNT");
|
auto it = defines.find("OBJECT_EVENT_TEMPLATES_COUNT");
|
||||||
|
@ -2573,7 +2593,7 @@ int Project::getNumMetatilesPrimary()
|
||||||
|
|
||||||
int Project::getNumMetatilesTotal()
|
int Project::getNumMetatilesTotal()
|
||||||
{
|
{
|
||||||
return Project::num_metatiles_total;
|
return Block::getMaxMetatileId() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Project::getNumPalettesPrimary()
|
int Project::getNumPalettesPrimary()
|
||||||
|
@ -2640,16 +2660,6 @@ int Project::getMaxObjectEvents()
|
||||||
return Project::max_object_events;
|
return Project::max_object_events;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Project::getMaxCollision()
|
|
||||||
{
|
|
||||||
return Project::max_collision;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Project::getMaxElevation()
|
|
||||||
{
|
|
||||||
return Project::max_elevation;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Project::setImportExportPath(QString filename)
|
void Project::setImportExportPath(QString filename)
|
||||||
{
|
{
|
||||||
this->importExportPath = QFileInfo(filename).absolutePath();
|
this->importExportPath = QFileInfo(filename).absolutePath();
|
||||||
|
|
|
@ -625,7 +625,7 @@ int MainWindow::getMetatileLayerType(int metatileId) {
|
||||||
Metatile * metatile = this->getMetatile(metatileId);
|
Metatile * metatile = this->getMetatile(metatileId);
|
||||||
if (!metatile)
|
if (!metatile)
|
||||||
return -1;
|
return -1;
|
||||||
return metatile->layerType;
|
return metatile->layerType();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setMetatileLayerType(int metatileId, int layerType) {
|
void MainWindow::setMetatileLayerType(int metatileId, int layerType) {
|
||||||
|
@ -640,7 +640,7 @@ int MainWindow::getMetatileEncounterType(int metatileId) {
|
||||||
Metatile * metatile = this->getMetatile(metatileId);
|
Metatile * metatile = this->getMetatile(metatileId);
|
||||||
if (!metatile)
|
if (!metatile)
|
||||||
return -1;
|
return -1;
|
||||||
return metatile->encounterType;
|
return metatile->encounterType();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setMetatileEncounterType(int metatileId, int encounterType) {
|
void MainWindow::setMetatileEncounterType(int metatileId, int encounterType) {
|
||||||
|
@ -655,7 +655,7 @@ int MainWindow::getMetatileTerrainType(int metatileId) {
|
||||||
Metatile * metatile = this->getMetatile(metatileId);
|
Metatile * metatile = this->getMetatile(metatileId);
|
||||||
if (!metatile)
|
if (!metatile)
|
||||||
return -1;
|
return -1;
|
||||||
return metatile->terrainType;
|
return metatile->terrainType();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setMetatileTerrainType(int metatileId, int terrainType) {
|
void MainWindow::setMetatileTerrainType(int metatileId, int terrainType) {
|
||||||
|
@ -670,7 +670,7 @@ int MainWindow::getMetatileBehavior(int metatileId) {
|
||||||
Metatile * metatile = this->getMetatile(metatileId);
|
Metatile * metatile = this->getMetatile(metatileId);
|
||||||
if (!metatile)
|
if (!metatile)
|
||||||
return -1;
|
return -1;
|
||||||
return metatile->behavior;
|
return metatile->behavior();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setMetatileBehavior(int metatileId, int behavior) {
|
void MainWindow::setMetatileBehavior(int metatileId, int behavior) {
|
||||||
|
|
|
@ -50,7 +50,7 @@ QImage getMetatileImage(
|
||||||
QPainter metatile_painter(&metatile_image);
|
QPainter metatile_painter(&metatile_image);
|
||||||
bool isTripleLayerMetatile = projectConfig.getTripleLayerMetatilesEnabled();
|
bool isTripleLayerMetatile = projectConfig.getTripleLayerMetatilesEnabled();
|
||||||
const int numLayers = 3; // When rendering, metatiles always have 3 layers
|
const int numLayers = 3; // When rendering, metatiles always have 3 layers
|
||||||
int layerType = metatile->layerType;
|
uint32_t layerType = metatile->layerType();
|
||||||
for (int layer = 0; layer < numLayers; layer++)
|
for (int layer = 0; layer < numLayers; layer++)
|
||||||
for (int y = 0; y < 2; y++)
|
for (int y = 0; y < 2; y++)
|
||||||
for (int x = 0; x < 2; x++) {
|
for (int x = 0; x < 2; x++) {
|
||||||
|
|
|
@ -103,16 +103,16 @@ void ProjectSettingsEditor::initUi() {
|
||||||
this->setBorderMetatilesUi(projectConfig.getUseCustomBorderSize());
|
this->setBorderMetatilesUi(projectConfig.getUseCustomBorderSize());
|
||||||
|
|
||||||
// Set spin box limits
|
// Set spin box limits
|
||||||
int maxMetatileId = Project::getNumMetatilesTotal() - 1;
|
int maxMetatileId = Block::getMaxMetatileId();
|
||||||
ui->spinBox_FillMetatile->setMaximum(maxMetatileId);
|
ui->spinBox_FillMetatile->setMaximum(maxMetatileId);
|
||||||
ui->spinBox_BorderMetatile1->setMaximum(maxMetatileId);
|
ui->spinBox_BorderMetatile1->setMaximum(maxMetatileId);
|
||||||
ui->spinBox_BorderMetatile2->setMaximum(maxMetatileId);
|
ui->spinBox_BorderMetatile2->setMaximum(maxMetatileId);
|
||||||
ui->spinBox_BorderMetatile3->setMaximum(maxMetatileId);
|
ui->spinBox_BorderMetatile3->setMaximum(maxMetatileId);
|
||||||
ui->spinBox_BorderMetatile4->setMaximum(maxMetatileId);
|
ui->spinBox_BorderMetatile4->setMaximum(maxMetatileId);
|
||||||
ui->spinBox_Elevation->setMaximum(Project::getMaxElevation());
|
ui->spinBox_Elevation->setMaximum(Block::getMaxElevation());
|
||||||
ui->spinBox_Collision->setMaximum(Project::getMaxCollision());
|
ui->spinBox_Collision->setMaximum(Block::getMaxCollision());
|
||||||
ui->spinBox_MaxElevation->setMaximum(Project::getMaxElevation());
|
ui->spinBox_MaxElevation->setMaximum(Block::getMaxElevation());
|
||||||
ui->spinBox_MaxCollision->setMaximum(Project::getMaxCollision());
|
ui->spinBox_MaxCollision->setMaximum(Block::getMaxCollision());
|
||||||
// TODO: Move to a global
|
// TODO: Move to a global
|
||||||
ui->spinBox_MetatileIdMask->setMinimum(0x1);
|
ui->spinBox_MetatileIdMask->setMinimum(0x1);
|
||||||
ui->spinBox_MetatileIdMask->setMaximum(0xFFFF); // Metatile IDs can use all 16 bits of a block
|
ui->spinBox_MetatileIdMask->setMaximum(0xFFFF); // Metatile IDs can use all 16 bits of a block
|
||||||
|
|
|
@ -113,7 +113,7 @@ void TilesetEditor::initUi() {
|
||||||
|
|
||||||
void TilesetEditor::setAttributesUi() {
|
void TilesetEditor::setAttributesUi() {
|
||||||
// Behavior
|
// Behavior
|
||||||
if (Metatile::getBehaviorMask()) {
|
if (projectConfig.getMetatileBehaviorMask() != 0) {
|
||||||
for (int num : project->metatileBehaviorMapInverse.keys()) {
|
for (int num : project->metatileBehaviorMapInverse.keys()) {
|
||||||
this->ui->comboBox_metatileBehaviors->addItem(project->metatileBehaviorMapInverse[num], num);
|
this->ui->comboBox_metatileBehaviors->addItem(project->metatileBehaviorMapInverse[num], num);
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ void TilesetEditor::setAttributesUi() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terrain Type
|
// Terrain Type
|
||||||
if (Metatile::getTerrainTypeMask()) {
|
if (projectConfig.getMetatileTerrainTypeMask()) {
|
||||||
this->ui->comboBox_terrainType->addItem("Normal", TERRAIN_NONE);
|
this->ui->comboBox_terrainType->addItem("Normal", TERRAIN_NONE);
|
||||||
this->ui->comboBox_terrainType->addItem("Grass", TERRAIN_GRASS);
|
this->ui->comboBox_terrainType->addItem("Grass", TERRAIN_GRASS);
|
||||||
this->ui->comboBox_terrainType->addItem("Water", TERRAIN_WATER);
|
this->ui->comboBox_terrainType->addItem("Water", TERRAIN_WATER);
|
||||||
|
@ -137,7 +137,7 @@ void TilesetEditor::setAttributesUi() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encounter Type
|
// Encounter Type
|
||||||
if (Metatile::getEncounterTypeMask()) {
|
if (projectConfig.getMetatileEncounterTypeMask()) {
|
||||||
this->ui->comboBox_encounterType->addItem("None", ENCOUNTER_NONE);
|
this->ui->comboBox_encounterType->addItem("None", ENCOUNTER_NONE);
|
||||||
this->ui->comboBox_encounterType->addItem("Land", ENCOUNTER_LAND);
|
this->ui->comboBox_encounterType->addItem("Land", ENCOUNTER_LAND);
|
||||||
this->ui->comboBox_encounterType->addItem("Water", ENCOUNTER_WATER);
|
this->ui->comboBox_encounterType->addItem("Water", ENCOUNTER_WATER);
|
||||||
|
@ -155,7 +155,7 @@ void TilesetEditor::setAttributesUi() {
|
||||||
this->ui->comboBox_layerType->addItem("Split - Bottom/Top", METATILE_LAYER_BOTTOM_TOP);
|
this->ui->comboBox_layerType->addItem("Split - Bottom/Top", METATILE_LAYER_BOTTOM_TOP);
|
||||||
this->ui->comboBox_layerType->setEditable(false);
|
this->ui->comboBox_layerType->setEditable(false);
|
||||||
this->ui->comboBox_layerType->setMinimumContentsLength(0);
|
this->ui->comboBox_layerType->setMinimumContentsLength(0);
|
||||||
if (!Metatile::getLayerTypeMask()) {
|
if (!projectConfig.getMetatileLayerTypeMask()) {
|
||||||
// User doesn't have triple layer metatiles, but has no layer type attribute.
|
// 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
|
// 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
|
// no mask set every metatile will be "Middle/Top", so just display the combo
|
||||||
|
@ -373,10 +373,10 @@ void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) {
|
||||||
this->ui->lineEdit_metatileLabel->setText(labels.owned);
|
this->ui->lineEdit_metatileLabel->setText(labels.owned);
|
||||||
this->ui->lineEdit_metatileLabel->setPlaceholderText(labels.shared);
|
this->ui->lineEdit_metatileLabel->setPlaceholderText(labels.shared);
|
||||||
|
|
||||||
this->ui->comboBox_metatileBehaviors->setNumberItem(this->metatile->behavior);
|
this->ui->comboBox_metatileBehaviors->setNumberItem(this->metatile->behavior());
|
||||||
this->ui->comboBox_layerType->setNumberItem(this->metatile->layerType);
|
this->ui->comboBox_layerType->setNumberItem(this->metatile->layerType());
|
||||||
this->ui->comboBox_encounterType->setNumberItem(this->metatile->encounterType);
|
this->ui->comboBox_encounterType->setNumberItem(this->metatile->encounterType());
|
||||||
this->ui->comboBox_terrainType->setNumberItem(this->metatile->terrainType);
|
this->ui->comboBox_terrainType->setNumberItem(this->metatile->terrainType());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TilesetEditor::onHoveredTileChanged(uint16_t tile) {
|
void TilesetEditor::onHoveredTileChanged(uint16_t tile) {
|
||||||
|
@ -505,7 +505,7 @@ void TilesetEditor::on_comboBox_metatileBehaviors_currentTextChanged(const QStri
|
||||||
|
|
||||||
// This function can also be called when the user selects
|
// This function can also be called when the user selects
|
||||||
// a different metatile. Stop this from being considered a change.
|
// a different metatile. Stop this from being considered a change.
|
||||||
if (this->metatile->behavior == static_cast<uint32_t>(behavior))
|
if (this->metatile->behavior() == static_cast<uint32_t>(behavior))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Metatile *prevMetatile = new Metatile(*this->metatile);
|
Metatile *prevMetatile = new Metatile(*this->metatile);
|
||||||
|
|
Loading…
Reference in a new issue