diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 68d458c9..d92666d5 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -7,15 +7,15 @@ 0 0 1117 - 747 + 788 porymap - - + + Qt::Horizontal @@ -512,7 +512,7 @@ - + <html><head/><body><p>Change a map layout's width and height.</p></body></html> @@ -561,8 +561,8 @@ 0 0 - 469 - 608 + 545 + 628 @@ -736,305 +736,6 @@ 3 - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - Primary Tileset - - - - - - - Qt::StrongFocus - - - <html><head/><body><p>Primary Tileset</p><p>Defines the first 0x200 metatiles available for the map.</p></body></html> - - - true - - - - - - - Secondary Tileset - - - - - - - Qt::StrongFocus - - - <html><head/><body><p>Secondary Tileset</p><p>Defines the second 0x200 metatiles available for the map.</p></body></html> - - - true - - - - - - - - - - - 0 - 0 - - - - - 0 - 92 - - - - - 16777215 - 92 - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - QLayout::SetDefaultConstraint - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Selection - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Plain - - - true - - - - - 0 - 0 - 324 - 77 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - - - Border - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 16777215 - 48 - - - - <html><head/><body><p>The border is a 2x2 metatile which is repeated outside of the map layout's boundary. Draw on this border area to modify it.</p></body></html> - - - QFrame::StyledPanel - - - QFrame::Sunken - - - Qt::ScrollBarAsNeeded - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - @@ -1064,10 +765,10 @@ - 0 + 8 0 - 307 - 362 + 221 + 324 @@ -1160,6 +861,309 @@ + + + + + 0 + 0 + + + + + 0 + 92 + + + + + 16777215 + 92 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Selection + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Plain + + + true + + + + + 0 + 0 + 256 + 74 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 110 + + + + + 16777215 + 110 + + + + Border + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + QLayout::SetMinAndMaxSize + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + true + + + + + 0 + 0 + 231 + 83 + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + + 1 + 1 + + + + + 16777215 + 16777215 + + + + <html><head/><body><p>The border is a group of metatiles which are repeated outside of the map layout's boundary. Draw on this border area to modify it.</p></body></html> + + + + + + + + + + + + + @@ -1176,6 +1180,64 @@ + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Primary Tileset + + + + + + + Qt::StrongFocus + + + <html><head/><body><p>Primary Tileset</p><p>Defines the first 0x200 metatiles available for the map.</p></body></html> + + + true + + + + + + + Secondary Tileset + + + + + + + Qt::StrongFocus + + + <html><head/><body><p>Secondary Tileset</p><p>Defines the second 0x200 metatiles available for the map.</p></body></html> + + + true + + + + + + @@ -1344,8 +1406,8 @@ 0 0 - 381 - 657 + 371 + 684 @@ -1618,7 +1680,7 @@ 0 0 430 - 568 + 575 @@ -2094,6 +2156,26 @@ + + + + Floor Number + + + + + + + <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> + + + -128 + + + 127 + + + @@ -2531,8 +2613,8 @@ 0 0 - 826 - 557 + 818 + 574 @@ -2796,7 +2878,7 @@ 0 0 1117 - 21 + 22 diff --git a/forms/newmappopup.ui b/forms/newmappopup.ui index 209c078d..b9595ec8 100644 --- a/forms/newmappopup.ui +++ b/forms/newmappopup.ui @@ -73,7 +73,7 @@ - Width + Map Width @@ -90,7 +90,7 @@ - Height + Map Height @@ -105,13 +105,47 @@ + + + Border Width + + + + + + + <html><head/><body><p>Width (in blocks) of the new map's border.</p></body></html> + + + 255 + + + + + + + Border Height + + + + + + + <html><head/><body><p>Height (in blocks) of the new map's border.</p></body></html> + + + 255 + + + + Primary Tileset - + <html><head/><body><p>The primary tileset for the new map.</p></body></html> @@ -121,14 +155,14 @@ - + Secondary Tileset - + <html><head/><body><p>The secondary tileset for the new map.</p></body></html> @@ -138,14 +172,14 @@ - + Type - + <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example. it determines whether biking or running is allowed.</p></body></html> @@ -155,14 +189,14 @@ - + Location - + <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.</p></body></html> @@ -172,14 +206,14 @@ - + Can Fly To - + <html><head/><body><p>Whether to add a heal location to the new map.</p></body></html> @@ -189,48 +223,65 @@ - + Allow Running - + Allow Biking - + Allow Escape Rope - + - + - + + + + + Floor Number + + + + + + + <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> + + + 127 + + + diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index 90b0cc15..e1e8810a 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -47,7 +47,7 @@ 0 0 272 - 539 + 625 @@ -198,30 +198,31 @@ false - - + + Metatile Label (Optional) - - - - - + + - Metatile Behavior + Layer Type - - - - - + + - Bottom/Top + Encounter Type + + + + + + + Terrain Type @@ -247,15 +248,15 @@ - - + + - Layer Type + Bottom/Top - - + + @@ -270,6 +271,38 @@ + + + + + + + + + + + + + Metatile Behavior + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -377,8 +410,8 @@ 0 0 - 400 - 367 + 384 + 265 @@ -394,19 +427,6 @@ 0 - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -420,16 +440,6 @@ - - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - @@ -443,6 +453,29 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + @@ -471,7 +504,7 @@ 0 0 700 - 21 + 22 @@ -572,6 +605,13 @@ + + + NoScrollComboBox + QWidget +
noscrollcombobox.h
+
+
diff --git a/include/config.h b/include/config.h index 2a14c399..e265df28 100644 --- a/include/config.h +++ b/include/config.h @@ -109,6 +109,8 @@ public: void setUsePoryScript(bool usePoryScript); bool getUsePoryScript(); void setProjectDir(QString projectDir); + void setUseCustomBorderSize(bool enable); + bool getUseCustomBorderSize(); protected: QString getConfigFilepath(); void parseConfigKeyValue(QString key, QString value); @@ -119,6 +121,7 @@ private: QString projectDir; bool useEncounterJson; bool usePoryScript; + bool useCustomBorderSize; }; extern ProjectConfig projectConfig; diff --git a/include/core/heallocation.h b/include/core/heallocation.h index 15790dff..fd00a22b 100644 --- a/include/core/heallocation.h +++ b/include/core/heallocation.h @@ -9,14 +9,17 @@ class HealLocation { public: HealLocation()=default; - HealLocation(QString, int, uint16_t, uint16_t); + HealLocation(QString, QString, int, uint16_t, uint16_t, QString = "", uint16_t = 0); friend QDebug operator<<(QDebug debug, const HealLocation &hl); public: - QString name; + QString idName; + QString mapName; int index; uint16_t x; uint16_t y; + QString respawnMap; + uint16_t respawnNPC; static HealLocation fromEvent(Event*); }; diff --git a/include/core/historyitem.h b/include/core/historyitem.h index 92675055..693d733c 100644 --- a/include/core/historyitem.h +++ b/include/core/historyitem.h @@ -6,9 +6,12 @@ class HistoryItem { public: Blockdata *metatiles; + Blockdata *border; int layoutWidth; int layoutHeight; - HistoryItem(Blockdata *metatiles, int layoutWidth, int layoutHeight); + int borderWidth; + int borderHeight; + HistoryItem(Blockdata *metatiles, Blockdata *border, int layoutWidth, int layoutHeight, int borderWidth, int borderHeight); ~HistoryItem(); }; diff --git a/include/core/map.h b/include/core/map.h index 74ba4f20..32eb6e70 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -14,6 +14,13 @@ #include #include +#define DEFAULT_BORDER_WIDTH 2 +#define DEFAULT_BORDER_HEIGHT 2 + +// Number of metatiles to draw out from edge of map. Could allow modification of this in the future. +// porymap will reflect changes to it, but the value is hard-coded in the projects at the moment +#define BORDER_DISTANCE 7 + class Map : public QObject { Q_OBJECT @@ -35,6 +42,7 @@ public: QString allowRunning; QString allowBiking; QString allowEscapeRope; + int floorNumber; QString battle_scene; QString sharedEventsMap = ""; QString sharedScriptsMap = ""; @@ -57,6 +65,8 @@ public: static QString bgEventsLabelFromName(QString mapName); int getWidth(); int getHeight(); + int getBorderWidth(); + int getBorderHeight(); QPixmap render(bool ignoreCache, MapLayout * fromLayout = nullptr); QPixmap renderCollision(qreal opacity, bool ignoreCache); bool mapBlockChanged(int i, Blockdata * cache); @@ -77,12 +87,14 @@ public: void addEvent(Event*); QPixmap renderConnection(MapConnection, MapLayout *); QPixmap renderBorder(); - void setDimensions(int newWidth, int newHeight, bool setNewBlockData = true); + void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true); + void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true); void cacheBorder(); bool hasUnsavedChanges(); private: void setNewDimensionsBlockdata(int newWidth, int newHeight); + void setNewBorderDimensionsBlockdata(int newWidth, int newHeight); signals: void mapChanged(Map *map); diff --git a/include/core/maplayout.h b/include/core/maplayout.h index df074785..c2c71d8e 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -15,6 +15,8 @@ public: QString name; QString width; QString height; + QString border_width; + QString border_height; QString border_path; QString blockdata_path; QString tileset_primary_label; diff --git a/include/core/metatile.h b/include/core/metatile.h index b1be4d75..77a3c687 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -11,8 +11,10 @@ public: Metatile(); public: QList *tiles = nullptr; - uint8_t behavior; + uint16_t behavior; // 8 bits RSE, 9 bits FRLG uint8_t layerType; + uint8_t encounterType; // FRLG only + uint8_t terrainType; // FRLG only QString label; Metatile *copy(); diff --git a/include/editor.h b/include/editor.h index 45706daf..a0ab0e7e 100644 --- a/include/editor.h +++ b/include/editor.h @@ -147,6 +147,7 @@ private: void updateMirroredConnectionMap(MapConnection*, QString); void updateMirroredConnection(MapConnection*, QString, QString, bool isDelete = false); void updateEncounterFields(EncounterFields newFields); + int getBorderDrawDistance(int dimension); Event* createNewObjectEvent(); Event* createNewWarpEvent(); Event* createNewHealLocationEvent(); diff --git a/include/mainwindow.h b/include/mainwindow.h index 8d5d3e20..27fe541c 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -68,6 +68,7 @@ private slots: void on_checkBox_AllowRunning_clicked(bool checked); void on_checkBox_AllowBiking_clicked(bool checked); void on_checkBox_AllowEscapeRope_clicked(bool checked); + void on_spinBox_FloorNumber_valueChanged(int offset); void on_tabWidget_currentChanged(int index); @@ -118,7 +119,7 @@ private slots: void on_comboBox_EmergeMap_currentTextChanged(const QString &mapName); void on_comboBox_PrimaryTileset_currentTextChanged(const QString &arg1); void on_comboBox_SecondaryTileset_currentTextChanged(const QString &arg1); - void on_pushButton_clicked(); + void on_pushButton_ChangeDimensions_clicked(); void on_checkBox_smartPaths_stateChanged(int selected); void on_checkBox_Visibility_clicked(bool checked); void on_checkBox_ToggleBorder_stateChanged(int arg1); @@ -215,6 +216,7 @@ private: bool openRecentProject(); void updateTilesetEditor(); QString getEventGroupFromTabWidget(QWidget *tab); + void closeSupplementaryWindows(); bool isProjectOpen(); }; diff --git a/include/project.h b/include/project.h index e3e3b21c..f4f59d7d 100644 --- a/include/project.h +++ b/include/project.h @@ -50,6 +50,7 @@ public: QStringList *coordEventWeatherNames = nullptr; QStringList *secretBaseIds = nullptr; QStringList *bgEventFacingDirections = nullptr; + QStringList *trainerTypes = nullptr; QMap metatileBehaviorMap; QMap metatileBehaviorMapInverse; QMap facingDirections; @@ -127,6 +128,7 @@ public: void saveTilesetTilesImage(Tileset*); void saveTilesetPalettes(Tileset*, bool); + QString defaultSong; QStringList getSongNames(); QStringList getVisibilities(); QMap getTilesetLabels(); @@ -143,6 +145,7 @@ public: bool readCoordEventWeatherNames(); bool readSecretBaseIds(); bool readBgEventFacingDirections(); + bool readTrainerTypes(); bool readMetatileBehaviors(); bool readHealLocations(); bool readMiscellaneousConstants(); diff --git a/include/ui/neweventtoolbutton.h b/include/ui/neweventtoolbutton.h index 007dcd51..1168d447 100644 --- a/include/ui/neweventtoolbutton.h +++ b/include/ui/neweventtoolbutton.h @@ -10,6 +10,14 @@ class NewEventToolButton : public QToolButton public: explicit NewEventToolButton(QWidget *parent = nullptr); QString getSelectedEventType(); + QAction *newObjectAction; + QAction *newWarpAction; + QAction *newHealLocationAction; + QAction *newTriggerAction; + QAction *newWeatherTriggerAction; + QAction *newSignAction; + QAction *newHiddenItemAction; + QAction *newSecretBaseAction; public slots: void newObject(); void newWarp(); @@ -23,14 +31,6 @@ signals: void newEventAdded(QString); private: QString selectedEventType; - QAction *newObjectAction; - QAction *newWarpAction; - QAction *newHealLocationAction; - QAction *newTriggerAction; - QAction *newWeatherTriggerAction; - QAction *newSignAction; - QAction *newHiddenItemAction; - QAction *newSecretBaseAction; void init(); }; diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 1095b0be..f4205b38 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -77,6 +77,10 @@ private slots: void on_comboBox_layerType_activated(int arg1); + void on_comboBox_encounterType_activated(int arg1); + + void on_comboBox_terrainType_activated(int arg1); + void on_actionExport_Primary_Tiles_Image_triggered(); void on_actionExport_Secondary_Tiles_Image_triggered(); diff --git a/src/config.cpp b/src/config.cpp index ed76cd1b..da684887 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -324,11 +324,13 @@ QString PorymapConfig::getTheme() { const QMap baseGameVersionMap = { {BaseGameVersion::pokeruby, "pokeruby"}, + {BaseGameVersion::pokefirered, "pokefirered"}, {BaseGameVersion::pokeemerald, "pokeemerald"}, }; const QMap baseGameVersionReverseMap = { {"pokeruby", BaseGameVersion::pokeruby}, + {"pokefirered", BaseGameVersion::pokefirered}, {"pokeemerald", BaseGameVersion::pokeemerald}, }; @@ -346,7 +348,7 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->baseGameVersion = baseGameVersionReverseMap.value(baseGameVersion); } else { this->baseGameVersion = BaseGameVersion::pokeemerald; - logWarn(QString("Invalid config value for base_game_version: '%1'. Must be 'pokeruby' or 'pokeemerald'.").arg(value)); + logWarn(QString("Invalid config value for base_game_version: '%1'. Must be 'pokeruby', 'pokefirered' or 'pokeemerald'.").arg(value)); } } else if (key == "use_encounter_json") { bool ok; @@ -354,12 +356,18 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { if (!ok) { logWarn(QString("Invalid config value for use_encounter_json: '%1'. Must be 0 or 1.").arg(value)); } - } else if(key == "use_poryscript") { + } else if (key == "use_poryscript") { bool ok; this->usePoryScript = value.toInt(&ok); - if(!ok) { + if (!ok) { logWarn(QString("Invalid config value for use_poryscript: '%1'. Must be 0 or 1.").arg(value)); } + } else if (key == "use_custom_border_size") { + bool ok; + this->useCustomBorderSize = value.toInt(&ok); + if (!ok) { + logWarn(QString("Invalid config value for use_custom_border_size: '%1'. Must be 0 or 1.").arg(value)); + } } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -370,6 +378,7 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("base_game_version", baseGameVersionMap.value(this->baseGameVersion)); map.insert("use_encounter_json", QString::number(this->useEncounterJson)); map.insert("use_poryscript", QString::number(this->usePoryScript)); + map.insert("use_custom_border_size", QString::number(this->useCustomBorderSize)); return map; } @@ -387,6 +396,7 @@ void ProjectConfig::onNewConfigFileCreated() { QComboBox *baseGameVersionComboBox = new QComboBox(); baseGameVersionComboBox->addItem("pokeruby", BaseGameVersion::pokeruby); + baseGameVersionComboBox->addItem("pokefirered", BaseGameVersion::pokefirered); baseGameVersionComboBox->addItem("pokeemerald", BaseGameVersion::pokeemerald); form.addRow(new QLabel("Game Version"), baseGameVersionComboBox); @@ -398,6 +408,7 @@ void ProjectConfig::onNewConfigFileCreated() { this->baseGameVersion = static_cast(baseGameVersionComboBox->currentData().toInt()); } } + this->useCustomBorderSize = this->baseGameVersion == BaseGameVersion::pokefirered; this->useEncounterJson = true; this->usePoryScript = false; } @@ -432,3 +443,12 @@ void ProjectConfig::setUsePoryScript(bool usePoryScript) { bool ProjectConfig::getUsePoryScript() { return this->usePoryScript; } + +void ProjectConfig::setUseCustomBorderSize(bool enable) { + this->useCustomBorderSize = enable; + this->save(); +} + +bool ProjectConfig::getUseCustomBorderSize() { + return this->useCustomBorderSize; +} diff --git a/src/core/event.cpp b/src/core/event.cpp index b90fb773..b52df755 100644 --- a/src/core/event.cpp +++ b/src/core/event.cpp @@ -1,6 +1,7 @@ #include "event.h" #include "map.h" #include "project.h" +#include "config.h" QString EventType::Object = "event_object"; QString EventType::Warp = "event_warp"; @@ -58,6 +59,9 @@ Event* Event::createNewObjectEvent(Project *project) event->put("event_type", EventType::Object); event->put("sprite", project->getEventObjGfxConstants().keys().first()); event->put("movement_type", project->movementTypes->first()); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + event->put("in_connection", false); + } event->put("radius_x", 0); event->put("radius_y", 0); event->put("script_label", "NULL"); @@ -86,7 +90,12 @@ Event* Event::createNewHealLocationEvent(QString map_name) event->put("event_group_type", "heal_event_group"); event->put("event_type", EventType::HealLocation); event->put("loc_name", QString(Map::mapConstantFromName(map_name)).remove(0,4)); + event->put("id_name", map_name.replace(QRegularExpression("([a-z])([A-Z])"), "\\1_\\2").toUpper()); event->put("elevation", 3); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + event->put("respawn_map", map_name); + event->put("respawn_npc", 1); + } return event; } @@ -131,6 +140,10 @@ Event* Event::createNewHiddenItemEvent(Project *project) event->put("item", project->itemNames->first()); event->put("flag", project->flagNames->first()); event->put("elevation", 3); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + event->put("quantity", 1); + event->put("underfoot", false); + } return event; } @@ -158,19 +171,36 @@ QMap Event::getExpectedFields() { QString type = this->get("event_type"); if (type == EventType::Object) { - return QMap { - {"graphics_id", true}, - {"x", true}, - {"y", true}, - {"elevation", true}, - {"movement_type", true}, - {"movement_range_x", true}, - {"movement_range_y", true}, - {"trainer_type", true}, - {"trainer_sight_or_berry_tree_id", true}, - {"script", true}, - {"flag", true}, - }; + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + return QMap { + {"graphics_id", true}, + {"in_connection", true}, + {"x", true}, + {"y", true}, + {"elevation", true}, + {"movement_type", true}, + {"movement_range_x", true}, + {"movement_range_y", true}, + {"trainer_type", true}, + {"trainer_sight_or_berry_tree_id", true}, + {"script", true}, + {"flag", true}, + }; + } else { + return QMap { + {"graphics_id", true}, + {"x", true}, + {"y", true}, + {"elevation", true}, + {"movement_type", true}, + {"movement_range_x", true}, + {"movement_range_y", true}, + {"trainer_type", true}, + {"trainer_sight_or_berry_tree_id", true}, + {"script", true}, + {"flag", true}, + }; + } } else if (type == EventType::Warp) { return QMap { {"x", true}, @@ -207,14 +237,27 @@ QMap Event::getExpectedFields() {"script", true}, }; } else if (type == EventType::HiddenItem) { - return QMap { - {"type", true}, - {"x", true}, - {"y", true}, - {"elevation", true}, - {"item", true}, - {"flag", true}, - }; + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + return QMap { + {"type", true}, + {"x", true}, + {"y", true}, + {"elevation", true}, + {"item", true}, + {"flag", true}, + {"quantity", true}, + {"underfoot", true}, + }; + } else { + return QMap { + {"type", true}, + {"x", true}, + {"y", true}, + {"elevation", true}, + {"item", true}, + {"flag", true}, + }; + } } else if (type == EventType::SecretBase) { return QMap { {"type", true}, @@ -252,6 +295,9 @@ OrderedJson::object Event::buildObjectEventJSON() { OrderedJson::object eventObj; eventObj["graphics_id"] = this->get("sprite"); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + eventObj["in_connection"] = this->getInt("in_connection") > 0 || this->get("in_connection") == "TRUE"; + } eventObj["x"] = this->getU16("x"); eventObj["y"] = this->getU16("y"); eventObj["elevation"] = this->getInt("elevation"); @@ -331,6 +377,10 @@ OrderedJson::object Event::buildHiddenItemEventJSON() hiddenItemObj["elevation"] = this->getInt("elevation"); hiddenItemObj["item"] = this->get("item"); hiddenItemObj["flag"] = this->get("flag"); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + hiddenItemObj["quantity"] = this->getInt("quantity"); + hiddenItemObj["underfoot"] = this->getInt("underfoot") > 0 || this->get("underfoot") == "TRUE"; + } this->addCustomValuesTo(&hiddenItemObj); return hiddenItemObj; diff --git a/src/core/heallocation.cpp b/src/core/heallocation.cpp index ba739831..e88d99e6 100644 --- a/src/core/heallocation.cpp +++ b/src/core/heallocation.cpp @@ -1,17 +1,23 @@ #include "heallocation.h" +#include "config.h" +#include "map.h" -HealLocation::HealLocation(QString map, int i, uint16_t x, uint16_t y) +HealLocation::HealLocation(QString id, QString map, int i, uint16_t x, uint16_t y, QString respawnMap, uint16_t respawnNPC) { - this->name = map; + this->idName = id; + this->mapName = map; this->index = i; this->x = x; this->y = y; + this->respawnMap = respawnMap; + this->respawnNPC = respawnNPC; } HealLocation HealLocation::fromEvent(Event *event) { HealLocation hl; - hl.name = event->get("loc_name"); + hl.idName = event->get("id_name"); + hl.mapName = event->get("loc_name"); try { hl.index = event->get("index").toInt(); } @@ -20,11 +26,15 @@ HealLocation HealLocation::fromEvent(Event *event) } hl.x = event->getU16("x"); hl.y = event->getU16("y"); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + hl.respawnNPC = event->getU16("respawn_npc"); + hl.respawnMap = Map::mapConstantFromName(event->get("respawn_map")).remove(0,4); + } return hl; } QDebug operator<<(QDebug debug, const HealLocation &hl) { - debug << "HealLocation_" + hl.name << "(" << hl.x << ',' << hl.y << ")"; + debug << "HealLocation_" + hl.mapName << "(" << hl.x << ',' << hl.y << ")"; return debug; } diff --git a/src/core/historyitem.cpp b/src/core/historyitem.cpp index 1b0f3888..6eb66fa3 100644 --- a/src/core/historyitem.cpp +++ b/src/core/historyitem.cpp @@ -1,13 +1,17 @@ #include "historyitem.h" -HistoryItem::HistoryItem(Blockdata *metatiles, int layoutWidth, int layoutHeight) { +HistoryItem::HistoryItem(Blockdata *metatiles, Blockdata *border, int layoutWidth, int layoutHeight, int borderWidth, int borderHeight) { this->metatiles = metatiles; + this->border = border; this->layoutWidth = layoutWidth; this->layoutHeight = layoutHeight; + this->borderWidth = borderWidth; + this->borderHeight = borderHeight; } HistoryItem::~HistoryItem() { if (this->metatiles) delete this->metatiles; + if (this->border) delete this->border; } RegionMapHistoryItem::RegionMapHistoryItem(int which, QVector tiles, QString cityMap) { diff --git a/src/core/map.cpp b/src/core/map.cpp index c4dfee2d..667c151a 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -59,6 +59,14 @@ int Map::getHeight() { return layout->height.toInt(nullptr, 0); } +int Map::getBorderWidth() { + return layout->border_width.toInt(nullptr, 0); +} + +int Map::getBorderHeight() { + return layout->border_height.toInt(nullptr, 0); +} + bool Map::mapBlockChanged(int i, Blockdata * cache) { if (!cache) return true; @@ -213,27 +221,33 @@ QPixmap Map::render(bool ignoreCache = false, MapLayout * fromLayout) { } QPixmap Map::renderBorder() { - bool changed_any = false; - int width_ = 2; - int height_ = 2; + bool changed_any = false, border_resized = false; + int width_ = getBorderWidth(); + int height_ = getBorderHeight(); if (layout->border_image.isNull()) { layout->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); changed_any = true; } + if (layout->border_image.width() != width_ * 16 || layout->border_image.height() != height_ * 16) { + layout->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + border_resized = true; + } if (!(layout->border && layout->border->blocks)) { layout->border_pixmap = layout->border_pixmap.fromImage(layout->border_image); return layout->border_pixmap; } QPainter painter(&layout->border_image); for (int i = 0; i < layout->border->blocks->length(); i++) { - if (!borderBlockChanged(i, layout->cached_border)) { + if (!border_resized && !borderBlockChanged(i, layout->cached_border)) { continue; } + changed_any = true; Block block = layout->border->blocks->value(i); - QImage metatile_image = getMetatileImage(block.tile, layout->tileset_primary, layout->tileset_secondary); - int map_y = i / width_; - int map_x = i % width_; + uint16_t tile = block.tile; + QImage metatile_image = getMetatileImage(tile, layout->tileset_primary, layout->tileset_secondary); + int map_y = width_ ? i / width_ : 0; + int map_x = width_ ? i % width_ : 0; painter.drawImage(QPoint(map_x * 16, map_y * 16), metatile_image); } painter.end(); @@ -249,23 +263,23 @@ QPixmap Map::renderConnection(MapConnection connection, MapLayout * fromLayout) int x, y, w, h; if (connection.direction == "up") { x = 0; - y = getHeight() - 6; + y = getHeight() - BORDER_DISTANCE; w = getWidth(); - h = 6; + h = BORDER_DISTANCE; } else if (connection.direction == "down") { x = 0; y = 0; w = getWidth(); - h = 6; + h = BORDER_DISTANCE; } else if (connection.direction == "left") { - x = getWidth() - 6; + x = getWidth() - BORDER_DISTANCE; y = 0; - w = 6; + w = BORDER_DISTANCE; h = getHeight(); } else if (connection.direction == "right") { x = 0; y = 0; - w = 6; + w = BORDER_DISTANCE; h = getHeight(); } else { // this should not happen @@ -298,6 +312,25 @@ void Map::setNewDimensionsBlockdata(int newWidth, int newHeight) { layout->blockdata->copyFrom(newBlockData); } +void Map::setNewBorderDimensionsBlockdata(int newWidth, int newHeight) { + int oldWidth = getBorderWidth(); + int oldHeight = getBorderHeight(); + + Blockdata* newBlockData = new Blockdata; + + for (int y = 0; y < newHeight; y++) + for (int x = 0; x < newWidth; x++) { + if (x < oldWidth && y < oldHeight) { + int index = y * oldWidth + x; + newBlockData->addBlock(layout->border->blocks->value(index)); + } else { + newBlockData->addBlock(0); + } + } + + layout->border->copyFrom(newBlockData); +} + void Map::setDimensions(int newWidth, int newHeight, bool setNewBlockdata) { if (setNewBlockdata) { setNewDimensionsBlockdata(newWidth, newHeight); @@ -309,6 +342,17 @@ void Map::setDimensions(int newWidth, int newHeight, bool setNewBlockdata) { emit mapChanged(this); } +void Map::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata) { + if (setNewBlockdata) { + setNewBorderDimensionsBlockdata(newWidth, newHeight); + } + + layout->border_width = QString::number(newWidth); + layout->border_height = QString::number(newHeight); + + emit mapChanged(this); +} + Block* Map::getBlock(int x, int y) { if (layout->blockdata && layout->blockdata->blocks) { if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) { @@ -363,35 +407,63 @@ void Map::_floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_ } void Map::undo() { + bool redraw = false, changed = false; HistoryItem *commit = metatileHistory.back(); if (!commit) return; if (layout->blockdata) { layout->blockdata->copyFrom(commit->metatiles); - if (commit->layoutWidth != this->getWidth() || commit->layoutHeight != this->getHeight()) - { + if (commit->layoutWidth != this->getWidth() || commit->layoutHeight != this->getHeight()) { this->setDimensions(commit->layoutWidth, commit->layoutHeight, false); - emit mapNeedsRedrawing(); + redraw = true; } + changed = true; + } + if (layout->border) { + layout->border->copyFrom(commit->border); + if (commit->borderWidth != this->getBorderWidth() || commit->borderHeight != this->getBorderHeight()) { + this->setBorderDimensions(commit->borderWidth, commit->borderHeight, false); + redraw = true; + } + changed = true; + } + if (redraw) { + emit mapNeedsRedrawing(); + } + if (changed) { emit mapChanged(this); } } void Map::redo() { + bool redraw = false, changed = false; HistoryItem *commit = metatileHistory.next(); if (!commit) return; if (layout->blockdata) { layout->blockdata->copyFrom(commit->metatiles); - if (commit->layoutWidth != this->getWidth() || commit->layoutHeight != this->getHeight()) - { + if (commit->layoutWidth != this->getWidth() || commit->layoutHeight != this->getHeight()) { this->setDimensions(commit->layoutWidth, commit->layoutHeight, false); - emit mapNeedsRedrawing(); + redraw = true; } + changed = true; + } + if (layout->border) { + layout->border->copyFrom(commit->border); + if (commit->borderWidth != this->getBorderWidth() || commit->borderHeight != this->getBorderHeight()) { + this->setBorderDimensions(commit->borderWidth, commit->borderHeight, false); + redraw = true; + } + changed = true; + } + if (redraw) { + emit mapNeedsRedrawing(); + } + if (changed) { emit mapChanged(this); } } @@ -401,14 +473,22 @@ void Map::commit() { return; } + int layoutWidth = this->getWidth(); + int layoutHeight = this->getHeight(); + int borderWidth = this->getBorderWidth(); + int borderHeight = this->getBorderHeight(); + if (layout->blockdata) { HistoryItem *item = metatileHistory.current(); bool atCurrentHistory = item && layout->blockdata->equals(item->metatiles) - && this->getWidth() == item->layoutWidth - && this->getHeight() == item->layoutHeight; + && layout->border->equals(item->border) + && layoutWidth == item->layoutWidth + && layoutHeight == item->layoutHeight + && borderWidth == item->borderWidth + && borderHeight == item->borderHeight; if (!atCurrentHistory) { - HistoryItem *commit = new HistoryItem(layout->blockdata->copy(), this->getWidth(), this->getHeight()); + HistoryItem *commit = new HistoryItem(layout->blockdata->copy(), layout->border->copy(), layoutWidth, layoutHeight, borderWidth, borderHeight); metatileHistory.push(commit); emit mapChanged(this); } diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index c35a7fcf..7bf3030d 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -11,6 +11,8 @@ Metatile* Metatile::copy() { Metatile *copy = new Metatile; copy->behavior = this->behavior; copy->layerType = this->layerType; + copy->encounterType = this->encounterType; + copy->terrainType = this->terrainType; copy->tiles = new QList; copy->label = this->label; for (Tile tile : *this->tiles) { @@ -22,6 +24,8 @@ Metatile* Metatile::copy() { void Metatile::copyInPlace(Metatile *other) { this->behavior = other->behavior; this->layerType = other->layerType; + this->encounterType = other->encounterType; + this->terrainType = other->terrainType; this->label = other->label; for (int i = 0; i < this->tiles->length(); i++) { (*this->tiles)[i] = other->tiles->at(i); diff --git a/src/core/metatileparser.cpp b/src/core/metatileparser.cpp index da7de6b1..d1b936ff 100644 --- a/src/core/metatileparser.cpp +++ b/src/core/metatileparser.cpp @@ -84,6 +84,8 @@ QList *MetatileParser::parse(QString filepath, bool *error, bool prim (static_cast(in.at(attrOffset + 1)) << 8); metatile->behavior = value & 0xFF; metatile->layerType = (value & 0xF000) >> 12; + metatile->encounterType = 0; + metatile->terrainType = 0; metatile->tiles = tiles; metatiles->append(metatile); } diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index 9b305e15..450458b8 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -2,6 +2,7 @@ #include "metatile.h" #include "project.h" #include "log.h" +#include "config.h" #include #include @@ -115,8 +116,13 @@ bool Tileset::appendToHeaders(QString headerFile, QString friendlyName){ dataString.append(QString("\t.4byte gTilesetTiles_%1\n").arg(friendlyName)); dataString.append(QString("\t.4byte gTilesetPalettes_%1\n").arg(friendlyName)); dataString.append(QString("\t.4byte gMetatiles_%1\n").arg(friendlyName)); - dataString.append(QString("\t.4byte gMetatileAttributes_%1\n").arg(friendlyName)); - dataString.append("\t.4byte NULL\n"); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + dataString.append("\t.4byte NULL\n"); + dataString.append(QString("\t.4byte gMetatileAttributes_%1\n").arg(friendlyName)); + } else { + dataString.append(QString("\t.4byte gMetatileAttributes_%1\n").arg(friendlyName)); + dataString.append("\t.4byte NULL\n"); + } file.write(dataString.toUtf8()); file.flush(); file.close(); diff --git a/src/editor.cpp b/src/editor.cpp index b52f81b7..2f8e38da 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -48,6 +48,8 @@ void Editor::undo() { map->undo(); map_item->draw(); collision_item->draw(); + selected_border_metatiles_item->draw(); + displayMapBorder(); } } @@ -56,6 +58,8 @@ void Editor::redo() { map->redo(); map_item->draw(); collision_item->draw(); + selected_border_metatiles_item->draw(); + displayMapBorder(); } } @@ -1136,10 +1140,10 @@ void Editor::displayMapMetatiles() { int tw = 16; int th = 16; scene->setSceneRect( - -6 * tw, - -6 * th, - map_item->pixmap().width() + 12 * tw, - map_item->pixmap().height() + 12 * th + -BORDER_DISTANCE * tw, + -BORDER_DISTANCE * th, + map_item->pixmap().width() + BORDER_DISTANCE * 2 * tw, + map_item->pixmap().height() + BORDER_DISTANCE * 2 * th ); } @@ -1334,9 +1338,13 @@ void Editor::displayMapBorder() { } borderItems.clear(); + int borderWidth = map->getBorderWidth(); + int borderHeight = map->getBorderHeight(); + int borderHorzDist = getBorderDrawDistance(borderWidth); + int borderVertDist = getBorderDrawDistance(borderHeight); QPixmap pixmap = map->renderBorder(); - for (int y = -6; y < map->getHeight() + 6; y += 2) - for (int x = -6; x < map->getWidth() + 6; x += 2) { + for (int y = -borderVertDist; y < map->getHeight() + borderVertDist; y += borderHeight) + for (int x = -borderHorzDist; x < map->getWidth() + borderHorzDist; x += borderWidth) { QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); item->setX(x * 16); item->setY(y * 16); @@ -1346,6 +1354,17 @@ void Editor::displayMapBorder() { } } +int Editor::getBorderDrawDistance(int dimension) { + // Draw sufficient border blocks to fill the player's view (BORDER_DISTANCE) + if (dimension >= BORDER_DISTANCE) { + return dimension; + } else if (dimension) { + return dimension * (BORDER_DISTANCE / dimension + (BORDER_DISTANCE % dimension ? 1 : 0)); + } else { + return BORDER_DISTANCE; + } +} + void Editor::displayMapGrid() { for (QGraphicsLineItem* item : gridLines) { if (item && item->scene()) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fc0c2f12..e0e64da0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -50,7 +50,6 @@ MainWindow::MainWindow(QWidget *parent) : // Re-initialize everything to a blank slate if opening the recent project failed. this->initWindow(); } - on_toolButton_Paint_clicked(); } @@ -160,19 +159,38 @@ void MainWindow::setProjectSpecificUIVisibility() ui->checkBox_AllowRunning->setVisible(false); ui->checkBox_AllowBiking->setVisible(false); ui->checkBox_AllowEscapeRope->setVisible(false); + ui->spinBox_FloorNumber->setVisible(false); ui->label_AllowRunning->setVisible(false); ui->label_AllowBiking->setVisible(false); ui->label_AllowEscapeRope->setVisible(false); + ui->label_FloorNumber->setVisible(false); + ui->actionRegion_Map_Editor->setVisible(true); break; case BaseGameVersion::pokeemerald: ui->checkBox_AllowRunning->setVisible(true); ui->checkBox_AllowBiking->setVisible(true); ui->checkBox_AllowEscapeRope->setVisible(true); + ui->spinBox_FloorNumber->setVisible(false); ui->label_AllowRunning->setVisible(true); ui->label_AllowBiking->setVisible(true); ui->label_AllowEscapeRope->setVisible(true); + ui->label_FloorNumber->setVisible(false); + ui->actionRegion_Map_Editor->setVisible(true); break; case BaseGameVersion::pokefirered: + ui->checkBox_AllowRunning->setVisible(true); + ui->checkBox_AllowBiking->setVisible(true); + ui->checkBox_AllowEscapeRope->setVisible(true); + ui->spinBox_FloorNumber->setVisible(true); + ui->label_AllowRunning->setVisible(true); + ui->label_AllowBiking->setVisible(true); + ui->label_AllowEscapeRope->setVisible(true); + ui->label_FloorNumber->setVisible(true); + ui->newEventToolButton->newWeatherTriggerAction->setVisible(false); + ui->newEventToolButton->newSecretBaseAction->setVisible(false); + // TODO: pokefirered is not set up for the Region Map Editor and vice versa. + // porymap will crash on attempt. Remove below once resolved + ui->actionRegion_Map_Editor->setVisible(false); break; } } @@ -275,6 +293,7 @@ bool MainWindow::openProject(QString dir) { projectConfig.setProjectDir(dir); projectConfig.load(); + this->closeSupplementaryWindows(); this->setProjectSpecificUIVisibility(); bool already_open = isProjectOpen() && (editor->project->root == dir); @@ -504,6 +523,7 @@ void MainWindow::displayMapProperties() { ui->checkBox_AllowRunning->setChecked(map->allowRunning.toInt() > 0 || map->allowRunning == "TRUE"); ui->checkBox_AllowBiking->setChecked(map->allowBiking.toInt() > 0 || map->allowBiking == "TRUE"); ui->checkBox_AllowEscapeRope->setChecked(map->allowEscapeRope.toInt() > 0 || map->allowEscapeRope == "TRUE"); + ui->spinBox_FloorNumber->setValue(map->floorNumber); // Custom fields table. ui->tableWidget_CustomHeaderFields->blockSignals(true); @@ -607,6 +627,13 @@ void MainWindow::on_checkBox_AllowEscapeRope_clicked(bool checked) } } +void MainWindow::on_spinBox_FloorNumber_valueChanged(int offset) +{ + if (editor && editor->map) { + editor->map->floorNumber = offset; + } +} + bool MainWindow::loadDataStructures() { Project *project = editor->project; bool success = project->readMapLayouts() @@ -619,15 +646,18 @@ bool MainWindow::loadDataStructures() { && project->readMapTypes() && project->readMapBattleScenes() && project->readWeatherNames() - && project->readCoordEventWeatherNames() - && project->readSecretBaseIds() && project->readBgEventFacingDirections() + && project->readTrainerTypes() && project->readMetatileBehaviors() && project->readTilesetProperties() && project->readHealLocations() && project->readMiscellaneousConstants() && project->readSpeciesIconPaths() && project->readWildMonData(); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeemerald || projectConfig.getBaseGameVersion() == BaseGameVersion::pokeruby) + success = success + && project->readSecretBaseIds() + && project->readCoordEventWeatherNames(); if (!success) { return false; } @@ -969,6 +999,8 @@ void MainWindow::on_actionNew_Tileset_triggered() { } mt->behavior = 0; mt->layerType = 0; + mt->encounterType = 0; + mt->terrainType = 0; newSet->metatiles->append(mt); } @@ -1334,6 +1366,7 @@ void MainWindow::updateSelectedObjects() { QList frames; + bool pokefirered = projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered; for (DraggablePixmapItem *item : *events) { EventPropertiesFrame *frame = new EventPropertiesFrame; // frame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); @@ -1387,17 +1420,20 @@ void MainWindow::updateSelectedObjects() { field_labels["radius_y"] = "Movement Radius Y"; field_labels["trainer_type"] = "Trainer Type"; field_labels["sight_radius_tree_id"] = "Sight Radius / Berry Tree ID"; + field_labels["in_connection"] = "In Connection"; field_labels["destination_warp"] = "Destination Warp"; field_labels["destination_map_name"] = "Destination Map"; field_labels["script_var"] = "Var"; field_labels["script_var_value"] = "Var Value"; field_labels["player_facing_direction"] = "Player Facing Direction"; field_labels["item"] = "Item"; - field_labels["item_unknown5"] = "Unknown 5"; - field_labels["item_unknown6"] = "Unknown 6"; + field_labels["quantity"] = "Quantity"; + field_labels["underfoot"] = "Requires Itemfinder"; field_labels["weather"] = "Weather"; field_labels["flag"] = "Flag"; field_labels["secret_base_id"] = "Secret Base Id"; + field_labels["respawn_map"] = "Respawn Map"; + field_labels["respawn_npc"] = "Respawn NPC"; QStringList fields; @@ -1425,6 +1461,9 @@ void MainWindow::updateSelectedObjects() { fields << "event_flag"; fields << "trainer_type"; fields << "sight_radius_tree_id"; + if (pokefirered) { + fields << "in_connection"; + } } else if (event_type == EventType::Warp) { fields << "destination_map_name"; @@ -1445,6 +1484,10 @@ void MainWindow::updateSelectedObjects() { else if (event_type == EventType::HiddenItem) { fields << "item"; fields << "flag"; + if (pokefirered) { + fields << "quantity"; + fields << "underfoot"; + } } else if (event_type == EventType::SecretBase) { fields << "secret_base_id"; @@ -1453,8 +1496,15 @@ void MainWindow::updateSelectedObjects() { // Hide elevation so users don't get impression that editing it is meaningful. frame->ui->spinBox_z->setVisible(false); frame->ui->label_z->setVisible(false); + if (pokefirered) { + fields << "respawn_map"; + fields << "respawn_npc"; + } } + // Some keys shouldn't use a combobox + QStringList spinKeys = {"quantity", "respawn_npc"}; + QStringList checkKeys = {"underfoot", "in_connection"}; for (QString key : fields) { QString value = item->event->get(key); QWidget *widget = new QWidget(frame); @@ -1462,30 +1512,17 @@ void MainWindow::updateSelectedObjects() { fl->setContentsMargins(9, 0, 9, 0); fl->setRowWrapPolicy(QFormLayout::WrapLongRows); - NoScrollComboBox *combo = new NoScrollComboBox(widget); - combo->setEditable(true); + NoScrollSpinBox *spin; + NoScrollComboBox *combo; + QCheckBox *check; - // trainer_type has custom values, so it has special signal logic. - if (key == "trainer_type") { - combo->setEditable(false); - combo->addItem("NONE", "0"); - combo->addItem("NORMAL", "1"); - combo->addItem("SEE ALL DIRECTIONS", "3"); - combo->setToolTip("The trainer type of this object event.\n" - "If it is not a trainer, use NONE. SEE ALL DIRECTIONS\n" - "should only be used with a sight radius of 1."); - combo->setMinimumContentsLength(10); - - int index = combo->findData(value); - if (index != -1) { - combo->setCurrentIndex(index); - } - - fl->addRow(new QLabel(field_labels[key], widget), combo); - widget->setLayout(fl); - frame->layout()->addWidget(widget); - item->bindToUserData(combo, key); - continue; + if (spinKeys.contains(key)) { + spin = new NoScrollSpinBox(widget); + } else if (checkKeys.contains(key)) { + check = new QCheckBox(widget); + } else { + combo = new NoScrollComboBox(widget); + combo->setEditable(true); } if (key == "destination_map_name") { @@ -1501,6 +1538,12 @@ void MainWindow::updateSelectedObjects() { combo->addItem(value); } combo->addItems(*editor->project->itemNames); + } else if (key == "quantity") { + spin->setToolTip("The number of items received when the hidden item is picked up."); + // Min 1 not needed. 0 is treated as a valid quantity and works as expected in-game. + spin->setMaximum(127); + } else if (key == "underfoot") { + check->setToolTip("If checked, hidden item can only be picked up using the Itemfinder"); } else if (key == "flag" || key == "event_flag") { if (!editor->project->flagNames->contains(value)) { combo->addItem(value); @@ -1564,20 +1607,72 @@ void MainWindow::updateSelectedObjects() { combo->setMinimumContentsLength(4); } else if (key == "script_label") { combo->setToolTip("The script which is executed with this event."); + } else if (key == "trainer_type") { + combo->addItems(*editor->project->trainerTypes); + combo->setToolTip("The trainer type of this object event.\n" + "If it is not a trainer, use NONE. SEE ALL DIRECTIONS\n" + "should only be used with a sight radius of 1."); } else if (key == "sight_radius_tree_id") { combo->setToolTip("The maximum sight range of a trainer,\n" "OR the unique id of the berry tree."); combo->setMinimumContentsLength(4); + } else if (key == "in_connection") { + check->setToolTip("Check if object is positioned in the connection to another map."); + } else if (key == "respawn_map") { + if (!editor->project->mapNames->contains(value)) { + combo->addItem(value); + } + combo->addItems(*editor->project->mapNames); + combo->setToolTip("The map where the player will respawn after whiteout."); + } else if (key == "respawn_npc") { + spin->setToolTip("event_object ID of the NPC the player interacts with\n" + "upon respawning after whiteout."); + spin->setMinimum(1); + spin->setMaximum(126); } else { combo->addItem(value); } - combo->setCurrentText(value); - fl->addRow(new QLabel(field_labels[key], widget), combo); - widget->setLayout(fl); - frame->layout()->addWidget(widget); + // Keys using spin boxes + if (spinKeys.contains(key)) { + spin->setValue(value.toInt()); - item->bind(combo, key); + fl->addRow(new QLabel(field_labels[key], widget), spin); + widget->setLayout(fl); + frame->layout()->addWidget(widget); + + connect(spin, QOverload::of(&NoScrollSpinBox::valueChanged), [item, key](int value) { + item->event->put(key, value); + }); + // Keys using check boxes + } else if (checkKeys.contains(key)) { + check->setChecked(value.toInt()); + + fl->addRow(new QLabel(field_labels[key], widget), check); + widget->setLayout(fl); + frame->layout()->addWidget(widget); + + connect(check, &QCheckBox::stateChanged, [item, key](int state) { + switch (state) + { + case Qt::Checked: + item->event->put(key, true); + break; + case Qt::Unchecked: + item->event->put(key, false); + break; + } + }); + // Keys using combo boxes + } else { + combo->setCurrentText(value); + + fl->addRow(new QLabel(field_labels[key], widget), combo); + widget->setLayout(fl); + frame->layout()->addWidget(widget); + + item->bind(combo, key); + } } // Custom fields table. @@ -2064,7 +2159,7 @@ void MainWindow::on_comboBox_SecondaryTileset_currentTextChanged(const QString & } } -void MainWindow::on_pushButton_clicked() +void MainWindow::on_pushButton_ChangeDimensions_clicked() { QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); dialog.setWindowTitle("Change Map Dimensions"); @@ -2074,15 +2169,31 @@ void MainWindow::on_pushButton_clicked() QSpinBox *widthSpinBox = new QSpinBox(); QSpinBox *heightSpinBox = new QSpinBox(); + QSpinBox *bwidthSpinBox = new QSpinBox(); + QSpinBox *bheightSpinBox = new QSpinBox(); widthSpinBox->setMinimum(1); heightSpinBox->setMinimum(1); + bwidthSpinBox->setMinimum(1); + bheightSpinBox->setMinimum(1); // See below for explanation of maximum map dimensions widthSpinBox->setMaximum(0x1E7); heightSpinBox->setMaximum(0x1D1); + // Maximum based only on data type (u8) of map border width/height + bwidthSpinBox->setMaximum(255); + bheightSpinBox->setMaximum(255); widthSpinBox->setValue(editor->map->getWidth()); heightSpinBox->setValue(editor->map->getHeight()); - form.addRow(new QLabel("Width"), widthSpinBox); - form.addRow(new QLabel("Height"), heightSpinBox); + bwidthSpinBox->setValue(editor->map->getBorderWidth()); + bheightSpinBox->setValue(editor->map->getBorderHeight()); + if (projectConfig.getUseCustomBorderSize()) { + form.addRow(new QLabel("Map Width"), widthSpinBox); + form.addRow(new QLabel("Map Height"), heightSpinBox); + form.addRow(new QLabel("Border Width"), bwidthSpinBox); + form.addRow(new QLabel("Border Height"), bheightSpinBox); + } else { + form.addRow(new QLabel("Width"), widthSpinBox); + form.addRow(new QLabel("Height"), heightSpinBox); + } QLabel *errorLabel = new QLabel(); QPalette errorPalette; @@ -2104,8 +2215,8 @@ void MainWindow::on_pushButton_clicked() dialog.accept(); } else { QString errorText = QString("Error: The specified width and height are too large.\n" - "The maximum width and height is the following: (width + 15) * (height + 14) <= 10240\n" - "The specified width and height was: (%1 + 15) * (%2 + 14) = %3") + "The maximum map width and height is the following: (width + 15) * (height + 14) <= 10240\n" + "The specified map width and height was: (%1 + 15) * (%2 + 14) = %3") .arg(widthSpinBox->value()) .arg(heightSpinBox->value()) .arg(numMetatiles); @@ -2119,6 +2230,7 @@ void MainWindow::on_pushButton_clicked() if (dialog.exec() == QDialog::Accepted) { editor->map->setDimensions(widthSpinBox->value(), heightSpinBox->value()); + editor->map->setBorderDimensions(bwidthSpinBox->value(), bheightSpinBox->value()); editor->map->commit(); onMapNeedsRedrawing(); } @@ -2299,6 +2411,17 @@ void MainWindow::on_actionRegion_Map_Editor_triggered() { } } +void MainWindow::closeSupplementaryWindows() { + if (this->tilesetEditor) + delete this->tilesetEditor; + if (this->regionMapEditor) + delete this->regionMapEditor; + if (this->mapImageExporter) + delete this->mapImageExporter; + if (this->newmapprompt) + delete this->newmapprompt; +} + void MainWindow::closeEvent(QCloseEvent *event) { if (projectHasUnsavedChanges || editor->map->hasUnsavedChanges()) { QMessageBox::StandardButton result = QMessageBox::question( diff --git a/src/project.cpp b/src/project.cpp index 4033faf0..fcdb1dcb 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -8,6 +8,7 @@ #include "tile.h" #include "tileset.h" #include "imageexport.h" +#include "map.h" #include "orderedjson.h" @@ -48,6 +49,7 @@ Project::Project() coordEventWeatherNames = new QStringList; secretBaseIds = new QStringList; bgEventFacingDirections = new QStringList; + trainerTypes = new QStringList; map_cache = new QMap; mapConstantsToMapNames = new QMap; mapNamesToMapConstants = new QMap; @@ -105,8 +107,8 @@ QMap Project::getTopLevelMapFields() { {"requires_flash", true}, {"weather", true}, {"map_type", true}, - {"allow_bike", true}, - {"allow_escape_rope", true}, + {"allow_cycling", true}, + {"allow_escaping", true}, {"allow_running", true}, {"show_map_name", true}, {"battle_scene", true}, @@ -118,6 +120,31 @@ QMap Project::getTopLevelMapFields() { {"shared_events_map", true}, {"shared_scripts_map", true}, }; + } else if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + return QMap + { + {"id", true}, + {"name", true}, + {"layout", true}, + {"music", true}, + {"region_map_section", true}, + {"requires_flash", true}, + {"weather", true}, + {"map_type", true}, + {"allow_cycling", true}, + {"allow_escaping", true}, + {"allow_running", true}, + {"show_map_name", true}, + {"floor_number", true}, + {"battle_scene", true}, + {"connections", true}, + {"object_events", true}, + {"warp_events", true}, + {"coord_events", true}, + {"bg_events", true}, + {"shared_events_map", true}, + {"shared_scripts_map", true}, + }; } else if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeruby) { return QMap { @@ -169,9 +196,14 @@ bool Project::loadMapData(Map* map) { map->show_location = QString::number(mapObj["show_map_name"].toBool()); map->battle_scene = mapObj["battle_scene"].toString(); if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeemerald) { - map->allowBiking = QString::number(mapObj["allow_bike"].toBool()); - map->allowEscapeRope = QString::number(mapObj["allow_escape_rope"].toBool()); + map->allowBiking = QString::number(mapObj["allow_cycling"].toBool()); + map->allowEscapeRope = QString::number(mapObj["allow_escaping"].toBool()); map->allowRunning = QString::number(mapObj["allow_running"].toBool()); + } else if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + map->allowBiking = QString::number(mapObj["allow_cycling"].toBool()); + map->allowEscapeRope = QString::number(mapObj["allow_escaping"].toBool()); + map->allowRunning = QString::number(mapObj["allow_running"].toBool()); + map->floorNumber = mapObj["floor_number"].toInt(); } map->sharedEventsMap = mapObj["shared_events_map"].toString(); map->sharedScriptsMap = mapObj["shared_scripts_map"].toString(); @@ -184,6 +216,9 @@ bool Project::loadMapData(Map* map) { Event *object = new Event(event, EventType::Object); object->put("map_name", map->name); object->put("sprite", event["graphics_id"].toString()); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + object->put("in_connection", event["in_connection"].toBool()); + } object->put("x", QString::number(event["x"].toInt())); object->put("y", QString::number(event["y"].toInt())); object->put("elevation", QString::number(event["elevation"].toInt())); @@ -230,17 +265,22 @@ bool Project::loadMapData(Map* map) { HealLocation loc = *it; //if TRUE map is flyable / has healing location - if (loc.name == QString(mapNamesToMapConstants->value(map->name)).remove(0,4)) { + if (loc.mapName == QString(mapNamesToMapConstants->value(map->name)).remove(0,4)) { Event *heal = new Event; heal->put("map_name", map->name); heal->put("x", loc.x); heal->put("y", loc.y); - heal->put("loc_name", loc.name); + heal->put("loc_name", loc.mapName); + heal->put("id_name", loc.idName); heal->put("index", loc.index); heal->put("elevation", 3); // TODO: change this? heal->put("destination_map_name", mapConstantsToMapNames->value(map->name)); heal->put("event_group_type", "heal_event_group"); heal->put("event_type", EventType::HealLocation); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + heal->put("respawn_map", mapConstantsToMapNames->value(QString("MAP_" + loc.respawnMap))); + heal->put("respawn_npc", loc.respawnNPC); + } map->events["heal_event_group"].append(heal); } @@ -300,6 +340,10 @@ bool Project::loadMapData(Map* map) { bg->put("elevation", QString::number(event["elevation"].toInt())); bg->put("item", event["item"].toString()); bg->put("flag", event["flag"].toString()); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + bg->put("quantity", event["quantity"].toInt()); + bg->put("underfoot", event["underfoot"].toBool()); + } bg->put("event_group_type", "bg_event_group"); map->events["bg_event_group"].append(bg); } else if (type == "secret_base") { @@ -378,12 +422,12 @@ QString Project::readMapLocation(QString map_name) { } void Project::setNewMapHeader(Map* map, int mapIndex) { - map->song = "MUS_DAN02"; map->layoutId = QString("%1").arg(mapIndex); - map->location = "MAPSEC_LITTLEROOT_TOWN"; + map->location = mapSectionValueToName.value(0); map->requiresFlash = "FALSE"; - map->weather = "WEATHER_SUNNY"; - map->type = "MAP_TYPE_TOWN"; + map->weather = weatherNames->value(0, "WEATHER_NONE"); + map->type = mapTypes->value(0, "MAP_TYPE_NONE"); + map->song = defaultSong; if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeruby) { map->show_location = "TRUE"; } else if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeemerald) { @@ -391,9 +435,15 @@ void Project::setNewMapHeader(Map* map, int mapIndex) { map->allowEscapeRope = "0"; map->allowRunning = "1"; map->show_location = "1"; + } else if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + map->allowBiking = "1"; + map->allowEscapeRope = "0"; + map->allowRunning = "1"; + map->show_location = "1"; + map->floorNumber = 0; } - map->battle_scene = "MAP_BATTLE_SCENE_NORMAL"; + map->battle_scene = mapBattleScenes->value(0, "MAP_BATTLE_SCENE_NORMAL"); } bool Project::loadMapLayout(Map* map) { @@ -408,9 +458,14 @@ bool Project::loadMapLayout(Map* map) { return false; } - return loadMapTilesets(map) - && loadBlockdata(map) - && loadMapBorder(map); + // Force these to run even if one fails + bool loadedTilesets = loadMapTilesets(map); + bool loadedBlockdata = loadBlockdata(map); + bool loadedBorder = loadMapBorder(map); + + return loadedTilesets + && loadedBlockdata + && loadedBorder; } bool Project::readMapLayouts() { @@ -449,8 +504,15 @@ bool Project::readMapLayouts() { "border_filepath", "blockdata_filepath", }; + bool useCustomBorderSize = projectConfig.getUseCustomBorderSize(); + if (useCustomBorderSize) { + requiredFields.append("border_width"); + requiredFields.append("border_height"); + } for (int i = 0; i < layouts.size(); i++) { QJsonObject layoutObj = layouts[i].toObject(); + if (layoutObj.isEmpty()) + continue; if (!parser.ensureFieldsExist(layoutObj, requiredFields)) { logError(QString("Layout %1 is missing field(s) in %2.").arg(i).arg(layoutsFilepath)); return false; @@ -478,6 +540,23 @@ bool Project::readMapLayouts() { return false; } layout->height = QString::number(lheight); + if (useCustomBorderSize) { + int bwidth = layoutObj["border_width"].toInt(); + if (bwidth <= 0) { // 0 is an expected border width/height that should be handled, GF used it for the RS layouts in FRLG + logWarn(QString("Invalid layout 'border_width' value '%1' on layout %2 in %3. Must be greater than 0. Using default (%4) instead.").arg(bwidth).arg(i).arg(layoutsFilepath).arg(DEFAULT_BORDER_WIDTH)); + bwidth = DEFAULT_BORDER_WIDTH; + } + layout->border_width = QString::number(bwidth); + int bheight = layoutObj["border_height"].toInt(); + if (bheight <= 0) { + logWarn(QString("Invalid layout 'border_height' value '%1' on layout %2 in %3. Must be greater than 0. Using default (%4) instead.").arg(bheight).arg(i).arg(layoutsFilepath).arg(DEFAULT_BORDER_HEIGHT)); + bheight = DEFAULT_BORDER_HEIGHT; + } + layout->border_height = QString::number(bheight); + } else { + layout->border_width = QString::number(DEFAULT_BORDER_WIDTH); + layout->border_height = QString::number(DEFAULT_BORDER_HEIGHT); + } layout->tileset_primary_label = layoutObj["primary_tileset"].toString(); if (layout->tileset_primary_label.isEmpty()) { logError(QString("Missing 'primary_tileset' value on layout %1 in %2").arg(i).arg(layoutsFilepath)); @@ -521,6 +600,7 @@ void Project::saveMapLayouts() { OrderedJson::object layoutsObj; layoutsObj["layouts_table_label"] = layoutsLabel; + bool useCustomBorderSize = projectConfig.getUseCustomBorderSize(); OrderedJson::array layoutsArr; for (QString layoutId : mapLayoutsTableMaster) { MapLayout *layout = mapLayouts.value(layoutId); @@ -529,6 +609,10 @@ void Project::saveMapLayouts() { layoutObj["name"] = layout->name; layoutObj["width"] = layout->width.toInt(nullptr, 0); layoutObj["height"] = layout->height.toInt(nullptr, 0); + if (useCustomBorderSize) { + layoutObj["border_width"] = layout->border_width.toInt(nullptr, 0); + layoutObj["border_height"] = layout->border_height.toInt(nullptr, 0); + } layoutObj["primary_tileset"] = layout->tileset_primary_label; layoutObj["secondary_tileset"] = layout->tileset_secondary_label; layoutObj["border_filepath"] = layout->border_path; @@ -549,10 +633,12 @@ void Project::setNewMapLayout(Map* map) { layout->name = QString("%1_Layout").arg(map->name); layout->width = "20"; layout->height = "20"; + layout->border_width = DEFAULT_BORDER_WIDTH; + layout->border_height = DEFAULT_BORDER_HEIGHT; layout->border_path = QString("data/layouts/%1/border.bin").arg(map->name); layout->blockdata_path = QString("data/layouts/%1/map.bin").arg(map->name); - layout->tileset_primary_label = "gTileset_General"; - layout->tileset_secondary_label = "gTileset_Petalburg"; + layout->tileset_primary_label = tilesetLabels["primary"].value(0, "gTileset_General"); + layout->tileset_secondary_label = tilesetLabels["secondary"].value(0, projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered ? "gTileset_PalletTown" : "gTileset_Petalburg"); map->layout = layout; map->layoutId = layout->id; @@ -716,9 +802,19 @@ void Project::saveMapConstantsHeader() { // saves heal location coords in root + /src/data/heal_locations.h // and indexes as defines in root + /include/constants/heal_locations.h void Project::saveHealLocationStruct(Map *map) { - QString data_text = QString("%1%2struct HealLocation sHealLocations[] =\n{\n") + QString constantPrefix, arrayName; + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + constantPrefix = "SPAWN_"; + arrayName = "sSpawnPoints"; + } else { + constantPrefix = "HEAL_LOCATION_"; + arrayName = "sHealLocations"; + } + + QString data_text = QString("%1%2struct HealLocation %3[] =\n{\n") .arg(dataQualifiers.value("heal_locations").isStatic ? "static " : "") - .arg(dataQualifiers.value("heal_locations").isConst ? "const " : ""); + .arg(dataQualifiers.value("heal_locations").isConst ? "const " : "") + .arg(arrayName); QString constants_text = QString("#ifndef GUARD_CONSTANTS_HEAL_LOCATIONS_H\n"); constants_text += QString("#define GUARD_CONSTANTS_HEAL_LOCATIONS_H\n\n"); @@ -729,7 +825,7 @@ void Project::saveHealLocationStruct(Map *map) { // set flyableMapsDupes and flyableMapsUnique for (auto it = flyableMaps.begin(); it != flyableMaps.end(); it++) { HealLocation loc = *it; - QString xname = loc.name; + QString xname = loc.idName; if (flyableMapsUnique.contains(xname)) { flyableMapsDupes[xname] = 1; } @@ -745,33 +841,58 @@ void Project::saveHealLocationStruct(Map *map) { } int i = 1; - for (auto map_in : flyableMaps) { - data_text += QString(" {MAP_GROUP(%1), MAP_NUM(%1), %2, %3},\n") - .arg(map_in.name) + // add numbered suffix for duplicate constants + if (flyableMapsDupes.keys().contains(map_in.idName)) { + map_in.idName += QString("_%1").arg(flyableMapsDupes[map_in.idName]); + flyableMapsDupes[map_in.idName]++; + } + + // Save first array (heal location coords), only data array in RSE + data_text += QString(" [%1%2 - 1] = {MAP_GROUP(%3), MAP_NUM(%3), %4, %5},\n") + .arg(constantPrefix) + .arg(map_in.idName) + .arg(map_in.mapName) .arg(map_in.x) .arg(map_in.y); - QString ending = QString(""); - - // must add _1 / _2 for maps that have duplicates - if (flyableMapsDupes.keys().contains(map_in.name)) { - // map contains multiple heal locations - ending += QString("_%1").arg(flyableMapsDupes[map_in.name]); - flyableMapsDupes[map_in.name]++; - } + // Save constants if (map_in.index != 0) { - constants_text += QString("#define HEAL_LOCATION_%1 %2\n") - .arg(map_in.name + ending) + constants_text += QString("#define %1%2 %3\n") + .arg(constantPrefix) + .arg(map_in.idName) .arg(map_in.index); - } - else { - constants_text += QString("#define HEAL_LOCATION_%1 %2\n") - .arg(map_in.name + ending) + } else { + constants_text += QString("#define %1%2 %3\n") + .arg(constantPrefix) + .arg(map_in.idName) .arg(i); } i++; } + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + // Save second array (map where player respawns for each heal location) + data_text += QString("};\n\n%1%2u16 sWhiteoutRespawnHealCenterMapIdxs[][2] =\n{\n") + .arg(dataQualifiers.value("heal_locations").isStatic ? "static " : "") + .arg(dataQualifiers.value("heal_locations").isConst ? "const " : ""); + for (auto map_in : flyableMaps) { + data_text += QString(" [%1%2 - 1] = {MAP_GROUP(%3), MAP_NUM(%3)},\n") + .arg(constantPrefix) + .arg(map_in.idName) + .arg(map_in.respawnMap); + } + + // Save third array (object id of NPC player speaks to upon respawning for each heal location) + data_text += QString("};\n\n%1%2u8 sWhiteoutRespawnHealerNpcIds[] =\n{\n") + .arg(dataQualifiers.value("heal_locations").isStatic ? "static " : "") + .arg(dataQualifiers.value("heal_locations").isConst ? "const " : ""); + for (auto map_in : flyableMaps) { + data_text += QString(" [%1%2 - 1] = %3,\n") + .arg(constantPrefix) + .arg(map_in.idName) + .arg(map_in.respawnNPC); + } + } data_text += QString("};\n"); constants_text += QString("\n#endif // GUARD_CONSTANTS_HEAL_LOCATIONS_H\n"); @@ -876,9 +997,21 @@ void Project::saveTilesetMetatileAttributes(Tileset *tileset) { QFile attrs_file(tileset->metatile_attrs_path); if (attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QByteArray data; - for (Metatile *metatile : *tileset->metatiles) { - data.append(static_cast(metatile->behavior)); - data.append(static_cast((metatile->layerType << 4) & 0xF0)); + + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + for (Metatile *metatile : *tileset->metatiles) { + data.append(static_cast(metatile->behavior)); + data.append(static_cast(metatile->behavior >> 8) | + static_cast(metatile->terrainType << 1)); + data.append(static_cast(0)); + data.append(static_cast(metatile->encounterType) | + static_cast(metatile->layerType << 5)); + } + } else { + for (Metatile *metatile : *tileset->metatiles) { + data.append(static_cast(metatile->behavior)); + data.append(static_cast((metatile->layerType << 4) & 0xF0)); + } } attrs_file.write(data); } else { @@ -927,14 +1060,26 @@ bool Project::loadMapTilesets(Map* map) { map->layout->tileset_primary = getTileset(map->layout->tileset_primary_label); if (!map->layout->tileset_primary) { - logError(QString("Map layout %1 has invalid primary tileset '%2'").arg(map->layout->id).arg(map->layout->tileset_primary_label)); - return false; + 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) { + 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) { - logError(QString("Map layout %1 has invalid secondary tileset '%2'").arg(map->layout->id).arg(map->layout->tileset_secondary_label)); - return false; + 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) { + logError(QString("Failed to set default secondary tileset.")); + return false; + } } return true; } @@ -954,8 +1099,13 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) { tileset->tiles_label = values->value(3); tileset->palettes_label = values->value(4); tileset->metatiles_label = values->value(5); - tileset->metatile_attrs_label = values->value(6); - tileset->callback_label = values->value(7); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + tileset->callback_label = values->value(6); + tileset->metatile_attrs_label = values->value(7); + } else { + tileset->metatile_attrs_label = values->value(6); + tileset->callback_label = values->value(7); + } loadTilesetAssets(tileset); @@ -997,7 +1147,7 @@ bool Project::loadMapBorder(Map *map) { QString path = QString("%1/%2").arg(root).arg(map->layout->border_path); map->layout->border = readBlockdata(path); - int borderLength = 4; + int borderLength = map->getBorderWidth() * map->getBorderHeight(); if (map->layout->border->blocks->count() != borderLength) { logWarn(QString("Layout border blockdata length %1 must be %2. Resizing border blockdata.") .arg(map->layout->border->blocks->count()) @@ -1009,10 +1159,21 @@ bool Project::loadMapBorder(Map *map) { void Project::setNewMapBorder(Map *map) { Blockdata *blockdata = new Blockdata; - blockdata->addBlock(qint16(0x01D4)); - blockdata->addBlock(qint16(0x01D5)); - blockdata->addBlock(qint16(0x01DC)); - blockdata->addBlock(qint16(0x01DD)); + if (map->getBorderWidth() != DEFAULT_BORDER_WIDTH || map->getBorderHeight() != DEFAULT_BORDER_HEIGHT) { + for (int i = 0; i < map->getBorderWidth() * map->getBorderHeight(); i++) { + blockdata->addBlock(0); + } + } else if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + blockdata->addBlock(qint16(0x0014)); + blockdata->addBlock(qint16(0x0015)); + blockdata->addBlock(qint16(0x001C)); + blockdata->addBlock(qint16(0x001D)); + } else { + blockdata->addBlock(qint16(0x01D4)); + blockdata->addBlock(qint16(0x01D5)); + blockdata->addBlock(qint16(0x01DC)); + blockdata->addBlock(qint16(0x01DD)); + } map->layout->border = blockdata; } @@ -1058,14 +1219,14 @@ void Project::saveMap(Map *map) { QString text = this->getScriptDefaultString(projectConfig.getUsePoryScript(), map->name); saveTextFile(root + "/data/maps/" + map->name + "/scripts" + this->getScriptFileExtension(projectConfig.getUsePoryScript()), text); - if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeruby) { + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeruby || projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { // Create file data/maps//text.inc saveTextFile(root + "/data/maps/" + map->name + "/text" + this->getScriptFileExtension(projectConfig.getUsePoryScript()), "\n"); } // Simply append to data/event_scripts.s. text = QString("\n\t.include \"data/maps/%1/scripts.inc\"\n").arg(map->name); - if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeruby) { + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeruby || projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { text += QString("\t.include \"data/maps/%1/text.inc\"\n").arg(map->name); } appendTextFile(root + "/data/event_scripts.s", text); @@ -1099,6 +1260,10 @@ void Project::saveMap(Map *map) { newLayoutObj["name"] = map->layout->name; newLayoutObj["width"] = map->layout->width.toInt(); newLayoutObj["height"] = map->layout->height.toInt(); + if (projectConfig.getUseCustomBorderSize()) { + newLayoutObj["border_width"] = map->layout->border_width.toInt(); + newLayoutObj["border_height"] = map->layout->border_height.toInt(); + } newLayoutObj["primary_tileset"] = map->layout->tileset_primary_label; newLayoutObj["secondary_tileset"] = map->layout->tileset_secondary_label; newLayoutObj["border_filepath"] = map->layout->border_path; @@ -1125,10 +1290,13 @@ void Project::saveMap(Map *map) { mapObj["requires_flash"] = map->requiresFlash.toInt() > 0 || map->requiresFlash == "TRUE"; mapObj["weather"] = map->weather; mapObj["map_type"] = map->type; - mapObj["allow_bike"] = map->allowBiking.toInt() > 0 || map->allowBiking == "TRUE"; - mapObj["allow_escape_rope"] = map->allowEscapeRope.toInt() > 0 || map->allowEscapeRope == "TRUE"; + mapObj["allow_cycling"] = map->allowBiking.toInt() > 0 || map->allowBiking == "TRUE"; + mapObj["allow_escaping"] = map->allowEscapeRope.toInt() > 0 || map->allowEscapeRope == "TRUE"; mapObj["allow_running"] = map->allowRunning.toInt() > 0 || map->allowRunning == "TRUE"; mapObj["show_map_name"] = map->show_location.toInt() > 0 || map->show_location == "TRUE"; + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + mapObj["floor_number"] = map->floorNumber; + } mapObj["battle_scene"] = map->battle_scene; // Connections @@ -1254,8 +1422,9 @@ void Project::loadTilesetAssets(Tileset* tileset) { if (tileset->name.isNull()) { return; } + QRegularExpression re("([a-z])([A-Z0-9])"); QString tilesetName = tileset->name; - QString dir_path = root + "/data/tilesets/" + category + "/" + tilesetName.replace("gTileset_", "").toLower(); + QString dir_path = root + "/data/tilesets/" + category + "/" + tilesetName.replace("gTileset_", "").replace(re, "\\1_\\2").toLower(); QList *graphics = parser.parseAsm("data/tilesets/graphics.inc"); QStringList *tiles_values = parser.getLabelValues(graphics, tileset->tiles_label); @@ -1394,16 +1563,43 @@ void Project::loadTilesetMetatiles(Tileset* tileset) { if (attrs_file.open(QIODevice::ReadOnly)) { QByteArray data = attrs_file.readAll(); int num_metatiles = tileset->metatiles->count(); - int num_metatileAttrs = data.length() / 2; - if (num_metatiles != num_metatileAttrs) { - logWarn(QString("Metatile count %1 does not match metatile attribute count %2 in %3").arg(num_metatiles).arg(num_metatileAttrs).arg(tileset->name)); - if (num_metatileAttrs > num_metatiles) - num_metatileAttrs = num_metatiles; - } - for (int i = 0; i < num_metatileAttrs; i++) { - int value = (static_cast(data.at(i * 2 + 1)) << 8) | static_cast(data.at(i * 2)); - tileset->metatiles->at(i)->behavior = value & 0xFF; - tileset->metatiles->at(i)->layerType = (value & 0xF000) >> 12; + + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + int num_metatileAttrs = data.length() / 4; + if (num_metatiles != num_metatileAttrs) { + logWarn(QString("Metatile count %1 does not match metatile attribute count %2 in %3").arg(num_metatiles).arg(num_metatileAttrs).arg(tileset->name)); + if (num_metatileAttrs > num_metatiles) + num_metatileAttrs = num_metatiles; + } + bool unusedAttribute = false; + for (int i = 0; i < num_metatileAttrs; i++) { + int value = (static_cast(data.at(i * 4 + 3)) << 24) | + (static_cast(data.at(i * 4 + 2)) << 16) | + (static_cast(data.at(i * 4 + 1)) << 8) | + (static_cast(data.at(i * 4 + 0))); + tileset->metatiles->at(i)->behavior = value & 0x1FF; + tileset->metatiles->at(i)->terrainType = (value & 0x3E00) >> 9; + tileset->metatiles->at(i)->encounterType = (value & 0x7000000) >> 24; + tileset->metatiles->at(i)->layerType = (value & 0x60000000) >> 29; + if (value & ~(0x67003FFF)) + unusedAttribute = true; + } + if (unusedAttribute) + logWarn(QString("Unrecognized metatile attributes in %1 will not be saved.").arg(tileset->metatile_attrs_path)); + } else { + int num_metatileAttrs = data.length() / 2; + if (num_metatiles != num_metatileAttrs) { + logWarn(QString("Metatile count %1 does not match metatile attribute count %2 in %3").arg(num_metatiles).arg(num_metatileAttrs).arg(tileset->name)); + if (num_metatileAttrs > num_metatiles) + num_metatileAttrs = num_metatiles; + } + for (int i = 0; i < num_metatileAttrs; i++) { + int value = (static_cast(data.at(i * 2 + 1)) << 8) | static_cast(data.at(i * 2)); + tileset->metatiles->at(i)->behavior = value & 0xFF; + tileset->metatiles->at(i)->layerType = (value & 0xF000) >> 12; + tileset->metatiles->at(i)->encounterType = 0; + tileset->metatiles->at(i)->terrainType = 0; + } } } else { logError(QString("Could not open tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path)); @@ -1812,21 +2008,46 @@ bool Project::readRegionMapSections() { bool Project::readHealLocations() { dataQualifiers.clear(); flyableMaps.clear(); - QString filename = "src/data/heal_locations.h"; QString text = parser.readTextFile(root + "/" + filename); text.replace(QRegularExpression("//.*?(\r\n?|\n)|/\\*.*?\\*/", QRegularExpression::DotMatchesEverythingOption), ""); - dataQualifiers.insert("heal_locations", getDataQualifiers(text, "sHealLocations")); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + dataQualifiers.insert("heal_locations", getDataQualifiers(text, "sSpawnPoints")); + QRegularExpression spawnRegex("SPAWN_(?[A-Za-z0-9_]+)\\s*- 1\\]\\s* = \\{MAP_GROUP[\\(\\s]+(?[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\2)[\\s\\)]+,\\s*(?[0-9A-Fa-fx]+),\\s*(?[0-9A-Fa-fx]+)"); + QRegularExpression respawnMapRegex("SPAWN_(?[A-Za-z0-9_]+)\\s*- 1\\]\\s* = \\{MAP_GROUP[\\(\\s]+(?[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\2)[\\s\\)]+}"); + QRegularExpression respawnNPCRegex("SPAWN_(?[A-Za-z0-9_]+)\\s*- 1\\]\\s* = (?[0-9]+)"); + QRegularExpressionMatchIterator spawns = spawnRegex.globalMatch(text); + QRegularExpressionMatchIterator respawnMaps = respawnMapRegex.globalMatch(text); + QRegularExpressionMatchIterator respawnNPCs = respawnNPCRegex.globalMatch(text); - QRegularExpression regex("MAP_GROUP[\\(\\s]+(?[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\1)[\\s\\)]+,\\s*(?[0-9A-Fa-fx]+),\\s*(?[0-9A-Fa-fx]+)"); - QRegularExpressionMatchIterator iter = regex.globalMatch(text); - for (int i = 1; iter.hasNext(); i++) { - QRegularExpressionMatch match = iter.next(); - QString mapName = match.captured("map"); - unsigned x = match.captured("x").toUShort(); - unsigned y = match.captured("y").toUShort(); - flyableMaps.append(HealLocation(mapName, i, x, y)); + // This would be better if idName was used to look up data from the other two arrays + // As it is, element total and order needs to be the same in the 3 arrays to work. This should always be true though + for (int i = 1; spawns.hasNext(); i++) { + QRegularExpressionMatch spawn = spawns.next(); + QRegularExpressionMatch respawnMap = respawnMaps.next(); + QRegularExpressionMatch respawnNPC = respawnNPCs.next(); + QString idName = spawn.captured("id"); + QString mapName = spawn.captured("map"); + QString respawnMapName = respawnMap.captured("map"); + unsigned x = spawn.captured("x").toUShort(); + unsigned y = spawn.captured("y").toUShort(); + unsigned npc = respawnNPC.captured("npc").toUShort(); + flyableMaps.append(HealLocation(idName, mapName, i, x, y, respawnMapName, npc)); + } + } else { + dataQualifiers.insert("heal_locations", getDataQualifiers(text, "sHealLocations")); + + QRegularExpression regex("HEAL_LOCATION_(?[A-Za-z0-9_]+)\\s*- 1\\]\\s* = \\{MAP_GROUP[\\(\\s]+(?[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\2)[\\s\\)]+,\\s*(?[0-9A-Fa-fx]+),\\s*(?[0-9A-Fa-fx]+)"); + QRegularExpressionMatchIterator iter = regex.globalMatch(text); + for (int i = 1; iter.hasNext(); i++) { + QRegularExpressionMatch match = iter.next(); + QString idName = match.captured("id"); + QString mapName = match.captured("map"); + unsigned x = match.captured("x").toUShort(); + unsigned y = match.captured("y").toUShort(); + flyableMaps.append(HealLocation(idName, mapName, i, x, y)); + } } return true; } @@ -1961,6 +2182,18 @@ bool Project::readBgEventFacingDirections() { return true; } +bool Project::readTrainerTypes() { + trainerTypes->clear(); + QStringList prefixes = (QStringList() << "TRAINER_TYPE_"); + QString filename = "include/constants/trainer_types.h"; + parser.readCDefinesSorted(filename, prefixes, trainerTypes); + if (trainerTypes->isEmpty()) { + logError(QString("Failed to read trainer type constants from %1").arg(filename)); + return false; + } + return true; +} + bool Project::readMetatileBehaviors() { this->metatileBehaviorMap.clear(); this->metatileBehaviorMapInverse.clear(); @@ -1969,7 +2202,7 @@ bool Project::readMetatileBehaviors() { QString filename = "include/constants/metatile_behaviors.h"; this->metatileBehaviorMap = parser.readCDefines(filename, prefixes); if (this->metatileBehaviorMap.isEmpty()) { - logError(QString("Failed to metatile behaviors from %1.").arg(filename)); + logError(QString("Failed to read metatile behaviors from %1.").arg(filename)); return false; } @@ -1984,6 +2217,7 @@ QStringList Project::getSongNames() { songDefinePrefixes << "SE_" << "MUS_"; QMap songDefines = parser.readCDefines("include/constants/songs.h", songDefinePrefixes); QStringList names = songDefines.keys(); + this->defaultSong = names.value(0, "MUS_DUMMY"); return names; } @@ -2085,16 +2319,19 @@ void Project::loadEventPixmaps(QList objects) { QImage spritesheet(root + "/" + path); if (!spritesheet.isNull()) { // Infer the sprite dimensions from the OAM labels. - int spriteWidth = spritesheet.width(); - int spriteHeight = spritesheet.height(); + int spriteWidth, spriteHeight; QRegularExpression re("\\S+_(\\d+)x(\\d+)"); QRegularExpressionMatch dimensionMatch = re.match(dimensions_label); - if (dimensionMatch.hasMatch()) { - QRegularExpressionMatch oamTablesMatch = re.match(subsprites_label); - if (oamTablesMatch.hasMatch()) { - spriteWidth = dimensionMatch.captured(1).toInt(); - spriteHeight = dimensionMatch.captured(2).toInt(); - } + QRegularExpressionMatch oamTablesMatch = re.match(subsprites_label); + if (oamTablesMatch.hasMatch()) { + spriteWidth = oamTablesMatch.captured(1).toInt(); + spriteHeight = oamTablesMatch.captured(2).toInt(); + } else if (dimensionMatch.hasMatch()) { + spriteWidth = dimensionMatch.captured(1).toInt(); + spriteHeight = dimensionMatch.captured(2).toInt(); + } else { + spriteWidth = spritesheet.width(); + spriteHeight = spritesheet.height(); } object->setPixmapFromSpritesheet(spritesheet, spriteWidth, spriteHeight, object->frame, object->hFlip); } diff --git a/src/ui/bordermetatilespixmapitem.cpp b/src/ui/bordermetatilespixmapitem.cpp index 2093eabe..e7a024b7 100644 --- a/src/ui/bordermetatilespixmapitem.cpp +++ b/src/ui/bordermetatilespixmapitem.cpp @@ -8,10 +8,12 @@ void BorderMetatilesPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) QPointF pos = event->pos(); int x = static_cast(pos.x()) / 16; int y = static_cast(pos.y()) / 16; + int width = map->getBorderWidth(); + int height = map->getBorderHeight(); - for (int i = 0; i < selectionDimensions.x() && (i + x) < 2; i++) { - for (int j = 0; j < selectionDimensions.y() && (j + y) < 2; j++) { - int blockIndex = (j + y) * 2 + (i + x); + for (int i = 0; i < selectionDimensions.x() && (i + x) < width; i++) { + for (int j = 0; j < selectionDimensions.y() && (j + y) < height; j++) { + int blockIndex = (j + y) * width + (i + x); uint16_t tile = selectedMetatiles->at(j * selectionDimensions.x() + i); (*map->layout->border->blocks)[blockIndex].tile = tile; } @@ -22,15 +24,17 @@ void BorderMetatilesPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) } void BorderMetatilesPixmapItem::draw() { - QImage image(32, 32, QImage::Format_RGBA8888); + int width = map->getBorderWidth(); + int height = map->getBorderHeight(); + QImage image(16 * width, 16 * height, QImage::Format_RGBA8888); QPainter painter(&image); QVector *blocks = map->layout->border->blocks; - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 2; j++) { + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { int x = i * 16; int y = j * 16; - int index = j * 2 + i; + int index = j * width + i; QImage metatile_image = getMetatileImage(blocks->value(index).tile, map->layout->tileset_primary, map->layout->tileset_secondary); QPoint metatile_origin = QPoint(x, y); painter.drawImage(metatile_origin, metatile_image); @@ -38,5 +42,6 @@ void BorderMetatilesPixmapItem::draw() { } painter.end(); + map->commit(); this->setPixmap(QPixmap::fromImage(image)); } diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index af7e180c..2cdd45e7 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -70,7 +70,7 @@ void MapImageExporter::updatePreview() { int borderHeight = 0, borderWidth = 0; bool forceDrawBorder = showUpConnections || showDownConnections || showLeftConnections || showRightConnections; if (showBorder || forceDrawBorder) { - borderHeight = 32 * 3, borderWidth = 32 * 3; + borderHeight = BORDER_DISTANCE * 16, borderWidth = BORDER_DISTANCE * 16; QPixmap newPreview = QPixmap(map->pixmap.width() + borderWidth * 2, map->pixmap.height() + borderHeight * 2); QPainter borderPainter(&newPreview); for (auto borderItem : editor->borderItems) { diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index e0938227..f35af69e 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -62,11 +62,15 @@ void NewMapPopup::setDefaultValues(int groupNum, QString mapSec) { ui->comboBox_NewMap_Secondary_Tileset->setCurrentText(project->mapLayouts.value(layoutId)->tileset_secondary_label); ui->spinBox_NewMap_Width->setDisabled(true); ui->spinBox_NewMap_Height->setDisabled(true); + ui->spinBox_NewMap_BorderWidth->setDisabled(true); + ui->spinBox_NewMap_BorderHeight->setDisabled(true); ui->comboBox_NewMap_Primary_Tileset->setDisabled(true); ui->comboBox_NewMap_Secondary_Tileset->setDisabled(true); } else { ui->spinBox_NewMap_Width->setValue(20); ui->spinBox_NewMap_Height->setValue(20); + ui->spinBox_NewMap_BorderWidth->setValue(DEFAULT_BORDER_WIDTH); + ui->spinBox_NewMap_BorderHeight->setValue(DEFAULT_BORDER_HEIGHT); } ui->comboBox_NewMap_Type->addItems(*project->mapTypes); @@ -81,21 +85,44 @@ void NewMapPopup::setDefaultValues(int groupNum, QString mapSec) { ui->checkBox_NewMap_Allow_Running->setVisible(false); ui->checkBox_NewMap_Allow_Biking->setVisible(false); ui->checkBox_NewMap_Allow_Escape_Rope->setVisible(false); + ui->spinBox_NewMap_Floor_Number->setVisible(false); ui->label_NewMap_Allow_Running->setVisible(false); ui->label_NewMap_Allow_Biking->setVisible(false); ui->label_NewMap_Allow_Escape_Rope->setVisible(false); + ui->label_NewMap_Floor_Number->setVisible(false); break; case BaseGameVersion::pokeemerald: ui->checkBox_NewMap_Allow_Running->setVisible(true); ui->checkBox_NewMap_Allow_Biking->setVisible(true); ui->checkBox_NewMap_Allow_Escape_Rope->setVisible(true); + ui->spinBox_NewMap_Floor_Number->setVisible(false); ui->label_NewMap_Allow_Running->setVisible(true); ui->label_NewMap_Allow_Biking->setVisible(true); ui->label_NewMap_Allow_Escape_Rope->setVisible(true); + ui->label_NewMap_Floor_Number->setVisible(false); break; case BaseGameVersion::pokefirered: + ui->checkBox_NewMap_Allow_Running->setVisible(true); + ui->checkBox_NewMap_Allow_Biking->setVisible(true); + ui->checkBox_NewMap_Allow_Escape_Rope->setVisible(true); + ui->spinBox_NewMap_Floor_Number->setVisible(true); + ui->label_NewMap_Allow_Running->setVisible(true); + ui->label_NewMap_Allow_Biking->setVisible(true); + ui->label_NewMap_Allow_Escape_Rope->setVisible(true); + ui->label_NewMap_Floor_Number->setVisible(true); break; } + if (projectConfig.getUseCustomBorderSize()) { + 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 { + ui->spinBox_NewMap_BorderWidth->setVisible(false); + ui->spinBox_NewMap_BorderHeight->setVisible(false); + ui->label_NewMap_BorderWidth->setVisible(false); + ui->label_NewMap_BorderHeight->setVisible(false); + } } void NewMapPopup::on_lineEdit_NewMap_Name_textChanged(const QString &text) { @@ -123,11 +150,11 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { newMap->name = newMapName; newMap->type = this->ui->comboBox_NewMap_Type->currentText(); newMap->location = this->ui->comboBox_NewMap_Location->currentText(); - newMap->song = "MUS_DAN02"; + newMap->song = this->project->defaultSong; newMap->requiresFlash = "0"; - newMap->weather = "WEATHER_SUNNY"; + newMap->weather = this->project->weatherNames->value(0, "WEATHER_NONE"); newMap->show_location = "1"; - newMap->battle_scene = "MAP_BATTLE_SCENE_NORMAL"; + newMap->battle_scene = this->project->mapBattleScenes->value(0, "MAP_BATTLE_SCENE_NORMAL"); if (this->existingLayout) { layout = this->project->mapLayouts.value(this->layoutId); @@ -138,6 +165,13 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { layout->name = QString("%1_Layout").arg(newMap->name); layout->width = QString::number(this->ui->spinBox_NewMap_Width->value()); layout->height = QString::number(this->ui->spinBox_NewMap_Height->value()); + if (projectConfig.getUseCustomBorderSize()) { + layout->border_width = QString::number(this->ui->spinBox_NewMap_BorderWidth->value()); + layout->border_height = QString::number(this->ui->spinBox_NewMap_BorderHeight->value()); + } else { + layout->border_width = QString::number(DEFAULT_BORDER_WIDTH); + layout->border_height = QString::number(DEFAULT_BORDER_HEIGHT); + } layout->tileset_primary_label = this->ui->comboBox_NewMap_Primary_Tileset->currentText(); layout->tileset_secondary_label = this->ui->comboBox_NewMap_Secondary_Tileset->currentText(); layout->border_path = QString("data/layouts/%1/border.bin").arg(newMapName); @@ -152,6 +186,11 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { newMap->allowRunning = this->ui->checkBox_NewMap_Allow_Running->isChecked() ? "1" : "0"; newMap->allowBiking = this->ui->checkBox_NewMap_Allow_Biking->isChecked() ? "1" : "0"; newMap->allowEscapeRope = this->ui->checkBox_NewMap_Allow_Escape_Rope->isChecked() ? "1" : "0"; + } else if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + newMap->allowRunning = this->ui->checkBox_NewMap_Allow_Running->isChecked() ? "1" : "0"; + newMap->allowBiking = this->ui->checkBox_NewMap_Allow_Biking->isChecked() ? "1" : "0"; + newMap->allowEscapeRope = this->ui->checkBox_NewMap_Allow_Escape_Rope->isChecked() ? "1" : "0"; + newMap->floorNumber = this->ui->spinBox_NewMap_Floor_Number->value(); } group = project->groupNames->indexOf(this->ui->comboBox_NewMap_Group->currentText()); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 2ace3e77..069a9e23 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -5,6 +5,7 @@ #include "metatileparser.h" #include "paletteutil.h" #include "imageexport.h" +#include "config.h" #include #include #include @@ -61,6 +62,25 @@ void TilesetEditor::init(Project *project, QString primaryTilesetLabel, QString this->ui->spinBox_paletteSelector->setMinimum(0); this->ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + this->ui->comboBox_encounterType->setVisible(true); + this->ui->label_encounterType->setVisible(true); + this->ui->comboBox_encounterType->addItem("None", 0); + this->ui->comboBox_encounterType->addItem("Land", 1); + this->ui->comboBox_encounterType->addItem("Water", 2); + this->ui->comboBox_terrainType->setVisible(true); + this->ui->label_terrainType->setVisible(true); + this->ui->comboBox_terrainType->addItem("Normal", 0); + this->ui->comboBox_terrainType->addItem("Grass", 1); + this->ui->comboBox_terrainType->addItem("Water", 2); + this->ui->comboBox_terrainType->addItem("Waterfall", 3); + } else { + this->ui->comboBox_encounterType->setVisible(false); + this->ui->label_encounterType->setVisible(false); + this->ui->comboBox_terrainType->setVisible(false); + this->ui->label_terrainType->setVisible(false); + } + //only allow characters valid for a symbol QRegExp expression("[_A-Za-z0-9]*$"); QRegExpValidator *validator = new QRegExpValidator(expression); @@ -206,6 +226,10 @@ void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) { this->ui->comboBox_metatileBehaviors->setCurrentIndex(this->ui->comboBox_metatileBehaviors->findData(this->metatile->behavior)); this->ui->lineEdit_metatileLabel->setText(this->metatile->label); this->ui->comboBox_layerType->setCurrentIndex(this->ui->comboBox_layerType->findData(this->metatile->layerType)); + if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) { + this->ui->comboBox_encounterType->setCurrentIndex(this->ui->comboBox_encounterType->findData(this->metatile->encounterType)); + this->ui->comboBox_terrainType->setCurrentIndex(this->ui->comboBox_terrainType->findData(this->metatile->terrainType)); + } } void TilesetEditor::onHoveredTileChanged(uint16_t tile) { @@ -355,6 +379,26 @@ void TilesetEditor::on_comboBox_layerType_activated(int layerType) } } +void TilesetEditor::on_comboBox_encounterType_activated(int encounterType) +{ + if (this->metatile) { + Metatile *prevMetatile = this->metatile->copy(); + this->metatile->encounterType = static_cast(encounterType); + MetatileHistoryItem *commit = new MetatileHistoryItem(metatileSelector->getSelectedMetatile(), prevMetatile, this->metatile->copy()); + metatileHistory.push(commit); + } +} + +void TilesetEditor::on_comboBox_terrainType_activated(int terrainType) +{ + if (this->metatile) { + Metatile *prevMetatile = this->metatile->copy(); + this->metatile->terrainType = static_cast(terrainType); + MetatileHistoryItem *commit = new MetatileHistoryItem(metatileSelector->getSelectedMetatile(), prevMetatile, this->metatile->copy()); + metatileHistory.push(commit); + } +} + void TilesetEditor::on_actionSave_Tileset_triggered() { saveMetatileLabel(); @@ -559,6 +603,8 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered() Metatile *metatile = new Metatile; metatile->behavior = 0; metatile->layerType = 0; + metatile->encounterType = 0; + metatile->terrainType = 0; for (int i = 0; i < 8; i++) { metatile->tiles->append(tile); } @@ -577,6 +623,8 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered() Metatile *metatile = new Metatile; metatile->behavior = 0; metatile->layerType = 0; + metatile->encounterType = 0; + metatile->terrainType = 0; for (int i = 0; i < 8; i++) { metatile->tiles->append(tile); }