Use custom attribute masks in API and Tileset Editor
This commit is contained in:
parent
1641ac00b0
commit
1283f5c19d
5 changed files with 113 additions and 70 deletions
|
@ -41,9 +41,9 @@ public:
|
|||
public:
|
||||
QList<Tile> tiles;
|
||||
uint32_t behavior;
|
||||
uint32_t layerType;
|
||||
uint32_t encounterType;
|
||||
uint32_t terrainType;
|
||||
uint32_t encounterType;
|
||||
uint32_t layerType;
|
||||
uint32_t unusedAttributes;
|
||||
QString label;
|
||||
|
||||
|
@ -61,22 +61,28 @@ public:
|
|||
|
||||
static const QHash<Metatile::Attr, Metatile::AttrLayout> defaultLayoutFRLG;
|
||||
static const QHash<Metatile::Attr, Metatile::AttrLayout> defaultLayoutRSE;
|
||||
static QHash<Metatile::Attr, Metatile::AttrLayout> customLayout;
|
||||
|
||||
uint32_t getAttributes();
|
||||
void setAttributes(uint32_t data);
|
||||
void convertAttributes(uint32_t data, BaseGameVersion version);
|
||||
|
||||
void setBehavior(uint32_t);
|
||||
void setTerrainType(uint32_t);
|
||||
void setEncounterType(uint32_t);
|
||||
void setLayerType(uint32_t);
|
||||
|
||||
static int getIndexInTileset(int);
|
||||
static QPoint coordFromPixmapCoord(const QPointF &pixelCoord);
|
||||
static int getDefaultAttributesSize(BaseGameVersion version);
|
||||
static void setCustomLayout();
|
||||
|
||||
private:
|
||||
static QHash<Metatile::Attr, Metatile::AttrLayout> customLayout;
|
||||
static uint32_t unusedAttrMask;
|
||||
|
||||
void setAttributes(uint32_t, const QHash<Metatile::Attr, Metatile::AttrLayout>*);
|
||||
static void setCustomAttributeLayout(Metatile::AttrLayout *, uint32_t, uint32_t, QString);
|
||||
static void setCustomAttributeLayout(Metatile::AttrLayout *, uint32_t, uint32_t);
|
||||
static bool isMaskTooSmall(Metatile::AttrLayout * layout, int n);
|
||||
static bool doMasksOverlap(QList<uint32_t>);
|
||||
};
|
||||
|
||||
|
|
|
@ -111,9 +111,7 @@ private slots:
|
|||
|
||||
private:
|
||||
void initUi();
|
||||
void setMetatileBehaviors();
|
||||
void setMetatileLayersUi();
|
||||
void setVersionSpecificUi();
|
||||
void setAttributesUi();
|
||||
void setMetatileLabelValidator();
|
||||
void initMetatileSelector();
|
||||
void initTileSelector();
|
||||
|
|
|
@ -21,17 +21,17 @@ const QHash<Metatile::Attr, Metatile::AttrLayout> Metatile::defaultLayoutRSE = {
|
|||
|
||||
Metatile::Metatile() :
|
||||
behavior(0),
|
||||
layerType(0),
|
||||
encounterType(0),
|
||||
terrainType(0),
|
||||
encounterType(0),
|
||||
layerType(0),
|
||||
unusedAttributes(0)
|
||||
{ }
|
||||
|
||||
Metatile::Metatile(const int numTiles) :
|
||||
behavior(0),
|
||||
layerType(0),
|
||||
encounterType(0),
|
||||
terrainType(0),
|
||||
encounterType(0),
|
||||
layerType(0),
|
||||
unusedAttributes(0)
|
||||
{
|
||||
Tile tile = Tile();
|
||||
|
@ -60,15 +60,25 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
|
|||
return QPoint(x, y);
|
||||
}
|
||||
|
||||
void Metatile::setCustomAttributeLayout(Metatile::AttrLayout * layout, uint32_t mask, uint32_t max, QString errName) {
|
||||
void Metatile::setCustomAttributeLayout(Metatile::AttrLayout * layout, uint32_t mask, uint32_t max) {
|
||||
if (mask > max) {
|
||||
logWarn(QString("Metatile %1 mask '%2' exceeds maximum size '%3'").arg(errName).arg(mask).arg(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()));
|
||||
}
|
||||
layout->mask = mask;
|
||||
layout->shift = log2(mask & ~(mask - 1)); // Get the position of the rightmost set bit
|
||||
}
|
||||
|
||||
// For checking whether a metatile attribute mask can contain all the hard-coded values
|
||||
bool Metatile::isMaskTooSmall(Metatile::AttrLayout * layout, int n) {
|
||||
if (!layout->mask) return false;
|
||||
uint32_t maxValue = n - 1;
|
||||
return (maxValue & (layout->mask >> layout->shift)) != maxValue;
|
||||
}
|
||||
|
||||
bool Metatile::doMasksOverlap(QList<uint32_t> masks) {
|
||||
for (int i = 0; i < masks.length(); i++)
|
||||
for (int j = i + 1; j < masks.length(); j++) {
|
||||
|
@ -88,10 +98,10 @@ void Metatile::setCustomLayout() {
|
|||
const uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0);
|
||||
|
||||
// Set custom attribute masks from the config file
|
||||
setCustomAttributeLayout(&customLayout[Attr::Behavior], projectConfig.getMetatileBehaviorMask(), maxMask, "behavior");
|
||||
setCustomAttributeLayout(&customLayout[Attr::TerrainType], projectConfig.getMetatileTerrainTypeMask(), maxMask, "terrain type");
|
||||
setCustomAttributeLayout(&customLayout[Attr::EncounterType], projectConfig.getMetatileEncounterTypeMask(), maxMask, "encounter type");
|
||||
setCustomAttributeLayout(&customLayout[Attr::LayerType], projectConfig.getMetatileLayerTypeMask(), maxMask, "layer type");
|
||||
setCustomAttributeLayout(&customLayout[Attr::Behavior], projectConfig.getMetatileBehaviorMask(), maxMask);
|
||||
setCustomAttributeLayout(&customLayout[Attr::TerrainType], projectConfig.getMetatileTerrainTypeMask(), maxMask);
|
||||
setCustomAttributeLayout(&customLayout[Attr::EncounterType], projectConfig.getMetatileEncounterTypeMask(), maxMask);
|
||||
setCustomAttributeLayout(&customLayout[Attr::LayerType], projectConfig.getMetatileLayerTypeMask(), maxMask);
|
||||
|
||||
// Set mask for preserving any attribute bits not used by Porymap
|
||||
Metatile::unusedAttrMask = ~(customLayout[Attr::Behavior].mask
|
||||
|
@ -100,13 +110,24 @@ void Metatile::setCustomLayout() {
|
|||
| customLayout[Attr::LayerType].mask);
|
||||
Metatile::unusedAttrMask &= maxMask;
|
||||
|
||||
// Overlapping masks are legal, but probably not intended
|
||||
// Overlapping masks are technically ok, but probably not intended.
|
||||
// Additionally, Porymap will not properly reflect that the values are linked.
|
||||
if (doMasksOverlap({customLayout[Attr::Behavior].mask,
|
||||
customLayout[Attr::TerrainType].mask,
|
||||
customLayout[Attr::EncounterType].mask,
|
||||
customLayout[Attr::LayerType].mask})) {
|
||||
logWarn("Metatile attribute masks are overlapping.");
|
||||
}
|
||||
|
||||
// The available options in the Tileset Editor for Terrain Type, Encounter Type, and Layer Type are hard-coded.
|
||||
// Warn the user if they have set a nonzero mask that is too small to contain these options.
|
||||
// They'll be allowed to select them, but they'll be truncated to a different value when revisited.
|
||||
if (isMaskTooSmall(&customLayout[Attr::TerrainType], NUM_METATILE_TERRAIN_TYPES))
|
||||
logWarn(QString("Metatile Terrain Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_TERRAIN_TYPES));
|
||||
if (isMaskTooSmall(&customLayout[Attr::EncounterType], NUM_METATILE_ENCOUNTER_TYPES))
|
||||
logWarn(QString("Metatile Encounter Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_ENCOUNTER_TYPES));
|
||||
if (isMaskTooSmall(&customLayout[Attr::LayerType], NUM_METATILE_LAYER_TYPES))
|
||||
logWarn(QString("Metatile Layer Type mask is too small to contain all %1 available options.").arg(NUM_METATILE_LAYER_TYPES));
|
||||
}
|
||||
|
||||
uint32_t Metatile::getAttributes() {
|
||||
|
@ -165,3 +186,19 @@ void Metatile::convertAttributes(uint32_t data, BaseGameVersion version) {
|
|||
// Clean data to fit the user's custom masks
|
||||
this->setAttributes(this->getAttributes());
|
||||
}
|
||||
|
||||
void Metatile::setBehavior(uint32_t value) {
|
||||
this->behavior = value & Metatile::customLayout[Attr::Behavior].mask;
|
||||
}
|
||||
|
||||
void Metatile::setTerrainType(uint32_t value) {
|
||||
this->terrainType = value & Metatile::customLayout[Attr::TerrainType].mask;
|
||||
}
|
||||
|
||||
void Metatile::setEncounterType(uint32_t value) {
|
||||
this->encounterType = value & Metatile::customLayout[Attr::EncounterType].mask;
|
||||
}
|
||||
|
||||
void Metatile::setLayerType(uint32_t value) {
|
||||
this->layerType = value & Metatile::customLayout[Attr::LayerType].mask;
|
||||
}
|
||||
|
|
|
@ -635,10 +635,9 @@ int MainWindow::getMetatileLayerType(int metatileId) {
|
|||
|
||||
void MainWindow::setMetatileLayerType(int metatileId, int layerType) {
|
||||
Metatile * metatile = this->getMetatile(metatileId);
|
||||
uint8_t u_layerType = static_cast<uint8_t>(layerType);
|
||||
if (!metatile || metatile->layerType == u_layerType || u_layerType >= NUM_METATILE_LAYER_TYPES)
|
||||
if (!metatile)
|
||||
return;
|
||||
metatile->layerType = u_layerType;
|
||||
metatile->setLayerType(layerType);
|
||||
this->saveMetatileAttributesByMetatileId(metatileId);
|
||||
}
|
||||
|
||||
|
@ -651,10 +650,9 @@ int MainWindow::getMetatileEncounterType(int metatileId) {
|
|||
|
||||
void MainWindow::setMetatileEncounterType(int metatileId, int encounterType) {
|
||||
Metatile * metatile = this->getMetatile(metatileId);
|
||||
uint8_t u_encounterType = static_cast<uint8_t>(encounterType);
|
||||
if (!metatile || metatile->encounterType == u_encounterType || u_encounterType >= NUM_METATILE_ENCOUNTER_TYPES)
|
||||
if (!metatile)
|
||||
return;
|
||||
metatile->encounterType = u_encounterType;
|
||||
metatile->setEncounterType(encounterType);
|
||||
this->saveMetatileAttributesByMetatileId(metatileId);
|
||||
}
|
||||
|
||||
|
@ -667,10 +665,9 @@ int MainWindow::getMetatileTerrainType(int metatileId) {
|
|||
|
||||
void MainWindow::setMetatileTerrainType(int metatileId, int terrainType) {
|
||||
Metatile * metatile = this->getMetatile(metatileId);
|
||||
uint8_t u_terrainType = static_cast<uint8_t>(terrainType);
|
||||
if (!metatile || metatile->terrainType == u_terrainType || u_terrainType >= NUM_METATILE_TERRAIN_TYPES)
|
||||
if (!metatile)
|
||||
return;
|
||||
metatile->terrainType = u_terrainType;
|
||||
metatile->setTerrainType(terrainType);
|
||||
this->saveMetatileAttributesByMetatileId(metatileId);
|
||||
}
|
||||
|
||||
|
@ -683,10 +680,9 @@ int MainWindow::getMetatileBehavior(int metatileId) {
|
|||
|
||||
void MainWindow::setMetatileBehavior(int metatileId, int behavior) {
|
||||
Metatile * metatile = this->getMetatile(metatileId);
|
||||
uint16_t u_behavior = static_cast<uint16_t>(behavior);
|
||||
if (!metatile || metatile->behavior == u_behavior)
|
||||
if (!metatile)
|
||||
return;
|
||||
metatile->behavior = u_behavior;
|
||||
metatile->setBehavior(behavior);
|
||||
this->saveMetatileAttributesByMetatileId(metatileId);
|
||||
}
|
||||
|
||||
|
|
|
@ -99,9 +99,7 @@ void TilesetEditor::initUi() {
|
|||
this->ui->spinBox_paletteSelector->setMinimum(0);
|
||||
this->ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1);
|
||||
|
||||
this->setMetatileBehaviors();
|
||||
this->setMetatileLayersUi();
|
||||
this->setVersionSpecificUi();
|
||||
this->setAttributesUi();
|
||||
this->setMetatileLabelValidator();
|
||||
|
||||
this->initMetatileSelector();
|
||||
|
@ -113,43 +111,55 @@ void TilesetEditor::initUi() {
|
|||
this->restoreWindowState();
|
||||
}
|
||||
|
||||
void TilesetEditor::setMetatileBehaviors() {
|
||||
void TilesetEditor::setAttributesUi() {
|
||||
// Behavior
|
||||
if (Metatile::customLayout[Metatile::Attr::Behavior].mask) {
|
||||
for (int num : project->metatileBehaviorMapInverse.keys()) {
|
||||
this->ui->comboBox_metatileBehaviors->addItem(project->metatileBehaviorMapInverse[num], num);
|
||||
}
|
||||
}
|
||||
|
||||
void TilesetEditor::setMetatileLayersUi() {
|
||||
if (!projectConfig.getTripleLayerMetatilesEnabled()) {
|
||||
this->ui->comboBox_layerType->addItem("Normal - Middle/Top", METATILE_LAYER_MIDDLE_TOP);
|
||||
this->ui->comboBox_layerType->addItem("Covered - Bottom/Middle", METATILE_LAYER_BOTTOM_MIDDLE);
|
||||
this->ui->comboBox_layerType->addItem("Split - Bottom/Top", METATILE_LAYER_BOTTOM_TOP);
|
||||
} else {
|
||||
this->ui->comboBox_layerType->setVisible(false);
|
||||
this->ui->label_layerType->setVisible(false);
|
||||
this->ui->label_BottomTop->setText("Bottom/Middle/Top");
|
||||
}
|
||||
this->ui->comboBox_metatileBehaviors->setVisible(false);
|
||||
this->ui->label_metatileBehavior->setVisible(false);
|
||||
}
|
||||
|
||||
void TilesetEditor::setVersionSpecificUi() {
|
||||
if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
|
||||
this->ui->comboBox_encounterType->setVisible(true);
|
||||
this->ui->label_encounterType->setVisible(true);
|
||||
this->ui->comboBox_encounterType->addItem("None", ENCOUNTER_NONE);
|
||||
this->ui->comboBox_encounterType->addItem("Land", ENCOUNTER_LAND);
|
||||
this->ui->comboBox_encounterType->addItem("Water", ENCOUNTER_WATER);
|
||||
this->ui->comboBox_terrainType->setVisible(true);
|
||||
this->ui->label_terrainType->setVisible(true);
|
||||
// Terrain Type
|
||||
if (Metatile::customLayout[Metatile::Attr::TerrainType].mask) {
|
||||
this->ui->comboBox_terrainType->addItem("Normal", TERRAIN_NONE);
|
||||
this->ui->comboBox_terrainType->addItem("Grass", TERRAIN_GRASS);
|
||||
this->ui->comboBox_terrainType->addItem("Water", TERRAIN_WATER);
|
||||
this->ui->comboBox_terrainType->addItem("Waterfall", TERRAIN_WATERFALL);
|
||||
} else {
|
||||
this->ui->comboBox_encounterType->setVisible(false);
|
||||
this->ui->label_encounterType->setVisible(false);
|
||||
this->ui->comboBox_terrainType->setVisible(false);
|
||||
this->ui->label_terrainType->setVisible(false);
|
||||
}
|
||||
|
||||
// Encounter Type
|
||||
if (Metatile::customLayout[Metatile::Attr::EncounterType].mask) {
|
||||
this->ui->comboBox_encounterType->addItem("None", ENCOUNTER_NONE);
|
||||
this->ui->comboBox_encounterType->addItem("Land", ENCOUNTER_LAND);
|
||||
this->ui->comboBox_encounterType->addItem("Water", ENCOUNTER_WATER);
|
||||
} else {
|
||||
this->ui->comboBox_encounterType->setVisible(false);
|
||||
this->ui->label_encounterType->setVisible(false);
|
||||
}
|
||||
|
||||
// Layer Type
|
||||
if (!projectConfig.getTripleLayerMetatilesEnabled()) {
|
||||
this->ui->comboBox_layerType->addItem("Normal - Middle/Top", METATILE_LAYER_MIDDLE_TOP);
|
||||
this->ui->comboBox_layerType->addItem("Covered - Bottom/Middle", METATILE_LAYER_BOTTOM_MIDDLE);
|
||||
this->ui->comboBox_layerType->addItem("Split - Bottom/Top", METATILE_LAYER_BOTTOM_TOP);
|
||||
if (!Metatile::customLayout[Metatile::Attr::LayerType].mask) {
|
||||
// User doesn't have triple layer metatiles, but has no layer type attribute.
|
||||
// Porymap is still using the layer type value to render metatiles, and with
|
||||
// no mask set every metatile will be "Middle/Top", so just display the combo
|
||||
// box but prevent the user from changing the value.
|
||||
this->ui->comboBox_layerType->setEnabled(false);
|
||||
}
|
||||
} else {
|
||||
this->ui->comboBox_layerType->setVisible(false);
|
||||
this->ui->label_layerType->setVisible(false);
|
||||
this->ui->label_BottomTop->setText("Bottom/Middle/Top");
|
||||
}
|
||||
}
|
||||
|
||||
void TilesetEditor::setMetatileLabelValidator() {
|
||||
|
@ -373,14 +383,10 @@ void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) {
|
|||
this->ui->graphicsView_metatileLayers->setFixedSize(this->metatileLayersItem->pixmap().width() + 2, this->metatileLayersItem->pixmap().height() + 2);
|
||||
this->ui->lineEdit_metatileLabel->setText(this->metatile->label);
|
||||
setComboValue(this->ui->comboBox_metatileBehaviors, this->metatile->behavior);
|
||||
if (!projectConfig.getTripleLayerMetatilesEnabled()) {
|
||||
setComboValue(this->ui->comboBox_layerType, this->metatile->layerType);
|
||||
}
|
||||
if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
|
||||
setComboValue(this->ui->comboBox_encounterType, this->metatile->encounterType);
|
||||
setComboValue(this->ui->comboBox_terrainType, this->metatile->terrainType);
|
||||
}
|
||||
}
|
||||
|
||||
void TilesetEditor::onHoveredTileChanged(uint16_t tile) {
|
||||
QString message = QString("Tile: 0x%1")
|
||||
|
@ -501,7 +507,7 @@ void TilesetEditor::on_comboBox_metatileBehaviors_textActivated(const QString &m
|
|||
{
|
||||
if (this->metatile) {
|
||||
Metatile *prevMetatile = new Metatile(*this->metatile);
|
||||
this->metatile->behavior = static_cast<uint16_t>(project->metatileBehaviorMap[metatileBehavior]);
|
||||
this->metatile->setBehavior(project->metatileBehaviorMap[metatileBehavior]);
|
||||
MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(),
|
||||
prevMetatile, new Metatile(*this->metatile));
|
||||
metatileHistory.push(commit);
|
||||
|
@ -537,7 +543,7 @@ void TilesetEditor::on_comboBox_layerType_activated(int layerType)
|
|||
{
|
||||
if (this->metatile) {
|
||||
Metatile *prevMetatile = new Metatile(*this->metatile);
|
||||
this->metatile->layerType = static_cast<uint8_t>(layerType);
|
||||
this->metatile->setLayerType(layerType);
|
||||
MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(),
|
||||
prevMetatile, new Metatile(*this->metatile));
|
||||
metatileHistory.push(commit);
|
||||
|
@ -550,7 +556,7 @@ void TilesetEditor::on_comboBox_encounterType_activated(int encounterType)
|
|||
{
|
||||
if (this->metatile) {
|
||||
Metatile *prevMetatile = new Metatile(*this->metatile);
|
||||
this->metatile->encounterType = static_cast<uint8_t>(encounterType);
|
||||
this->metatile->setEncounterType(encounterType);
|
||||
MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(),
|
||||
prevMetatile, new Metatile(*this->metatile));
|
||||
metatileHistory.push(commit);
|
||||
|
@ -562,7 +568,7 @@ void TilesetEditor::on_comboBox_terrainType_activated(int terrainType)
|
|||
{
|
||||
if (this->metatile) {
|
||||
Metatile *prevMetatile = new Metatile(*this->metatile);
|
||||
this->metatile->terrainType = static_cast<uint8_t>(terrainType);
|
||||
this->metatile->setTerrainType(terrainType);
|
||||
MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(),
|
||||
prevMetatile, new Metatile(*this->metatile));
|
||||
metatileHistory.push(commit);
|
||||
|
|
Loading…
Reference in a new issue