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
-
+
+
+ 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
+
+
+ NoScrollComboBox
+ QWidget
+
+
+
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]+(?