add options to count metatiles to tileset editor

- count the total number of usages across all maps of specific metatiles
- display the count and/or display unused metatiles
- this does not account for metatiles used in code (most of these are labeled)
This commit is contained in:
garak 2021-07-19 22:02:31 -04:00
parent 353a0b017f
commit 601e671fc8
10 changed files with 288 additions and 67 deletions

View file

@ -527,6 +527,8 @@
<addaction name="actionImport_Primary_Metatiles"/>
<addaction name="actionImport_Secondary_Metatiles"/>
<addaction name="actionChange_Metatiles_Count"/>
<addaction name="actionShow_Unused"/>
<addaction name="actionShow_Counts"/>
<addaction name="actionChange_Palettes"/>
<addaction name="separator"/>
<addaction name="actionExport_Primary_Tiles_Image"/>
@ -572,6 +574,22 @@
<string>Palette Editor</string>
</property>
</action>
<action name="actionShow_Unused">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Display Unused Metatiles</string>
</property>
</action>
<action name="actionShow_Counts">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Display Metatile Usage Counts</string>
</property>
</action>
<action name="actionUndo">
<property name="text">
<string>Undo</string>

View file

@ -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

View file

@ -95,7 +95,8 @@ public:
QMap<QString, QStringList> 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<QString, bool> 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();

View file

@ -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<MetatileHistoryItem*> metatileHistory;

View file

@ -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<uint16_t> 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();

View file

@ -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) {

View file

@ -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);
}

View file

@ -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);

View file

@ -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]++;
}
}
}
}
}

View file

@ -3,6 +3,15 @@
#include "project.h"
#include <QPainter>
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);
}