diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index 4f492cea..572cd478 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -527,6 +527,8 @@ + + @@ -572,6 +574,22 @@ Palette Editor + + + true + + + Display Unused Metatiles + + + + + true + + + Display Metatile Usage Counts + + Undo diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 38bb3f23..630bdeae 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -35,6 +35,11 @@ public: Blockdata blocks; QSize dimensions; } lastCommitMapBlocks; // to track map changes + + int getWidth(); + int getHeight(); + int getBorderWidth(); + int getBorderHeight(); }; #endif // MAPLAYOUT_H diff --git a/include/project.h b/include/project.h index b33dcee9..355ba6ba 100644 --- a/include/project.h +++ b/include/project.h @@ -95,7 +95,8 @@ public: QMap tilesetLabels; Blockdata readBlockdata(QString); - bool loadBlockdata(Map*); + bool loadBlockdata(MapLayout*); + bool loadLayoutBorder(MapLayout*); void saveTextFile(QString path, QString text); void appendTextFile(QString path, QString text); @@ -122,8 +123,9 @@ public: QMap getTopLevelMapFields(); bool loadMapData(Map*); bool readMapLayouts(); + bool loadLayout(MapLayout *); bool loadMapLayout(Map*); - bool loadMapTilesets(Map*); + bool loadLayoutTilesets(MapLayout*); void loadTilesetAssets(Tileset*); void loadTilesetTiles(Tileset*, QImage); void loadTilesetMetatiles(Tileset*); @@ -182,8 +184,6 @@ public: QStringList getEventScriptsFilePaths() const; QCompleter *getEventScriptLabelCompleter(QStringList additionalScriptLabels); - bool loadMapBorder(Map *map); - void saveMapHealEvents(Map *map); static int getNumTilesPrimary(); diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index a65cb75d..c95bd521 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -75,6 +75,9 @@ private slots: void on_actionChange_Palettes_triggered(); + void on_actionShow_Unused_toggled(bool checked); + void on_actionShow_Counts_toggled(bool checked); + void on_actionUndo_triggered(); void on_actionRedo_triggered(); @@ -119,6 +122,7 @@ private: void refresh(); void saveMetatileLabel(); void closeEvent(QCloseEvent*); + void countMetatileUsage(); Ui::TilesetEditor *ui; History metatileHistory; diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index 87133a0b..05223fd4 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -8,21 +8,20 @@ class TilesetEditorMetatileSelector: public SelectablePixmapItem { Q_OBJECT public: - TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Map *map): SelectablePixmapItem(32, 32, 1, 1) { - this->primaryTileset = primaryTileset; - this->secondaryTileset = secondaryTileset; - this->numMetatilesWide = 8; - this->map = map; - setAcceptHoverEvents(true); - } + TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Map *map); Map *map = nullptr; void draw(); bool select(uint16_t metatileId); - void setTilesets(Tileset*, Tileset*); + void setTilesets(Tileset*, Tileset*, bool draw = true); uint16_t getSelectedMetatile(); void updateSelectedMetatile(); QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId); + // + QVector usedMetatiles; + bool selectorShowUnused = false; + bool selectorShowCounts = false; + protected: void mousePressEvent(QGraphicsSceneMouseEvent*); void mouseMoveEvent(QGraphicsSceneMouseEvent*); @@ -39,6 +38,10 @@ private: QPoint getMetatileIdCoords(uint16_t); bool shouldAcceptEvent(QGraphicsSceneMouseEvent*); + void drawFilters(); + void drawUnused(); + void drawCounts(); + signals: void hoveredMetatileChanged(uint16_t); void hoveredMetatileCleared(); diff --git a/src/core/map.cpp b/src/core/map.cpp index 202d797b..ee4ee91b 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -63,19 +63,19 @@ QString Map::bgEventsLabelFromName(QString mapName) } int Map::getWidth() { - return layout->width.toInt(nullptr, 0); + return layout->getWidth(); } int Map::getHeight() { - return layout->height.toInt(nullptr, 0); + return layout->getHeight(); } int Map::getBorderWidth() { - return layout->border_width.toInt(nullptr, 0); + return layout->getBorderWidth(); } int Map::getBorderHeight() { - return layout->border_height.toInt(nullptr, 0); + return layout->getBorderHeight(); } bool Map::mapBlockChanged(int i, const Blockdata &cache) { diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 98b99d1e..8b1a1d3f 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -14,3 +14,19 @@ QString MapLayout::layoutConstantFromName(QString mapName) { return constantName; } + +int MapLayout::getWidth() { + return width.toInt(nullptr, 0); +} + +int MapLayout::getHeight() { + return height.toInt(nullptr, 0); +} + +int MapLayout::getBorderWidth() { + return border_width.toInt(nullptr, 0); +} + +int MapLayout::getBorderHeight() { + return border_height.toInt(nullptr, 0); +} diff --git a/src/project.cpp b/src/project.cpp index 860c1087..64a2f741 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -447,6 +447,17 @@ void Project::setNewMapHeader(Map* map, int mapIndex) { map->battle_scene = mapBattleScenes.value(0, "MAP_BATTLE_SCENE_NORMAL"); } +bool Project::loadLayout(MapLayout *layout) { + // Force these to run even if one fails + bool loadedTilesets = loadLayoutTilesets(layout); + bool loadedBlockdata = loadBlockdata(layout); + bool loadedBorder = loadLayoutBorder(layout); + + return loadedTilesets + && loadedBlockdata + && loadedBorder; +} + bool Project::loadMapLayout(Map* map) { if (!map->isPersistedToFile) { return true; @@ -459,14 +470,11 @@ bool Project::loadMapLayout(Map* map) { return false; } - // Force these to run even if one fails - bool loadedTilesets = loadMapTilesets(map); - bool loadedBlockdata = loadBlockdata(map); - bool loadedBorder = loadMapBorder(map); - - return loadedTilesets - && loadedBlockdata - && loadedBorder; + if (map->hasUnsavedChanges()) { + return true; + } else { + return loadLayout(map->layout); + } } bool Project::readMapLayouts() { @@ -1080,30 +1088,31 @@ void Project::saveTilesetPalettes(Tileset *tileset) { } } -bool Project::loadMapTilesets(Map* map) { - if (map->hasUnsavedChanges()) { - return true; - } +bool Project::loadLayoutTilesets(MapLayout *layout) { + // TODO: investigate where to put this now + // if (map->hasUnsavedChanges()) { + // return true; + // } - map->layout->tileset_primary = getTileset(map->layout->tileset_primary_label); - if (!map->layout->tileset_primary) { + layout->tileset_primary = getTileset(layout->tileset_primary_label); + if (!layout->tileset_primary) { QString defaultTileset = tilesetLabels["primary"].value(0, "gTileset_General"); - logWarn(QString("Map layout %1 has invalid primary tileset '%2'. Using default '%3'").arg(map->layout->id).arg(map->layout->tileset_primary_label).arg(defaultTileset)); - map->layout->tileset_primary_label = defaultTileset; - map->layout->tileset_primary = getTileset(map->layout->tileset_primary_label); - if (!map->layout->tileset_primary) { + logWarn(QString("Map layout %1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->id).arg(layout->tileset_primary_label).arg(defaultTileset)); + layout->tileset_primary_label = defaultTileset; + layout->tileset_primary = getTileset(layout->tileset_primary_label); + if (!layout->tileset_primary) { logError(QString("Failed to set default primary tileset.")); return false; } } - map->layout->tileset_secondary = getTileset(map->layout->tileset_secondary_label); - if (!map->layout->tileset_secondary) { + layout->tileset_secondary = getTileset(layout->tileset_secondary_label); + if (!layout->tileset_secondary) { QString defaultTileset = tilesetLabels["secondary"].value(0, projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered ? "gTileset_PalletTown" : "gTileset_Petalburg"); - logWarn(QString("Map layout %1 has invalid secondary tileset '%2'. Using default '%3'").arg(map->layout->id).arg(map->layout->tileset_secondary_label).arg(defaultTileset)); - map->layout->tileset_secondary_label = defaultTileset; - map->layout->tileset_secondary = getTileset(map->layout->tileset_secondary_label); - if (!map->layout->tileset_secondary) { + logWarn(QString("Map layout %1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->id).arg(layout->tileset_secondary_label).arg(defaultTileset)); + layout->tileset_secondary_label = defaultTileset; + layout->tileset_secondary = getTileset(layout->tileset_secondary_label); + if (!layout->tileset_secondary) { logError(QString("Failed to set default secondary tileset.")); return false; } @@ -1140,23 +1149,23 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) { return tileset; } -bool Project::loadBlockdata(Map *map) { - if (map->hasUnsavedChanges()) { - return true; - } +bool Project::loadBlockdata(MapLayout *layout) { + // if (map->hasUnsavedChanges()) { + // return true; + // } - QString path = QString("%1/%2").arg(root).arg(map->layout->blockdata_path); - map->layout->blockdata = readBlockdata(path); - map->layout->lastCommitMapBlocks.blocks = map->layout->blockdata; - map->layout->lastCommitMapBlocks.dimensions = QSize(map->getWidth(), map->getHeight()); + QString path = QString("%1/%2").arg(root).arg(layout->blockdata_path); + layout->blockdata = readBlockdata(path); + layout->lastCommitMapBlocks.blocks = layout->blockdata; + layout->lastCommitMapBlocks.dimensions = QSize(layout->getWidth(), layout->getHeight()); - if (map->layout->blockdata.count() != map->getWidth() * map->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.") - .arg(map->layout->blockdata.count()) - .arg(map->getWidth()) - .arg(map->getHeight()) - .arg(map->getWidth() * map->getHeight())); - map->layout->blockdata.resize(map->getWidth() * map->getHeight()); + .arg(layout->blockdata.count()) + .arg(layout->getWidth()) + .arg(layout->getHeight()) + .arg(layout->getWidth() * layout->getHeight())); + layout->blockdata.resize(layout->getWidth() * layout->getHeight()); } return true; } @@ -1170,19 +1179,19 @@ void Project::setNewMapBlockdata(Map *map) { map->layout->lastCommitMapBlocks.dimensions = QSize(map->getWidth(), map->getHeight()); } -bool Project::loadMapBorder(Map *map) { - if (map->hasUnsavedChanges()) { - return true; - } +bool Project::loadLayoutBorder(MapLayout *layout) { + // if (map->hasUnsavedChanges()) { + // return true; + // } - QString path = QString("%1/%2").arg(root).arg(map->layout->border_path); - map->layout->border = readBlockdata(path); - int borderLength = map->getBorderWidth() * map->getBorderHeight(); - if (map->layout->border.count() != borderLength) { + QString path = QString("%1/%2").arg(root).arg(layout->border_path); + layout->border = readBlockdata(path); + int borderLength = layout->getBorderWidth() * layout->getBorderHeight(); + if (layout->border.count() != borderLength) { logWarn(QString("Layout border blockdata length %1 must be %2. Resizing border blockdata.") - .arg(map->layout->border.count()) + .arg(layout->border.count()) .arg(borderLength)); - map->layout->border.resize(borderLength); + layout->border.resize(borderLength); } return true; } @@ -1856,7 +1865,8 @@ Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool setNewMapBorder(newMap); } - loadMapTilesets(newMap); + //loadMapTilesets(newMap); + loadLayoutTilesets(newMap->layout); setNewMapEvents(newMap); setNewMapConnections(newMap); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 1054feab..5a4de7a9 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -281,6 +281,13 @@ void TilesetEditor::refresh() { this->metatileSelector->select(this->metatileSelector->getSelectedMetatile()); this->drawSelectedTiles(); + if (metatileSelector) { + if (metatileSelector->selectorShowUnused || metatileSelector->selectorShowCounts) { + countMetatileUsage(); + this->metatileSelector->draw(); + } + } + this->ui->graphicsView_Tiles->setSceneRect(0, 0, this->tileSelector->pixmap().width() + 2, this->tileSelector->pixmap().height() + 2); this->ui->graphicsView_Tiles->setFixedSize(this->tileSelector->pixmap().width() + 2, this->tileSelector->pixmap().height() + 2); this->ui->graphicsView_Metatiles->setSceneRect(0, 0, this->metatileSelector->pixmap().width() + 2, this->metatileSelector->pixmap().height() + 2); @@ -909,3 +916,60 @@ void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary) this->refresh(); this->hasUnsavedChanges = true; } + +void TilesetEditor::on_actionShow_Unused_toggled(bool checked) { + this->metatileSelector->selectorShowUnused = checked; + + if (checked) countMetatileUsage(); + + this->metatileSelector->draw(); +} + +void TilesetEditor::on_actionShow_Counts_toggled(bool checked) { + this->metatileSelector->selectorShowCounts = checked; + + if (checked) countMetatileUsage(); + + this->metatileSelector->draw(); +} + +void TilesetEditor::countMetatileUsage() { + // do not double count + metatileSelector->usedMetatiles.fill(0); + + for (auto layout : this->project->mapLayouts.values()) { + bool usesPrimary = false; + bool usesSecondary = false; + + if (layout->tileset_primary_label == this->primaryTileset->name) { + usesPrimary = true; + } + + if (layout->tileset_secondary_label == this->secondaryTileset->name) { + usesSecondary = true; + } + + if (usesPrimary || usesSecondary) { + this->project->loadLayout(layout); + + // for each block in the layout, mark in the vector that it is used + for (int i = 0; i < layout->blockdata.length(); i++) { + uint16_t tile = layout->blockdata.at(i).tile; + if (tile < this->project->getNumMetatilesPrimary()) { + if (usesPrimary) metatileSelector->usedMetatiles[tile]++; + } else { + if (usesSecondary) metatileSelector->usedMetatiles[tile]++; + } + } + + for (int i = 0; i < layout->border.length(); i++) { + uint16_t tile = layout->border.at(i).tile; + if (tile < this->project->getNumMetatilesPrimary()) { + if (usesPrimary) metatileSelector->usedMetatiles[tile]++; + } else { + if (usesSecondary) metatileSelector->usedMetatiles[tile]++; + } + } + } + } +} diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index c7df404b..687dbe5f 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -3,6 +3,15 @@ #include "project.h" #include +TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Map *map) + : SelectablePixmapItem(32, 32, 1, 1) { + this->setTilesets(primaryTileset, secondaryTileset, false); + this->numMetatilesWide = 8; + this->map = map; + setAcceptHoverEvents(true); + this->usedMetatiles.resize(Project::getNumMetatilesTotal()); +} + void TilesetEditorMetatileSelector::draw() { if (!this->primaryTileset || !this->secondaryTileset) { this->setPixmap(QPixmap()); @@ -39,6 +48,8 @@ void TilesetEditorMetatileSelector::draw() { painter.end(); this->setPixmap(QPixmap::fromImage(image)); this->drawSelection(); + + drawFilters(); } bool TilesetEditorMetatileSelector::select(uint16_t metatileId) { @@ -50,10 +61,11 @@ bool TilesetEditorMetatileSelector::select(uint16_t metatileId) { return true; } -void TilesetEditorMetatileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { +void TilesetEditorMetatileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset, bool draw) { this->primaryTileset = primaryTileset; this->secondaryTileset = secondaryTileset; - this->draw(); + + if (draw) this->draw(); } void TilesetEditorMetatileSelector::updateSelectedMetatile() { @@ -131,3 +143,92 @@ QPoint TilesetEditorMetatileSelector::getMetatileIdCoordsOnWidget(uint16_t metat pos.ry() = (pos.y() * this->cellHeight) + (this->cellHeight / 2); return pos; } + +void TilesetEditorMetatileSelector::drawFilters() { + if (selectorShowUnused) { + drawUnused(); + } + if (selectorShowCounts) { + drawCounts(); + } +} + +void TilesetEditorMetatileSelector::drawUnused() { + // setup the circle with a line through it image to layer above unused metatiles + QPixmap redX(32, 32); + redX.fill(Qt::transparent); + + QPen whitePen(Qt::white); + whitePen.setWidth(1); + QPen redPen(Qt::magenta); + redPen.setWidth(1); + + QPainter oPainter(&redX); + + oPainter.setPen(whitePen); + oPainter.drawEllipse(QRect(1, 1, 30, 30)); + oPainter.setPen(redPen); + oPainter.drawEllipse(QRect(2, 2, 28, 28)); + oPainter.drawEllipse(QRect(3, 3, 26, 26)); + + oPainter.setPen(whitePen); + oPainter.drawEllipse(QRect(4, 4, 24, 24)); + + whitePen.setWidth(5); + oPainter.setPen(whitePen); + oPainter.drawLine(0, 0, 31, 31); + + redPen.setWidth(3); + oPainter.setPen(redPen); + oPainter.drawLine(2, 2, 29, 29); + + oPainter.end(); + + // draw symbol on unused metatiles + QPixmap metatilesPixmap = this->pixmap(); + + QPainter unusedPainter(&metatilesPixmap); + + for (int tile = 0; tile < this->usedMetatiles.size(); tile++) { + if (!usedMetatiles[tile]) { + unusedPainter.drawPixmap((tile % 8) * 32, (tile / 8) * 32, redX); + } + } + + unusedPainter.end(); + + this->setPixmap(metatilesPixmap); +} + +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); + + 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 + 1, (tile / 8) * 32 + 32 - 1, countText); + } + + countPainter.end(); + + this->setPixmap(metatilesPixmap); +}