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_species,
|
||||
constants_fieldmap,
|
||||
global_fieldmap,
|
||||
initial_facing_table,
|
||||
pokemon_icon_table,
|
||||
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 &);
|
||||
bool operator ==(Block) const;
|
||||
bool operator !=(Block) const;
|
||||
void setMetatileId(uint16_t metatileId) { m_metatileId = metatileId; }
|
||||
void setCollision(uint16_t collision) { m_collision = collision; }
|
||||
void setElevation(uint16_t elevation) { m_elevation = elevation; }
|
||||
void setMetatileId(uint16_t metatileId);
|
||||
void setCollision(uint16_t collision);
|
||||
void setElevation(uint16_t elevation);
|
||||
uint16_t metatileId() const { return m_metatileId; }
|
||||
uint16_t collision() const { return m_collision; }
|
||||
uint16_t elevation() const { return m_elevation; }
|
||||
uint16_t rawValue() const;
|
||||
static void setLayout();
|
||||
static uint16_t getMaxMetatileId();
|
||||
static uint16_t getMaxCollision();
|
||||
static uint16_t getMaxElevation();
|
||||
|
||||
private:
|
||||
uint16_t m_metatileId; // 10
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
|
||||
#include "tile.h"
|
||||
#include "config.h"
|
||||
#include "bitpacker.h"
|
||||
#include <QImage>
|
||||
#include <QPoint>
|
||||
#include <QString>
|
||||
|
||||
class Project;
|
||||
|
||||
// TODO: Reevaluate enums
|
||||
enum {
|
||||
METATILE_LAYER_MIDDLE_TOP,
|
||||
METATILE_LAYER_BOTTOM_MIDDLE,
|
||||
|
@ -32,67 +34,46 @@ 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:
|
||||
Metatile();
|
||||
Metatile() = default;
|
||||
Metatile(const Metatile &other) = default;
|
||||
Metatile &operator=(const Metatile &other) = default;
|
||||
Metatile(const int numTiles);
|
||||
|
||||
enum Attr {
|
||||
Behavior,
|
||||
TerrainType,
|
||||
EncounterType,
|
||||
LayerType,
|
||||
Unused, // Preserve bits not used by the other attributes
|
||||
};
|
||||
|
||||
public:
|
||||
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, BaseGameVersion version);
|
||||
void setAttribute(Metatile::Attr attr, uint32_t value);
|
||||
|
||||
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);
|
||||
// For convenience
|
||||
uint32_t behavior() const { return this->getAttribute(Attr::Behavior); }
|
||||
uint32_t terrainType() const { return this->getAttribute(Attr::TerrainType); }
|
||||
uint32_t encounterType() const { return this->getAttribute(Attr::EncounterType); }
|
||||
uint32_t layerType() const { return this->getAttribute(Attr::LayerType); }
|
||||
void setBehavior(int value) { this->setAttribute(Attr::Behavior, static_cast<uint32_t>(value)); }
|
||||
void setTerrainType(int value) { this->setAttribute(Attr::TerrainType, static_cast<uint32_t>(value)); }
|
||||
void setEncounterType(int value) { this->setAttribute(Attr::EncounterType, static_cast<uint32_t>(value)); }
|
||||
void setLayerType(int value) { this->setAttribute(Attr::LayerType, static_cast<uint32_t>(value)); }
|
||||
|
||||
static int getIndexInTileset(int);
|
||||
static QPoint coordFromPixmapCoord(const QPointF &pixelCoord);
|
||||
static uint32_t getDefaultAttributesMask(BaseGameVersion version, Metatile::Attr attr);
|
||||
static int getDefaultAttributesSize(BaseGameVersion version);
|
||||
static void setCustomLayout(Project*);
|
||||
static void setLayout(Project*);
|
||||
static QString getMetatileIdString(uint16_t metatileId) {
|
||||
return "0x" + QString("%1").arg(metatileId, 3, 16, QChar('0')).toUpper();
|
||||
};
|
||||
|
@ -103,37 +84,18 @@ public:
|
|||
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:
|
||||
// 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;
|
||||
QMap<Metatile::Attr, uint32_t> attributes;
|
||||
|
||||
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) {
|
||||
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
|
||||
|
|
|
@ -85,6 +85,11 @@ public:
|
|||
QMap<QString, qint64> modifiedFileTimestamps;
|
||||
bool usingAsmTilesets;
|
||||
QString importExportPath;
|
||||
bool parsedMetatileIdMask;
|
||||
bool parsedCollisionMask;
|
||||
bool parsedElevationMask;
|
||||
bool parsedBehaviorMask;
|
||||
bool parsedLayerTypeMask;
|
||||
|
||||
void set_root(QString);
|
||||
|
||||
|
@ -196,6 +201,7 @@ public:
|
|||
bool readObjEventGfxConstants();
|
||||
bool readSongNames();
|
||||
bool readEventGraphics();
|
||||
bool readFieldmapMasks();
|
||||
QMap<QString, QMap<QString, QString>> readObjEventGfxInfo();
|
||||
|
||||
void setEventPixmap(Event *event, bool forceLoad = false);
|
||||
|
@ -229,8 +235,6 @@ public:
|
|||
static bool mapDimensionsValid(int width, int height);
|
||||
bool calculateDefaultMapSize();
|
||||
static int getMaxObjectEvents();
|
||||
static int getMaxCollision();
|
||||
static int getMaxElevation();
|
||||
|
||||
private:
|
||||
void updateMapLayout(Map*);
|
||||
|
@ -248,14 +252,11 @@ private:
|
|||
static int num_tiles_primary;
|
||||
static int num_tiles_total;
|
||||
static int num_metatiles_primary;
|
||||
static int num_metatiles_total;
|
||||
static int num_pals_primary;
|
||||
static int num_pals_total;
|
||||
static int max_map_data_size;
|
||||
static int default_map_size;
|
||||
static int max_object_events;
|
||||
static int max_collision;
|
||||
static int max_elevation;
|
||||
|
||||
QStringListModel eventScriptLabelModel;
|
||||
QCompleter eventScriptLabelCompleter;
|
||||
|
|
|
@ -16,6 +16,7 @@ QMAKE_CXXFLAGS += -std=c++17 -Wall
|
|||
QMAKE_TARGET_BUNDLE_PREFIX = com.pret
|
||||
|
||||
SOURCES += src/core/block.cpp \
|
||||
src/core/bitpacker.cpp \
|
||||
src/core/blockdata.cpp \
|
||||
src/core/events.cpp \
|
||||
src/core/heallocation.cpp \
|
||||
|
@ -104,6 +105,7 @@ SOURCES += src/core/block.cpp \
|
|||
src/ui/uintspinbox.cpp
|
||||
|
||||
HEADERS += include/core/block.h \
|
||||
include/core/bitpacker.h \
|
||||
include/core/blockdata.h \
|
||||
include/core/events.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_species, { "constants_species", "include/constants/species.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::initial_facing_table, { "initial_facing_table", "src/event_object_movement.c"}},
|
||||
{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("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("metatile_behavior_mask")) this->metatileBehaviorMask = Metatile::getDefaultAttributesMask(this->baseGameVersion, Metatile::Attr::Behavior);
|
||||
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::getDefaultAttributesMask(this->baseGameVersion, Metatile::Attr::EncounterType);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
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 "bitpacker.h"
|
||||
#include "config.h"
|
||||
|
||||
static BitPacker bitsMetatileId = BitPacker(0x3FF);
|
||||
static BitPacker bitsCollision = BitPacker(0xC00);
|
||||
static BitPacker bitsElevation = BitPacker(0xF000);
|
||||
|
||||
Block::Block() :
|
||||
m_metatileId(0),
|
||||
|
@ -12,10 +18,10 @@ Block::Block(uint16_t metatileId, uint16_t collision, uint16_t elevation) :
|
|||
m_elevation(elevation)
|
||||
{ }
|
||||
|
||||
Block::Block(uint16_t word) :
|
||||
m_metatileId(word & 0x3ff),
|
||||
m_collision((word >> 10) & 0x3),
|
||||
m_elevation((word >> 12) & 0xf)
|
||||
Block::Block(uint16_t data) :
|
||||
m_metatileId(bitsMetatileId.unpack(data)),
|
||||
m_collision(bitsCollision.unpack(data)),
|
||||
m_elevation(bitsElevation.unpack(data))
|
||||
{ }
|
||||
|
||||
Block::Block(const Block &other) :
|
||||
|
@ -32,16 +38,53 @@ Block &Block::operator=(const Block &other) {
|
|||
}
|
||||
|
||||
uint16_t Block::rawValue() const {
|
||||
return static_cast<uint16_t>(
|
||||
(m_metatileId & 0x3ff) +
|
||||
((m_collision & 0x3) << 10) +
|
||||
((m_elevation & 0xf) << 12));
|
||||
return bitsMetatileId.pack(m_metatileId)
|
||||
| bitsCollision.pack(m_collision)
|
||||
| bitsElevation.pack(m_elevation);
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
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 "project.h"
|
||||
|
||||
const QHash<QString, MetatileAttr> Metatile::defaultLayoutFRLG = {
|
||||
{"behavior", MetatileAttr(0x000001FF, 0) },
|
||||
{"terrainType", MetatileAttr(0x00003E00, 9) },
|
||||
{"encounterType", MetatileAttr(0x07000000, 24) },
|
||||
{"layerType", MetatileAttr(0x60000000, 29) },
|
||||
// Stores how each attribute should be laid out for all metatiles, according to the vanilla games.
|
||||
// Used to set default config values and import maps with AdvanceMap.
|
||||
static const QMap<Metatile::Attr, BitPacker> attributePackersFRLG = {
|
||||
{Metatile::Attr::Behavior, BitPacker(0x000001FF) },
|
||||
{Metatile::Attr::TerrainType, BitPacker(0x00003E00) },
|
||||
{Metatile::Attr::EncounterType, BitPacker(0x07000000) },
|
||||
{Metatile::Attr::LayerType, BitPacker(0x60000000) },
|
||||
//{Metatile::Attr::Unused, BitPacker(0x98FFC000) },
|
||||
};
|
||||
static const QMap<Metatile::Attr, BitPacker> attributePackersRSE = {
|
||||
{Metatile::Attr::Behavior, BitPacker(0x00FF) },
|
||||
//{Metatile::Attr::Unused, BitPacker(0x0F00) },
|
||||
{Metatile::Attr::LayerType, BitPacker(0xF000) },
|
||||
};
|
||||
|
||||
const QHash<QString, MetatileAttr> Metatile::defaultLayoutRSE = {
|
||||
{"behavior", MetatileAttr(0x00FF, 0) },
|
||||
{"terrainType", MetatileAttr() },
|
||||
{"encounterType", MetatileAttr() },
|
||||
{"layerType", MetatileAttr(0xF000, 12) },
|
||||
};
|
||||
static QMap<Metatile::Attr, BitPacker> attributePackers;
|
||||
|
||||
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),
|
||||
terrainType(0),
|
||||
encounterType(0),
|
||||
layerType(0),
|
||||
unusedAttributes(0)
|
||||
{ }
|
||||
|
||||
Metatile::Metatile(const int numTiles) :
|
||||
behavior(0),
|
||||
terrainType(0),
|
||||
encounterType(0),
|
||||
layerType(0),
|
||||
unusedAttributes(0)
|
||||
{
|
||||
Metatile::Metatile(const int numTiles) {
|
||||
Tile tile = Tile();
|
||||
for (int i = 0; i < numTiles; i++) {
|
||||
this->tiles.append(tile);
|
||||
|
@ -74,31 +40,46 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
|
|||
return QPoint(x, y);
|
||||
}
|
||||
|
||||
// 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()));
|
||||
// Read and pack together this metatile's attributes.
|
||||
uint32_t Metatile::getAttributes() const {
|
||||
uint32_t data = 0;
|
||||
for (auto i = this->attributes.cbegin(), end = this->attributes.cend(); i != end; i++){
|
||||
const auto packer = attributePackers.value(i.key());
|
||||
data |= packer.pack(i.value());
|
||||
}
|
||||
attr->mask = mask;
|
||||
attr->shift = mask ? log2(mask & ~(mask - 1)) : 0; // Get position of the least significant set bit
|
||||
return data;
|
||||
}
|
||||
|
||||
// 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;
|
||||
// Unpack and insert metatile attributes from the given data.
|
||||
void Metatile::setAttributes(uint32_t data) {
|
||||
for (auto i = attributePackers.cbegin(), end = attributePackers.cend(); i != end; i++){
|
||||
const auto packer = i.value();
|
||||
this->setAttribute(i.key(), packer.unpack(data));
|
||||
}
|
||||
}
|
||||
|
||||
// Get position of the most significant set bit
|
||||
uint32_t n = log2(max);
|
||||
// Unpack and insert metatile attributes from the given data using a vanilla layout. For AdvanceMap import
|
||||
void Metatile::setAttributes(uint32_t data, BaseGameVersion version) {
|
||||
const auto vanillaPackers = (version == BaseGameVersion::pokefirered) ? attributePackersFRLG : attributePackersRSE;
|
||||
for (auto i = vanillaPackers.cbegin(), end = vanillaPackers.cend(); i != end; i++){
|
||||
const auto packer = i.value();
|
||||
this->setAttribute(i.key(), packer.unpack(data));
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
// Set the value for a metatile attribute, and fit it within the valid value range.
|
||||
void Metatile::setAttribute(Metatile::Attr attr, uint32_t value) {
|
||||
const auto packer = attributePackers.value(attr);
|
||||
this->attributes.insert(attr, packer.clamp(value));
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -110,84 +91,63 @@ bool Metatile::doMasksOverlap(QList<uint32_t> masks) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void Metatile::setCustomLayout(Project * project) {
|
||||
// Get the maximum size of any attribute mask
|
||||
void Metatile::setLayout(Project * project) {
|
||||
// Read masks from the config and limit them based on the specified attribute size.
|
||||
const QHash<int, uint32_t> maxMasks = {
|
||||
{1, 0xFF},
|
||||
{2, 0xFFFF},
|
||||
{4, 0xFFFFFFFF},
|
||||
};
|
||||
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;
|
||||
uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0);
|
||||
uint32_t behaviorMask = projectConfig.getMetatileBehaviorMask() & maxMask;
|
||||
uint32_t terrainTypeMask = projectConfig.getMetatileTerrainTypeMask() & maxMask;
|
||||
uint32_t encounterTypeMask = projectConfig.getMetatileEncounterTypeMask() & maxMask;
|
||||
uint32_t layerTypeMask = projectConfig.getMetatileLayerTypeMask() & maxMask;
|
||||
|
||||
// Overlapping masks are technically ok, but probably not intended.
|
||||
// Additionally, Porymap will not properly reflect that the values are linked.
|
||||
if (doMasksOverlap({getBehaviorMask(), getTerrainTypeMask(), getEncounterTypeMask(), getLayerTypeMask()})) {
|
||||
if (doMasksOverlap({behaviorMask, terrainTypeMask, encounterTypeMask, layerTypeMask})) {
|
||||
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));
|
||||
|
||||
// Calculate mask of bits not used by standard behaviors so we can preserve this data.
|
||||
uint32_t unusedMask = ~(behaviorMask | terrainTypeMask | encounterTypeMask | layerTypeMask);
|
||||
unusedMask &= maxMask;
|
||||
|
||||
BitPacker packer = BitPacker(unusedMask);
|
||||
attributePackers.clear();
|
||||
attributePackers.insert(Metatile::Attr::Unused, unusedMask);
|
||||
|
||||
// TODO: Test displaying 32 bit behavior
|
||||
// TODO: Logging masks to hex
|
||||
// Validate metatile behavior mask
|
||||
packer.setMask(behaviorMask);
|
||||
if (behaviorMask && !project->metatileBehaviorMapInverse.isEmpty()) {
|
||||
uint32_t maxBehavior = project->metatileBehaviorMapInverse.lastKey();
|
||||
if (packer.clamp(maxBehavior) != maxBehavior)
|
||||
logWarn(QString("Metatile Behavior mask '%1' is insufficient to contain all available options.").arg(behaviorMask));
|
||||
}
|
||||
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));
|
||||
}
|
||||
attributePackers.insert(Metatile::Attr::Behavior, packer);
|
||||
|
||||
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;
|
||||
}
|
||||
// Validate terrain type mask
|
||||
packer.setMask(terrainTypeMask);
|
||||
const uint32_t maxTerrainType = NUM_METATILE_TERRAIN_TYPES - 1;
|
||||
if (terrainTypeMask && packer.clamp(maxTerrainType) != maxTerrainType)
|
||||
logWarn(QString("Metatile Terrain Type mask '%1' is insufficient to contain all %2 available options.").arg(terrainTypeMask).arg(maxTerrainType + 1));
|
||||
attributePackers.insert(Metatile::Attr::TerrainType, packer);
|
||||
|
||||
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;
|
||||
}
|
||||
// Validate encounter type mask
|
||||
packer.setMask(encounterTypeMask);
|
||||
const uint32_t maxEncounterType = NUM_METATILE_ENCOUNTER_TYPES - 1;
|
||||
if (encounterTypeMask && packer.clamp(maxEncounterType) != maxEncounterType)
|
||||
logWarn(QString("Metatile Encounter Type mask '%1' is insufficient to contain all %2 available options.").arg(encounterTypeMask).arg(maxEncounterType + 1));
|
||||
attributePackers.insert(Metatile::Attr::EncounterType, packer);
|
||||
|
||||
// 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;
|
||||
// Validate terrain type mask
|
||||
packer.setMask(layerTypeMask);
|
||||
const uint32_t maxLayerType = NUM_METATILE_LAYER_TYPES - 1;
|
||||
if (layerTypeMask && packer.clamp(maxLayerType) != maxLayerType)
|
||||
logWarn(QString("Metatile Layer Type mask '%1' is insufficient to contain all %2 available options.").arg(layerTypeMask).arg(maxLayerType + 1));
|
||||
attributePackers.insert(Metatile::Attr::LayerType, packer);
|
||||
}
|
||||
|
|
|
@ -941,8 +941,8 @@ QString Editor::getMetatileDisplayMessage(uint16_t metatileId) {
|
|||
QString message = QString("Metatile: %1").arg(Metatile::getMetatileIdString(metatileId));
|
||||
if (label.size())
|
||||
message += QString(" \"%1\"").arg(label);
|
||||
if (metatile && metatile->behavior) // Skip MB_NORMAL
|
||||
message += QString(", Behavior: %1").arg(this->project->metatileBehaviorMapInverse.value(metatile->behavior, QString::number(metatile->behavior)));
|
||||
if (metatile && metatile->behavior()) // Skip MB_NORMAL
|
||||
message += QString(", Behavior: %1").arg(this->project->metatileBehaviorMapInverse.value(metatile->behavior(), QString::number(metatile->behavior())));
|
||||
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.
|
||||
const int w = 16, h = 16;
|
||||
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.
|
||||
// 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.
|
||||
int x = ((collision < imgColumns) ? collision : (imgColumns - 1)) * w;
|
||||
|
||||
QList<const QImage*> sublist;
|
||||
for (int elevation = 0; elevation <= Project::getMaxElevation(); elevation++) {
|
||||
for (int elevation = 0; elevation <= Block::getMaxElevation(); elevation++) {
|
||||
if (elevation < imgRows) {
|
||||
// This elevation has an icon on the image sheet, add it to the list
|
||||
int y = elevation * h;
|
||||
|
|
|
@ -395,8 +395,8 @@ void MainWindow::setProjectSpecificUI()
|
|||
|
||||
Event::setIcons();
|
||||
editor->setCollisionGraphics();
|
||||
ui->spinBox_SelectedElevation->setMaximum(Project::getMaxElevation());
|
||||
ui->spinBox_SelectedCollision->setMaximum(Project::getMaxCollision());
|
||||
ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation());
|
||||
ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision());
|
||||
}
|
||||
|
||||
void MainWindow::mapSortOrder_changed(QAction *action)
|
||||
|
@ -936,6 +936,7 @@ bool MainWindow::loadDataStructures() {
|
|||
&& project->readTilesetProperties()
|
||||
&& project->readTilesetLabels()
|
||||
&& project->readTilesetMetatileLabels()
|
||||
&& project->readFieldmapMasks()
|
||||
&& project->readMaxMapDataSize()
|
||||
&& project->readHealLocations()
|
||||
&& project->readMiscellaneousConstants()
|
||||
|
@ -946,7 +947,8 @@ bool MainWindow::loadDataStructures() {
|
|||
&& project->readEventGraphics()
|
||||
&& project->readSongNames();
|
||||
|
||||
Metatile::setCustomLayout(project);
|
||||
Block::setLayout();
|
||||
Metatile::setLayout(project);
|
||||
Scripting::populateGlobalObject(this);
|
||||
|
||||
return success && loadProjectCombos();
|
||||
|
|
|
@ -27,18 +27,13 @@ using OrderedJsonDoc = poryjson::JsonDoc;
|
|||
|
||||
int Project::num_tiles_primary = 512;
|
||||
int Project::num_tiles_total = 1024;
|
||||
int Project::num_metatiles_primary = 512;
|
||||
int Project::num_metatiles_total = 1024;
|
||||
int Project::num_metatiles_primary = 512; // TODO: Verify fits within max
|
||||
int Project::num_pals_primary = 6;
|
||||
int Project::num_pals_total = 13;
|
||||
int Project::max_map_data_size = 10240; // 0x2800
|
||||
int Project::default_map_size = 20;
|
||||
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) :
|
||||
QObject(parent),
|
||||
eventScriptLabelModel(this),
|
||||
|
@ -1870,8 +1865,7 @@ bool Project::readTilesetLabels() {
|
|||
}
|
||||
|
||||
bool Project::readTilesetProperties() {
|
||||
QStringList definePrefixes;
|
||||
definePrefixes << "\\bNUM_";
|
||||
static const QStringList definePrefixes{ "\\bNUM_" };
|
||||
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
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.")
|
||||
.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");
|
||||
if (it != defines.end()) {
|
||||
Project::num_pals_primary = it.value();
|
||||
|
@ -1927,9 +1913,43 @@ bool Project::readTilesetProperties() {
|
|||
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() {
|
||||
QStringList definePrefixes;
|
||||
definePrefixes << "\\bMAX_";
|
||||
static const QStringList definePrefixes{ "\\bMAX_" };
|
||||
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); // already in fileWatcher from readTilesetProperties
|
||||
QMap<QString, int> defines = parser.readCDefines(filename, definePrefixes);
|
||||
|
||||
|
@ -2272,7 +2292,7 @@ bool Project::readMiscellaneousConstants() {
|
|||
|
||||
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_global);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QStringList definePrefixes("\\bOBJECT_");
|
||||
static const QStringList definePrefixes("\\bOBJECT_");
|
||||
QMap<QString, int> defines = parser.readCDefines(filename, definePrefixes);
|
||||
|
||||
auto it = defines.find("OBJECT_EVENT_TEMPLATES_COUNT");
|
||||
|
@ -2573,7 +2593,7 @@ int Project::getNumMetatilesPrimary()
|
|||
|
||||
int Project::getNumMetatilesTotal()
|
||||
{
|
||||
return Project::num_metatiles_total;
|
||||
return Block::getMaxMetatileId() + 1;
|
||||
}
|
||||
|
||||
int Project::getNumPalettesPrimary()
|
||||
|
@ -2640,16 +2660,6 @@ int Project::getMaxObjectEvents()
|
|||
return Project::max_object_events;
|
||||
}
|
||||
|
||||
int Project::getMaxCollision()
|
||||
{
|
||||
return Project::max_collision;
|
||||
}
|
||||
|
||||
int Project::getMaxElevation()
|
||||
{
|
||||
return Project::max_elevation;
|
||||
}
|
||||
|
||||
void Project::setImportExportPath(QString filename)
|
||||
{
|
||||
this->importExportPath = QFileInfo(filename).absolutePath();
|
||||
|
|
|
@ -625,7 +625,7 @@ int MainWindow::getMetatileLayerType(int metatileId) {
|
|||
Metatile * metatile = this->getMetatile(metatileId);
|
||||
if (!metatile)
|
||||
return -1;
|
||||
return metatile->layerType;
|
||||
return metatile->layerType();
|
||||
}
|
||||
|
||||
void MainWindow::setMetatileLayerType(int metatileId, int layerType) {
|
||||
|
@ -640,7 +640,7 @@ int MainWindow::getMetatileEncounterType(int metatileId) {
|
|||
Metatile * metatile = this->getMetatile(metatileId);
|
||||
if (!metatile)
|
||||
return -1;
|
||||
return metatile->encounterType;
|
||||
return metatile->encounterType();
|
||||
}
|
||||
|
||||
void MainWindow::setMetatileEncounterType(int metatileId, int encounterType) {
|
||||
|
@ -655,7 +655,7 @@ int MainWindow::getMetatileTerrainType(int metatileId) {
|
|||
Metatile * metatile = this->getMetatile(metatileId);
|
||||
if (!metatile)
|
||||
return -1;
|
||||
return metatile->terrainType;
|
||||
return metatile->terrainType();
|
||||
}
|
||||
|
||||
void MainWindow::setMetatileTerrainType(int metatileId, int terrainType) {
|
||||
|
@ -670,7 +670,7 @@ int MainWindow::getMetatileBehavior(int metatileId) {
|
|||
Metatile * metatile = this->getMetatile(metatileId);
|
||||
if (!metatile)
|
||||
return -1;
|
||||
return metatile->behavior;
|
||||
return metatile->behavior();
|
||||
}
|
||||
|
||||
void MainWindow::setMetatileBehavior(int metatileId, int behavior) {
|
||||
|
|
|
@ -50,7 +50,7 @@ QImage getMetatileImage(
|
|||
QPainter metatile_painter(&metatile_image);
|
||||
bool isTripleLayerMetatile = projectConfig.getTripleLayerMetatilesEnabled();
|
||||
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 y = 0; y < 2; y++)
|
||||
for (int x = 0; x < 2; x++) {
|
||||
|
|
|
@ -103,16 +103,16 @@ void ProjectSettingsEditor::initUi() {
|
|||
this->setBorderMetatilesUi(projectConfig.getUseCustomBorderSize());
|
||||
|
||||
// Set spin box limits
|
||||
int maxMetatileId = Project::getNumMetatilesTotal() - 1;
|
||||
int maxMetatileId = Block::getMaxMetatileId();
|
||||
ui->spinBox_FillMetatile->setMaximum(maxMetatileId);
|
||||
ui->spinBox_BorderMetatile1->setMaximum(maxMetatileId);
|
||||
ui->spinBox_BorderMetatile2->setMaximum(maxMetatileId);
|
||||
ui->spinBox_BorderMetatile3->setMaximum(maxMetatileId);
|
||||
ui->spinBox_BorderMetatile4->setMaximum(maxMetatileId);
|
||||
ui->spinBox_Elevation->setMaximum(Project::getMaxElevation());
|
||||
ui->spinBox_Collision->setMaximum(Project::getMaxCollision());
|
||||
ui->spinBox_MaxElevation->setMaximum(Project::getMaxElevation());
|
||||
ui->spinBox_MaxCollision->setMaximum(Project::getMaxCollision());
|
||||
ui->spinBox_Elevation->setMaximum(Block::getMaxElevation());
|
||||
ui->spinBox_Collision->setMaximum(Block::getMaxCollision());
|
||||
ui->spinBox_MaxElevation->setMaximum(Block::getMaxElevation());
|
||||
ui->spinBox_MaxCollision->setMaximum(Block::getMaxCollision());
|
||||
// TODO: Move to a global
|
||||
ui->spinBox_MetatileIdMask->setMinimum(0x1);
|
||||
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() {
|
||||
// Behavior
|
||||
if (Metatile::getBehaviorMask()) {
|
||||
if (projectConfig.getMetatileBehaviorMask() != 0) {
|
||||
for (int num : project->metatileBehaviorMapInverse.keys()) {
|
||||
this->ui->comboBox_metatileBehaviors->addItem(project->metatileBehaviorMapInverse[num], num);
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ void TilesetEditor::setAttributesUi() {
|
|||
}
|
||||
|
||||
// Terrain Type
|
||||
if (Metatile::getTerrainTypeMask()) {
|
||||
if (projectConfig.getMetatileTerrainTypeMask()) {
|
||||
this->ui->comboBox_terrainType->addItem("Normal", TERRAIN_NONE);
|
||||
this->ui->comboBox_terrainType->addItem("Grass", TERRAIN_GRASS);
|
||||
this->ui->comboBox_terrainType->addItem("Water", TERRAIN_WATER);
|
||||
|
@ -137,7 +137,7 @@ void TilesetEditor::setAttributesUi() {
|
|||
}
|
||||
|
||||
// Encounter Type
|
||||
if (Metatile::getEncounterTypeMask()) {
|
||||
if (projectConfig.getMetatileEncounterTypeMask()) {
|
||||
this->ui->comboBox_encounterType->addItem("None", ENCOUNTER_NONE);
|
||||
this->ui->comboBox_encounterType->addItem("Land", ENCOUNTER_LAND);
|
||||
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->setEditable(false);
|
||||
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.
|
||||
// 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
|
||||
|
@ -373,10 +373,10 @@ void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) {
|
|||
this->ui->lineEdit_metatileLabel->setText(labels.owned);
|
||||
this->ui->lineEdit_metatileLabel->setPlaceholderText(labels.shared);
|
||||
|
||||
this->ui->comboBox_metatileBehaviors->setNumberItem(this->metatile->behavior);
|
||||
this->ui->comboBox_layerType->setNumberItem(this->metatile->layerType);
|
||||
this->ui->comboBox_encounterType->setNumberItem(this->metatile->encounterType);
|
||||
this->ui->comboBox_terrainType->setNumberItem(this->metatile->terrainType);
|
||||
this->ui->comboBox_metatileBehaviors->setNumberItem(this->metatile->behavior());
|
||||
this->ui->comboBox_layerType->setNumberItem(this->metatile->layerType());
|
||||
this->ui->comboBox_encounterType->setNumberItem(this->metatile->encounterType());
|
||||
this->ui->comboBox_terrainType->setNumberItem(this->metatile->terrainType());
|
||||
}
|
||||
|
||||
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
|
||||
// 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;
|
||||
|
||||
Metatile *prevMetatile = new Metatile(*this->metatile);
|
||||
|
|
Loading…
Reference in a new issue