From 05c07e5a007a64f39743f0d6ed1a0b87d5cb18bf Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 5 Feb 2025 13:48:38 -0500 Subject: [PATCH] Keep metatile images on separate rows, fix metatile usage count --- CHANGELOG.md | 2 + include/ui/metatileselector.h | 1 + include/ui/tileseteditormetatileselector.h | 1 + src/project.cpp | 10 +- src/ui/metatileselector.cpp | 16 ++- src/ui/tileseteditor.cpp | 33 +++--- src/ui/tileseteditormetatileselector.cpp | 112 ++++++++++----------- 7 files changed, 85 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b225d6..41e7f5d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Unrecognized map names in Event or Connections data will no longer be overwritten. - Map names and ``MAP_NAME`` constants are no longer required to match. - Porymap will no longer overwrite ``include/constants/map_groups.h`` or ``include/constants/layouts.h``. +- Primary/secondary metatile images are now kept on separate rows, rather than blending together if the primary size is not divisible by 8. ### Fixed - Fix `Add Region Map...` not updating the region map settings file. @@ -76,6 +77,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix some problems with tileset detection when importing maps from AdvanceMap. - Fix certain input fields allowing invalid identifiers, like names starting with numbers. - Fix crash in the Shortcuts Editor when applying changes after closing certain windows. +- Fix `Display Metatile Usage Counts` sometimes changing the counts after repeated use. ## [5.4.1] - 2024-03-21 ### Fixed diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h index ae11c58e..fba0993d 100644 --- a/include/ui/metatileselector.h +++ b/include/ui/metatileselector.h @@ -79,6 +79,7 @@ private: bool positionIsValid(const QPoint &pos) const; bool selectionIsValid(); void hoverChanged(); + int numPrimaryMetatilesRounded() const; signals: void hoveredMetatileSelectionChanged(uint16_t); diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index 760da8e4..afc77ffe 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -51,6 +51,7 @@ private: void drawCounts(); QImage buildAllMetatilesImage(); QImage buildImage(int metatileIdStart, int numMetatiles); + int numPrimaryMetatilesRounded() const; signals: void hoveredMetatileChanged(uint16_t); diff --git a/src/project.cpp b/src/project.cpp index 712ee87f..5912ad4c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1056,7 +1056,7 @@ bool Project::loadLayoutTilesets(Layout *layout) { layout->tileset_primary = getTileset(layout->tileset_primary_label); if (!layout->tileset_primary) { QString defaultTileset = this->getDefaultPrimaryTilesetLabel(); - logWarn(QString("Map layout %1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->id).arg(layout->tileset_primary_label).arg(defaultTileset)); + logWarn(QString("%1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_primary_label).arg(defaultTileset)); layout->tileset_primary_label = defaultTileset; layout->tileset_primary = getTileset(layout->tileset_primary_label); if (!layout->tileset_primary) { @@ -1068,7 +1068,7 @@ bool Project::loadLayoutTilesets(Layout *layout) { layout->tileset_secondary = getTileset(layout->tileset_secondary_label); if (!layout->tileset_secondary) { QString defaultTileset = this->getDefaultSecondaryTilesetLabel(); - logWarn(QString("Map layout %1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->id).arg(layout->tileset_secondary_label).arg(defaultTileset)); + logWarn(QString("%1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_secondary_label).arg(defaultTileset)); layout->tileset_secondary_label = defaultTileset; layout->tileset_secondary = getTileset(layout->tileset_secondary_label); if (!layout->tileset_secondary) { @@ -1137,7 +1137,8 @@ bool Project::loadBlockdata(Layout *layout) { layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); if (layout->blockdata.count() != layout->getWidth() * layout->getHeight()) { - logWarn(QString("Layout blockdata length %1 does not match dimensions %2x%3 (should be %4). Resizing blockdata.") + logWarn(QString("%1 blockdata length %2 does not match dimensions %3x%4 (should be %5). Resizing blockdata.") + .arg(layout->name) .arg(layout->blockdata.count()) .arg(layout->getWidth()) .arg(layout->getHeight()) @@ -1174,7 +1175,8 @@ bool Project::loadLayoutBorder(Layout *layout) { int borderLength = layout->getBorderWidth() * layout->getBorderHeight(); if (layout->border.count() != borderLength) { - logWarn(QString("Layout border blockdata length %1 must be %2. Resizing border blockdata.") + logWarn(QString("%1 border blockdata length %2 must be %3. Resizing border blockdata.") + .arg(layout->name) .arg(layout->border.count()) .arg(borderLength)); layout->border.resize(borderLength); diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp index 1214e289..b8bafefc 100644 --- a/src/ui/metatileselector.cpp +++ b/src/ui/metatileselector.cpp @@ -9,12 +9,17 @@ QPoint MetatileSelector::getSelectionDimensions() { return SelectablePixmapItem::getSelectionDimensions(); } +int MetatileSelector::numPrimaryMetatilesRounded() const { + // We round up the number of primary metatiles to keep the tilesets on separate rows. + return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide; +} + void MetatileSelector::draw() { if (!this->primaryTileset || !this->secondaryTileset) { this->setPixmap(QPixmap()); } - int primaryLength = this->primaryTileset->numMetatiles(); + int primaryLength = this->numPrimaryMetatilesRounded(); int length_ = primaryLength + this->secondaryTileset->numMetatiles(); int height_ = length_ / this->numMetatilesWide; if (length_ % this->numMetatilesWide != 0) { @@ -149,7 +154,7 @@ void MetatileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { void MetatileSelector::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { QPoint pos = this->getCellPos(event->pos()); - if (!positionIsValid(pos) || this->cellPos == pos) + if (this->cellPos == pos) return; this->cellPos = pos; @@ -199,10 +204,11 @@ void MetatileSelector::updateExternalSelectedMetatiles() { uint16_t MetatileSelector::getMetatileId(int x, int y) const { int index = y * this->numMetatilesWide + x; - if (index < this->primaryTileset->numMetatiles()) { + int numPrimary = this->numPrimaryMetatilesRounded(); + if (index < numPrimary) { return static_cast(index); } else { - return static_cast(Project::getNumMetatilesPrimary() + index - this->primaryTileset->numMetatiles()); + return static_cast(Project::getNumMetatilesPrimary() + index - numPrimary); } } @@ -215,7 +221,7 @@ QPoint MetatileSelector::getMetatileIdCoords(uint16_t metatileId) { int index = metatileId < Project::getNumMetatilesPrimary() ? metatileId - : metatileId - Project::getNumMetatilesPrimary() + this->primaryTileset->numMetatiles(); + : metatileId - Project::getNumMetatilesPrimary() + this->numPrimaryMetatilesRounded(); return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index e05750dc..23942a8a 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -29,13 +29,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) this->tileYFlip = ui->checkBox_yFlip->isChecked(); this->paletteId = ui->spinBox_paletteSelector->value(); - // TODO: The dividing line at the moment is only accurate if the number of primary metatiles is divisible by 8. - // If it's not, the secondary metatiles will wrap above the line. This has other problems (like skewing - // metatile groups the user may have designed) so this should be fixed by filling the primary metatiles - // image with invalid magenta metatiles until it's divisible by 8. Then the line can be re-enabled as-is. - ui->actionShow_Tileset_Divider->setChecked(/*porymapConfig.showTilesetEditorDivider*/false); - ui->actionShow_Tileset_Divider->setVisible(false); - + ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider); ui->spinBox_paletteSelector->setMinimum(0); ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); ui->lineEdit_metatileLabel->setValidator(new IdentifierValidator(this)); @@ -759,7 +753,7 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered() { QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); dialog.setWindowTitle("Change Number of Metatiles"); - dialog.setWindowModality(Qt::NonModal); + dialog.setWindowModality(Qt::WindowModal); QFormLayout form(&dialog); @@ -1051,20 +1045,19 @@ void TilesetEditor::countMetatileUsage() { // do not double count metatileSelector->usedMetatiles.fill(0); - for (auto layout : this->project->mapLayouts.values()) { - bool usesPrimary = false; - bool usesSecondary = false; + for (auto layout : this->project->mapLayouts) { + // It's possible for a layout's tileset labels to change if they are invalid, + // so we need to load all the tilesets even if they aren't the tileset we're looking for. + // Otherwise the metatile usage counts may change because the layouts with invalid tilesets + // were updated to use a tileset we were looking for. + this->project->loadLayoutTilesets(layout); - if (layout->tileset_primary_label == this->primaryTileset->name) { - usesPrimary = true; - } - - if (layout->tileset_secondary_label == this->secondaryTileset->name) { - usesSecondary = true; - } + bool usesPrimary = (layout->tileset_primary_label == this->primaryTileset->name); + bool usesSecondary = (layout->tileset_secondary_label == this->secondaryTileset->name); if (usesPrimary || usesSecondary) { - this->project->loadLayout(layout); + if (!this->project->loadLayout(layout)) + continue; // for each block in the layout, mark in the vector that it is used for (int i = 0; i < layout->blockdata.length(); i++) { @@ -1097,9 +1090,9 @@ void TilesetEditor::countTileUsage() { QSet secondaryTilesets; for (auto layout : this->project->mapLayouts.values()) { + this->project->loadLayoutTilesets(layout); if (layout->tileset_primary_label == this->primaryTileset->name || layout->tileset_secondary_label == this->secondaryTileset->name) { - this->project->loadLayoutTilesets(layout); // need to check metatiles if (layout->tileset_primary && layout->tileset_secondary) { primaryTilesets.insert(layout->tileset_primary); diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index 6175a923..07e50bba 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -22,11 +22,16 @@ int TilesetEditorMetatileSelector::numRows(int numMetatiles) { } int TilesetEditorMetatileSelector::numRows() { - return this->numRows(this->primaryTileset->numMetatiles() + this->secondaryTileset->numMetatiles()); + return this->numRows(this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles()); +} + +int TilesetEditorMetatileSelector::numPrimaryMetatilesRounded() const { + // We round up the number of primary metatiles to keep the tilesets on separate rows. + return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide; } QImage TilesetEditorMetatileSelector::buildAllMetatilesImage() { - return this->buildImage(0, this->primaryTileset->numMetatiles() + this->secondaryTileset->numMetatiles()); + return this->buildImage(0, this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles()); } QImage TilesetEditorMetatileSelector::buildPrimaryMetatilesImage() { @@ -39,11 +44,11 @@ QImage TilesetEditorMetatileSelector::buildSecondaryMetatilesImage() { QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMetatiles) { int numMetatilesHigh = this->numRows(numMetatiles); - int numPrimary = this->primaryTileset->numMetatiles(); + int numPrimary = this->numPrimaryMetatilesRounded(); int maxPrimary = Project::getNumMetatilesPrimary(); bool includesPrimary = metatileIdStart < maxPrimary; - QImage image(this->numMetatilesWide * 32, numMetatilesHigh * 32, QImage::Format_RGBA8888); + QImage image(this->numMetatilesWide * this->cellWidth, numMetatilesHigh * this->cellHeight, QImage::Format_RGBA8888); image.fill(Qt::magenta); QPainter painter(&image); for (int i = 0; i < numMetatiles; i++) { @@ -57,10 +62,10 @@ QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMet this->layout->metatileLayerOrder, this->layout->metatileLayerOpacity, true) - .scaled(32, 32); + .scaled(this->cellWidth, this->cellHeight); int map_y = i / this->numMetatilesWide; int map_x = i % this->numMetatilesWide; - QPoint metatile_origin = QPoint(map_x * 32, map_y * 32); + QPoint metatile_origin = QPoint(map_x * this->cellWidth, map_y * this->cellHeight); painter.drawImage(metatile_origin, metatile_image); } painter.end(); @@ -107,10 +112,11 @@ uint16_t TilesetEditorMetatileSelector::getSelectedMetatileId() { uint16_t TilesetEditorMetatileSelector::getMetatileId(int x, int y) { int index = y * this->numMetatilesWide + x; - if (index < this->primaryTileset->numMetatiles()) { + int numPrimary = numPrimaryMetatilesRounded(); + if (index < numPrimary) { return static_cast(index); } else { - return static_cast(Project::getNumMetatilesPrimary() + index - this->primaryTileset->numMetatiles()); + return static_cast(Project::getNumMetatilesPrimary() + index - numPrimary); } } @@ -156,7 +162,7 @@ QPoint TilesetEditorMetatileSelector::getMetatileIdCoords(uint16_t metatileId) { } int index = metatileId < Project::getNumMetatilesPrimary() ? metatileId - : metatileId - Project::getNumMetatilesPrimary() + this->primaryTileset->numMetatiles(); + : metatileId - Project::getNumMetatilesPrimary() + this->numPrimaryMetatilesRounded(); return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); } @@ -176,12 +182,12 @@ void TilesetEditorMetatileSelector::drawGrid() { const int numColumns = this->numMetatilesWide; const int numRows = this->numRows(); for (int column = 1; column < numColumns; column++) { - int x = column * 32; - painter.drawLine(x, 0, x, numRows * 32); + int x = column * this->cellWidth; + painter.drawLine(x, 0, x, numRows * this->cellHeight); } for (int row = 1; row < numRows; row++) { - int y = row * 32; - painter.drawLine(0, y, numColumns * 32, y); + int y = row * this->cellHeight; + painter.drawLine(0, y, numColumns * this->cellWidth, y); } painter.end(); this->setPixmap(pixmap); @@ -191,12 +197,12 @@ void TilesetEditorMetatileSelector::drawDivider() { if (!this->showDivider) return; - const int y = this->numRows(this->primaryTileset->numMetatiles()) * 32; + const int y = this->numRows(this->numPrimaryMetatilesRounded()) * this->cellHeight; QPixmap pixmap = this->pixmap(); QPainter painter(&pixmap); painter.setPen(Qt::white); - painter.drawLine(0, y, this->numMetatilesWide * 32, y); + painter.drawLine(0, y, this->numMetatilesWide * this->cellWidth, y); painter.end(); this->setPixmap(pixmap); } @@ -212,7 +218,7 @@ void TilesetEditorMetatileSelector::drawFilters() { void TilesetEditorMetatileSelector::drawUnused() { // setup the circle with a line through it image to layer above unused metatiles - QPixmap redX(32, 32); + QPixmap redX(this->cellWidth, this->cellHeight); redX.fill(Qt::transparent); QPen whitePen(Qt::white); @@ -223,21 +229,21 @@ void TilesetEditorMetatileSelector::drawUnused() { QPainter oPainter(&redX); oPainter.setPen(whitePen); - oPainter.drawEllipse(QRect(1, 1, 30, 30)); + oPainter.drawEllipse(QRect(1, 1, this->cellWidth - 2, this->cellHeight - 2)); oPainter.setPen(pinkPen); - oPainter.drawEllipse(QRect(2, 2, 28, 28)); - oPainter.drawEllipse(QRect(3, 3, 26, 26)); + oPainter.drawEllipse(QRect(2, 2, this->cellWidth - 4, this->cellHeight - 4)); + oPainter.drawEllipse(QRect(3, 3, this->cellWidth - 6, this->cellHeight - 6)); oPainter.setPen(whitePen); - oPainter.drawEllipse(QRect(4, 4, 24, 24)); + oPainter.drawEllipse(QRect(4, 4, this->cellHeight - 8, this->cellHeight - 8)); whitePen.setWidth(5); oPainter.setPen(whitePen); - oPainter.drawLine(0, 0, 31, 31); + oPainter.drawLine(0, 0, this->cellWidth - 1, this->cellHeight - 1); pinkPen.setWidth(3); oPainter.setPen(pinkPen); - oPainter.drawLine(2, 2, 29, 29); + oPainter.drawLine(2, 2, this->cellWidth - 3, this->cellHeight - 3); oPainter.end(); @@ -247,19 +253,13 @@ void TilesetEditorMetatileSelector::drawUnused() { QPainter unusedPainter(&metatilesPixmap); unusedPainter.setOpacity(0.5); - int primaryLength = this->primaryTileset->numMetatiles(); - int length_ = primaryLength + this->secondaryTileset->numMetatiles(); - - for (int i = 0; i < length_; i++) { - int tile = i; - if (i >= primaryLength) { - tile += Project::getNumMetatilesPrimary() - primaryLength; - } - if (!usedMetatiles[tile]) { - unusedPainter.drawPixmap((i % 8) * 32, (i / 8) * 32, redX); - } + for (int metatileId = 0; metatileId < this->usedMetatiles.size(); metatileId++) { + if (this->usedMetatiles.at(metatileId) || !Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) + continue; + // Adjust position from center to top-left corner + QPoint pos = getMetatileIdCoordsOnWidget(metatileId) - QPoint(this->cellWidth / 2, this->cellHeight / 2); + unusedPainter.drawPixmap(pos.x(), pos.y(), redX); } - unusedPainter.end(); this->setPixmap(metatilesPixmap); @@ -268,38 +268,28 @@ void TilesetEditorMetatileSelector::drawUnused() { void TilesetEditorMetatileSelector::drawCounts() { QPen blackPen(Qt::black); blackPen.setWidth(1); - - QPixmap metatilesPixmap = this->pixmap(); - - QPainter countPainter(&metatilesPixmap); - countPainter.setPen(blackPen); - - for (int tile = 0; tile < this->usedMetatiles.size(); tile++) { - int count = usedMetatiles[tile]; - QString countText = QString::number(count); - if (count > 1000) countText = ">1k"; - countPainter.drawText((tile % 8) * 32, (tile / 8) * 32 + 32, countText); - } - - // write in white and black for contrast QPen whitePen(Qt::white); whitePen.setWidth(1); - countPainter.setPen(whitePen); - int primaryLength = this->primaryTileset->numMetatiles(); - int length_ = primaryLength + this->secondaryTileset->numMetatiles(); + QPixmap metatilesPixmap = this->pixmap(); + QPainter countPainter(&metatilesPixmap); - for (int i = 0; i < length_; i++) { - int tile = i; - if (i >= primaryLength) { - tile += Project::getNumMetatilesPrimary() - primaryLength; - } - int count = usedMetatiles[tile]; - QString countText = QString::number(count); - if (count > 1000) countText = ">1k"; - countPainter.drawText((i % 8) * 32 + 1, (i / 8) * 32 + 32 - 1, countText); + for (int metatileId = 0; metatileId < this->usedMetatiles.size(); metatileId++) { + if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) + continue; + + int count = this->usedMetatiles.at(metatileId); + QString countText = (count > 1000) ? QStringLiteral(">1k") : QString::number(count); + + // Adjust position from center to bottom-left corner + QPoint pos = getMetatileIdCoordsOnWidget(metatileId) + QPoint(-(this->cellWidth / 2), this->cellHeight / 2); + + // write in black and white for contrast + countPainter.setPen(blackPen); + countPainter.drawText(pos.x(), pos.y(), countText); + countPainter.setPen(whitePen); + countPainter.drawText(pos.x() + 1, pos.y() - 1, countText); } - countPainter.end(); this->setPixmap(metatilesPixmap);