Add ability to import metatiles from Advance Map (.bvd files)

This commit is contained in:
Marcus Huderle 2019-01-08 18:04:41 -06:00
parent b469cc047f
commit 3f88072981
8 changed files with 190 additions and 6 deletions

View file

@ -477,6 +477,8 @@
</property>
<addaction name="actionImport_Primary_Tiles"/>
<addaction name="actionImport_Secondary_Tiles"/>
<addaction name="actionImport_Primary_Metatiles"/>
<addaction name="actionImport_Secondary_Metatiles"/>
<addaction name="actionChange_Metatiles_Count"/>
<addaction name="actionChange_Palettes"/>
<addaction name="separator"/>
@ -505,12 +507,12 @@
</action>
<action name="actionImport_Primary_Tiles">
<property name="text">
<string>Import Primary Tiles...</string>
<string>Import Primary Tiles Image...</string>
</property>
</action>
<action name="actionImport_Secondary_Tiles">
<property name="text">
<string>Import Secondary Tiles...</string>
<string>Import Secondary Tiles Image...</string>
</property>
</action>
<action name="actionChange_Metatiles_Count">
@ -549,6 +551,16 @@
<string>Export Secondary Tiles Image...</string>
</property>
</action>
<action name="actionImport_Primary_Metatiles">
<property name="text">
<string>Import Primary Metatiles from Advance Map 1.92...</string>
</property>
</action>
<action name="actionImport_Secondary_Metatiles">
<property name="text">
<string>Import Secondary Metatiles from Advance Map 1.92...</string>
</property>
</action>
</widget>
<resources/>
<connections/>

View file

@ -70,6 +70,7 @@ extern PorymapConfig porymapConfig;
enum BaseGameVersion {
pokeruby,
pokefirered,
pokeemerald,
};

View file

@ -0,0 +1,15 @@
#ifndef METATILEPARSER_H
#define METATILEPARSER_H
#include "metatile.h"
#include <QList>
#include <QString>
class MetatileParser
{
public:
MetatileParser();
QList<Metatile*> *parse(QString filepath, bool *error, bool primaryTileset);
};
#endif // METATILEPARSER_H

View file

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

View file

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

View file

@ -0,0 +1,92 @@
#include "metatileparser.h"
#include "config.h"
#include "log.h"
#include "project.h"
MetatileParser::MetatileParser()
{
}
QList<Metatile*> *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<unsigned char>(in.at(0)) |
(static_cast<unsigned char>(in.at(1)) << 8) |
(static_cast<unsigned char>(in.at(2)) << 16) |
(static_cast<unsigned char>(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<Metatile*> *metatiles = new QList<Metatile*>();
for (int i = 0; i < numMetatiles; i++) {
Metatile *metatile = new Metatile();
QList<Tile> *tiles = new QList<Tile>();
for (int j = 0; j < 8; j++) {
int metatileOffset = 4 + i * metatileSize + j * 2;
uint16_t word = static_cast<uint16_t>(
static_cast<unsigned char>(in.at(metatileOffset)) |
(static_cast<unsigned char>(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<unsigned char>(in.at(attrOffset)) |
(static_cast<unsigned char>(in.at(attrOffset + 1)) << 8);
metatile->behavior = value & 0xFF;
metatile->layerType = (value & 0xF000) >> 12;
metatile->tiles = tiles;
metatiles->append(metatile);
}
return metatiles;
}

View file

@ -124,7 +124,7 @@ QList<QRgb> 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<QRgb>();
}
@ -133,16 +133,16 @@ QList<QRgb> 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<QRgb>();
}
QList<QRgb> palette;
int i = 0;
while (i < in.length()) {
unsigned char blue = static_cast<unsigned char>(in.at(i));
unsigned char red = static_cast<unsigned char>(in.at(i));
unsigned char green = static_cast<unsigned char>(in.at(i + 1));
unsigned char red = static_cast<unsigned char>(in.at(i + 2));
unsigned char blue = static_cast<unsigned char>(in.at(i + 2));
palette.append(qRgb(this->clampColorValue(red),
this->clampColorValue(green),
this->clampColorValue(blue)));

View file

@ -2,6 +2,7 @@
#include "ui_tileseteditor.h"
#include "log.h"
#include "imageproviders.h"
#include "metatileparser.h"
#include "paletteparser.h"
#include <QFileDialog>
#include <QMessageBox>
@ -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<Metatile*> *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<uint16_t>(metatileIdBase + i), prevMetatile, metatiles->at(i)->copy());
metatileHistory.push(commit);
}
tileset->metatiles = metatiles;
this->refresh();
this->hasUnsavedChanges = true;
}