diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui
index a0687826..626351cb 100644
--- a/forms/tileseteditor.ui
+++ b/forms/tileseteditor.ui
@@ -477,6 +477,8 @@
+
+
@@ -505,12 +507,12 @@
- Import Primary Tiles...
+ Import Primary Tiles Image...
- Import Secondary Tiles...
+ Import Secondary Tiles Image...
@@ -549,6 +551,16 @@
Export Secondary Tiles Image...
+
+
+ Import Primary Metatiles from Advance Map 1.92...
+
+
+
+
+ Import Secondary Metatiles from Advance Map 1.92...
+
+
diff --git a/include/config.h b/include/config.h
index efc76e5a..b7672746 100644
--- a/include/config.h
+++ b/include/config.h
@@ -70,6 +70,7 @@ extern PorymapConfig porymapConfig;
enum BaseGameVersion {
pokeruby,
+ pokefirered,
pokeemerald,
};
diff --git a/include/core/metatileparser.h b/include/core/metatileparser.h
new file mode 100644
index 00000000..1d423a72
--- /dev/null
+++ b/include/core/metatileparser.h
@@ -0,0 +1,15 @@
+#ifndef METATILEPARSER_H
+#define METATILEPARSER_H
+
+#include "metatile.h"
+#include
+#include
+
+class MetatileParser
+{
+public:
+ MetatileParser();
+ QList *parse(QString filepath, bool *error, bool primaryTileset);
+};
+
+#endif // METATILEPARSER_H
diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h
index 0a58c30c..6e3ed3df 100644
--- a/include/ui/tileseteditor.h
+++ b/include/ui/tileseteditor.h
@@ -79,6 +79,10 @@ private slots:
void on_actionExport_Secondary_Tiles_Image_triggered();
+ void on_actionImport_Primary_Metatiles_triggered();
+
+ void on_actionImport_Secondary_Metatiles_triggered();
+
private:
void closeEvent(QCloseEvent*);
void initMetatileSelector();
@@ -87,6 +91,7 @@ private:
void initMetatileLayersItem();
void drawSelectedTiles();
void importTilesetTiles(Tileset*, bool);
+ void importTilesetMetatiles(Tileset*, bool);
void refresh();
Ui::TilesetEditor *ui;
History metatileHistory;
diff --git a/porymap.pro b/porymap.pro
index 2f440ee3..6bee230d 100644
--- a/porymap.pro
+++ b/porymap.pro
@@ -22,6 +22,7 @@ SOURCES += src/core/block.cpp \
src/core/map.cpp \
src/core/maplayout.cpp \
src/core/metatile.cpp \
+ src/core/metatileparser.cpp \
src/core/paletteparser.cpp \
src/core/parseutil.cpp \
src/core/tile.cpp \
@@ -67,6 +68,7 @@ HEADERS += include/core/block.h \
include/core/mapconnection.h \
include/core/maplayout.h \
include/core/metatile.h \
+ include/core/metatileparser.h \
include/core/paletteparser.h \
include/core/parseutil.h \
include/core/tile.h \
diff --git a/src/core/metatileparser.cpp b/src/core/metatileparser.cpp
new file mode 100644
index 00000000..da7de6b1
--- /dev/null
+++ b/src/core/metatileparser.cpp
@@ -0,0 +1,92 @@
+#include "metatileparser.h"
+#include "config.h"
+#include "log.h"
+#include "project.h"
+
+MetatileParser::MetatileParser()
+{
+
+}
+
+QList *MetatileParser::parse(QString filepath, bool *error, bool primaryTileset)
+{
+ QFile file(filepath);
+ if (!file.open(QIODevice::ReadOnly)) {
+ *error = true;
+ logError(QString("Could not open Advance Map 1.92 Metatile .bvd file '%1': ").arg(filepath) + file.errorString());
+ return nullptr;
+ }
+
+ QByteArray in = file.readAll();
+ file.close();
+
+ if (in.length() < 9 || in.length() % 2 != 0) {
+ *error = true;
+ logError(QString("Advance Map 1.92 Metatile .bvd file '%1' is an unexpected size.").arg(filepath));
+ return nullptr;
+ }
+
+ int projIdOffset = in.length() - 4;
+ int metatileSize = 16;
+ int attrSize;
+ BaseGameVersion version;
+ if (in.at(projIdOffset + 0) == 'R'
+ && in.at(projIdOffset + 1) == 'S'
+ && in.at(projIdOffset + 2) == 'E'
+ && in.at(projIdOffset + 3) == ' ') {
+ // ruby and emerald are handled equally here.
+ version = BaseGameVersion::pokeemerald;
+ attrSize = 2;
+ } else {
+ *error = true;
+ logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE '."));
+ return nullptr;
+ }
+
+ int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary();
+ int numMetatiles = static_cast(in.at(0)) |
+ (static_cast(in.at(1)) << 8) |
+ (static_cast(in.at(2)) << 16) |
+ (static_cast(in.at(3)) << 24);
+ if (numMetatiles > maxMetatiles) {
+ *error = true;
+ logError(QString(".bvd file contains data for %1 metatiles, but the maximum number of metatiles is %2.").arg(numMetatiles).arg(maxMetatiles));
+ return nullptr;
+ }
+ if (numMetatiles < 1) {
+ *error = true;
+ logError(QString(".bvd file contains no data for metatiles."));
+ return nullptr;
+ }
+
+ int expectedFileSize = 4 + (metatileSize * numMetatiles) + (attrSize * numMetatiles) + 4;
+ if (in.length() != expectedFileSize) {
+ *error = true;
+ logError(QString(".bvd file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length()));
+ return nullptr;
+ }
+
+ QList *metatiles = new QList();
+ for (int i = 0; i < numMetatiles; i++) {
+ Metatile *metatile = new Metatile();
+ QList *tiles = new QList();
+ for (int j = 0; j < 8; j++) {
+ int metatileOffset = 4 + i * metatileSize + j * 2;
+ uint16_t word = static_cast(
+ static_cast(in.at(metatileOffset)) |
+ (static_cast(in.at(metatileOffset + 1)) << 8));
+ Tile tile(word & 0x3ff, (word >> 10) & 1, (word >> 11) & 1, (word >> 12) & 0xf);
+ tiles->append(tile);
+ }
+
+ int attrOffset = 4 + (numMetatiles * metatileSize) + (i * attrSize);
+ int value = static_cast(in.at(attrOffset)) |
+ (static_cast(in.at(attrOffset + 1)) << 8);
+ metatile->behavior = value & 0xFF;
+ metatile->layerType = (value & 0xF000) >> 12;
+ metatile->tiles = tiles;
+ metatiles->append(metatile);
+ }
+
+ return metatiles;
+}
diff --git a/src/core/paletteparser.cpp b/src/core/paletteparser.cpp
index 6d2661d1..5a024628 100644
--- a/src/core/paletteparser.cpp
+++ b/src/core/paletteparser.cpp
@@ -124,7 +124,7 @@ QList PaletteParser::parseAdvanceMapPal(QString filepath, bool *error) {
QFile file(filepath);
if (!file.open(QIODevice::ReadOnly)) {
*error = true;
- logError(QString("Could not open Advance Map palette file '%1': ").arg(filepath) + file.errorString());
+ logError(QString("Could not open Advance Map 1.92 palette file '%1': ").arg(filepath) + file.errorString());
return QList();
}
@@ -133,16 +133,16 @@ QList PaletteParser::parseAdvanceMapPal(QString filepath, bool *error) {
if (in.length() % 4 != 0) {
*error = true;
- logError(QString("Advance Map palette file '%1' had an unexpected format. File's length must be a multiple of 4, but the length is %2.").arg(filepath).arg(in.length()));
+ logError(QString("Advance Map 1.92 palette file '%1' had an unexpected format. File's length must be a multiple of 4, but the length is %2.").arg(filepath).arg(in.length()));
return QList();
}
QList palette;
int i = 0;
while (i < in.length()) {
- unsigned char blue = static_cast(in.at(i));
+ unsigned char red = static_cast(in.at(i));
unsigned char green = static_cast(in.at(i + 1));
- unsigned char red = static_cast(in.at(i + 2));
+ unsigned char blue = static_cast(in.at(i + 2));
palette.append(qRgb(this->clampColorValue(red),
this->clampColorValue(green),
this->clampColorValue(blue)));
diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp
index df85deda..f8904511 100644
--- a/src/ui/tileseteditor.cpp
+++ b/src/ui/tileseteditor.cpp
@@ -2,6 +2,7 @@
#include "ui_tileseteditor.h"
#include "log.h"
#include "imageproviders.h"
+#include "metatileparser.h"
#include "paletteparser.h"
#include
#include
@@ -626,3 +627,59 @@ void TilesetEditor::on_actionExport_Secondary_Tiles_Image_triggered()
image.save(filepath);
}
}
+
+void TilesetEditor::on_actionImport_Primary_Metatiles_triggered()
+{
+ this->importTilesetMetatiles(this->primaryTileset, true);
+}
+
+void TilesetEditor::on_actionImport_Secondary_Metatiles_triggered()
+{
+ this->importTilesetMetatiles(this->secondaryTileset, false);
+}
+
+void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary)
+{
+ QString descriptor = primary ? "primary" : "secondary";
+ QString descriptorCaps = primary ? "Primary" : "Secondary";
+
+ QString filepath = QFileDialog::getOpenFileName(
+ this,
+ QString("Import %1 Tileset Metatiles from Advance Map 1.92").arg(descriptorCaps),
+ this->project->root,
+ "Advance Map 1.92 Metatile Files (*.bvd)");
+ if (filepath.isEmpty()) {
+ return;
+ }
+
+ MetatileParser parser;
+ bool error = false;
+ QList *metatiles = parser.parse(filepath, &error, primary);
+ if (error) {
+ QMessageBox msgBox(this);
+ msgBox.setText("Failed to import metatiles from Advance Map 1.92 .bvd file.");
+ QString message = QString("The .bvd file could not be processed. View porymap.log for specific errors.");
+ msgBox.setInformativeText(message);
+ msgBox.setDefaultButton(QMessageBox::Ok);
+ msgBox.setIcon(QMessageBox::Icon::Critical);
+ msgBox.exec();
+ return;
+ }
+\
+ // TODO: This is crude because it makes a history entry for every newly-imported metatile.
+ // Revisit this when tiles and num metatiles are added to tileset editory history.
+ int metatileIdBase = primary ? 0 : Project::getNumMetatilesPrimary();
+ for (int i = 0; i < metatiles->length(); i++) {
+ if (i >= tileset->metatiles->length()) {
+ break;
+ }
+
+ Metatile *prevMetatile = tileset->metatiles->at(i)->copy();
+ MetatileHistoryItem *commit = new MetatileHistoryItem(static_cast(metatileIdBase + i), prevMetatile, metatiles->at(i)->copy());
+ metatileHistory.push(commit);
+ }
+
+ tileset->metatiles = metatiles;
+ this->refresh();
+ this->hasUnsavedChanges = true;
+}