diff --git a/include/core/metatile.h b/include/core/metatile.h index f606dcdb..49fb5c7b 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -70,7 +70,6 @@ public: uint32_t encounterType; uint32_t layerType; uint32_t unusedAttributes; - QString label; uint32_t getAttributes(); void setAttributes(uint32_t data); @@ -121,7 +120,6 @@ inline bool operator==(const Metatile &a, const Metatile &b) { a.encounterType == b.encounterType && a.terrainType == b.terrainType && a.unusedAttributes == b.unusedAttributes && - a.label == b.label && a.tiles == b.tiles; } diff --git a/include/core/tileset.h b/include/core/tileset.h index 2c6d658e..c25cbc62 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -5,6 +5,7 @@ #include "metatile.h" #include "tile.h" #include +#include class Tileset { @@ -28,12 +29,15 @@ public: QList tiles; QList metatiles; + QHash metatileLabels; QList> palettes; QList> palettePreviews; static Tileset* getMetatileTileset(int, Tileset*, Tileset*); static Tileset* getTileTileset(int, Tileset*, Tileset*); static Metatile* getMetatile(int, Tileset*, Tileset*); + static QString getMetatileLabel(int, Tileset *, Tileset *, bool * isAlternateLabel = nullptr); + static bool setMetatileLabel(int, QString, Tileset *, Tileset *); static QList> getBlockPalettes(Tileset*, Tileset*, bool useTruePalettes = false); static QList getPalette(int, Tileset*, Tileset*, bool useTruePalettes = false); static bool metatileIsValid(uint16_t metatileId, Tileset *, Tileset *); diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index fcdd3bed..5501c8f8 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -127,7 +127,7 @@ private: void importTilesetTiles(Tileset*, bool); void importTilesetMetatiles(Tileset*, bool); void refresh(); - void saveMetatileLabel(); + void commitMetatileLabel(); void closeEvent(QCloseEvent*); void countMetatileUsage(); void countTileUsage(); @@ -146,6 +146,7 @@ private: Map *map = nullptr; Metatile *metatile = nullptr; Metatile *copiedMetatile = nullptr; + QString copiedMetatileLabel; int paletteId; bool tileXFlip; bool tileYFlip; diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index e3d00d8c..33ca9272 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -20,6 +20,7 @@ Tileset::Tileset(const Tileset &other) tilesImagePath(other.tilesImagePath), tilesImage(other.tilesImage.copy()), palettePaths(other.palettePaths), + metatileLabels(other.metatileLabels), palettes(other.palettes), palettePreviews(other.palettePreviews) { @@ -44,6 +45,7 @@ Tileset &Tileset::operator=(const Tileset &other) { tilesImagePath = other.tilesImagePath; tilesImage = other.tilesImage.copy(); palettePaths = other.palettePaths; + metatileLabels = other.metatileLabels; palettes = other.palettes; palettePreviews = other.palettePreviews; @@ -82,13 +84,52 @@ Tileset* Tileset::getMetatileTileset(int metatileId, Tileset *primaryTileset, Ti Metatile* Tileset::getMetatile(int metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) { Tileset *tileset = Tileset::getMetatileTileset(metatileId, primaryTileset, secondaryTileset); - int index = Metatile::getIndexInTileset(metatileId); if (!tileset) { return nullptr; } + int index = Metatile::getIndexInTileset(metatileId); return tileset->metatiles.value(index, nullptr); } +// Metatile labels are stored per-tileset. When looking for a metatile label, first search in the tileset +// that the metatile belongs to. If one isn't found, search in the other tileset. Labels coming from the +// tileset that the metatile does not belong to cannot be edited via Porymap. +QString Tileset::getMetatileLabel(int metatileId, Tileset *primaryTileset, Tileset *secondaryTileset, bool * isAlternateLabel) { + Tileset *mainTileset = nullptr; + Tileset *backupTileset = nullptr; + if (isAlternateLabel) *isAlternateLabel = false; + if (metatileId < Project::getNumMetatilesPrimary()) { + mainTileset = primaryTileset; + backupTileset = secondaryTileset; + } else if (metatileId < Project::getNumMetatilesTotal()) { + mainTileset = secondaryTileset; + backupTileset = primaryTileset; + } + + if (mainTileset && mainTileset->metatileLabels.contains(metatileId)) { + return mainTileset->metatileLabels.value(metatileId); + } else if (backupTileset && backupTileset->metatileLabels.contains(metatileId)) { + if (isAlternateLabel) *isAlternateLabel = true; + return backupTileset->metatileLabels.value(metatileId); + } + return QString(); +} + +bool Tileset::setMetatileLabel(int metatileId, QString label, Tileset *primaryTileset, Tileset *secondaryTileset) { + Tileset *tileset = Tileset::getMetatileTileset(metatileId, primaryTileset, secondaryTileset); + if (!tileset) + return false; + + static const QRegularExpression expression("[_A-Za-z0-9]*$"); + QRegularExpressionValidator validator(expression); + int pos = 0; + if (validator.validate(label, pos) != QValidator::Acceptable) + return false; + + tileset->metatileLabels[metatileId] = label; + return true; +} + bool Tileset::metatileIsValid(uint16_t metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) { if (metatileId >= Project::getNumMetatilesTotal()) return false; diff --git a/src/editor.cpp b/src/editor.cpp index 3f5b2e1a..c06f2589 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -932,14 +932,13 @@ void Editor::onHoveredMovementPermissionCleared() { QString Editor::getMetatileDisplayMessage(uint16_t metatileId) { Metatile *metatile = Tileset::getMetatile(metatileId, map->layout->tileset_primary, map->layout->tileset_secondary); + QString label = Tileset::getMetatileLabel(metatileId, map->layout->tileset_primary, map->layout->tileset_secondary); QString hexString = QString("%1").arg(metatileId, 3, 16, QChar('0')).toUpper(); QString message = QString("Metatile: 0x%1").arg(hexString); - if (metatile) { - if (metatile->label.size()) - message += QString(" \"%1\"").arg(metatile->label); - if (metatile->behavior) // Skip MB_NORMAL - message += QString(", Behavior: %1").arg(this->project->metatileBehaviorMapInverse.value(metatile->behavior, QString::number(metatile->behavior))); - } + 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))); return message; } diff --git a/src/project.cpp b/src/project.cpp index a16c5038..23fce463 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -913,21 +913,15 @@ void Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *second } // Add the new labels. - for (int i = 0; i < primaryTileset->metatiles.size(); i++) { - Metatile *metatile = primaryTileset->metatiles.at(i); - if (metatile->label.size() != 0) { - QString defineName = QString("%1%2").arg(primaryPrefix, metatile->label); - defines.insert(defineName, i); - definesFileModified = true; - } + for (int metatileId : primaryTileset->metatileLabels.keys()) { + QString defineName = QString("%1%2").arg(primaryPrefix, primaryTileset->metatileLabels.value(metatileId)); + defines.insert(defineName, metatileId); + definesFileModified = true; } - for (int i = 0; i < secondaryTileset->metatiles.size(); i++) { - Metatile *metatile = secondaryTileset->metatiles.at(i); - if (metatile->label.size() != 0) { - QString defineName = QString("%1%2").arg(secondaryPrefix, metatile->label); - defines.insert(defineName, i + Project::num_tiles_primary); - definesFileModified = true; - } + for (int metatileId : secondaryTileset->metatileLabels.keys()) { + QString defineName = QString("%1%2").arg(secondaryPrefix, secondaryTileset->metatileLabels.value(metatileId)); + defines.insert(defineName, metatileId); + definesFileModified = true; } if (!definesFileModified) { @@ -1521,17 +1515,10 @@ bool Project::readTilesetMetatileLabels() { void Project::loadTilesetMetatileLabels(Tileset* tileset) { QString tilesetPrefix = QString("METATILE_%1_").arg(QString(tileset->name).replace("gTileset_", "")); + // Reverse map for faster lookup by metatile id for (QString labelName : metatileLabelsMap[tileset->name].keys()) { int metatileId = metatileLabelsMap[tileset->name][labelName]; - // subtract Project::num_tiles_primary from secondary metatiles - int offset = tileset->is_secondary ? Project::num_tiles_primary : 0; - Metatile *metatile = Tileset::getMetatile(metatileId - offset, tileset, nullptr); - if (metatile) { - metatile->label = labelName.replace(tilesetPrefix, ""); - } else { - QString hexString = QString("%1").arg(metatileId, 3, 16, QChar('0')).toUpper(); - logError(QString("Metatile 0x%1 (%2) cannot be found in tileset '%3'").arg(hexString, labelName, tileset->name)); - } + tileset->metatileLabels[metatileId] = labelName.replace(tilesetPrefix, ""); } } diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 046474de..4baa83f9 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -598,32 +598,30 @@ Metatile * MainWindow::getMetatile(int metatileId) { } QString MainWindow::getMetatileLabel(int metatileId) { - Metatile * metatile = this->getMetatile(metatileId); - if (!metatile || metatile->label.size() == 0) + if (!this->editor || !this->editor->map || !this->editor->map->layout) return QString(); - return metatile->label; + return Tileset::getMetatileLabel(metatileId, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); } void MainWindow::setMetatileLabel(int metatileId, QString label) { - Metatile * metatile = this->getMetatile(metatileId); - if (!metatile) + if (!this->editor || !this->editor->map || !this->editor->map->layout) return; - static const QRegularExpression expression("[_A-Za-z0-9]*$"); - QRegularExpressionValidator validator(expression); - int pos = 0; - if (validator.validate(label, pos) != QValidator::Acceptable) { - logError(QString("Invalid metatile label %1").arg(label)); - return; - } - - if (this->tilesetEditor && this->tilesetEditor->getSelectedMetatileId() == metatileId) { + // If the Tileset Editor is opened on this metatile we need to update the text box + if (this->tilesetEditor && this->tilesetEditor->getSelectedMetatileId() == metatileId){ this->tilesetEditor->setMetatileLabel(label); - } else if (metatile->label != label) { - metatile->label = label; - if (this->editor->project) - this->editor->project->saveTilesetMetatileLabels(this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + return; } + + if (!Tileset::setMetatileLabel(metatileId, label, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary)) { + logError("Failed to set metatile label. Must be a valid metatile id and a label containing only letters, numbers, and underscores."); + return; + } + + // The user may not have the Tileset Editor open. This forcefully saves the change for them. + // If they do have the Tileset Editor open, this has the unintended side effect of saving other unsaved label changes. + if (this->editor->project) + this->editor->project->saveTilesetMetatileLabels(this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); } int MainWindow::getMetatileLayerType(int metatileId) { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 7f5d7695..a5352fd3 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -343,13 +343,11 @@ void TilesetEditor::drawSelectedTiles() { } void TilesetEditor::onHoveredMetatileChanged(uint16_t metatileId) { - Metatile *metatile = Tileset::getMetatile(metatileId, this->primaryTileset, this->secondaryTileset); - QString message; + QString label = Tileset::getMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset); QString hexString = QString("%1").arg(metatileId, 3, 16, QChar('0')).toUpper(); - if (metatile && metatile->label.size() != 0) { - message = QString("Metatile: 0x%1 \"%2\"").arg(hexString, metatile->label); - } else { - message = QString("Metatile: 0x%1").arg(hexString); + QString message = QString("Metatile: 0x%1").arg(hexString); + if (label.size() != 0) { + message += QString(" \"%1\"").arg(label); } this->ui->statusbar->showMessage(message); } @@ -381,7 +379,12 @@ void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) { this->metatileLayersItem->setMetatile(metatile); this->metatileLayersItem->draw(); this->ui->graphicsView_metatileLayers->setFixedSize(this->metatileLayersItem->pixmap().width() + 2, this->metatileLayersItem->pixmap().height() + 2); - this->ui->lineEdit_metatileLabel->setText(this->metatile->label); + + bool isAlternateLabel = false; + QString label = Tileset::getMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset, &isAlternateLabel); + this->ui->lineEdit_metatileLabel->setText(label); + this->ui->lineEdit_metatileLabel->setReadOnly(isAlternateLabel); + setComboValue(this->ui->comboBox_metatileBehaviors, this->metatile->behavior); setComboValue(this->ui->comboBox_layerType, this->metatile->layerType); setComboValue(this->ui->comboBox_encounterType, this->metatile->encounterType); @@ -532,24 +535,31 @@ void TilesetEditor::on_comboBox_metatileBehaviors_currentTextChanged(const QStri void TilesetEditor::setMetatileLabel(QString label) { + if (this->ui->lineEdit_metatileLabel->isReadOnly()) + return; this->ui->lineEdit_metatileLabel->setText(label); - saveMetatileLabel(); + commitMetatileLabel(); } void TilesetEditor::on_lineEdit_metatileLabel_editingFinished() { - saveMetatileLabel(); + commitMetatileLabel(); } -void TilesetEditor::saveMetatileLabel() +void TilesetEditor::commitMetatileLabel() { + // TODO: Reimplement edit history for labels + // Only commit if the field has changed. - if (this->metatile && this->metatile->label != this->ui->lineEdit_metatileLabel->text()) { - Metatile *prevMetatile = new Metatile(*this->metatile); - this->metatile->label = this->ui->lineEdit_metatileLabel->text(); - MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(), + uint16_t metatileId = this->getSelectedMetatileId(); + QString currentLabel = Tileset::getMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset); + QString newLabel = this->ui->lineEdit_metatileLabel->text(); + if (currentLabel != newLabel) { + //Metatile *prevMetatile = new Metatile(*this->metatile); + Tileset::setMetatileLabel(metatileId, newLabel, this->primaryTileset, this->secondaryTileset); + /*MetatileHistoryItem *commit = new MetatileHistoryItem(this->getSelectedMetatileId(), prevMetatile, new Metatile(*this->metatile)); - metatileHistory.push(commit); + metatileHistory.push(commit);*/ this->hasUnsavedChanges = true; } } @@ -597,8 +607,6 @@ void TilesetEditor::on_actionSave_Tileset_triggered() // when the tilesetsSaved signal is sent, it will be reset to the current map metatile uint16_t reselectMetatileID = this->metatileSelector->getSelectedMetatileId(); - saveMetatileLabel(); - this->project->saveTilesets(this->primaryTileset, this->secondaryTileset); emit this->tilesetsSaved(this->primaryTileset->name, this->secondaryTileset->name); this->metatileSelector->select(reselectMetatileID); @@ -882,6 +890,7 @@ void TilesetEditor::on_actionCut_triggered() Metatile * empty = new Metatile(projectConfig.getNumTilesInMetatile()); this->copyMetatile(true); this->pasteMetatile(empty); + this->setMetatileLabel(""); delete empty; } @@ -893,17 +902,27 @@ void TilesetEditor::on_actionCopy_triggered() void TilesetEditor::on_actionPaste_triggered() { this->pasteMetatile(this->copiedMetatile); + this->setMetatileLabel(this->copiedMetatileLabel); } void TilesetEditor::copyMetatile(bool cut) { - Metatile * toCopy = Tileset::getMetatile(this->getSelectedMetatileId(), this->primaryTileset, this->secondaryTileset); + uint16_t metatileId = this->getSelectedMetatileId(); + Metatile * toCopy = Tileset::getMetatile(metatileId, this->primaryTileset, this->secondaryTileset); if (!toCopy) return; if (!this->copiedMetatile) this->copiedMetatile = new Metatile(*toCopy); else *this->copiedMetatile = *toCopy; - if (!cut) this->copiedMetatile->label = ""; // Don't copy the label unless it's a cut, these should be unique to each metatile + + // Don't try to copy the label unless it's a cut, these should be unique to each metatile + this->copiedMetatileLabel = ""; + if (cut) { + bool isAlternateLabel = false; + QString label = Tileset::getMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset, &isAlternateLabel); + if (!isAlternateLabel) + this->copiedMetatileLabel = label; + } } void TilesetEditor::pasteMetatile(const Metatile * toPaste) @@ -1146,6 +1165,7 @@ void TilesetEditor::countTileUsage() { } void TilesetEditor::on_copyButton_metatileLabel_clicked() { + // TODO: Handle alternate labels QString label = this->ui->lineEdit_metatileLabel->text(); if (label.isEmpty()) return; Tileset * tileset = Tileset::getMetatileTileset(this->getSelectedMetatileId(), this->primaryTileset, this->secondaryTileset);