From 3eca227d07e0a47c1493b23ae7b0c42d88cb20c6 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Wed, 11 Mar 2020 01:52:00 -0400
Subject: [PATCH 01/33] Basic pokefirered empty layout, floor number, version
 selection support

---
 forms/mainwindow.ui    | 17 ++++++++++++
 forms/newmappopup.ui   | 17 ++++++++++++
 include/core/map.h     |  1 +
 src/config.cpp         |  5 +++-
 src/mainwindow.cpp     | 16 ++++++++++-
 src/project.cpp        | 60 ++++++++++++++++++++++++++++++++++++------
 src/ui/newmappopup.cpp | 17 ++++++++++++
 7 files changed, 123 insertions(+), 10 deletions(-)

diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui
index 68d458c9..676b8174 100644
--- a/forms/mainwindow.ui
+++ b/forms/mainwindow.ui
@@ -2094,6 +2094,23 @@
               </property>
              </widget>
             </item>
+            <item row="11" column="0">
+              <widget class="QLabel" name="label_FloorNumber">
+               <property name="text">
+                <string>Floor Number</string>
+               </property>
+              </widget>
+             </item>
+             <item row="11" column="1">
+              <widget class="QSpinBox" name="spinBox_FloorNumber">
+               <property name="toolTip">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Floor number to be used for maps with elevators.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="maximum">
+                <number>127</number>
+               </property>
+              </widget>
+            </item>
            </layout>
           </widget>
          </item>
diff --git a/forms/newmappopup.ui b/forms/newmappopup.ui
index 209c078d..41aaaf77 100644
--- a/forms/newmappopup.ui
+++ b/forms/newmappopup.ui
@@ -231,6 +231,23 @@
          </property>
         </widget>
        </item>
+       <item row="12" column="0">
+        <widget class="QLabel" name="label_NewMap_Floor_Number">
+         <property name="text">
+          <string>Floor Number</string>
+         </property>
+        </widget>
+       </item>
+       <item row="12" column="1">
+        <widget class="QSpinBox" name="spinBox_NewMap_Floor_Number">
+         <property name="toolTip">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Floor number to be used for maps with elevators.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+         <property name="maximum">
+          <number>127</number>
+         </property>
+        </widget>
+       </item>
       </layout>
      </widget>
     </item>
diff --git a/include/core/map.h b/include/core/map.h
index 8ecc0a9e..7f9ff989 100644
--- a/include/core/map.h
+++ b/include/core/map.h
@@ -35,6 +35,7 @@ public:
     QString allowRunning;
     QString allowBiking;
     QString allowEscapeRope;
+    int floorNumber;
     QString battle_scene;
     QString sharedEventsMap = "";
     QString sharedScriptsMap = "";
diff --git a/src/config.cpp b/src/config.cpp
index ed76cd1b..50e5ca83 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -324,11 +324,13 @@ QString PorymapConfig::getTheme() {
 
 const QMap<BaseGameVersion, QString> baseGameVersionMap = {
     {BaseGameVersion::pokeruby, "pokeruby"},
+    {BaseGameVersion::pokefirered, "pokefirered"},
     {BaseGameVersion::pokeemerald, "pokeemerald"},
 };
 
 const QMap<QString, BaseGameVersion> 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;
@@ -387,6 +389,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);
 
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index fc0c2f12..4fe9f98b 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -160,19 +160,31 @@ 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);
         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);
         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);
         break;
     }
 }
@@ -504,6 +516,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);
@@ -620,7 +633,6 @@ bool MainWindow::loadDataStructures() {
                 && project->readMapBattleScenes()
                 && project->readWeatherNames()
                 && project->readCoordEventWeatherNames()
-                && project->readSecretBaseIds()
                 && project->readBgEventFacingDirections()
                 && project->readMetatileBehaviors()
                 && project->readTilesetProperties()
@@ -628,6 +640,8 @@ bool MainWindow::loadDataStructures() {
                 && project->readMiscellaneousConstants()
                 && project->readSpeciesIconPaths()
                 && project->readWildMonData();
+    if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeemerald || projectConfig.getBaseGameVersion() == BaseGameVersion::pokeruby)
+        success = success && project->readSecretBaseIds();
     if (!success) {
         return false;
     }
diff --git a/src/project.cpp b/src/project.cpp
index 20c864cc..294b6d45 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -100,8 +100,8 @@ QMap<QString, bool> 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},
@@ -113,6 +113,31 @@ QMap<QString, bool> Project::getTopLevelMapFields() {
             {"shared_events_map", true},
             {"shared_scripts_map", true},
         };
+    } else if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
+        return QMap<QString, bool>
+        {
+            {"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<QString, bool>
         {
@@ -164,9 +189,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();
@@ -386,6 +416,12 @@ 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";
@@ -446,6 +482,8 @@ bool Project::readMapLayouts() {
     };
     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;
@@ -1048,14 +1086,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/<map_name>/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);
@@ -1115,10 +1153,11 @@ 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";
+    mapObj["floor_number"] = map->floorNumber;
     mapObj["battle_scene"] = map->battle_scene;
 
     // Connections
@@ -1863,6 +1902,9 @@ bool Project::readMovementTypes() {
 }
 
 bool Project::readInitialFacingDirections() {
+    // TODO: This file is not yet decompiled in pokefirered. Remove once resolved
+    if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered)
+        return true;
     QString filename = "src/event_object_movement.c";
     facingDirections = parser.readNamedIndexCArray(filename, "gInitialMovementTypeFacingDirections");
     if (facingDirections.isEmpty()) {
@@ -1909,6 +1951,8 @@ bool Project::readWeatherNames() {
 }
 
 bool Project::readCoordEventWeatherNames() {
+    if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered)
+        return true;
     coordEventWeatherNames->clear();
     QStringList prefixes = (QStringList() << "COORD_EVENT_WEATHER_");
     QString filename = "include/constants/weather.h";
diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp
index e0938227..9ba8936f 100644
--- a/src/ui/newmappopup.cpp
+++ b/src/ui/newmappopup.cpp
@@ -81,19 +81,31 @@ 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;
     }
 }
@@ -152,6 +164,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());

From eb44201f5b21d3a362d0090db53a910a0055e26b Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Wed, 11 Mar 2020 02:35:24 -0400
Subject: [PATCH 02/33] Skip MUS_DAN02 for pokefirered

---
 src/project.cpp        | 4 +++-
 src/ui/newmappopup.cpp | 6 +++++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/project.cpp b/src/project.cpp
index 294b6d45..83de74f1 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -403,20 +403,22 @@ 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->requiresFlash = "FALSE";
     map->weather = "WEATHER_SUNNY";
     map->type = "MAP_TYPE_TOWN";
     if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeruby) {
+        map->song = "MUS_DAN02";
         map->show_location = "TRUE";
     } else if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeemerald) {
+        map->song = "MUS_DAN02";
         map->allowBiking = "1";
         map->allowEscapeRope = "0";
         map->allowRunning = "1";
         map->show_location = "1";
     }  else if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
+        map->song = "MUS_MASARA";
         map->allowBiking = "1";
         map->allowEscapeRope = "0";
         map->allowRunning = "1";
diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp
index 9ba8936f..9191a73f 100644
--- a/src/ui/newmappopup.cpp
+++ b/src/ui/newmappopup.cpp
@@ -135,7 +135,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";
+    if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
+        newMap->song = "MUS_MASARA";
+    } else {
+        newMap->song = "MUS_DAN02";
+    }
     newMap->requiresFlash = "0";
     newMap->weather = "WEATHER_SUNNY";
     newMap->show_location = "1";

From 7125cd7c8d8e4a985b970b9561e610e18ae2a469 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Wed, 11 Mar 2020 16:23:07 -0400
Subject: [PATCH 03/33] Add underfoot and quantity to hidden items

---
 src/core/event.cpp | 38 ++++++++++++++++++++------
 src/mainwindow.cpp | 67 +++++++++++++++++++++++++++++++++++++++-------
 src/project.cpp    |  4 +++
 3 files changed, 92 insertions(+), 17 deletions(-)

diff --git a/src/core/event.cpp b/src/core/event.cpp
index 1015cc5e..e8750971 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";
@@ -131,6 +132,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;
 }
 
@@ -207,14 +212,27 @@ QMap<QString, bool> Event::getExpectedFields()
             {"script", true},
         };
     } else if (type == EventType::HiddenItem) {
-        return QMap<QString, bool> {
-            {"type", true},
-            {"x", true},
-            {"y", true},
-            {"elevation", true},
-            {"item", true},
-            {"flag", true},
-        };
+        if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
+            return QMap<QString, bool> {
+                {"type", true},
+                {"x", true},
+                {"y", true},
+                {"elevation", true},
+                {"item", true},
+                {"flag", true},
+                {"quantity", true},
+                {"underfoot", true},
+            };
+        } else {
+            return QMap<QString, bool> {
+                {"type", true},
+                {"x", true},
+                {"y", true},
+                {"elevation", true},
+                {"item", true},
+                {"flag", true},
+            };
+        }
     } else if (type == EventType::SecretBase) {
         return QMap<QString, bool> {
             {"type", true},
@@ -331,6 +349,10 @@ QJsonObject 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/mainwindow.cpp b/src/mainwindow.cpp
index 4fe9f98b..15f74808 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1407,8 +1407,8 @@ void MainWindow::updateSelectedObjects() {
         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";
@@ -1459,6 +1459,10 @@ void MainWindow::updateSelectedObjects() {
         else if (event_type == EventType::HiddenItem) {
             fields << "item";
             fields << "flag";
+            if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
+                fields << "quantity";
+                fields << "underfoot";
+            }
         }
         else if (event_type == EventType::SecretBase) {
             fields << "secret_base_id";
@@ -1476,8 +1480,19 @@ 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;
+
+            // Some keys shouldn't use a combobox. This isn't very scalable
+            if (key == "quantity") {
+                spin = new NoScrollSpinBox(widget);
+            } else if (key == "underfoot") {
+                check = new QCheckBox(widget);
+            } else {
+                combo = new NoScrollComboBox(widget);
+                combo->setEditable(true);
+            }
 
             // trainer_type has custom values, so it has special signal logic.
             if (key == "trainer_type") {
@@ -1515,6 +1530,25 @@ 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.");
+                spin->setMaximum(127);
+                connect(spin, QOverload<int>::of(&NoScrollSpinBox::valueChanged), [item, key](int value) {
+                    item->event->put(key, value);
+                });
+            } else if (key == "underfoot") {
+                check->setToolTip("If checked, hidden item can only be picked up using the Itemfinder");
+                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;
+                    }
+                });
             } else if (key == "flag" || key == "event_flag") {
                 if (!editor->project->flagNames->contains(value)) {
                     combo->addItem(value);
@@ -1585,13 +1619,28 @@ void MainWindow::updateSelectedObjects() {
             } else {
                 combo->addItem(value);
             }
-            combo->setCurrentText(value);
 
-            fl->addRow(new QLabel(field_labels[key], widget), combo);
-            widget->setLayout(fl);
-            frame->layout()->addWidget(widget);
+            if (key == "quantity") {
+                spin->setValue(value.toInt());
 
-            item->bind(combo, key);
+                fl->addRow(new QLabel(field_labels[key], widget), spin);
+                widget->setLayout(fl);
+                frame->layout()->addWidget(widget);
+            } else if (key == "underfoot") {
+                check->setChecked(value.toInt());
+
+                fl->addRow(new QLabel(field_labels[key], widget), check);
+                widget->setLayout(fl);
+                frame->layout()->addWidget(widget);
+            } 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.
diff --git a/src/project.cpp b/src/project.cpp
index 83de74f1..3906f0a6 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -325,6 +325,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") {

From 8d89b370b46cf43ff260010218881f1c65251532 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Wed, 11 Mar 2020 16:45:52 -0400
Subject: [PATCH 04/33] Add in_connection to object events

---
 src/core/event.cpp | 49 ++++++++++++++++++++++++++++++++++------------
 src/mainwindow.cpp | 41 +++++++++++++++++++++++---------------
 src/project.cpp    |  3 +++
 3 files changed, 64 insertions(+), 29 deletions(-)

diff --git a/src/core/event.cpp b/src/core/event.cpp
index e8750971..a13ed790 100644
--- a/src/core/event.cpp
+++ b/src/core/event.cpp
@@ -59,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");
@@ -163,19 +166,36 @@ QMap<QString, bool> Event::getExpectedFields()
 {
     QString type = this->get("event_type");
     if (type == EventType::Object) {
-        return QMap<QString, bool> {
-            {"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<QString, bool> {
+                {"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<QString, bool> {
+                {"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<QString, bool> {
             {"x", true},
@@ -270,6 +290,9 @@ QJsonObject Event::buildObjectEventJSON()
 {
     QJsonObject 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");
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 15f74808..590c8f88 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1401,6 +1401,7 @@ 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";
@@ -1439,6 +1440,9 @@ void MainWindow::updateSelectedObjects() {
             fields << "event_flag";
             fields << "trainer_type";
             fields << "sight_radius_tree_id";
+            if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
+                fields << "in_connection";
+            }
         }
         else if (event_type == EventType::Warp) {
             fields << "destination_map_name";
@@ -1487,7 +1491,7 @@ void MainWindow::updateSelectedObjects() {
             // Some keys shouldn't use a combobox. This isn't very scalable
             if (key == "quantity") {
                 spin = new NoScrollSpinBox(widget);
-            } else if (key == "underfoot") {
+            } else if (key == "underfoot" || key == "in_connection") {
                 check = new QCheckBox(widget);
             } else {
                 combo = new NoScrollComboBox(widget);
@@ -1533,22 +1537,8 @@ void MainWindow::updateSelectedObjects() {
             } else if (key == "quantity") {
                 spin->setToolTip("The number of items received when the hidden item is picked up.");
                 spin->setMaximum(127);
-                connect(spin, QOverload<int>::of(&NoScrollSpinBox::valueChanged), [item, key](int value) {
-                    item->event->put(key, value);
-                });
             } else if (key == "underfoot") {
                 check->setToolTip("If checked, hidden item can only be picked up using the Itemfinder");
-                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;
-                    }
-                });
             } else if (key == "flag" || key == "event_flag") {
                 if (!editor->project->flagNames->contains(value)) {
                     combo->addItem(value);
@@ -1616,6 +1606,8 @@ void MainWindow::updateSelectedObjects() {
                 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 {
                 combo->addItem(value);
             }
@@ -1626,12 +1618,29 @@ void MainWindow::updateSelectedObjects() {
                 fl->addRow(new QLabel(field_labels[key], widget), spin);
                 widget->setLayout(fl);
                 frame->layout()->addWidget(widget);
-            } else if (key == "underfoot") {
+
+                connect(spin, QOverload<int>::of(&NoScrollSpinBox::valueChanged), [item, key](int value) {
+                    item->event->put(key, value);
+                });
+
+            } else if (key == "underfoot" || key == "in_connection") {
                 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;
+                    }
+                });
             } else {
                 combo->setCurrentText(value);
 
diff --git a/src/project.cpp b/src/project.cpp
index 3906f0a6..f2a2ee6e 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -209,6 +209,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()));

From a2f38341f52e68981414134b8e899428f11a2392 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Wed, 11 Mar 2020 17:06:26 -0400
Subject: [PATCH 05/33] Save changes to floor_number

---
 include/mainwindow.h |  1 +
 src/mainwindow.cpp   | 11 ++++++++++-
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/include/mainwindow.h b/include/mainwindow.h
index 8d5d3e20..48df4608 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);
 
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 590c8f88..2a1ed2d4 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -620,6 +620,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()
@@ -1612,6 +1619,7 @@ void MainWindow::updateSelectedObjects() {
                 combo->addItem(value);
             }
 
+            // Keys using spin boxes
             if (key == "quantity") {
                 spin->setValue(value.toInt());
 
@@ -1622,7 +1630,7 @@ void MainWindow::updateSelectedObjects() {
                 connect(spin, QOverload<int>::of(&NoScrollSpinBox::valueChanged), [item, key](int value) {
                     item->event->put(key, value);
                 });
-
+            // Keys using check boxes
             } else if (key == "underfoot" || key == "in_connection") {
                 check->setChecked(value.toInt());
 
@@ -1641,6 +1649,7 @@ void MainWindow::updateSelectedObjects() {
                             break;
                     }
                 });
+            // Keys using combo boxes
             } else {
                 combo->setCurrentText(value);
 

From a8b381a0b5024d559a5216f77021bb1af6c068f2 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Wed, 11 Mar 2020 19:33:22 -0400
Subject: [PATCH 06/33] Match tileset names with underscores and unknown paths

---
 src/project.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/project.cpp b/src/project.cpp
index f2a2ee6e..3783f4b2 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -1291,8 +1291,9 @@ void Project::loadTilesetAssets(Tileset* tileset) {
     if (tileset->name.isNull()) {
         return;
     }
+    QRegularExpression re("([a-z])([A-Z])");
     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<QStringList> *graphics = parser.parseAsm("data/tilesets/graphics.inc");
     QStringList *tiles_values = parser.getLabelValues(graphics, tileset->tiles_label);

From a5c47b6333a5036271658edc5506ad940be4f8d7 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Thu, 12 Mar 2020 12:30:37 -0400
Subject: [PATCH 07/33] Disable RME(temporarily), secret bases, and weather
 triggers for pokefirered

---
 include/ui/neweventtoolbutton.h | 16 ++++++++--------
 src/mainwindow.cpp              | 11 ++++++++---
 src/project.cpp                 |  2 --
 3 files changed, 16 insertions(+), 13 deletions(-)

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/src/mainwindow.cpp b/src/mainwindow.cpp
index 2a1ed2d4..510ff4e9 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();
 }
 
@@ -185,6 +184,11 @@ void MainWindow::setProjectSpecificUIVisibility()
         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;
     }
 }
@@ -639,7 +643,6 @@ bool MainWindow::loadDataStructures() {
                 && project->readMapTypes()
                 && project->readMapBattleScenes()
                 && project->readWeatherNames()
-                && project->readCoordEventWeatherNames()
                 && project->readBgEventFacingDirections()
                 && project->readMetatileBehaviors()
                 && project->readTilesetProperties()
@@ -648,7 +651,9 @@ bool MainWindow::loadDataStructures() {
                 && project->readSpeciesIconPaths()
                 && project->readWildMonData();
     if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeemerald || projectConfig.getBaseGameVersion() == BaseGameVersion::pokeruby)
-        success = success && project->readSecretBaseIds();
+        success = success 
+               && project->readSecretBaseIds() 
+               && project->readCoordEventWeatherNames();
     if (!success) {
         return false;
     }
diff --git a/src/project.cpp b/src/project.cpp
index 3783f4b2..a7a2ccfe 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -1961,8 +1961,6 @@ bool Project::readWeatherNames() {
 }
 
 bool Project::readCoordEventWeatherNames() {
-    if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered)
-        return true;
     coordEventWeatherNames->clear();
     QStringList prefixes = (QStringList() << "COORD_EVENT_WEATHER_");
     QString filename = "include/constants/weather.h";

From 629abd3c06922097f44a0672f490f39e2617956e Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Fri, 13 Mar 2020 02:23:47 -0400
Subject: [PATCH 08/33] Support reading/displaying custom border sizes

---
 include/config.h                     |  3 ++
 include/core/map.h                   |  5 +++
 include/core/maplayout.h             |  2 ++
 src/config.cpp                       | 21 ++++++++++--
 src/core/map.cpp                     | 12 +++++--
 src/editor.cpp                       |  4 +--
 src/project.cpp                      | 51 ++++++++++++++++++++++++----
 src/ui/bordermetatilespixmapitem.cpp | 14 ++++----
 src/ui/newmappopup.cpp               |  2 ++
 9 files changed, 95 insertions(+), 19 deletions(-)

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/map.h b/include/core/map.h
index 7f9ff989..8be435a8 100644
--- a/include/core/map.h
+++ b/include/core/map.h
@@ -14,6 +14,9 @@
 #include <QGraphicsPixmapItem>
 #include <math.h>
 
+#define DEFAULT_BORDER_WIDTH 2
+#define DEFAULT_BORDER_HEIGHT 2
+
 class Map : public QObject
 {
     Q_OBJECT
@@ -58,6 +61,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 blockChanged(int, Blockdata*);
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/src/config.cpp b/src/config.cpp
index 50e5ca83..da684887 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -356,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));
     }
@@ -372,6 +378,7 @@ QMap<QString, QString> 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;
 }
 
@@ -401,6 +408,7 @@ void ProjectConfig::onNewConfigFileCreated() {
             this->baseGameVersion = static_cast<BaseGameVersion>(baseGameVersionComboBox->currentData().toInt());
         }
     }
+    this->useCustomBorderSize = this->baseGameVersion == BaseGameVersion::pokefirered;
     this->useEncounterJson = true;
     this->usePoryScript = false;
 }
@@ -435,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/map.cpp b/src/core/map.cpp
index f9df72fd..c4e88dd4 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::blockChanged(int i, Blockdata *cache) {
     if (!cache)
         return true;
@@ -197,8 +205,8 @@ QPixmap Map::render(bool ignoreCache = false, MapLayout * fromLayout) {
 
 QPixmap Map::renderBorder() {
     bool changed_any = false;
-    int width_ = 2;
-    int height_ = 2;
+    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;
diff --git a/src/editor.cpp b/src/editor.cpp
index b52f81b7..d424254f 100644
--- a/src/editor.cpp
+++ b/src/editor.cpp
@@ -1335,8 +1335,8 @@ void Editor::displayMapBorder() {
     borderItems.clear();
 
     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 = -6; y < map->getHeight() + 6; y += map->getBorderHeight())
+    for (int x = -6; x < map->getWidth() + 6; x += map->getBorderWidth()) {
         QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap);
         item->setX(x * 16);
         item->setY(y * 16);
diff --git a/src/project.cpp b/src/project.cpp
index a7a2ccfe..ab840edb 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 <QDir>
 #include <QJsonArray>
@@ -489,6 +490,11 @@ 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())
@@ -520,6 +526,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));
@@ -536,7 +559,7 @@ bool Project::readMapLayouts() {
             return false;
         }
         layout->blockdata_path = layoutObj["blockdata_filepath"].toString();
-        if (layout->border_path.isEmpty()) {
+        if (layout->blockdata_path.isEmpty()) {
             logError(QString("Missing 'blockdata_filepath' value on layout %1 in %2").arg(i).arg(layoutsFilepath));
             return false;
         }
@@ -563,6 +586,7 @@ void Project::saveMapLayouts() {
     QJsonObject layoutsObj;
     layoutsObj["layouts_table_label"] = layoutsLabel;
 
+    bool useCustomBorderSize = projectConfig.getUseCustomBorderSize();
     QJsonArray layoutsArr;
     for (QString layoutId : mapLayoutsTableMaster) {
         MapLayout *layout = mapLayouts.value(layoutId);
@@ -571,6 +595,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;
@@ -1034,7 +1062,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())
@@ -1046,10 +1074,17 @@ 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 (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;
 }
 
@@ -1136,6 +1171,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;
diff --git a/src/ui/bordermetatilespixmapitem.cpp b/src/ui/bordermetatilespixmapitem.cpp
index 2093eabe..f5f4d2c4 100644
--- a/src/ui/bordermetatilespixmapitem.cpp
+++ b/src/ui/bordermetatilespixmapitem.cpp
@@ -9,9 +9,9 @@ void BorderMetatilesPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
     int x = static_cast<int>(pos.x()) / 16;
     int y = static_cast<int>(pos.y()) / 16;
 
-    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) < map->getBorderWidth(); i++) {
+        for (int j = 0; j < selectionDimensions.y() && (j + y) < map->getBorderHeight(); j++) {
+            int blockIndex = (j + y) * map->getBorderWidth() + (i + x);
             uint16_t tile = selectedMetatiles->at(j * selectionDimensions.x() + i);
             (*map->layout->border->blocks)[blockIndex].tile = tile;
         }
@@ -22,15 +22,15 @@ void BorderMetatilesPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
 }
 
 void BorderMetatilesPixmapItem::draw() {
-    QImage image(32, 32, QImage::Format_RGBA8888);
+    QImage image(16 * map->getBorderWidth(), 16 * map->getBorderHeight(), QImage::Format_RGBA8888);
     QPainter painter(&image);
     QVector<Block> *blocks = map->layout->border->blocks;
 
-    for (int i = 0; i < 2; i++) {
-        for (int j = 0; j < 2; j++) {
+    for (int i = 0; i < map->getBorderWidth(); i++) {
+        for (int j = 0; j < map->getBorderHeight(); j++) {
             int x = i * 16;
             int y = j * 16;
-            int index = j * 2 + i;
+            int index = j * map->getBorderWidth() + 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);
diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp
index 9191a73f..8323278a 100644
--- a/src/ui/newmappopup.cpp
+++ b/src/ui/newmappopup.cpp
@@ -154,6 +154,8 @@ 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());
+        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);

From c0a512803effdddeb7dd2b81f425eb71fdfedba2 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Sat, 14 Mar 2020 03:44:55 -0400
Subject: [PATCH 09/33] Allow editing border dimensions

---
 forms/mainwindow.ui                  | 720 +++++++++++++++------------
 forms/newmappopup.ui                 |  72 ++-
 include/core/historyitem.h           |   5 +-
 include/core/map.h                   |  11 +-
 include/mainwindow.h                 |   2 +-
 src/core/historyitem.cpp             |   6 +-
 src/core/map.cpp                     | 136 ++++-
 src/editor.cpp                       |  12 +-
 src/mainwindow.cpp                   |  29 +-
 src/project.cpp                      |   8 +-
 src/ui/bordermetatilespixmapitem.cpp |  18 +-
 src/ui/newmappopup.cpp               |  24 +-
 12 files changed, 645 insertions(+), 398 deletions(-)

diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui
index 676b8174..c65e6cde 100644
--- a/forms/mainwindow.ui
+++ b/forms/mainwindow.ui
@@ -7,15 +7,15 @@
     <x>0</x>
     <y>0</y>
     <width>1117</width>
-    <height>747</height>
+    <height>788</height>
    </rect>
   </property>
   <property name="windowTitle">
    <string>porymap</string>
   </property>
   <widget class="QWidget" name="centralWidget">
-   <layout class="QVBoxLayout" name="verticalLayout_3">
-    <item>
+   <layout class="QGridLayout" name="gridLayout_15">
+    <item row="0" column="0">
      <widget class="QSplitter" name="splitter_main">
       <property name="orientation">
        <enum>Qt::Horizontal</enum>
@@ -512,7 +512,7 @@
                  </spacer>
                 </item>
                 <item>
-                 <widget class="QPushButton" name="pushButton">
+                 <widget class="QPushButton" name="pushButton_ChangeDimensions">
                   <property name="toolTip">
                    <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Change a map layout's width and height.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
                   </property>
@@ -561,8 +561,8 @@
                     <rect>
                      <x>0</x>
                      <y>0</y>
-                     <width>469</width>
-                     <height>608</height>
+                     <width>545</width>
+                     <height>628</height>
                     </rect>
                    </property>
                    <layout class="QGridLayout" name="gridLayout_8">
@@ -736,305 +736,6 @@
                  <property name="spacing">
                   <number>3</number>
                  </property>
-                 <item row="0" column="0">
-                  <widget class="QFrame" name="frame_Tilesets">
-                   <property name="sizePolicy">
-                    <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-                     <horstretch>0</horstretch>
-                     <verstretch>0</verstretch>
-                    </sizepolicy>
-                   </property>
-                   <property name="frameShape">
-                    <enum>QFrame::StyledPanel</enum>
-                   </property>
-                   <property name="frameShadow">
-                    <enum>QFrame::Raised</enum>
-                   </property>
-                   <layout class="QFormLayout" name="formLayout">
-                    <item row="0" column="0">
-                     <widget class="QLabel" name="label_PrimaryTileset">
-                      <property name="text">
-                       <string>Primary Tileset</string>
-                      </property>
-                     </widget>
-                    </item>
-                    <item row="0" column="1">
-                     <widget class="NoScrollComboBox" name="comboBox_PrimaryTileset">
-                      <property name="focusPolicy">
-                       <enum>Qt::StrongFocus</enum>
-                      </property>
-                      <property name="toolTip">
-                       <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Primary Tileset&lt;/p&gt;&lt;p&gt;Defines the first 0x200 metatiles available for the map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-                      </property>
-                      <property name="editable">
-                       <bool>true</bool>
-                      </property>
-                     </widget>
-                    </item>
-                    <item row="1" column="0">
-                     <widget class="QLabel" name="label_SecondaryTileset">
-                      <property name="text">
-                       <string>Secondary Tileset</string>
-                      </property>
-                     </widget>
-                    </item>
-                    <item row="1" column="1">
-                     <widget class="NoScrollComboBox" name="comboBox_SecondaryTileset">
-                      <property name="focusPolicy">
-                       <enum>Qt::StrongFocus</enum>
-                      </property>
-                      <property name="toolTip">
-                       <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Secondary Tileset&lt;/p&gt;&lt;p&gt;Defines the second 0x200 metatiles available for the map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-                      </property>
-                      <property name="editable">
-                       <bool>true</bool>
-                      </property>
-                     </widget>
-                    </item>
-                   </layout>
-                  </widget>
-                 </item>
-                 <item row="2" column="0">
-                  <widget class="QFrame" name="frame_currentMetatileSelection">
-                   <property name="sizePolicy">
-                    <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-                     <horstretch>0</horstretch>
-                     <verstretch>0</verstretch>
-                    </sizepolicy>
-                   </property>
-                   <property name="minimumSize">
-                    <size>
-                     <width>0</width>
-                     <height>92</height>
-                    </size>
-                   </property>
-                   <property name="maximumSize">
-                    <size>
-                     <width>16777215</width>
-                     <height>92</height>
-                    </size>
-                   </property>
-                   <property name="frameShape">
-                    <enum>QFrame::NoFrame</enum>
-                   </property>
-                   <property name="frameShadow">
-                    <enum>QFrame::Raised</enum>
-                   </property>
-                   <layout class="QHBoxLayout" name="horizontalLayout_5">
-                    <property name="spacing">
-                     <number>0</number>
-                    </property>
-                    <property name="sizeConstraint">
-                     <enum>QLayout::SetDefaultConstraint</enum>
-                    </property>
-                    <property name="leftMargin">
-                     <number>0</number>
-                    </property>
-                    <property name="topMargin">
-                     <number>0</number>
-                    </property>
-                    <property name="rightMargin">
-                     <number>0</number>
-                    </property>
-                    <property name="bottomMargin">
-                     <number>0</number>
-                    </property>
-                    <item>
-                     <widget class="QGroupBox" name="groupBox_2">
-                      <property name="title">
-                       <string>Selection</string>
-                      </property>
-                      <layout class="QGridLayout" name="gridLayout_17">
-                       <property name="leftMargin">
-                        <number>0</number>
-                       </property>
-                       <property name="topMargin">
-                        <number>0</number>
-                       </property>
-                       <property name="rightMargin">
-                        <number>0</number>
-                       </property>
-                       <property name="bottomMargin">
-                        <number>0</number>
-                       </property>
-                       <property name="spacing">
-                        <number>0</number>
-                       </property>
-                       <item row="0" column="0">
-                        <widget class="QScrollArea" name="scrollArea_6">
-                         <property name="frameShape">
-                          <enum>QFrame::NoFrame</enum>
-                         </property>
-                         <property name="frameShadow">
-                          <enum>QFrame::Plain</enum>
-                         </property>
-                         <property name="widgetResizable">
-                          <bool>true</bool>
-                         </property>
-                         <widget class="QWidget" name="scrollAreaWidgetContents_6">
-                          <property name="geometry">
-                           <rect>
-                            <x>0</x>
-                            <y>0</y>
-                            <width>324</width>
-                            <height>77</height>
-                           </rect>
-                          </property>
-                          <layout class="QHBoxLayout" name="horizontalLayout_7">
-                           <property name="spacing">
-                            <number>0</number>
-                           </property>
-                           <property name="leftMargin">
-                            <number>0</number>
-                           </property>
-                           <property name="topMargin">
-                            <number>0</number>
-                           </property>
-                           <property name="rightMargin">
-                            <number>0</number>
-                           </property>
-                           <property name="bottomMargin">
-                            <number>0</number>
-                           </property>
-                           <item>
-                            <spacer name="horizontalSpacer_16">
-                             <property name="orientation">
-                              <enum>Qt::Horizontal</enum>
-                             </property>
-                             <property name="sizeHint" stdset="0">
-                              <size>
-                               <width>40</width>
-                               <height>20</height>
-                              </size>
-                             </property>
-                            </spacer>
-                           </item>
-                           <item>
-                            <widget class="QGraphicsView" name="graphicsView_currentMetatileSelection">
-                             <property name="sizePolicy">
-                              <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
-                               <horstretch>0</horstretch>
-                               <verstretch>0</verstretch>
-                              </sizepolicy>
-                             </property>
-                             <property name="maximumSize">
-                              <size>
-                               <width>16777215</width>
-                               <height>16777215</height>
-                              </size>
-                             </property>
-                             <property name="verticalScrollBarPolicy">
-                              <enum>Qt::ScrollBarAlwaysOff</enum>
-                             </property>
-                             <property name="horizontalScrollBarPolicy">
-                              <enum>Qt::ScrollBarAlwaysOff</enum>
-                             </property>
-                             <property name="interactive">
-                              <bool>true</bool>
-                             </property>
-                            </widget>
-                           </item>
-                           <item>
-                            <spacer name="horizontalSpacer_17">
-                             <property name="orientation">
-                              <enum>Qt::Horizontal</enum>
-                             </property>
-                             <property name="sizeHint" stdset="0">
-                              <size>
-                               <width>40</width>
-                               <height>20</height>
-                              </size>
-                             </property>
-                            </spacer>
-                           </item>
-                          </layout>
-                         </widget>
-                        </widget>
-                       </item>
-                      </layout>
-                     </widget>
-                    </item>
-                   </layout>
-                  </widget>
-                 </item>
-                 <item row="1" column="0">
-                  <widget class="QGroupBox" name="groupBox">
-                   <property name="title">
-                    <string>Border</string>
-                   </property>
-                   <layout class="QGridLayout" name="gridLayout_16">
-                    <property name="leftMargin">
-                     <number>0</number>
-                    </property>
-                    <property name="topMargin">
-                     <number>0</number>
-                    </property>
-                    <property name="rightMargin">
-                     <number>0</number>
-                    </property>
-                    <property name="bottomMargin">
-                     <number>0</number>
-                    </property>
-                    <property name="spacing">
-                     <number>0</number>
-                    </property>
-                    <item row="0" column="1">
-                     <widget class="QGraphicsView" name="graphicsView_BorderMetatile">
-                      <property name="sizePolicy">
-                       <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
-                        <horstretch>0</horstretch>
-                        <verstretch>0</verstretch>
-                       </sizepolicy>
-                      </property>
-                      <property name="maximumSize">
-                       <size>
-                        <width>16777215</width>
-                        <height>48</height>
-                       </size>
-                      </property>
-                      <property name="toolTip">
-                       <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The border is a 2x2 metatile which is repeated outside of the map layout's boundary. Draw on this border area to modify it.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-                      </property>
-                      <property name="frameShape">
-                       <enum>QFrame::StyledPanel</enum>
-                      </property>
-                      <property name="frameShadow">
-                       <enum>QFrame::Sunken</enum>
-                      </property>
-                      <property name="verticalScrollBarPolicy">
-                       <enum>Qt::ScrollBarAsNeeded</enum>
-                      </property>
-                     </widget>
-                    </item>
-                    <item row="0" column="0">
-                     <spacer name="horizontalSpacer_12">
-                      <property name="orientation">
-                       <enum>Qt::Horizontal</enum>
-                      </property>
-                      <property name="sizeHint" stdset="0">
-                       <size>
-                        <width>40</width>
-                        <height>20</height>
-                       </size>
-                      </property>
-                     </spacer>
-                    </item>
-                    <item row="0" column="2">
-                     <spacer name="horizontalSpacer_13">
-                      <property name="orientation">
-                       <enum>Qt::Horizontal</enum>
-                      </property>
-                      <property name="sizeHint" stdset="0">
-                       <size>
-                        <width>40</width>
-                        <height>20</height>
-                       </size>
-                      </property>
-                     </spacer>
-                    </item>
-                   </layout>
-                  </widget>
-                 </item>
                  <item row="3" column="0">
                   <widget class="QScrollArea" name="scrollArea_2">
                    <property name="sizePolicy">
@@ -1064,10 +765,10 @@
                     </property>
                     <property name="geometry">
                      <rect>
-                      <x>0</x>
+                      <x>8</x>
                       <y>0</y>
-                      <width>307</width>
-                      <height>362</height>
+                      <width>221</width>
+                      <height>324</height>
                      </rect>
                     </property>
                     <property name="sizePolicy">
@@ -1160,6 +861,309 @@
                    </widget>
                   </widget>
                  </item>
+                 <item row="2" column="0">
+                  <widget class="QFrame" name="frame_currentMetatileSelection">
+                   <property name="sizePolicy">
+                    <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+                     <horstretch>0</horstretch>
+                     <verstretch>0</verstretch>
+                    </sizepolicy>
+                   </property>
+                   <property name="minimumSize">
+                    <size>
+                     <width>0</width>
+                     <height>92</height>
+                    </size>
+                   </property>
+                   <property name="maximumSize">
+                    <size>
+                     <width>16777215</width>
+                     <height>92</height>
+                    </size>
+                   </property>
+                   <property name="frameShape">
+                    <enum>QFrame::NoFrame</enum>
+                   </property>
+                   <property name="frameShadow">
+                    <enum>QFrame::Raised</enum>
+                   </property>
+                   <layout class="QHBoxLayout" name="horizontalLayout_5">
+                    <property name="spacing">
+                     <number>0</number>
+                    </property>
+                    <property name="sizeConstraint">
+                     <enum>QLayout::SetDefaultConstraint</enum>
+                    </property>
+                    <property name="leftMargin">
+                     <number>0</number>
+                    </property>
+                    <property name="topMargin">
+                     <number>0</number>
+                    </property>
+                    <property name="rightMargin">
+                     <number>0</number>
+                    </property>
+                    <property name="bottomMargin">
+                     <number>0</number>
+                    </property>
+                    <item>
+                     <widget class="QGroupBox" name="groupBox_2">
+                      <property name="title">
+                       <string>Selection</string>
+                      </property>
+                      <layout class="QGridLayout" name="gridLayout_17">
+                       <property name="leftMargin">
+                        <number>0</number>
+                       </property>
+                       <property name="topMargin">
+                        <number>0</number>
+                       </property>
+                       <property name="rightMargin">
+                        <number>0</number>
+                       </property>
+                       <property name="bottomMargin">
+                        <number>0</number>
+                       </property>
+                       <property name="spacing">
+                        <number>0</number>
+                       </property>
+                       <item row="0" column="0">
+                        <widget class="QScrollArea" name="scrollArea_6">
+                         <property name="frameShape">
+                          <enum>QFrame::NoFrame</enum>
+                         </property>
+                         <property name="frameShadow">
+                          <enum>QFrame::Plain</enum>
+                         </property>
+                         <property name="widgetResizable">
+                          <bool>true</bool>
+                         </property>
+                         <widget class="QWidget" name="scrollAreaWidgetContents_6">
+                          <property name="geometry">
+                           <rect>
+                            <x>0</x>
+                            <y>0</y>
+                            <width>256</width>
+                            <height>74</height>
+                           </rect>
+                          </property>
+                          <layout class="QHBoxLayout" name="horizontalLayout_7">
+                           <property name="spacing">
+                            <number>0</number>
+                           </property>
+                           <property name="leftMargin">
+                            <number>0</number>
+                           </property>
+                           <property name="topMargin">
+                            <number>0</number>
+                           </property>
+                           <property name="rightMargin">
+                            <number>0</number>
+                           </property>
+                           <property name="bottomMargin">
+                            <number>0</number>
+                           </property>
+                           <item>
+                            <spacer name="horizontalSpacer_16">
+                             <property name="orientation">
+                              <enum>Qt::Horizontal</enum>
+                             </property>
+                             <property name="sizeHint" stdset="0">
+                              <size>
+                               <width>40</width>
+                               <height>20</height>
+                              </size>
+                             </property>
+                            </spacer>
+                           </item>
+                           <item>
+                            <widget class="QGraphicsView" name="graphicsView_currentMetatileSelection">
+                             <property name="sizePolicy">
+                              <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+                               <horstretch>0</horstretch>
+                               <verstretch>0</verstretch>
+                              </sizepolicy>
+                             </property>
+                             <property name="maximumSize">
+                              <size>
+                               <width>16777215</width>
+                               <height>16777215</height>
+                              </size>
+                             </property>
+                             <property name="verticalScrollBarPolicy">
+                              <enum>Qt::ScrollBarAlwaysOff</enum>
+                             </property>
+                             <property name="horizontalScrollBarPolicy">
+                              <enum>Qt::ScrollBarAlwaysOff</enum>
+                             </property>
+                             <property name="interactive">
+                              <bool>true</bool>
+                             </property>
+                            </widget>
+                           </item>
+                           <item>
+                            <spacer name="horizontalSpacer_17">
+                             <property name="orientation">
+                              <enum>Qt::Horizontal</enum>
+                             </property>
+                             <property name="sizeHint" stdset="0">
+                              <size>
+                               <width>40</width>
+                               <height>20</height>
+                              </size>
+                             </property>
+                            </spacer>
+                           </item>
+                          </layout>
+                         </widget>
+                        </widget>
+                       </item>
+                      </layout>
+                     </widget>
+                    </item>
+                   </layout>
+                  </widget>
+                 </item>
+                 <item row="1" column="0">
+                  <widget class="QGroupBox" name="groupBox">
+                   <property name="sizePolicy">
+                    <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+                     <horstretch>0</horstretch>
+                     <verstretch>0</verstretch>
+                    </sizepolicy>
+                   </property>
+                   <property name="minimumSize">
+                    <size>
+                     <width>0</width>
+                     <height>110</height>
+                    </size>
+                   </property>
+                   <property name="maximumSize">
+                    <size>
+                     <width>16777215</width>
+                     <height>110</height>
+                    </size>
+                   </property>
+                   <property name="title">
+                    <string>Border</string>
+                   </property>
+                   <layout class="QGridLayout" name="gridLayout_16">
+                    <property name="leftMargin">
+                     <number>0</number>
+                    </property>
+                    <property name="topMargin">
+                     <number>0</number>
+                    </property>
+                    <property name="rightMargin">
+                     <number>0</number>
+                    </property>
+                    <property name="bottomMargin">
+                     <number>0</number>
+                    </property>
+                    <property name="spacing">
+                     <number>0</number>
+                    </property>
+                    <item row="0" column="0">
+                     <widget class="QWidget" name="widget" native="true">
+                      <property name="sizePolicy">
+                       <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+                        <horstretch>0</horstretch>
+                        <verstretch>0</verstretch>
+                       </sizepolicy>
+                      </property>
+                      <layout class="QGridLayout" name="gridLayout">
+                       <property name="sizeConstraint">
+                        <enum>QLayout::SetMinAndMaxSize</enum>
+                       </property>
+                       <property name="leftMargin">
+                        <number>0</number>
+                       </property>
+                       <property name="topMargin">
+                        <number>0</number>
+                       </property>
+                       <property name="rightMargin">
+                        <number>0</number>
+                       </property>
+                       <property name="bottomMargin">
+                        <number>0</number>
+                       </property>
+                       <property name="spacing">
+                        <number>0</number>
+                       </property>
+                       <item row="0" column="0">
+                        <widget class="QScrollArea" name="scrollArea_4">
+                         <property name="sizePolicy">
+                          <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+                           <horstretch>0</horstretch>
+                           <verstretch>0</verstretch>
+                          </sizepolicy>
+                         </property>
+                         <property name="minimumSize">
+                          <size>
+                           <width>0</width>
+                           <height>0</height>
+                          </size>
+                         </property>
+                         <property name="maximumSize">
+                          <size>
+                           <width>16777215</width>
+                           <height>16777215</height>
+                          </size>
+                         </property>
+                         <property name="widgetResizable">
+                          <bool>true</bool>
+                         </property>
+                         <widget class="QWidget" name="scrollAreaWidgetContents">
+                          <property name="geometry">
+                           <rect>
+                            <x>0</x>
+                            <y>0</y>
+                            <width>231</width>
+                            <height>83</height>
+                           </rect>
+                          </property>
+                          <property name="sizePolicy">
+                           <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+                            <horstretch>0</horstretch>
+                            <verstretch>0</verstretch>
+                           </sizepolicy>
+                          </property>
+                          <layout class="QGridLayout" name="gridLayout_18">
+                           <item row="0" column="0">
+                            <widget class="QGraphicsView" name="graphicsView_BorderMetatile">
+                             <property name="sizePolicy">
+                              <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+                               <horstretch>0</horstretch>
+                               <verstretch>0</verstretch>
+                              </sizepolicy>
+                             </property>
+                             <property name="minimumSize">
+                              <size>
+                               <width>1</width>
+                               <height>1</height>
+                              </size>
+                             </property>
+                             <property name="maximumSize">
+                              <size>
+                               <width>16777215</width>
+                               <height>16777215</height>
+                              </size>
+                             </property>
+                             <property name="toolTip">
+                              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                             </property>
+                            </widget>
+                           </item>
+                          </layout>
+                         </widget>
+                        </widget>
+                       </item>
+                      </layout>
+                     </widget>
+                    </item>
+                   </layout>
+                  </widget>
+                 </item>
                  <item row="4" column="0">
                   <widget class="QSlider" name="horizontalSlider_MetatileZoom">
                    <property name="minimum">
@@ -1176,6 +1180,64 @@
                    </property>
                   </widget>
                  </item>
+                 <item row="0" column="0">
+                  <widget class="QFrame" name="frame_Tilesets">
+                   <property name="sizePolicy">
+                    <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+                     <horstretch>0</horstretch>
+                     <verstretch>0</verstretch>
+                    </sizepolicy>
+                   </property>
+                   <property name="frameShape">
+                    <enum>QFrame::StyledPanel</enum>
+                   </property>
+                   <property name="frameShadow">
+                    <enum>QFrame::Raised</enum>
+                   </property>
+                   <layout class="QFormLayout" name="formLayout">
+                    <item row="0" column="0">
+                     <widget class="QLabel" name="label_PrimaryTileset">
+                      <property name="text">
+                       <string>Primary Tileset</string>
+                      </property>
+                     </widget>
+                    </item>
+                    <item row="0" column="1">
+                     <widget class="NoScrollComboBox" name="comboBox_PrimaryTileset">
+                      <property name="focusPolicy">
+                       <enum>Qt::StrongFocus</enum>
+                      </property>
+                      <property name="toolTip">
+                       <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Primary Tileset&lt;/p&gt;&lt;p&gt;Defines the first 0x200 metatiles available for the map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                      </property>
+                      <property name="editable">
+                       <bool>true</bool>
+                      </property>
+                     </widget>
+                    </item>
+                    <item row="1" column="0">
+                     <widget class="QLabel" name="label_SecondaryTileset">
+                      <property name="text">
+                       <string>Secondary Tileset</string>
+                      </property>
+                     </widget>
+                    </item>
+                    <item row="1" column="1">
+                     <widget class="NoScrollComboBox" name="comboBox_SecondaryTileset">
+                      <property name="focusPolicy">
+                       <enum>Qt::StrongFocus</enum>
+                      </property>
+                      <property name="toolTip">
+                       <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Secondary Tileset&lt;/p&gt;&lt;p&gt;Defines the second 0x200 metatiles available for the map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                      </property>
+                      <property name="editable">
+                       <bool>true</bool>
+                      </property>
+                     </widget>
+                    </item>
+                   </layout>
+                  </widget>
+                 </item>
                 </layout>
                </widget>
                <widget class="QWidget" name="tab_collision">
@@ -1344,8 +1406,8 @@
               <rect>
                <x>0</x>
                <y>0</y>
-               <width>381</width>
-               <height>657</height>
+               <width>371</width>
+               <height>684</height>
               </rect>
              </property>
              <layout class="QGridLayout" name="gridLayout_7">
@@ -1618,7 +1680,7 @@
                       <x>0</x>
                       <y>0</y>
                       <width>430</width>
-                      <height>568</height>
+                      <height>575</height>
                      </rect>
                     </property>
                     <property name="sizePolicy">
@@ -2095,21 +2157,21 @@
              </widget>
             </item>
             <item row="11" column="0">
-              <widget class="QLabel" name="label_FloorNumber">
-               <property name="text">
-                <string>Floor Number</string>
-               </property>
-              </widget>
-             </item>
-             <item row="11" column="1">
-              <widget class="QSpinBox" name="spinBox_FloorNumber">
-               <property name="toolTip">
-                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Floor number to be used for maps with elevators.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-               </property>
-               <property name="maximum">
-                <number>127</number>
-               </property>
-              </widget>
+             <widget class="QLabel" name="label_FloorNumber">
+              <property name="text">
+               <string>Floor Number</string>
+              </property>
+             </widget>
+            </item>
+            <item row="11" column="1">
+             <widget class="QSpinBox" name="spinBox_FloorNumber">
+              <property name="toolTip">
+               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Floor number to be used for maps with elevators.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+              </property>
+              <property name="maximum">
+               <number>127</number>
+              </property>
+             </widget>
             </item>
            </layout>
           </widget>
@@ -2548,8 +2610,8 @@
                    <rect>
                     <x>0</x>
                     <y>0</y>
-                    <width>826</width>
-                    <height>557</height>
+                    <width>818</width>
+                    <height>574</height>
                    </rect>
                   </property>
                   <layout class="QGridLayout" name="gridLayout_14">
@@ -2813,7 +2875,7 @@
      <x>0</x>
      <y>0</y>
      <width>1117</width>
-     <height>21</height>
+     <height>22</height>
     </rect>
    </property>
    <widget class="QMenu" name="menuFile">
diff --git a/forms/newmappopup.ui b/forms/newmappopup.ui
index 41aaaf77..b9595ec8 100644
--- a/forms/newmappopup.ui
+++ b/forms/newmappopup.ui
@@ -73,7 +73,7 @@
        <item row="2" column="0">
         <widget class="QLabel" name="label_NewMap_Width">
          <property name="text">
-          <string>Width</string>
+          <string>Map Width</string>
          </property>
         </widget>
        </item>
@@ -90,7 +90,7 @@
        <item row="3" column="0">
         <widget class="QLabel" name="label_NewMap_Height">
          <property name="text">
-          <string>Height</string>
+          <string>Map Height</string>
          </property>
         </widget>
        </item>
@@ -105,13 +105,47 @@
         </widget>
        </item>
        <item row="4" column="0">
+        <widget class="QLabel" name="label_NewMap_BorderWidth">
+         <property name="text">
+          <string>Border Width</string>
+         </property>
+        </widget>
+       </item>
+       <item row="4" column="1">
+        <widget class="QSpinBox" name="spinBox_NewMap_BorderWidth">
+         <property name="toolTip">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Width (in blocks) of the new map's border.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+         <property name="maximum">
+          <number>255</number>
+         </property>
+        </widget>
+       </item>
+       <item row="5" column="0">
+        <widget class="QLabel" name="label_NewMap_BorderHeight">
+         <property name="text">
+          <string>Border Height</string>
+         </property>
+        </widget>
+       </item>
+       <item row="5" column="1">
+        <widget class="QSpinBox" name="spinBox_NewMap_BorderHeight">
+         <property name="toolTip">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Height (in blocks) of the new map's border.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+         <property name="maximum">
+          <number>255</number>
+         </property>
+        </widget>
+       </item>
+       <item row="6" column="0">
         <widget class="QLabel" name="label_NewMap_Primary_Tileset">
          <property name="text">
           <string>Primary Tileset</string>
          </property>
         </widget>
        </item>
-       <item row="4" column="1">
+       <item row="6" column="1">
         <widget class="NoScrollComboBox" name="comboBox_NewMap_Primary_Tileset">
          <property name="toolTip">
           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The primary tileset for the new map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@@ -121,14 +155,14 @@
          </property>
         </widget>
        </item>
-       <item row="5" column="0">
+       <item row="7" column="0">
         <widget class="QLabel" name="label_NewMap_Secondary_Tileset">
          <property name="text">
           <string>Secondary Tileset</string>
          </property>
         </widget>
        </item>
-       <item row="5" column="1">
+       <item row="7" column="1">
         <widget class="NoScrollComboBox" name="comboBox_NewMap_Secondary_Tileset">
          <property name="toolTip">
           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The secondary tileset for the new map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@@ -138,14 +172,14 @@
          </property>
         </widget>
        </item>
-       <item row="6" column="0">
+       <item row="8" column="0">
         <widget class="QLabel" name="label_NewMap_Type">
          <property name="text">
           <string>Type</string>
          </property>
         </widget>
        </item>
-       <item row="6" column="1">
+       <item row="8" column="1">
         <widget class="NoScrollComboBox" name="comboBox_NewMap_Type">
          <property name="toolTip">
           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The map type is a general attribute, which is used for many different things. For example. it determines whether biking or running is allowed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@@ -155,14 +189,14 @@
          </property>
         </widget>
        </item>
-       <item row="7" column="0">
+       <item row="9" column="0">
         <widget class="QLabel" name="label_NewMap_Location">
          <property name="text">
           <string>Location</string>
          </property>
         </widget>
        </item>
-       <item row="7" column="1">
+       <item row="9" column="1">
         <widget class="NoScrollComboBox" name="comboBox_NewMap_Location">
          <property name="toolTip">
           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@@ -172,14 +206,14 @@
          </property>
         </widget>
        </item>
-       <item row="8" column="0">
+       <item row="10" column="0">
         <widget class="QLabel" name="label_NewMap_Flyable">
          <property name="text">
           <string>Can Fly To</string>
          </property>
         </widget>
        </item>
-       <item row="8" column="1">
+       <item row="10" column="1">
         <widget class="QCheckBox" name="checkBox_NewMap_Flyable">
          <property name="toolTip">
           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Whether to add a heal location to the new map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@@ -189,56 +223,56 @@
          </property>
         </widget>
        </item>
-       <item row="9" column="0">
+       <item row="11" column="0">
         <widget class="QLabel" name="label_NewMap_Allow_Running">
          <property name="text">
           <string>Allow Running</string>
          </property>
         </widget>
        </item>
-       <item row="10" column="0">
+       <item row="12" column="0">
         <widget class="QLabel" name="label_NewMap_Allow_Biking">
          <property name="text">
           <string>Allow Biking</string>
          </property>
         </widget>
        </item>
-       <item row="11" column="0">
+       <item row="13" column="0">
         <widget class="QLabel" name="label_NewMap_Allow_Escape_Rope">
          <property name="text">
           <string>Allow Escape Rope</string>
          </property>
         </widget>
        </item>
-       <item row="9" column="1">
+       <item row="11" column="1">
         <widget class="QCheckBox" name="checkBox_NewMap_Allow_Running">
          <property name="text">
           <string/>
          </property>
         </widget>
        </item>
-       <item row="10" column="1">
+       <item row="12" column="1">
         <widget class="QCheckBox" name="checkBox_NewMap_Allow_Biking">
          <property name="text">
           <string/>
          </property>
         </widget>
        </item>
-       <item row="11" column="1">
+       <item row="13" column="1">
         <widget class="QCheckBox" name="checkBox_NewMap_Allow_Escape_Rope">
          <property name="text">
           <string/>
          </property>
         </widget>
        </item>
-       <item row="12" column="0">
+       <item row="14" column="0">
         <widget class="QLabel" name="label_NewMap_Floor_Number">
          <property name="text">
           <string>Floor Number</string>
          </property>
         </widget>
        </item>
-       <item row="12" column="1">
+       <item row="14" column="1">
         <widget class="QSpinBox" name="spinBox_NewMap_Floor_Number">
          <property name="toolTip">
           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Floor number to be used for maps with elevators.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
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 8be435a8..2873844f 100644
--- a/include/core/map.h
+++ b/include/core/map.h
@@ -17,6 +17,10 @@
 #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 6
+
 class Map : public QObject
 {
     Q_OBJECT
@@ -65,7 +69,8 @@ public:
     int getBorderHeight();
     QPixmap render(bool ignoreCache, MapLayout * fromLayout = nullptr);
     QPixmap renderCollision(qreal opacity, bool ignoreCache);
-    bool blockChanged(int, Blockdata*);
+    bool mapBlockChanged(int i, Blockdata * cache);
+    bool borderBlockChanged(int i, Blockdata * cache);
     void cacheBlockdata();
     void cacheCollision();
     Block *getBlock(int x, int y);
@@ -82,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/mainwindow.h b/include/mainwindow.h
index 48df4608..9113e227 100644
--- a/include/mainwindow.h
+++ b/include/mainwindow.h
@@ -119,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);
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<uint8_t> tiles, QString cityMap) {
diff --git a/src/core/map.cpp b/src/core/map.cpp
index c4e88dd4..4f89c6c6 100644
--- a/src/core/map.cpp
+++ b/src/core/map.cpp
@@ -67,7 +67,7 @@ int Map::getBorderHeight() {
     return layout->border_height.toInt(nullptr, 0);
 }
 
-bool Map::blockChanged(int i, Blockdata *cache) {
+bool Map::mapBlockChanged(int i, Blockdata *cache) {
     if (!cache)
         return true;
     if (!layout->blockdata)
@@ -84,6 +84,23 @@ bool Map::blockChanged(int i, Blockdata *cache) {
     return layout->blockdata->blocks->value(i) != cache->blocks->value(i);
 }
 
+bool Map::borderBlockChanged(int i, Blockdata *cache) {
+    if (!cache)
+        return true;
+    if (!layout->border)
+        return true;
+    if (!cache->blocks)
+        return true;
+    if (!layout->border->blocks)
+        return true;
+    if (cache->blocks->length() <= i)
+        return true;
+    if (layout->border->blocks->length() <= i)
+        return true;
+
+    return layout->border->blocks->value(i) != cache->blocks->value(i);
+}
+
 void Map::cacheBorder() {
     if (layout->cached_border) delete layout->cached_border;
     layout->cached_border = new Blockdata;
@@ -135,7 +152,7 @@ QPixmap Map::renderCollision(qreal opacity, bool ignoreCache) {
     }
     QPainter painter(&collision_image);
     for (int i = 0; i < layout->blockdata->blocks->length(); i++) {
-        if (!ignoreCache && layout->cached_collision && !blockChanged(i, layout->cached_collision)) {
+        if (!ignoreCache && layout->cached_collision && !mapBlockChanged(i, layout->cached_collision)) {
             continue;
         }
         changed_any = true;
@@ -179,7 +196,7 @@ QPixmap Map::render(bool ignoreCache = false, MapLayout * fromLayout) {
 
     QPainter painter(&image);
     for (int i = 0; i < layout->blockdata->blocks->length(); i++) {
-        if (!ignoreCache && !blockChanged(i, layout->cached_blockdata)) {
+        if (!ignoreCache && !mapBlockChanged(i, layout->cached_blockdata)) {
             continue;
         }
         changed_any = true;
@@ -204,27 +221,33 @@ QPixmap Map::render(bool ignoreCache = false, MapLayout * fromLayout) {
 }
 
 QPixmap Map::renderBorder() {
-    bool changed_any = false;
+    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 (!blockChanged(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();
@@ -240,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
@@ -289,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);
@@ -300,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()) {
@@ -354,35 +407,64 @@ 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);
     }
 }
@@ -392,14 +474,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/editor.cpp b/src/editor.cpp
index d424254f..ff0b6d8b 100644
--- a/src/editor.cpp
+++ b/src/editor.cpp
@@ -1136,10 +1136,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
     );
 }
 
@@ -1335,8 +1335,8 @@ void Editor::displayMapBorder() {
     borderItems.clear();
 
     QPixmap pixmap = map->renderBorder();
-    for (int y = -6; y < map->getHeight() + 6; y += map->getBorderHeight())
-    for (int x = -6; x < map->getWidth() + 6; x += map->getBorderWidth()) {
+    for (int y = -BORDER_DISTANCE; y < map->getHeight() + BORDER_DISTANCE; y += map->getBorderHeight())
+    for (int x = -BORDER_DISTANCE; x < map->getWidth() + BORDER_DISTANCE; x += map->getBorderWidth()) {
         QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap);
         item->setX(x * 16);
         item->setY(y * 16);
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 510ff4e9..89bc9bc5 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -2150,7 +2150,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");
@@ -2160,15 +2160,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;
@@ -2178,7 +2194,7 @@ void MainWindow::on_pushButton_clicked()
 
     QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog);
     form.addRow(&buttonBox);
-    connect(&buttonBox, &QDialogButtonBox::accepted, [&dialog, &widthSpinBox, &heightSpinBox, &errorLabel](){
+    connect(&buttonBox, &QDialogButtonBox::accepted, [&dialog, &widthSpinBox, &heightSpinBox, &bwidthSpinBox, &bheightSpinBox, &errorLabel](){
         // Ensure width and height are an acceptable size.
         // The maximum number of metatiles in a map is the following:
         //    max = (width + 15) * (height + 14)
@@ -2190,8 +2206,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);
@@ -2205,6 +2221,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();
     }
diff --git a/src/project.cpp b/src/project.cpp
index ab840edb..55db6cd3 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -617,6 +617,8 @@ 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";
@@ -1074,7 +1076,11 @@ bool Project::loadMapBorder(Map *map) {
 
 void Project::setNewMapBorder(Map *map) {
     Blockdata *blockdata = new Blockdata;
-    if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
+    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));
diff --git a/src/ui/bordermetatilespixmapitem.cpp b/src/ui/bordermetatilespixmapitem.cpp
index f5f4d2c4..ad31a7ca 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<int>(pos.x()) / 16;
     int y = static_cast<int>(pos.y()) / 16;
+    int width = map->getBorderWidth();
+    int height = map->getBorderHeight();
 
-    for (int i = 0; i < selectionDimensions.x() && (i + x) < map->getBorderWidth(); i++) {
-        for (int j = 0; j < selectionDimensions.y() && (j + y) < map->getBorderHeight(); j++) {
-            int blockIndex = (j + y) * map->getBorderWidth() + (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(16 * map->getBorderWidth(), 16 * map->getBorderHeight(), QImage::Format_RGBA8888);
+    int width = map->getBorderWidth();
+    int height = map->getBorderHeight();
+    QImage image(16 * width, 16 * height, QImage::Format_RGBA8888);
     QPainter painter(&image);
     QVector<Block> *blocks = map->layout->border->blocks;
 
-    for (int i = 0; i < map->getBorderWidth(); i++) {
-        for (int j = 0; j < map->getBorderHeight(); 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 * map->getBorderWidth() + 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);
diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp
index 8323278a..833d35a0 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);
@@ -108,6 +112,17 @@ void NewMapPopup::setDefaultValues(int groupNum, QString mapSec) {
         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) {
@@ -154,8 +169,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());
-        layout->border_width = QString::number(DEFAULT_BORDER_WIDTH);
-        layout->border_height = QString::number(DEFAULT_BORDER_HEIGHT);
+        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);

From ffddc35b607f4f7dbb8e95b5bd9dcc5defa96d86 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Sun, 15 Mar 2020 03:03:12 -0400
Subject: [PATCH 10/33] Parse pokefirered metatile behaviors/layer types

---
 src/project.cpp | 39 +++++++++++++++++++++++++++++----------
 1 file changed, 29 insertions(+), 10 deletions(-)

diff --git a/src/project.cpp b/src/project.cpp
index 55db6cd3..e9677e37 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -1477,16 +1477,35 @@ 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<unsigned char>(data.at(i * 2 + 1)) << 8) | static_cast<unsigned char>(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;
+            }
+            for (int i = 0; i < num_metatileAttrs; i++) {
+                int value = (static_cast<unsigned char>(data.at(i * 4 + 3)) << 24) | 
+                            (static_cast<unsigned char>(data.at(i * 4 + 2)) << 16) | 
+                            (static_cast<unsigned char>(data.at(i * 4 + 1)) << 8) | 
+                            (static_cast<unsigned char>(data.at(i * 4 + 0)));
+
+                tileset->metatiles->at(i)->behavior = value & 0x1FF;
+                tileset->metatiles->at(i)->layerType = (value & 0x60000000) >> 29;
+            }
+        } 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<unsigned char>(data.at(i * 2 + 1)) << 8) | static_cast<unsigned char>(data.at(i * 2));
+                tileset->metatiles->at(i)->behavior = value & 0xFF;
+                tileset->metatiles->at(i)->layerType = (value & 0xF000) >> 12;
+            }
         }
     } else {
         logError(QString("Could not open tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path));

From 3fa77609c684f92ac1a279a98da361a5569e3fae Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Mon, 16 Mar 2020 03:57:39 -0400
Subject: [PATCH 11/33] Remove hard-coded new map constants

---
 include/project.h      |  1 +
 src/project.cpp        | 13 ++++++-------
 src/ui/newmappopup.cpp | 10 +++-------
 3 files changed, 10 insertions(+), 14 deletions(-)

diff --git a/include/project.h b/include/project.h
index 4948b7e0..1c01b07a 100644
--- a/include/project.h
+++ b/include/project.h
@@ -126,6 +126,7 @@ public:
     void saveTilesetTilesImage(Tileset*);
     void saveTilesetPalettes(Tileset*, bool);
 
+    QString defaultSong;
     QStringList getSongNames();
     QStringList getVisibilities();
     QMap<QString, QStringList> getTilesetLabels();
diff --git a/src/project.cpp b/src/project.cpp
index e9677e37..d48616b7 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -412,21 +412,19 @@ QString Project::readMapLocation(QString map_name) {
 
 void Project::setNewMapHeader(Map* map, int mapIndex) {
     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->at(0);
+    map->type = mapTypes->at(0);
+    map->song = defaultSong;
     if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeruby) {
-        map->song = "MUS_DAN02";
         map->show_location = "TRUE";
     } else if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokeemerald) {
-        map->song = "MUS_DAN02";
         map->allowBiking = "1";
         map->allowEscapeRope = "0";
         map->allowRunning = "1";
         map->show_location = "1";
     }  else if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
-        map->song = "MUS_MASARA";
         map->allowBiking = "1";
         map->allowEscapeRope = "0";
         map->allowRunning = "1";
@@ -434,7 +432,7 @@ void Project::setNewMapHeader(Map* map, int mapIndex) {
         map->floorNumber = 0;
     }
 
-    map->battle_scene = "MAP_BATTLE_SCENE_NORMAL";
+    map->battle_scene = mapBattleScenes->at(0);
 }
 
 bool Project::loadMapLayout(Map* map) {
@@ -2083,6 +2081,7 @@ QStringList Project::getSongNames() {
     songDefinePrefixes << "SE_" << "MUS_";
     QMap<QString, int> songDefines = parser.readCDefines("include/constants/songs.h", songDefinePrefixes);
     QStringList names = songDefines.keys();
+    this->defaultSong = names.at(0);
 
     return names;
 }
diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp
index 833d35a0..18767135 100644
--- a/src/ui/newmappopup.cpp
+++ b/src/ui/newmappopup.cpp
@@ -150,15 +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();
-    if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
-        newMap->song = "MUS_MASARA";
-    } else {
-        newMap->song = "MUS_DAN02";
-    }
+    newMap->song = this->project->defaultSong;
     newMap->requiresFlash = "0";
-    newMap->weather = "WEATHER_SUNNY";
+    newMap->weather = this->project->weatherNames->at(0);
     newMap->show_location = "1";
-    newMap->battle_scene = "MAP_BATTLE_SCENE_NORMAL";
+    newMap->battle_scene = this->project->mapBattleScenes->at(0);
 
     if (this->existingLayout) {
         layout = this->project->mapLayouts.value(this->layoutId);

From bf72ecede9d7d773499873b59f6274eb88fcddda Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Mon, 16 Mar 2020 16:31:08 -0400
Subject: [PATCH 12/33] Parse remaining pokefirered metatile attributes, allow
 editing/saving them

---
 forms/tileseteditor.ui     | 132 ++++++++++++++++++++++++-------------
 include/core/metatile.h    |   4 +-
 include/ui/tileseteditor.h |   4 ++
 src/core/map.cpp           |   1 -
 src/core/metatile.cpp      |   4 ++
 src/mainwindow.cpp         |   2 +
 src/project.cpp            |  31 +++++++--
 src/ui/tileseteditor.cpp   |  48 ++++++++++++++
 8 files changed, 173 insertions(+), 53 deletions(-)

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 @@
          <x>0</x>
          <y>0</y>
          <width>272</width>
-         <height>539</height>
+         <height>625</height>
         </rect>
        </property>
        <layout class="QGridLayout" name="gridLayout">
@@ -198,30 +198,31 @@
              <bool>false</bool>
             </property>
             <layout class="QGridLayout" name="gridLayout_3">
-             <item row="4" column="0" colspan="3">
-              <widget class="QLabel" name="label_8">
+             <item row="13" column="0" colspan="3">
+              <widget class="QLabel" name="label_metatileLabel">
                <property name="text">
                 <string>Metatile Label (Optional)</string>
                </property>
               </widget>
              </item>
-             <item row="5" column="0" colspan="3">
-              <widget class="QLineEdit" name="lineEdit_metatileLabel"/>
-             </item>
-             <item row="2" column="0" colspan="3">
-              <widget class="QLabel" name="label_5">
+             <item row="0" column="2">
+              <widget class="QLabel" name="label_layerType">
                <property name="text">
-                <string>Metatile Behavior</string>
+                <string>Layer Type</string>
                </property>
               </widget>
              </item>
-             <item row="3" column="0" colspan="3">
-              <widget class="NoScrollComboBox" name="comboBox_metatileBehaviors"/>
-             </item>
-             <item row="0" column="0">
-              <widget class="QLabel" name="label_4">
+             <item row="9" column="0">
+              <widget class="QLabel" name="label_encounterType">
                <property name="text">
-                <string>Bottom/Top</string>
+                <string>Encounter Type</string>
+               </property>
+              </widget>
+             </item>
+             <item row="11" column="0">
+              <widget class="QLabel" name="label_terrainType">
+               <property name="text">
+                <string>Terrain Type</string>
                </property>
               </widget>
              </item>
@@ -247,15 +248,15 @@
                </property>
               </widget>
              </item>
-             <item row="0" column="2">
-              <widget class="QLabel" name="label_6">
+             <item row="0" column="0">
+              <widget class="QLabel" name="label_BottomTop">
                <property name="text">
-                <string>Layer Type</string>
+                <string>Bottom/Top</string>
                </property>
               </widget>
              </item>
-             <item row="1" column="2">
-              <widget class="QComboBox" name="comboBox_layerType"/>
+             <item row="10" column="0">
+              <widget class="QComboBox" name="comboBox_encounterType"/>
              </item>
              <item row="1" column="1">
               <spacer name="horizontalSpacer_5">
@@ -270,6 +271,38 @@
                </property>
               </spacer>
              </item>
+             <item row="14" column="0" colspan="3">
+              <widget class="QLineEdit" name="lineEdit_metatileLabel"/>
+             </item>
+             <item row="1" column="2">
+              <widget class="QComboBox" name="comboBox_layerType"/>
+             </item>
+             <item row="8" column="0" colspan="3">
+              <widget class="NoScrollComboBox" name="comboBox_metatileBehaviors" native="true"/>
+             </item>
+             <item row="3" column="0" colspan="3">
+              <widget class="QLabel" name="label_metatileBehavior">
+               <property name="text">
+                <string>Metatile Behavior</string>
+               </property>
+              </widget>
+             </item>
+             <item row="12" column="0">
+              <widget class="QComboBox" name="comboBox_terrainType"/>
+             </item>
+             <item row="2" column="0">
+              <spacer name="verticalSpacer_5">
+               <property name="orientation">
+                <enum>Qt::Vertical</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>20</width>
+                 <height>40</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
             </layout>
            </widget>
           </item>
@@ -377,8 +410,8 @@
            <rect>
             <x>0</x>
             <y>0</y>
-            <width>400</width>
-            <height>367</height>
+            <width>384</width>
+            <height>265</height>
            </rect>
           </property>
           <layout class="QGridLayout" name="gridLayout_2">
@@ -394,19 +427,6 @@
            <property name="bottomMargin">
             <number>0</number>
            </property>
-           <item row="1" column="2">
-            <spacer name="horizontalSpacer_4">
-             <property name="orientation">
-              <enum>Qt::Horizontal</enum>
-             </property>
-             <property name="sizeHint" stdset="0">
-              <size>
-               <width>40</width>
-               <height>20</height>
-              </size>
-             </property>
-            </spacer>
-           </item>
            <item row="0" column="1">
             <spacer name="verticalSpacer_3">
              <property name="orientation">
@@ -420,16 +440,6 @@
              </property>
             </spacer>
            </item>
-           <item row="1" column="1">
-            <widget class="QGraphicsView" name="graphicsView_Tiles">
-             <property name="verticalScrollBarPolicy">
-              <enum>Qt::ScrollBarAlwaysOff</enum>
-             </property>
-             <property name="horizontalScrollBarPolicy">
-              <enum>Qt::ScrollBarAlwaysOff</enum>
-             </property>
-            </widget>
-           </item>
            <item row="2" column="1">
             <spacer name="verticalSpacer_4">
              <property name="orientation">
@@ -443,6 +453,29 @@
              </property>
             </spacer>
            </item>
+           <item row="1" column="2">
+            <spacer name="horizontalSpacer_4">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item row="1" column="1">
+            <widget class="QGraphicsView" name="graphicsView_Tiles">
+             <property name="verticalScrollBarPolicy">
+              <enum>Qt::ScrollBarAlwaysOff</enum>
+             </property>
+             <property name="horizontalScrollBarPolicy">
+              <enum>Qt::ScrollBarAlwaysOff</enum>
+             </property>
+            </widget>
+           </item>
            <item row="1" column="0">
             <spacer name="horizontalSpacer_3">
              <property name="orientation">
@@ -471,7 +504,7 @@
      <x>0</x>
      <y>0</y>
      <width>700</width>
-     <height>21</height>
+     <height>22</height>
     </rect>
    </property>
    <widget class="QMenu" name="menuFile">
@@ -572,6 +605,13 @@
    </property>
   </action>
  </widget>
+ <customwidgets>
+  <customwidget>
+   <class>NoScrollComboBox</class>
+   <extends>QWidget</extends>
+   <header>noscrollcombobox.h</header>
+  </customwidget>
+ </customwidgets>
  <resources/>
  <connections/>
 </ui>
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<Tile> *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/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/core/map.cpp b/src/core/map.cpp
index 4f89c6c6..8573cc29 100644
--- a/src/core/map.cpp
+++ b/src/core/map.cpp
@@ -412,7 +412,6 @@ void Map::undo() {
     if (!commit)
         return;
 
-
     if (layout->blockdata) {
         layout->blockdata->copyFrom(commit->metatiles);
         if (commit->layoutWidth != this->getWidth() || commit->layoutHeight != this->getHeight()) {
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<Tile>;
     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/mainwindow.cpp b/src/mainwindow.cpp
index 89bc9bc5..c5130f2e 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -995,6 +995,8 @@ void MainWindow::on_actionNew_Tileset_triggered() {
             }
             mt->behavior = 0;
             mt->layerType = 0;
+            mt->encounterType = 0;
+            mt->terrainType = 0;
 
             newSet->metatiles->append(mt);
         }
diff --git a/src/project.cpp b/src/project.cpp
index d48616b7..85640f2b 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -941,9 +941,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<char>(metatile->behavior));
-            data.append(static_cast<char>((metatile->layerType << 4) & 0xF0));
+
+        if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
+            for (Metatile *metatile : *tileset->metatiles) {
+                data.append(static_cast<char>(metatile->behavior));
+                data.append(static_cast<char>(metatile->behavior >> 8) |
+                            static_cast<char>(metatile->terrainType << 1));
+                data.append(static_cast<char>(0));
+                data.append(static_cast<char>(metatile->encounterType) | 
+                            static_cast<char>(metatile->layerType << 5));
+            }
+        } else {
+            for (Metatile *metatile : *tileset->metatiles) {
+                data.append(static_cast<char>(metatile->behavior));
+                data.append(static_cast<char>((metatile->layerType << 4) & 0xF0));
+            }
         }
         attrs_file.write(data);
     } else {
@@ -1483,15 +1495,22 @@ void Project::loadTilesetMetatiles(Tileset* tileset) {
                 if (num_metatileAttrs > num_metatiles)
                     num_metatileAttrs = num_metatiles;
             }
+            bool unusedAttribute = false;
             for (int i = 0; i < num_metatileAttrs; i++) {
                 int value = (static_cast<unsigned char>(data.at(i * 4 + 3)) << 24) | 
                             (static_cast<unsigned char>(data.at(i * 4 + 2)) << 16) | 
                             (static_cast<unsigned char>(data.at(i * 4 + 1)) << 8) | 
                             (static_cast<unsigned char>(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;
+                logInfo(QString("").arg(tileset->metatiles->at(i)->terrainType));
             }
+            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) {
@@ -1503,6 +1522,8 @@ void Project::loadTilesetMetatiles(Tileset* tileset) {
                 int value = (static_cast<unsigned char>(data.at(i * 2 + 1)) << 8) | static_cast<unsigned char>(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 {
@@ -1974,7 +1995,7 @@ bool Project::readMovementTypes() {
 }
 
 bool Project::readInitialFacingDirections() {
-    // TODO: This file is not yet decompiled in pokefirered. Remove once resolved
+    // TODO: This array is not yet decompiled in pokefirered. Remove once resolved
     if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered)
         return true;
     QString filename = "src/event_object_movement.c";
diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp
index 85b94c35..1c36e60d 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 <QFileDialog>
 #include <QMessageBox>
 #include <QDialogButtonBox>
@@ -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("Grass", 1);
+        this->ui->comboBox_encounterType->addItem("Surf", 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<uint8_t>(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<uint8_t>(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);
             }

From f15d7102fc7ca74ada6d729eae54910838473018 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Mon, 16 Mar 2020 16:34:08 -0400
Subject: [PATCH 13/33] Remove dummied logInfo

---
 src/project.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/project.cpp b/src/project.cpp
index 85640f2b..98e5daab 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -1507,7 +1507,6 @@ void Project::loadTilesetMetatiles(Tileset* tileset) {
                 tileset->metatiles->at(i)->layerType = (value & 0x60000000) >> 29;
                 if (value & ~(0x67003FFF))
                     unusedAttribute = true;
-                logInfo(QString("").arg(tileset->metatiles->at(i)->terrainType));
             }
             if (unusedAttribute)
                 logWarn(QString("Unrecognized metatile attributes in %1 will not be saved.").arg(tileset->metatile_attrs_path));

From 2a3b222e3e053a05636f1926e60638bd13600ee2 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Tue, 17 Mar 2020 00:29:54 -0400
Subject: [PATCH 14/33] Locate tileset folders with _[0-9]*

---
 src/project.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/project.cpp b/src/project.cpp
index 98e5daab..c9822afa 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -1346,7 +1346,7 @@ void Project::loadTilesetAssets(Tileset* tileset) {
     if (tileset->name.isNull()) {
         return;
     }
-    QRegularExpression re("([a-z])([A-Z])");
+    QRegularExpression re("([a-z])([A-Z0-9])");
     QString tilesetName = tileset->name;
     QString dir_path = root + "/data/tilesets/" + category + "/" + tilesetName.replace("gTileset_", "").replace(re, "\\1_\\2").toLower();
 

From 083874ce9e56f91f0f698514a7bcc492313a4900 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Wed, 18 Mar 2020 03:12:43 -0400
Subject: [PATCH 15/33] Minor fixes

---
 src/core/metatileparser.cpp | 2 ++
 src/mainwindow.cpp          | 2 ++
 src/project.cpp             | 6 ++++--
 3 files changed, 8 insertions(+), 2 deletions(-)

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<Metatile*> *MetatileParser::parse(QString filepath, bool *error, bool prim
                     (static_cast<unsigned char>(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/mainwindow.cpp b/src/mainwindow.cpp
index c5130f2e..436a1702 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -164,6 +164,7 @@ void MainWindow::setProjectSpecificUIVisibility()
         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);
@@ -174,6 +175,7 @@ void MainWindow::setProjectSpecificUIVisibility()
         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);
diff --git a/src/project.cpp b/src/project.cpp
index c9822afa..00f6a894 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -533,7 +533,7 @@ bool Project::readMapLayouts() {
             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));
+                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);
@@ -1221,7 +1221,9 @@ void Project::saveMap(Map *map) {
     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";
-    mapObj["floor_number"] = map->floorNumber;
+    if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
+        mapObj["floor_number"] = map->floorNumber;
+    }
     mapObj["battle_scene"] = map->battle_scene;
 
     // Connections

From d365ebb66418e77ccb6e8f582f059b4734aa9caa Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Fri, 20 Mar 2020 03:09:48 -0400
Subject: [PATCH 16/33] Support reading/saving pokefirered heal locations

---
 include/core/heallocation.h |   8 ++-
 src/core/event.cpp          |   8 ++-
 src/core/heallocation.cpp   |  28 ++++++--
 src/project.cpp             | 129 +++++++++++++++++++++++++++---------
 4 files changed, 134 insertions(+), 39 deletions(-)

diff --git a/include/core/heallocation.h b/include/core/heallocation.h
index 15790dff..8867b100 100644
--- a/include/core/heallocation.h
+++ b/include/core/heallocation.h
@@ -9,14 +9,18 @@ class HealLocation {
 
 public:
     HealLocation()=default;
-    HealLocation(QString, int, uint16_t, uint16_t);
+    HealLocation(QString, QString, int, uint16_t, uint16_t);
+    HealLocation(QString, QString, int, uint16_t, uint16_t, QString, uint16_t);
     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/src/core/event.cpp b/src/core/event.cpp
index a13ed790..4d08012d 100644
--- a/src/core/event.cpp
+++ b/src/core/event.cpp
@@ -87,10 +87,16 @@ Event* Event::createNewWarpEvent(QString map_name)
 Event* Event::createNewHealLocationEvent(QString map_name)
 {
     Event *event = new Event;
+    QString mapConstant = QString(Map::mapConstantFromName(map_name)).remove(0,4);
     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("loc_name", mapConstant);
+    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", mapConstant);
+        event->put("respawn_npc", 1);
+    }
     return event;
 }
 
diff --git a/src/core/heallocation.cpp b/src/core/heallocation.cpp
index ba739831..ae4c1e91 100644
--- a/src/core/heallocation.cpp
+++ b/src/core/heallocation.cpp
@@ -1,17 +1,33 @@
 #include "heallocation.h"
+#include "config.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)
 {
-    this->name = map;
+    this->idName = id;
+    this->mapName = map;
     this->index = i;
     this->x = x;
     this->y = y;
+    this->respawnMap = "";
+    this->respawnNPC = 0;
+}
+
+HealLocation::HealLocation(QString id, QString map, int i, uint16_t x, uint16_t y, QString respawnMap, uint16_t respawnNPC)
+{
+    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 +36,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 = event->get("respawn_map");
+    }
     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/project.cpp b/src/project.cpp
index 00f6a894..4b67f068 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -259,17 +259,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", loc.respawnMap);
+                heal->put("respawn_npc", loc.respawnNPC);
+            }
             map->events["heal_event_group"].append(heal);
         }
 
@@ -781,9 +786,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");
@@ -794,7 +809,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;
         }
@@ -810,33 +825,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");
@@ -1928,21 +1968,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_(?<id>[A-Za-z0-9_]+)\\s*- 1\\]\\s* = \\{MAP_GROUP[\\(\\s]+(?<map>[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\2)[\\s\\)]+,\\s*(?<x>[0-9A-Fa-fx]+),\\s*(?<y>[0-9A-Fa-fx]+)");
+        QRegularExpression respawnMapRegex("SPAWN_(?<id>[A-Za-z0-9_]+)\\s*- 1\\]\\s* = \\{MAP_GROUP[\\(\\s]+(?<map>[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\2)[\\s\\)]+}");
+        QRegularExpression respawnNPCRegex("SPAWN_(?<id>[A-Za-z0-9_]+)\\s*- 1\\]\\s* = (?<npc>[0-9]+)");
+        QRegularExpressionMatchIterator spawns = spawnRegex.globalMatch(text);
+        QRegularExpressionMatchIterator respawnMaps = respawnMapRegex.globalMatch(text);
+        QRegularExpressionMatchIterator respawnNPCs = respawnNPCRegex.globalMatch(text);
 
-    QRegularExpression regex("MAP_GROUP[\\(\\s]+(?<map>[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\1)[\\s\\)]+,\\s*(?<x>[0-9A-Fa-fx]+),\\s*(?<y>[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_(?<id>[A-Za-z0-9_]+)\\s*- 1\\]\\s* = \\{MAP_GROUP[\\(\\s]+(?<map>[A-Za-z0-9_]+)[\\s\\)]+,\\s*MAP_NUM[\\(\\s]+(\\2)[\\s\\)]+,\\s*(?<x>[0-9A-Fa-fx]+),\\s*(?<y>[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;
 }

From 20b112de525d1760694cb52444dac438663ea22e Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Fri, 20 Mar 2020 13:41:40 -0400
Subject: [PATCH 17/33] Support editing heal location respawns

---
 src/core/event.cpp        |  5 ++---
 src/core/heallocation.cpp |  3 ++-
 src/mainwindow.cpp        | 27 ++++++++++++++++++++++-----
 src/project.cpp           |  2 +-
 4 files changed, 27 insertions(+), 10 deletions(-)

diff --git a/src/core/event.cpp b/src/core/event.cpp
index 4d08012d..8a0c4541 100644
--- a/src/core/event.cpp
+++ b/src/core/event.cpp
@@ -87,14 +87,13 @@ Event* Event::createNewWarpEvent(QString map_name)
 Event* Event::createNewHealLocationEvent(QString map_name)
 {
     Event *event = new Event;
-    QString mapConstant = QString(Map::mapConstantFromName(map_name)).remove(0,4);
     event->put("event_group_type", "heal_event_group");
     event->put("event_type", EventType::HealLocation);
-    event->put("loc_name", mapConstant);
+    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", mapConstant);
+        event->put("respawn_map", map_name);
         event->put("respawn_npc", 1);
     }
     return event;
diff --git a/src/core/heallocation.cpp b/src/core/heallocation.cpp
index ae4c1e91..3653443d 100644
--- a/src/core/heallocation.cpp
+++ b/src/core/heallocation.cpp
@@ -1,5 +1,6 @@
 #include "heallocation.h"
 #include "config.h"
+#include "map.h"
 
 HealLocation::HealLocation(QString id, QString map, int i, uint16_t x, uint16_t y)
 {
@@ -38,7 +39,7 @@ HealLocation HealLocation::fromEvent(Event *event)
     hl.y     = event->getU16("y");
     if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
         hl.respawnNPC = event->getU16("respawn_npc");
-        hl.respawnMap = event->get("respawn_map");
+        hl.respawnMap = Map::mapConstantFromName(event->get("respawn_map")).remove(0,4);
     }
     return hl;
 }
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 436a1702..235ad23e 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1429,6 +1429,8 @@ void MainWindow::updateSelectedObjects() {
         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;
 
@@ -1491,8 +1493,13 @@ 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);
+            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);
@@ -1504,10 +1511,9 @@ void MainWindow::updateSelectedObjects() {
             NoScrollComboBox *combo;
             QCheckBox *check;
 
-            // Some keys shouldn't use a combobox. This isn't very scalable
-            if (key == "quantity") {
+            if (spinKeys.contains(key)) {
                 spin = new NoScrollSpinBox(widget);
-            } else if (key == "underfoot" || key == "in_connection") {
+            } else if (checkKeys.contains(key)) {
                 check = new QCheckBox(widget);
             } else {
                 combo = new NoScrollComboBox(widget);
@@ -1552,6 +1558,7 @@ void MainWindow::updateSelectedObjects() {
                 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");
@@ -1624,12 +1631,22 @@ void MainWindow::updateSelectedObjects() {
                 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 upon respawning after whiteout.");
+                spin->setMinimum(1);
+                spin->setMaximum(126);
             } else {
                 combo->addItem(value);
             }
 
             // Keys using spin boxes
-            if (key == "quantity") {
+            if (spinKeys.contains(key)) {
                 spin->setValue(value.toInt());
 
                 fl->addRow(new QLabel(field_labels[key], widget), spin);
@@ -1640,7 +1657,7 @@ void MainWindow::updateSelectedObjects() {
                     item->event->put(key, value);
                 });
             // Keys using check boxes
-            } else if (key == "underfoot" || key == "in_connection") {
+            } else if (checkKeys.contains(key)) {
                 check->setChecked(value.toInt());
 
                 fl->addRow(new QLabel(field_labels[key], widget), check);
diff --git a/src/project.cpp b/src/project.cpp
index 4b67f068..674850c8 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -272,7 +272,7 @@ bool Project::loadMapData(Map* map) {
             heal->put("event_group_type", "heal_event_group");
             heal->put("event_type", EventType::HealLocation);
             if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
-                heal->put("respawn_map", loc.respawnMap);
+                heal->put("respawn_map", mapConstantsToMapNames->value(QString("MAP_" + loc.respawnMap)));
                 heal->put("respawn_npc", loc.respawnNPC);
             }
             map->events["heal_event_group"].append(heal);

From 8137abd656031fef9cd7ddeac11bec1c14629f6c Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Fri, 20 Mar 2020 13:52:30 -0400
Subject: [PATCH 18/33] Disable respawn editing for pokeemerald/pokeruby

---
 src/mainwindow.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 235ad23e..cafb6252 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1493,8 +1493,10 @@ 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);
-            fields << "respawn_map";
-            fields << "respawn_npc";
+            if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
+                fields << "respawn_map";
+                fields << "respawn_npc";
+            }
         }
 
         // Some keys shouldn't use a combobox

From 9924a36950ddf4d254ec28a53576d85af0674da6 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Tue, 24 Mar 2020 00:33:10 -0400
Subject: [PATCH 19/33] Set floor minimum

---
 forms/mainwindow.ui | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui
index c65e6cde..d92666d5 100644
--- a/forms/mainwindow.ui
+++ b/forms/mainwindow.ui
@@ -2168,6 +2168,9 @@
               <property name="toolTip">
                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Floor number to be used for maps with elevators.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
               </property>
+              <property name="minimum">
+               <number>-128</number>
+              </property>
               <property name="maximum">
                <number>127</number>
               </property>

From 8e26f4c3f5397e8ae5f78a01464d3b7fae9246f0 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Thu, 26 Mar 2020 14:59:33 -0400
Subject: [PATCH 20/33] Enable readInitialFacingDirections for pokefirered

---
 src/project.cpp | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/project.cpp b/src/project.cpp
index 674850c8..4f6c554d 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -2061,9 +2061,6 @@ bool Project::readMovementTypes() {
 }
 
 bool Project::readInitialFacingDirections() {
-    // TODO: This array is not yet decompiled in pokefirered. Remove once resolved
-    if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered)
-        return true;
     QString filename = "src/event_object_movement.c";
     facingDirections = parser.readNamedIndexCArray(filename, "gInitialMovementTypeFacingDirections");
     if (facingDirections.isEmpty()) {

From 0fb483b5d3194bb6496030bb7b49b2612eb7085e Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Thu, 26 Mar 2020 15:11:42 -0400
Subject: [PATCH 21/33] Editable trainer type, minor fixes

---
 src/mainwindow.cpp       | 26 ++++++++++++++------------
 src/ui/tileseteditor.cpp |  4 ++--
 2 files changed, 16 insertions(+), 14 deletions(-)

diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index cafb6252..f0ff4b9e 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1364,6 +1364,7 @@ void MainWindow::updateSelectedObjects() {
 
     QList<EventPropertiesFrame *> frames;
 
+    bool pokefirered = projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered;
     for (DraggablePixmapItem *item : *events) {
         EventPropertiesFrame *frame = new EventPropertiesFrame;
 //        frame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
@@ -1458,7 +1459,7 @@ void MainWindow::updateSelectedObjects() {
             fields << "event_flag";
             fields << "trainer_type";
             fields << "sight_radius_tree_id";
-            if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
+            if (pokefirered) {
                 fields << "in_connection";
             }
         }
@@ -1481,7 +1482,7 @@ void MainWindow::updateSelectedObjects() {
         else if (event_type == EventType::HiddenItem) {
             fields << "item";
             fields << "flag";
-            if (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
+            if (pokefirered) {
                 fields << "quantity";
                 fields << "underfoot";
             }
@@ -1493,7 +1494,7 @@ 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 (projectConfig.getBaseGameVersion() == BaseGameVersion::pokefirered) {
+            if (pokefirered) {
                 fields << "respawn_map";
                 fields << "respawn_npc";
             }
@@ -1521,10 +1522,8 @@ void MainWindow::updateSelectedObjects() {
                 combo = new NoScrollComboBox(widget);
                 combo->setEditable(true);
             }
-
             // 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");
@@ -1536,6 +1535,8 @@ void MainWindow::updateSelectedObjects() {
                 int index = combo->findData(value);
                 if (index != -1) {
                     combo->setCurrentIndex(index);
+                } else {
+                    combo->setCurrentText(value);
                 }
 
                 fl->addRow(new QLabel(field_labels[key], widget), combo);
@@ -1640,7 +1641,8 @@ void MainWindow::updateSelectedObjects() {
                 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 upon respawning after whiteout.");
+                spin->setToolTip("event_object ID of the NPC the player interacts with\n" 
+                                 "upon respawning after whiteout.");
                 spin->setMinimum(1);
                 spin->setMaximum(126);
             } else {
@@ -1669,12 +1671,12 @@ void MainWindow::updateSelectedObjects() {
                 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;
+                    case Qt::Checked:
+                        item->event->put(key, true);
+                        break;
+                    case Qt::Unchecked:
+                        item->event->put(key, false);
+                        break;
                     }
                 });
             // Keys using combo boxes
diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp
index 1c36e60d..17cdcaf7 100644
--- a/src/ui/tileseteditor.cpp
+++ b/src/ui/tileseteditor.cpp
@@ -66,8 +66,8 @@ void TilesetEditor::init(Project *project, QString primaryTilesetLabel, QString
         this->ui->comboBox_encounterType->setVisible(true);
         this->ui->label_encounterType->setVisible(true);
         this->ui->comboBox_encounterType->addItem("None", 0);
-        this->ui->comboBox_encounterType->addItem("Grass", 1);
-        this->ui->comboBox_encounterType->addItem("Surf", 2);
+        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);

From 1a2e7623efc388b0d71622a56d9ade207cccba95 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Fri, 27 Mar 2020 09:22:54 -0400
Subject: [PATCH 22/33] Prefer oamtable name for sprite dimensions

---
 src/project.cpp | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/src/project.cpp b/src/project.cpp
index 4f6c554d..5b79b29f 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -2267,16 +2267,19 @@ void Project::loadEventPixmaps(QList<Event*> 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);
                 }

From 76a8c0dc441e4024bb2861a28f091503cd9058a7 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Fri, 27 Mar 2020 10:51:57 -0400
Subject: [PATCH 23/33] Read trainer type constants

---
 include/project.h  |  2 ++
 src/mainwindow.cpp | 29 ++++++-----------------------
 src/project.cpp    | 15 ++++++++++++++-
 3 files changed, 22 insertions(+), 24 deletions(-)

diff --git a/include/project.h b/include/project.h
index 1c01b07a..7cd77693 100644
--- a/include/project.h
+++ b/include/project.h
@@ -49,6 +49,7 @@ public:
     QStringList *coordEventWeatherNames = nullptr;
     QStringList *secretBaseIds = nullptr;
     QStringList *bgEventFacingDirections = nullptr;
+    QStringList *trainerTypes = nullptr;
     QMap<QString, int> metatileBehaviorMap;
     QMap<int, QString> metatileBehaviorMapInverse;
     QMap<QString, QString> facingDirections;
@@ -143,6 +144,7 @@ public:
     bool readCoordEventWeatherNames();
     bool readSecretBaseIds();
     bool readBgEventFacingDirections();
+    bool readTrainerTypes();
     bool readMetatileBehaviors();
     bool readHealLocations();
     bool readMiscellaneousConstants();
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index f0ff4b9e..595f4606 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -646,6 +646,7 @@ bool MainWindow::loadDataStructures() {
                 && project->readMapBattleScenes()
                 && project->readWeatherNames()
                 && project->readBgEventFacingDirections()
+                && project->readTrainerTypes()
                 && project->readMetatileBehaviors()
                 && project->readTilesetProperties()
                 && project->readHealLocations()
@@ -1522,29 +1523,6 @@ void MainWindow::updateSelectedObjects() {
                 combo = new NoScrollComboBox(widget);
                 combo->setEditable(true);
             }
-            // trainer_type has custom values, so it has special signal logic.
-            if (key == "trainer_type") {
-                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);
-                } else {
-                    combo->setCurrentText(value);
-                }
-
-                fl->addRow(new QLabel(field_labels[key], widget), combo);
-                widget->setLayout(fl);
-                frame->layout()->addWidget(widget);
-                item->bindToUserData(combo, key);
-                continue;
-            }
 
             if (key == "destination_map_name") {
                 if (!editor->project->mapNames->contains(value)) {
@@ -1628,6 +1606,11 @@ 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.");
diff --git a/src/project.cpp b/src/project.cpp
index 5b79b29f..f0862c5a 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -44,6 +44,7 @@ Project::Project()
     coordEventWeatherNames = new QStringList;
     secretBaseIds = new QStringList;
     bgEventFacingDirections = new QStringList;
+    trainerTypes = new QStringList;
     map_cache = new QMap<QString, Map*>;
     mapConstantsToMapNames = new QMap<QString, QString>;
     mapNamesToMapConstants = new QMap<QString, QString>;
@@ -2142,6 +2143,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();
@@ -2150,7 +2163,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;
     }
 

From c28730e834ff71ccbf5d89eb0dd94e848904489f Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Tue, 31 Mar 2020 12:41:15 -0400
Subject: [PATCH 24/33] Fix crash with open windows when switching projects

---
 include/mainwindow.h |  1 +
 src/mainwindow.cpp   | 12 ++++++++++++
 2 files changed, 13 insertions(+)

diff --git a/include/mainwindow.h b/include/mainwindow.h
index 9113e227..27fe541c 100644
--- a/include/mainwindow.h
+++ b/include/mainwindow.h
@@ -216,6 +216,7 @@ private:
     bool openRecentProject();
     void updateTilesetEditor();
     QString getEventGroupFromTabWidget(QWidget *tab);
+    void closeSupplementaryWindows();
 
     bool isProjectOpen();
 };
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 595f4606..a0fcd6d0 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -293,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);
@@ -2410,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(

From e4a41cf201b46dceda6a6a6ff0114ee92816a5a9 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Fri, 3 Apr 2020 16:29:40 -0400
Subject: [PATCH 25/33] Align border blocks

---
 include/core/map.h |  4 ++--
 src/core/map.cpp   | 12 ++++++------
 src/editor.cpp     | 20 ++++++++++++--------
 3 files changed, 20 insertions(+), 16 deletions(-)

diff --git a/include/core/map.h b/include/core/map.h
index 2873844f..425ce639 100644
--- a/include/core/map.h
+++ b/include/core/map.h
@@ -17,9 +17,9 @@
 #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.
+// Number of border blocks 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 6
+#define NUM_BORDER_BLOCKS 3
 
 class Map : public QObject
 {
diff --git a/src/core/map.cpp b/src/core/map.cpp
index 667c151a..2320152a 100644
--- a/src/core/map.cpp
+++ b/src/core/map.cpp
@@ -263,23 +263,23 @@ QPixmap Map::renderConnection(MapConnection connection, MapLayout * fromLayout)
     int x, y, w, h;
     if (connection.direction == "up") {
         x = 0;
-        y = getHeight() - BORDER_DISTANCE;
+        y = getHeight() - (getBorderHeight() * NUM_BORDER_BLOCKS);
         w = getWidth();
-        h = BORDER_DISTANCE;
+        h = getBorderHeight() * NUM_BORDER_BLOCKS;
     } else if (connection.direction == "down") {
         x = 0;
         y = 0;
         w = getWidth();
-        h = BORDER_DISTANCE;
+        h = getBorderHeight() * NUM_BORDER_BLOCKS;
     } else if (connection.direction == "left") {
-        x = getWidth() - BORDER_DISTANCE;
+        x = getWidth() - (getBorderWidth() * NUM_BORDER_BLOCKS);
         y = 0;
-        w = BORDER_DISTANCE;
+        w = getBorderWidth() * NUM_BORDER_BLOCKS;
         h = getHeight();
     } else if (connection.direction == "right") {
         x = 0;
         y = 0;
-        w = BORDER_DISTANCE;
+        w = getBorderWidth() * NUM_BORDER_BLOCKS;
         h = getHeight();
     } else {
         // this should not happen
diff --git a/src/editor.cpp b/src/editor.cpp
index ff0b6d8b..d87ad832 100644
--- a/src/editor.cpp
+++ b/src/editor.cpp
@@ -1118,6 +1118,8 @@ void Editor::displayMetatileSelector() {
 }
 
 void Editor::displayMapMetatiles() {
+    int borderHorzDist = map->getBorderWidth() * NUM_BORDER_BLOCKS * 16;
+    int borderVertDist = map->getBorderHeight() * NUM_BORDER_BLOCKS * 16;
     map_item = new MapPixmapItem(map, this->metatile_selector_item, this->settings);
     connect(map_item, SIGNAL(mouseEvent(QGraphicsSceneMouseEvent*,MapPixmapItem*)),
             this, SLOT(mouseEvent_map(QGraphicsSceneMouseEvent*,MapPixmapItem*)));
@@ -1133,13 +1135,11 @@ void Editor::displayMapMetatiles() {
     map_item->draw(true);
     scene->addItem(map_item);
 
-    int tw = 16;
-    int th = 16;
     scene->setSceneRect(
-        -BORDER_DISTANCE * tw,
-        -BORDER_DISTANCE * th,
-        map_item->pixmap().width() + (BORDER_DISTANCE * 2) * tw,
-        map_item->pixmap().height() + (BORDER_DISTANCE * 2) * th
+        -borderHorzDist,
+        -borderVertDist,
+        map_item->pixmap().width() + borderHorzDist * 2,
+        map_item->pixmap().height() + borderVertDist * 2
     );
 }
 
@@ -1334,9 +1334,13 @@ void Editor::displayMapBorder() {
     }
     borderItems.clear();
 
+    int borderWidth = map->getBorderWidth();
+    int borderHeight = map->getBorderHeight();
+    int borderHorzDist = borderWidth * NUM_BORDER_BLOCKS;
+    int borderVertDist = borderHeight * NUM_BORDER_BLOCKS;
     QPixmap pixmap = map->renderBorder();
-    for (int y = -BORDER_DISTANCE; y < map->getHeight() + BORDER_DISTANCE; y += map->getBorderHeight())
-    for (int x = -BORDER_DISTANCE; x < map->getWidth() + BORDER_DISTANCE; x += map->getBorderWidth()) {
+    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);

From d4cf3edfc7c7613b8199fe445637c95f40188540 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Sat, 4 Apr 2020 14:14:16 -0400
Subject: [PATCH 26/33] Use default tileset for invalid tilesets, fix segfault
 caused by loadMapLayout

---
 src/project.cpp | 33 ++++++++++++++++++++++++---------
 1 file changed, 24 insertions(+), 9 deletions(-)

diff --git a/src/project.cpp b/src/project.cpp
index f0862c5a..fa6cf593 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -453,9 +453,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() {
@@ -625,8 +630,8 @@ void Project::setNewMapLayout(Map* map) {
     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"].at(0);
+    layout->tileset_secondary_label = tilesetLabels["secondary"].at(0);
     map->layout = layout;
     map->layoutId = layout->id;
 
@@ -1045,14 +1050,24 @@ 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;
+        logWarn(QString("Map layout %1 has invalid primary tileset '%2'. Using default '%3'").arg(map->layout->id).arg(map->layout->tileset_primary_label).arg(tilesetLabels["primary"].at(0)));
+        map->layout->tileset_primary_label = tilesetLabels["primary"].at(0);
+        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;
+        logWarn(QString("Map layout %1 has invalid secondary tileset '%2'. Using default '%3'").arg(map->layout->id).arg(map->layout->tileset_secondary_label).arg(tilesetLabels["secondary"].at(0)));
+        map->layout->tileset_secondary_label = tilesetLabels["secondary"].at(0);
+        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;
 }

From d5908c00453259f6fae194a9de532bb240056b1f Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Sun, 5 Apr 2020 00:03:36 -0400
Subject: [PATCH 27/33] Draw border up to players view

---
 include/core/map.h          |  4 ++--
 include/editor.h            |  1 +
 src/core/map.cpp            | 12 ++++++------
 src/editor.cpp              | 25 +++++++++++++++++--------
 src/ui/mapimageexporter.cpp |  2 +-
 5 files changed, 27 insertions(+), 17 deletions(-)

diff --git a/include/core/map.h b/include/core/map.h
index 425ce639..32eb6e70 100644
--- a/include/core/map.h
+++ b/include/core/map.h
@@ -17,9 +17,9 @@
 #define DEFAULT_BORDER_WIDTH 2
 #define DEFAULT_BORDER_HEIGHT 2
 
-// Number of border blocks to draw out from edge of map. Could allow modification of this in the future.
+// 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 NUM_BORDER_BLOCKS 3
+#define BORDER_DISTANCE 7
 
 class Map : public QObject
 {
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/src/core/map.cpp b/src/core/map.cpp
index 2320152a..667c151a 100644
--- a/src/core/map.cpp
+++ b/src/core/map.cpp
@@ -263,23 +263,23 @@ QPixmap Map::renderConnection(MapConnection connection, MapLayout * fromLayout)
     int x, y, w, h;
     if (connection.direction == "up") {
         x = 0;
-        y = getHeight() - (getBorderHeight() * NUM_BORDER_BLOCKS);
+        y = getHeight() - BORDER_DISTANCE;
         w = getWidth();
-        h = getBorderHeight() * NUM_BORDER_BLOCKS;
+        h = BORDER_DISTANCE;
     } else if (connection.direction == "down") {
         x = 0;
         y = 0;
         w = getWidth();
-        h = getBorderHeight() * NUM_BORDER_BLOCKS;
+        h = BORDER_DISTANCE;
     } else if (connection.direction == "left") {
-        x = getWidth() - (getBorderWidth() * NUM_BORDER_BLOCKS);
+        x = getWidth() - BORDER_DISTANCE;
         y = 0;
-        w = getBorderWidth() * NUM_BORDER_BLOCKS;
+        w = BORDER_DISTANCE;
         h = getHeight();
     } else if (connection.direction == "right") {
         x = 0;
         y = 0;
-        w = getBorderWidth() * NUM_BORDER_BLOCKS;
+        w = BORDER_DISTANCE;
         h = getHeight();
     } else {
         // this should not happen
diff --git a/src/editor.cpp b/src/editor.cpp
index d87ad832..0cf52cdd 100644
--- a/src/editor.cpp
+++ b/src/editor.cpp
@@ -1118,8 +1118,6 @@ void Editor::displayMetatileSelector() {
 }
 
 void Editor::displayMapMetatiles() {
-    int borderHorzDist = map->getBorderWidth() * NUM_BORDER_BLOCKS * 16;
-    int borderVertDist = map->getBorderHeight() * NUM_BORDER_BLOCKS * 16;
     map_item = new MapPixmapItem(map, this->metatile_selector_item, this->settings);
     connect(map_item, SIGNAL(mouseEvent(QGraphicsSceneMouseEvent*,MapPixmapItem*)),
             this, SLOT(mouseEvent_map(QGraphicsSceneMouseEvent*,MapPixmapItem*)));
@@ -1136,10 +1134,10 @@ void Editor::displayMapMetatiles() {
     scene->addItem(map_item);
 
     scene->setSceneRect(
-        -borderHorzDist,
-        -borderVertDist,
-        map_item->pixmap().width() + borderHorzDist * 2,
-        map_item->pixmap().height() + borderVertDist * 2
+        -BORDER_DISTANCE * 16,
+        -BORDER_DISTANCE * 16,
+        map_item->pixmap().width() + (BORDER_DISTANCE * 16) * 2,
+        map_item->pixmap().height() + (BORDER_DISTANCE * 16) * 2
     );
 }
 
@@ -1336,8 +1334,8 @@ void Editor::displayMapBorder() {
 
     int borderWidth = map->getBorderWidth();
     int borderHeight = map->getBorderHeight();
-    int borderHorzDist = borderWidth * NUM_BORDER_BLOCKS;
-    int borderVertDist = borderHeight * NUM_BORDER_BLOCKS;
+    int borderHorzDist = getBorderDrawDistance(borderWidth);
+    int borderVertDist = getBorderDrawDistance(borderHeight);
     QPixmap pixmap = map->renderBorder();
     for (int y = -borderVertDist; y < map->getHeight() + borderVertDist; y += borderHeight)
     for (int x = -borderHorzDist; x < map->getWidth() + borderHorzDist; x += borderWidth) {
@@ -1350,6 +1348,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/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) {

From c9b6f87e06946c4b1d90088f4accb12f37fe34a7 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Sun, 5 Apr 2020 11:56:13 -0400
Subject: [PATCH 28/33] Apply undo/redo to border drawing

---
 src/editor.cpp                       | 4 ++++
 src/ui/bordermetatilespixmapitem.cpp | 1 +
 2 files changed, 5 insertions(+)

diff --git a/src/editor.cpp b/src/editor.cpp
index 0cf52cdd..aac160a3 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();
     }
 }
 
diff --git a/src/ui/bordermetatilespixmapitem.cpp b/src/ui/bordermetatilespixmapitem.cpp
index ad31a7ca..e7a024b7 100644
--- a/src/ui/bordermetatilespixmapitem.cpp
+++ b/src/ui/bordermetatilespixmapitem.cpp
@@ -42,5 +42,6 @@ void BorderMetatilesPixmapItem::draw() {
     }
 
     painter.end();
+    map->commit();
     this->setPixmap(QPixmap::fromImage(image));
 }

From 129db88453bab91840cce27a8db8f9de1016af0d Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Mon, 6 Apr 2020 12:09:45 -0400
Subject: [PATCH 29/33] Minor cleanup

---
 include/core/heallocation.h |  3 +--
 src/core/heallocation.cpp   | 11 -----------
 src/editor.cpp              | 10 ++++++----
 src/mainwindow.cpp          |  2 +-
 4 files changed, 8 insertions(+), 18 deletions(-)

diff --git a/include/core/heallocation.h b/include/core/heallocation.h
index 8867b100..fd00a22b 100644
--- a/include/core/heallocation.h
+++ b/include/core/heallocation.h
@@ -9,8 +9,7 @@ class HealLocation {
 
 public:
     HealLocation()=default;
-    HealLocation(QString, QString, int, uint16_t, uint16_t);
-    HealLocation(QString, QString, int, uint16_t, uint16_t, QString, uint16_t);
+    HealLocation(QString, QString, int, uint16_t, uint16_t, QString = "", uint16_t = 0);
     friend QDebug operator<<(QDebug debug, const HealLocation &hl);
 
 public:
diff --git a/src/core/heallocation.cpp b/src/core/heallocation.cpp
index 3653443d..e88d99e6 100644
--- a/src/core/heallocation.cpp
+++ b/src/core/heallocation.cpp
@@ -2,17 +2,6 @@
 #include "config.h"
 #include "map.h"
 
-HealLocation::HealLocation(QString id, QString map, int i, uint16_t x, uint16_t y)
-{
-    this->idName = id;
-    this->mapName = map;
-    this->index = i;
-    this->x = x;
-    this->y = y;
-    this->respawnMap = "";
-    this->respawnNPC = 0;
-}
-
 HealLocation::HealLocation(QString id, QString map, int i, uint16_t x, uint16_t y, QString respawnMap, uint16_t respawnNPC)
 {
     this->idName = id;
diff --git a/src/editor.cpp b/src/editor.cpp
index aac160a3..2f8e38da 100644
--- a/src/editor.cpp
+++ b/src/editor.cpp
@@ -1137,11 +1137,13 @@ void Editor::displayMapMetatiles() {
     map_item->draw(true);
     scene->addItem(map_item);
 
+    int tw = 16;
+    int th = 16;
     scene->setSceneRect(
-        -BORDER_DISTANCE * 16,
-        -BORDER_DISTANCE * 16,
-        map_item->pixmap().width() + (BORDER_DISTANCE * 16) * 2,
-        map_item->pixmap().height() + (BORDER_DISTANCE * 16) * 2
+        -BORDER_DISTANCE * tw,
+        -BORDER_DISTANCE * th,
+        map_item->pixmap().width() + BORDER_DISTANCE * 2 * tw,
+        map_item->pixmap().height() + BORDER_DISTANCE * 2 * th
     );
 }
 
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index a0fcd6d0..e0e64da0 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -2203,7 +2203,7 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked()
 
     QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog);
     form.addRow(&buttonBox);
-    connect(&buttonBox, &QDialogButtonBox::accepted, [&dialog, &widthSpinBox, &heightSpinBox, &bwidthSpinBox, &bheightSpinBox, &errorLabel](){
+    connect(&buttonBox, &QDialogButtonBox::accepted, [&dialog, &widthSpinBox, &heightSpinBox, &errorLabel](){
         // Ensure width and height are an acceptable size.
         // The maximum number of metatiles in a map is the following:
         //    max = (width + 15) * (height + 14)

From 5f16ae7dd06bd93ecf5287dee9350feec6740943 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Sun, 19 Apr 2020 10:16:49 -0400
Subject: [PATCH 30/33] Use value() instead of at() for defaults

---
 src/project.cpp        | 22 ++++++++++++----------
 src/ui/newmappopup.cpp |  4 ++--
 2 files changed, 14 insertions(+), 12 deletions(-)

diff --git a/src/project.cpp b/src/project.cpp
index eaf12d96..0c7c9651 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -425,8 +425,8 @@ void Project::setNewMapHeader(Map* map, int mapIndex) {
     map->layoutId = QString("%1").arg(mapIndex);
     map->location = mapSectionValueToName.value(0);
     map->requiresFlash = "FALSE";
-    map->weather = weatherNames->at(0);
-    map->type = mapTypes->at(0);
+    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";
@@ -443,7 +443,7 @@ void Project::setNewMapHeader(Map* map, int mapIndex) {
         map->floorNumber = 0;
     }
 
-    map->battle_scene = mapBattleScenes->at(0);
+    map->battle_scene = mapBattleScenes->value(0, "MAP_BATTLE_SCENE_NORMAL");
 }
 
 bool Project::loadMapLayout(Map* map) {
@@ -637,8 +637,8 @@ void Project::setNewMapLayout(Map* map) {
     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 = tilesetLabels["primary"].at(0);
-    layout->tileset_secondary_label = tilesetLabels["secondary"].at(0);
+    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;
 
@@ -1060,8 +1060,9 @@ bool Project::loadMapTilesets(Map* map) {
 
     map->layout->tileset_primary = getTileset(map->layout->tileset_primary_label);
     if (!map->layout->tileset_primary) {
-        logWarn(QString("Map layout %1 has invalid primary tileset '%2'. Using default '%3'").arg(map->layout->id).arg(map->layout->tileset_primary_label).arg(tilesetLabels["primary"].at(0)));
-        map->layout->tileset_primary_label = tilesetLabels["primary"].at(0);
+        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."));
@@ -1071,8 +1072,9 @@ bool Project::loadMapTilesets(Map* map) {
 
     map->layout->tileset_secondary = getTileset(map->layout->tileset_secondary_label);
     if (!map->layout->tileset_secondary) {
-        logWarn(QString("Map layout %1 has invalid secondary tileset '%2'. Using default '%3'").arg(map->layout->id).arg(map->layout->tileset_secondary_label).arg(tilesetLabels["secondary"].at(0)));
-        map->layout->tileset_secondary_label = tilesetLabels["secondary"].at(0);
+        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."));
@@ -2210,7 +2212,7 @@ QStringList Project::getSongNames() {
     songDefinePrefixes << "SE_" << "MUS_";
     QMap<QString, int> songDefines = parser.readCDefines("include/constants/songs.h", songDefinePrefixes);
     QStringList names = songDefines.keys();
-    this->defaultSong = names.at(0);
+    this->defaultSong = names.value(0, "MUS_DUMMY");
 
     return names;
 }
diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp
index 18767135..f35af69e 100644
--- a/src/ui/newmappopup.cpp
+++ b/src/ui/newmappopup.cpp
@@ -152,9 +152,9 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() {
     newMap->location = this->ui->comboBox_NewMap_Location->currentText();
     newMap->song = this->project->defaultSong;
     newMap->requiresFlash = "0";
-    newMap->weather = this->project->weatherNames->at(0);
+    newMap->weather = this->project->weatherNames->value(0, "WEATHER_NONE");
     newMap->show_location = "1";
-    newMap->battle_scene = this->project->mapBattleScenes->at(0);
+    newMap->battle_scene = this->project->mapBattleScenes->value(0, "MAP_BATTLE_SCENE_NORMAL");
 
     if (this->existingLayout) {
         layout = this->project->mapLayouts.value(this->layoutId);

From f2ae83b33e0aca957ae7ff83f427b3457af2f321 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Sun, 19 Apr 2020 18:11:45 -0400
Subject: [PATCH 31/33] Use metatile attributes path from header

---
 src/project.cpp | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/project.cpp b/src/project.cpp
index 0c7c9651..ca6b46c5 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -1099,8 +1099,12 @@ 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->metatile_attrs_label = values->value(7);
+    } else {
+        tileset->metatile_attrs_label = values->value(6);
+        tileset->callback_label = values->value(7);
+    }
 
     loadTilesetAssets(tileset);
 

From d91107a07d58d9d0261975c171d30e3d3ec663fe Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Sun, 19 Apr 2020 18:23:37 -0400
Subject: [PATCH 32/33] Read callback_label for FRLG

---
 src/project.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/project.cpp b/src/project.cpp
index ca6b46c5..fcdb1dcb 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -1100,6 +1100,7 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) {
     tileset->palettes_label = values->value(4);
     tileset->metatiles_label = values->value(5);
     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);

From 0c92dc05da9136946d0742e659b4ef019a369630 Mon Sep 17 00:00:00 2001
From: GriffinR <griffin.g.richards@gmail.com>
Date: Mon, 20 Apr 2020 13:28:56 -0400
Subject: [PATCH 33/33] Save metatile attributes label in new order for FRLG

---
 src/core/tileset.cpp | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

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 <QPainter>
 #include <QImage>
@@ -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();