diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 8b4a5dd4..d63110e7 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2642,6 +2642,8 @@ + + @@ -2956,6 +2958,11 @@ Open Config Folder + + + Import Map from Advance Map 1.92... + + diff --git a/include/core/mapparser.h b/include/core/mapparser.h new file mode 100644 index 00000000..21e3074e --- /dev/null +++ b/include/core/mapparser.h @@ -0,0 +1,16 @@ +#ifndef MAPPARSER_H +#define MAPPARSER_H + +#include "maplayout.h" +#include "project.h" +#include +#include + +class MapParser +{ +public: + MapParser(); + MapLayout *parse(QString filepath, bool *error, Project *project); +}; + +#endif // MAPPARSER_H diff --git a/include/mainwindow.h b/include/mainwindow.h index 5481a3e4..a0fb8ae8 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -132,8 +132,10 @@ private slots: void onTilesetsSaved(QString, QString); void onWildMonDataChanged(); void openNewMapPopupWindow(int, QVariant); + void openNewMapPopupWindowImportMap(MapLayout *); void onNewMapCreated(); void onMapCacheCleared(); + void importMapFromAdvanceMap1_92(); void onMapRulerStatusChanged(const QString &); void applyUserShortcuts(); @@ -193,6 +195,7 @@ private slots: void on_action_Export_Map_Image_triggered(); void on_actionExport_Stitched_Map_Image_triggered(); void on_actionExport_Map_Timelapse_Image_triggered(); + void on_actionImport_Map_from_Advance_Map_1_92_triggered(); void on_comboBox_ConnectionDirection_currentTextChanged(const QString &arg1); void on_spinBox_ConnectionOffset_valueChanged(int offset); diff --git a/include/project.h b/include/project.h index 355ba6ba..da5ade6b 100644 --- a/include/project.h +++ b/include/project.h @@ -93,6 +93,7 @@ public: Tileset* loadTileset(QString, Tileset *tileset = nullptr); Tileset* getTileset(QString, bool forceLoad = false); QMap tilesetLabels; + QList tilesetLabelsOrdered; Blockdata readBlockdata(QString); bool loadBlockdata(MapLayout*); @@ -103,7 +104,7 @@ public: void deleteFile(QString path); bool readMapGroups(); - Map* addNewMapToGroup(QString, int, Map*, bool); + Map* addNewMapToGroup(QString, int, Map*, bool, bool); QString getNewMapName(); QString getProjectTitle(); diff --git a/include/ui/newmappopup.h b/include/ui/newmappopup.h index c77470ca..5c4f5761 100644 --- a/include/ui/newmappopup.h +++ b/include/ui/newmappopup.h @@ -20,8 +20,10 @@ public: Map *map; int group; bool existingLayout; + bool importedMap; QString layoutId; void init(int, int, QString, QString); + void initImportMap(MapLayout *); void useLayout(QString); void connectSignals(); @@ -32,6 +34,8 @@ private: Ui::NewMapPopup *ui; Project *project; void setDefaultValues(int, QString); + void setDefaultValuesImportMap(MapLayout *); + void setDefaultValuesProjectConfig(bool, MapLayout*); bool checkNewMapDimensions(); bool checkNewMapGroup(); diff --git a/porymap.pro b/porymap.pro index dd263db3..b6863c3c 100644 --- a/porymap.pro +++ b/porymap.pro @@ -22,6 +22,7 @@ SOURCES += src/core/block.cpp \ src/core/imageexport.cpp \ src/core/map.cpp \ src/core/maplayout.cpp \ + src/core/mapparser.cpp \ src/core/metatile.cpp \ src/core/metatileparser.cpp \ src/core/paletteutil.cpp \ @@ -93,6 +94,7 @@ HEADERS += include/core/block.h \ include/core/map.h \ include/core/mapconnection.h \ include/core/maplayout.h \ + include/core/mapparser.h \ include/core/metatile.h \ include/core/metatileparser.h \ include/core/paletteutil.h \ diff --git a/src/core/mapparser.cpp b/src/core/mapparser.cpp new file mode 100644 index 00000000..88592ab0 --- /dev/null +++ b/src/core/mapparser.cpp @@ -0,0 +1,97 @@ +#include "mapparser.h" +#include "config.h" +#include "log.h" +#include "project.h" + +MapParser::MapParser() +{ +} + +MapLayout *MapParser::parse(QString filepath, bool *error, Project *project) +{ + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly)) { + *error = true; + logError(QString("Could not open Advance Map 1.92 Map .map file '%1': ").arg(filepath) + file.errorString()); + return nullptr; + } + + QByteArray in = file.readAll(); + file.close(); + + if (in.length() < 20 || in.length() % 2 != 0) { + *error = true; + logError(QString("Advance Map 1.92 Map .map file '%1' is an unexpected size.").arg(filepath)); + return nullptr; + } + + int borderWidth = static_cast(in.at(16)); // 0 in RSE .map files + int borderHeight = static_cast(in.at(17)); // 0 in RSE .map files + int numBorderTiles = borderWidth * borderHeight; // 0 if RSE + + int mapDataOffset = 20 + (numBorderTiles * 2); // FRLG .map files store border metatile data after the header + int mapWidth = static_cast(in.at(0)) | + (static_cast(in.at(1)) << 8) | + (static_cast(in.at(2)) << 16) | + (static_cast(in.at(3)) << 24); + int mapHeight = static_cast(in.at(4)) | + (static_cast(in.at(5)) << 8) | + (static_cast(in.at(6)) << 16) | + (static_cast(in.at(7)) << 24); + int mapPrimaryTilesetNum = static_cast(in.at(8)) | + (static_cast(in.at(9)) << 8) | + (static_cast(in.at(10)) << 16) | + (static_cast(in.at(11)) << 24); + int mapSecondaryTilesetNum = static_cast(in.at(12)) | + (static_cast(in.at(13)) << 8) | + (static_cast(in.at(14)) << 16) | + (static_cast(in.at(15)) << 24); + + int numMetatiles = mapWidth * mapHeight; + int expectedFileSize = 20 + (numBorderTiles * 2) + (numMetatiles * 2); + if (in.length() != expectedFileSize) { + *error = true; + logError(QString(".map file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length())); + return nullptr; + } + + Blockdata blockdata; + for (int i = mapDataOffset; (i + 1) < in.length(); i += 2) { + uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); + blockdata.append(word); + } + + Blockdata border; + if (numBorderTiles != 0) { + for (int i = 20; (i + 1) < mapDataOffset; i += 2) { + uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); + border.append(word); + } + } + + MapLayout *mapLayout = new MapLayout(); + mapLayout->width = QString::number(mapWidth); + mapLayout->height = QString::number(mapHeight); + mapLayout->border_width = (borderWidth == 0) ? QString::number(2) : QString::number(borderWidth); + mapLayout->border_height = (borderHeight == 0) ? QString::number(2) : QString::number(borderHeight); + + QList tilesets = project->tilesetLabelsOrdered; + + if (mapPrimaryTilesetNum > tilesets.size()) + mapLayout->tileset_primary_label = tilesets.at(0); + else + mapLayout->tileset_primary_label = tilesets.at(mapPrimaryTilesetNum); + + if (mapSecondaryTilesetNum > tilesets.size()) + mapLayout->tileset_secondary_label = tilesets.at(1); + else + mapLayout->tileset_secondary_label = tilesets.at(mapSecondaryTilesetNum); + + mapLayout->blockdata = blockdata; + + if (!border.isEmpty()) { + mapLayout->border = border; + } + + return mapLayout; +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 69e1ba1a..7a1bc40f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -15,6 +15,7 @@ #include "editcommands.h" #include "flowlayout.h" #include "shortcut.h" +#include "mapparser.h" #include #include @@ -1161,8 +1162,9 @@ void MainWindow::onNewMapCreated() { int newMapGroup = this->newmapprompt->group; Map *newMap = this->newmapprompt->map; bool existingLayout = this->newmapprompt->existingLayout; + bool importedMap = this->newmapprompt->importedMap; - newMap = editor->project->addNewMapToGroup(newMapName, newMapGroup, newMap, existingLayout); + newMap = editor->project->addNewMapToGroup(newMapName, newMapGroup, newMap, existingLayout, importedMap); logInfo(QString("Created a new map named %1.").arg(newMapName)); @@ -1215,6 +1217,24 @@ void MainWindow::openNewMapPopupWindow(int type, QVariant data) { this->newmapprompt->setAttribute(Qt::WA_DeleteOnClose); } +void MainWindow::openNewMapPopupWindowImportMap(MapLayout *mapLayout) { + if (!this->newmapprompt) { + this->newmapprompt = new NewMapPopup(this, this->editor->project); + } + if (!this->newmapprompt->isVisible()) { + this->newmapprompt->show(); + } else { + this->newmapprompt->raise(); + this->newmapprompt->activateWindow(); + } + + this->newmapprompt->initImportMap(mapLayout); + + connect(this->newmapprompt, SIGNAL(applied()), this, SLOT(onNewMapCreated())); + connect(this->newmapprompt, &QObject::destroyed, [=](QObject *) { this->newmapprompt = nullptr; }); + this->newmapprompt->setAttribute(Qt::WA_DeleteOnClose); +} + void MainWindow::on_action_NewMap_triggered() { openNewMapPopupWindow(MapSortOrder::Group, 0); } @@ -2726,6 +2746,38 @@ void MainWindow::on_actionExport_Map_Timelapse_Image_triggered() { showExportMapImageWindow(ImageExporterMode::Timelapse); } +void MainWindow::on_actionImport_Map_from_Advance_Map_1_92_triggered(){ + importMapFromAdvanceMap1_92(); +} + +void MainWindow::importMapFromAdvanceMap1_92() +{ + QString filepath = QFileDialog::getOpenFileName( + this, + QString("Import Map from Advance Map 1.92"), + this->editor->project->root, + "Advance Map 1.92 Map Files (*.map)"); + if (filepath.isEmpty()) { + return; + } + + MapParser parser; + bool error = false; + MapLayout *mapLayout = parser.parse(filepath, &error, editor->project); + if (error) { + QMessageBox msgBox(this); + msgBox.setText("Failed to import map from Advance Map 1.92 .map file."); + QString message = QString("The .map 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; + } + + openNewMapPopupWindowImportMap(mapLayout); +} + void MainWindow::showExportMapImageWindow(ImageExporterMode mode) { if (!editor->project) return; diff --git a/src/project.cpp b/src/project.cpp index 074eef4c..2bf35b8e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1848,7 +1848,7 @@ bool Project::readMapGroups() { return true; } -Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool existingLayout) { +Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool existingLayout, bool importedMap) { mapNames.append(mapName); mapGroups.insert(mapName, groupNum); groupedMapNames[groupNum].append(mapName); @@ -1861,8 +1861,12 @@ Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool if (!existingLayout) { mapLayouts.insert(newMap->layoutId, newMap->layout); mapLayoutsTable.append(newMap->layoutId); - setNewMapBlockdata(newMap); - setNewMapBorder(newMap); + if (!importedMap) { + setNewMapBlockdata(newMap); + } + if (newMap->layout->border.isEmpty()) { + setNewMapBorder(newMap); + } } loadLayoutTilesets(newMap->layout); @@ -1910,6 +1914,7 @@ QMap Project::getTilesetLabels() { QStringList secondaryTilesets; allTilesets.insert("primary", primaryTilesets); allTilesets.insert("secondary", secondaryTilesets); + QList tilesetLabelsOrdered; QString filename = "data/tilesets/headers.inc"; QString headers_text = parser.readTextFile(root + "/" + filename); @@ -1938,8 +1943,10 @@ QMap Project::getTilesetLabels() { allTilesets["secondary"].append(tilesetLabel); else allTilesets["primary"].append(tilesetLabel); + tilesetLabelsOrdered.append(tilesetLabel); } this->tilesetLabels = allTilesets; + this->tilesetLabelsOrdered = tilesetLabelsOrdered; return allTilesets; } diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index c53c6416..0d96f42b 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -17,6 +17,7 @@ NewMapPopup::NewMapPopup(QWidget *parent, Project *project) : ui->setupUi(this); this->project = project; this->existingLayout = false; + this->importedMap = false; } NewMapPopup::~NewMapPopup() @@ -41,6 +42,12 @@ void NewMapPopup::init(int type, int group, QString sec, QString layoutId) { connectSignals(); } +void NewMapPopup::initImportMap(MapLayout *mapLayout) { + this->importedMap = true; + setDefaultValuesImportMap(mapLayout); + connectSignals(); +} + bool NewMapPopup::checkNewMapDimensions() { int numMetatiles = project->getMapDataSize(ui->spinBox_NewMap_Width->value(), ui->spinBox_NewMap_Height->value()); int maxMetatiles = project->getMaxMapDataSize(); @@ -136,6 +143,42 @@ void NewMapPopup::setDefaultValues(int groupNum, QString mapSec) { ui->frame_NewMap_Options->setEnabled(true); + setDefaultValuesProjectConfig(false, NULL); +} + +void NewMapPopup::setDefaultValuesImportMap(MapLayout *mapLayout) { + ui->lineEdit_NewMap_Name->setText(project->getNewMapName()); + + QMap tilesets = project->getTilesetLabels(); + ui->comboBox_NewMap_Primary_Tileset->addItems(tilesets.value("primary")); + ui->comboBox_NewMap_Secondary_Tileset->addItems(tilesets.value("secondary")); + + ui->comboBox_NewMap_Group->addItems(project->groupNames); + ui->comboBox_NewMap_Group->setCurrentText(project->groupNames.at(0)); + + ui->spinBox_NewMap_Width->setValue(mapLayout->width.toInt(nullptr, 0)); + ui->spinBox_NewMap_Height->setValue(mapLayout->height.toInt(nullptr, 0)); + ui->comboBox_NewMap_Primary_Tileset->setCurrentText(mapLayout->tileset_primary_label); + ui->comboBox_NewMap_Secondary_Tileset->setCurrentText(mapLayout->tileset_secondary_label); + + ui->comboBox_NewMap_Type->addItems(project->mapTypes); + ui->comboBox_NewMap_Location->addItems(project->mapSectionValueToName.values()); + ui->checkBox_NewMap_Show_Location->setChecked(true); + + ui->frame_NewMap_Options->setEnabled(true); + + setDefaultValuesProjectConfig(true, mapLayout); + + map = new Map(); + map->layout = new MapLayout(); + map->layout->blockdata = mapLayout->blockdata; + + if (!mapLayout->border.isEmpty()) { + map->layout->border = mapLayout->border; + } +} + +void NewMapPopup::setDefaultValuesProjectConfig(bool importedMap, MapLayout *mapLayout) { switch (projectConfig.getBaseGameVersion()) { case BaseGameVersion::pokeruby: @@ -164,11 +207,19 @@ void NewMapPopup::setDefaultValues(int groupNum, QString mapSec) { break; } if (projectConfig.getUseCustomBorderSize()) { + if (importedMap) { + ui->spinBox_NewMap_BorderWidth->setValue(mapLayout->border_width.toInt(nullptr, 0)); + ui->spinBox_NewMap_BorderHeight->setValue(mapLayout->border_height.toInt(nullptr, 0)); + } ui->spinBox_NewMap_BorderWidth->setVisible(true); ui->spinBox_NewMap_BorderHeight->setVisible(true); ui->label_NewMap_BorderWidth->setVisible(true); ui->label_NewMap_BorderHeight->setVisible(true); } else { + if (importedMap) { + ui->spinBox_NewMap_BorderWidth->setValue(DEFAULT_BORDER_WIDTH); + ui->spinBox_NewMap_BorderHeight->setValue(DEFAULT_BORDER_HEIGHT); + } ui->spinBox_NewMap_BorderWidth->setVisible(false); ui->spinBox_NewMap_BorderHeight->setVisible(false); ui->label_NewMap_BorderWidth->setVisible(false); @@ -242,6 +293,12 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { layout->blockdata_path = QString("data/layouts/%1/map.bin").arg(newMapName); } + if (this->importedMap) { + layout->blockdata = map->layout->blockdata; + if (!map->layout->border.isEmpty()) + layout->border = map->layout->border; + } + if (this->ui->checkBox_NewMap_Flyable->isChecked()) { newMap->isFlyable = "TRUE"; }