mirror of
https://github.com/huderlem/porymap.git
synced 2026-08-25 18:16:57 -05:00
Add ability to import metatiles from Advance Map (.bvd files)
This commit is contained in:
92
src/core/metatileparser.cpp
Normal file
92
src/core/metatileparser.cpp
Normal 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;
|
||||
}
|
||||
@@ -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)));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user