Added expansion multi-region support

This commit is contained in:
Starkiller4011 2025-11-10 09:20:35 -05:00
parent 66f26ab057
commit f49da2c926
12 changed files with 230 additions and 49 deletions

View File

@ -47,6 +47,7 @@ The filepath that Porymap expects for each file can be overridden on the ``Files
src/data/object_events/object_event_graphics.h, yes, no, ``data_obj_event_gfx``, "to locate object event sprites using data from ``data_obj_event_pic_tables``"
src/data/graphics/pokemon.h, yes, no, ``data_pokemon_gfx``, "if ``symbol_pokemon_icon_table`` is read this file will be searched for filepaths to species icon"
src/data/region_map/region_map_sections.json, yes, yes, ``json_region_map_entries``, "for populating the locations list and for region map data"
src/data/region_map/regions.json, yes, yes, ``json_region_entries``, "For populating the regions list and for region map data"
src/data/region_map/porymap_config.json, yes, yes, ``json_region_porymap_cfg``, "Porymap's config file for the region map editor"
include/constants/global.h, yes, no, ``constants_global``, "to evaluate ``define_obj_event_count``"
include/constants/items.h, yes, no, ``constants_items``, "to find ``regex_items`` names"

View File

@ -596,145 +596,151 @@ to a file, it probably is not a good idea to edit yourself unless otherwise note
<td><p><code class="docutils literal notranslate"><span class="pre">json_region_map_entries</span></code></p></td>
<td><p>for populating the locations list and for region map data</p></td>
</tr>
<tr class="row-even"><td><p>src/data/region_map/porymap_config.json</p></td>
<tr class="row-even"><td><p>src/data/region_map/regions.json</p></td>
<td><p>yes</p></td>
<td><p>yes</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">json_region_entries</span></code></p></td>
<td><p>For populating the regions list and for region map data</p></td>
</tr>
<tr class="row-odd"><td><p>src/data/region_map/porymap_config.json</p></td>
<td><p>yes</p></td>
<td><p>yes</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">json_region_porymap_cfg</span></code></p></td>
<td><p>Porymaps config file for the region map editor</p></td>
</tr>
<tr class="row-odd"><td><p>include/constants/global.h</p></td>
<tr class="row-even"><td><p>include/constants/global.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_global</span></code></p></td>
<td><p>to evaluate <code class="docutils literal notranslate"><span class="pre">define_obj_event_count</span></code></p></td>
</tr>
<tr class="row-even"><td><p>include/constants/items.h</p></td>
<tr class="row-odd"><td><p>include/constants/items.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_items</span></code></p></td>
<td><p>to find <code class="docutils literal notranslate"><span class="pre">regex_items</span></code> names</p></td>
</tr>
<tr class="row-odd"><td><p>include/constants/flags.h</p></td>
<tr class="row-even"><td><p>include/constants/flags.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_flags</span></code></p></td>
<td><p>to find <code class="docutils literal notranslate"><span class="pre">regex_flags</span></code> names</p></td>
</tr>
<tr class="row-even"><td><p>include/constants/vars.h</p></td>
<tr class="row-odd"><td><p>include/constants/vars.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_vars</span></code></p></td>
<td><p>to find <code class="docutils literal notranslate"><span class="pre">regex_vars</span></code> names</p></td>
</tr>
<tr class="row-odd"><td><p>include/constants/weather.h</p></td>
<tr class="row-even"><td><p>include/constants/weather.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_weather</span></code></p></td>
<td><p>to find <code class="docutils literal notranslate"><span class="pre">regex_weather</span></code> names</p></td>
</tr>
<tr class="row-even"><td><p>include/constants/songs.h</p></td>
<tr class="row-odd"><td><p>include/constants/songs.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_songs</span></code></p></td>
<td><p>to find <code class="docutils literal notranslate"><span class="pre">regex_music</span></code> names</p></td>
</tr>
<tr class="row-odd"><td><p>include/constants/pokemon.h</p></td>
<tr class="row-even"><td><p>include/constants/pokemon.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_pokemon</span></code></p></td>
<td><p>to evaluate <code class="docutils literal notranslate"><span class="pre">define_min_level</span></code> and <code class="docutils literal notranslate"><span class="pre">define_max_level</span></code></p></td>
</tr>
<tr class="row-even"><td><p>include/constants/map_types.h</p></td>
<tr class="row-odd"><td><p>include/constants/map_types.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_map_types</span></code></p></td>
<td><p>to find <code class="docutils literal notranslate"><span class="pre">regex_map_types</span></code> and <code class="docutils literal notranslate"><span class="pre">regex_battle_scenes</span></code> names</p></td>
</tr>
<tr class="row-odd"><td><p>include/constants/trainer_types.h</p></td>
<tr class="row-even"><td><p>include/constants/trainer_types.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_trainer_types</span></code></p></td>
<td><p>to find <code class="docutils literal notranslate"><span class="pre">regex_trainer_types</span></code> names</p></td>
</tr>
<tr class="row-even"><td><p>include/constants/secret_bases.h</p></td>
<tr class="row-odd"><td><p>include/constants/secret_bases.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_secret_bases</span></code></p></td>
<td><p>to find <code class="docutils literal notranslate"><span class="pre">regex_secret_bases</span></code> names</p></td>
</tr>
<tr class="row-odd"><td><p>include/constants/event_object_movement.h</p></td>
<tr class="row-even"><td><p>include/constants/event_object_movement.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_obj_event_movement</span></code></p></td>
<td><p>to find <code class="docutils literal notranslate"><span class="pre">regex_movement_types</span></code> names</p></td>
</tr>
<tr class="row-even"><td><p>include/constants/event_objects.h</p></td>
<tr class="row-odd"><td><p>include/constants/event_objects.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_obj_events</span></code></p></td>
<td><p>to evaluate <code class="docutils literal notranslate"><span class="pre">regex_obj_event_gfx</span></code> constants</p></td>
</tr>
<tr class="row-odd"><td><p>include/constants/event_bg.h</p></td>
<tr class="row-even"><td><p>include/constants/event_bg.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_event_bg</span></code></p></td>
<td><p>to find <code class="docutils literal notranslate"><span class="pre">regex_sign_facing_directions</span></code> names</p></td>
</tr>
<tr class="row-even"><td><p>include/constants/metatile_labels.h</p></td>
<tr class="row-odd"><td><p>include/constants/metatile_labels.h</p></td>
<td><p>yes</p></td>
<td><p>yes</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_metatile_labels</span></code></p></td>
<td><p>to read/write metatile labels using <code class="docutils literal notranslate"><span class="pre">define_metatile_label_prefix</span></code></p></td>
</tr>
<tr class="row-odd"><td><p>include/constants/metatile_behaviors.h</p></td>
<tr class="row-even"><td><p>include/constants/metatile_behaviors.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_metatile_behaviors</span></code></p></td>
<td><p>to evaluate <code class="docutils literal notranslate"><span class="pre">regex_behaviors</span></code> constants</p></td>
</tr>
<tr class="row-even"><td><p>include/constants/species.h</p></td>
<tr class="row-odd"><td><p>include/constants/species.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_species</span></code></p></td>
<td><p>to find names using <code class="docutils literal notranslate"><span class="pre">define_species_prefix</span></code></p></td>
</tr>
<tr class="row-odd"><td><p>include/global.fieldmap.h</p></td>
<tr class="row-even"><td><p>include/global.fieldmap.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">global_fieldmap</span></code></p></td>
<td><p>to evaluate map and tileset data masks, and to read <code class="docutils literal notranslate"><span class="pre">regex_encounter_types</span></code> / <code class="docutils literal notranslate"><span class="pre">regex_terrain_types</span></code></p></td>
</tr>
<tr class="row-even"><td><p>include/fieldmap.h</p></td>
<tr class="row-odd"><td><p>include/fieldmap.h</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">constants_fieldmap</span></code></p></td>
<td><p>to evaluate a variety of tileset and map constants</p></td>
</tr>
<tr class="row-odd"><td><p>src/fieldmap.c</p></td>
<tr class="row-even"><td><p>src/fieldmap.c</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">fieldmap</span></code></p></td>
<td><p>to read <code class="docutils literal notranslate"><span class="pre">symbol_attribute_table</span></code></p></td>
</tr>
<tr class="row-even"><td><p>src/event_object_movement.c</p></td>
<tr class="row-odd"><td><p>src/event_object_movement.c</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">initial_facing_table</span></code></p></td>
<td><p>to read <code class="docutils literal notranslate"><span class="pre">symbol_facing_directions</span></code></p></td>
</tr>
<tr class="row-odd"><td><p>src/wild_encounter.c</p></td>
<tr class="row-even"><td><p>src/wild_encounter.c</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">wild_encounter</span></code></p></td>
<td><p>to evaluate <code class="docutils literal notranslate"><span class="pre">define_max_encounter_rate</span></code></p></td>
</tr>
<tr class="row-even"><td><p>src/pokemon_icon.c</p></td>
<tr class="row-odd"><td><p>src/pokemon_icon.c</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">pokemon_icon_table</span></code></p></td>
<td><p>to read <code class="docutils literal notranslate"><span class="pre">symbol_pokemon_icon_table</span></code></p></td>
</tr>
<tr class="row-odd"><td><p>graphics/pokemon/</p></td>
<tr class="row-even"><td><p>graphics/pokemon/</p></td>
<td><p>yes</p></td>
<td><p>no</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">pokemon_gfx</span></code></p></td>

View File

@ -47,6 +47,7 @@ The filepath that Porymap expects for each file can be overridden on the ``Files
src/data/object_events/object_event_graphics.h, yes, no, ``data_obj_event_gfx``, "to locate object event sprites using data from ``data_obj_event_pic_tables``"
src/data/graphics/pokemon.h, yes, no, ``data_pokemon_gfx``, "if ``symbol_pokemon_icon_table`` is read this file will be searched for filepaths to species icon"
src/data/region_map/region_map_sections.json, yes, yes, ``json_region_map_entries``, "for populating the locations list and for region map data"
src/data/region_map/regions.json, yes, yes, ``json_region_entries``, "For populating the regions list and for region map data"
src/data/region_map/porymap_config.json, yes, yes, ``json_region_porymap_cfg``, "Porymap's config file for the region map editor"
include/constants/global.h, yes, no, ``constants_global``, "to evaluate ``define_obj_event_count``"
include/constants/items.h, yes, no, ``constants_items``, "to find ``regex_items`` names"

View File

@ -40,14 +40,14 @@
</property>
</widget>
</item>
<item row="1" column="0">
<item row="2" column="0">
<widget class="QLabel" name="label_Location">
<property name="text">
<string>Location</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="2" column="1">
<widget class="NoScrollComboBox" name="comboBox_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>
@ -60,14 +60,14 @@
</property>
</widget>
</item>
<item row="3" column="0">
<item row="6" column="0">
<widget class="QLabel" name="label_RequiresFlash">
<property name="text">
<string>Requires Flash</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="6" column="1">
<widget class="QCheckBox" name="checkBox_RequiresFlash">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the player will need to use Flash to see fully on this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -77,14 +77,14 @@
</property>
</widget>
</item>
<item row="4" column="0">
<item row="7" column="0">
<widget class="QLabel" name="label_Weather">
<property name="text">
<string>Weather</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="7" column="1">
<widget class="NoScrollComboBox" name="comboBox_Weather">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default weather on this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -97,14 +97,14 @@
</property>
</widget>
</item>
<item row="5" column="0">
<item row="8" column="0">
<widget class="QLabel" name="label_Type">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="8" column="1">
<widget class="NoScrollComboBox" name="comboBox_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, underground type maps will have a special transition effect when the player enters/exits the map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -117,14 +117,14 @@
</property>
</widget>
</item>
<item row="6" column="0">
<item row="9" column="0">
<widget class="QLabel" name="label_BattleScene">
<property name="text">
<string>Battle Scene</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="9" column="1">
<widget class="NoScrollComboBox" name="comboBox_BattleScene">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field is used to help determine what graphics to use in the background of battles on this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -137,14 +137,14 @@
</property>
</widget>
</item>
<item row="7" column="0">
<item row="10" column="0">
<widget class="QLabel" name="label_ShowLocationName">
<property name="text">
<string>Show Location Name</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="10" column="1">
<widget class="QCheckBox" name="checkBox_ShowLocationName">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, a map name popup will appear when the player enters this map. The name that appears on this popup depends on the Location field.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -154,14 +154,14 @@
</property>
</widget>
</item>
<item row="8" column="0">
<item row="11" column="0">
<widget class="QLabel" name="label_AllowRunning">
<property name="text">
<string>Allow Running</string>
</property>
</widget>
</item>
<item row="8" column="1">
<item row="11" column="1">
<widget class="QCheckBox" name="checkBox_AllowRunning">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the player will be allowed to run on this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -171,14 +171,14 @@
</property>
</widget>
</item>
<item row="9" column="0">
<item row="12" column="0">
<widget class="QLabel" name="label_AllowBiking">
<property name="text">
<string>Allow Biking</string>
</property>
</widget>
</item>
<item row="9" column="1">
<item row="12" column="1">
<widget class="QCheckBox" name="checkBox_AllowBiking">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the player will be allowed to get on their bike on this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -188,14 +188,14 @@
</property>
</widget>
</item>
<item row="10" column="0">
<item row="13" column="0">
<widget class="QLabel" name="label_AllowEscaping">
<property name="text">
<string>Allow Dig &amp; Escape Rope</string>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="13" column="1">
<widget class="QCheckBox" name="checkBox_AllowEscaping">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the player will be allowed to use Dig or Escape Rope on this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -205,34 +205,44 @@
</property>
</widget>
</item>
<item row="11" column="0">
<item row="14" column="0">
<widget class="QLabel" name="label_FloorNumber">
<property name="text">
<string>Floor Number</string>
</property>
</widget>
</item>
<item row="11" column="1">
<item row="14" column="1">
<widget class="NoScrollSpinBox" 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>
</widget>
</item>
<item row="2" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_LocationName">
<property name="text">
<string>Location Name</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="4" column="1">
<widget class="QLineEdit" name="lineEdit_LocationName">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The name that will be displayed in-game for this Location. This name will be shared with any other map that has the same Location.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_Region">
<property name="text">
<string>Region</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="NoScrollComboBox" name="comboBox_Region"/>
</item>
</layout>
</widget>
<customwidgets>

View File

@ -225,6 +225,9 @@ enum ProjectIdentifier {
define_map_empty,
define_map_section_prefix,
define_map_section_empty,
define_region_prefix,
define_region_empty,
define_region_count,
define_species_prefix,
define_species_empty,
regex_behaviors,
@ -259,6 +262,7 @@ enum ProjectFilePath {
json_wild_encounters,
json_heal_locations,
json_region_map_entries,
json_region_entries,
json_region_porymap_cfg,
tilesets_headers,
tilesets_graphics,

View File

@ -14,6 +14,7 @@ public:
bool operator==(const MapHeader& other) const {
return m_song == other.m_song
&& m_location == other.m_location
&& m_region == other.m_region
&& m_requiresFlash == other.m_requiresFlash
&& m_weather == other.m_weather
&& m_type == other.m_type
@ -30,6 +31,8 @@ public:
void setSong(const QString &song);
void setLocation(const QString &location);
void setRegion(const QString &region);
void setUsesMultiRegion(bool usesMultiRegion);
void setRequiresFlash(bool requiresFlash);
void setWeather(const QString &weather);
void setType(const QString &type);
@ -42,6 +45,8 @@ public:
QString song() const { return m_song; }
QString location() const { return m_location; }
QString region() const { return m_region; }
bool usesMultiRegion() const { return m_usesMultiRegion; }
bool requiresFlash() const { return m_requiresFlash; }
QString weather() const { return m_weather; }
QString type() const { return m_type; }
@ -55,6 +60,8 @@ public:
signals:
void songChanged(QString);
void locationChanged(QString);
void regionChanged(QString);
void usesMultiRegionChanged(bool);
void requiresFlashChanged(bool);
void weatherChanged(QString);
void typeChanged(QString);
@ -69,6 +76,8 @@ signals:
private:
QString m_song;
QString m_location;
QString m_region;
bool m_usesMultiRegion = false;
bool m_requiresFlash = false;
QString m_weather;
QString m_type;

View File

@ -52,6 +52,11 @@ public:
QStringList bgEventFacingDirections;
QStringList trainerTypes;
QStringList globalScriptLabels;
QStringList mapSectionIdNamesSaveOrder;
QStringList mapSectionIdNames;
QStringList regionIdNamesSaveOrder;
QStringList regionIdNames;
bool usingMultiRegion;
QMap<uint32_t, QString> encounterTypeToName;
QMap<uint32_t, QString> terrainTypeToName;
QMap<QString, QMap<QString, uint16_t>> metatileLabelsMap;
@ -187,6 +192,7 @@ public:
bool readTilesetLabels();
bool readTilesetMetatileLabels();
bool readRegionMapSections();
bool readRegionEntries();
bool readItemNames();
bool readFlagNames();
bool readVarNames();
@ -273,6 +279,7 @@ public:
static int getNumPalettesTotal() { return num_pals_total; }
static int getNumPalettesSecondary() { return getNumPalettesTotal() - getNumPalettesPrimary(); }
static QString getEmptyMapsecName();
static QString getEmptyRegionName();
static QString getMapGroupPrefix();
private:
@ -289,8 +296,6 @@ private:
QStringList orderedLayoutIdsMaster;
QHash<QString, Layout*> mapLayouts;
QHash<QString, Layout*> mapLayoutsMaster;
QStringList mapSectionIdNamesSaveOrder;
QStringList mapSectionIdNames;
// Fields for preserving top-level JSON data that Porymap isn't expecting.
QJsonObject customLayoutsData;
@ -339,6 +344,14 @@ private:
};
QHash<QString, LocationData> locationData;
// The extra data that can be associated with each REGION name.
struct RegionData
{
QString displayName;
QString displayText;
};
QHash<QString, RegionData> regionData;
QJsonDocument readMapJson(const QString &mapName, QString *error = nullptr);
void setNewLayoutBlockdata(Layout *layout);
@ -384,6 +397,7 @@ signals:
void mapSectionAdded(const QString &idName);
void mapSectionDisplayNameChanged(const QString &idName, const QString &displayName);
void mapSectionIdNamesChanged(const QStringList &idNames);
void regionIdNamesChanged(const QStringList &idNames);
void eventScriptLabelsRead();
};

View File

@ -35,6 +35,7 @@ public:
void setSong(const QString &song);
void setLocation(const QString &location);
void setRegion(const QString &region);
void setLocationName(const QString &locationName);
void setRequiresFlash(bool requiresFlash);
void setWeather(const QString &weather);
@ -48,6 +49,7 @@ public:
QString song() const;
QString location() const;
QString region() const;
QString locationName() const;
bool requiresFlash() const;
QString weather() const;
@ -68,10 +70,12 @@ private:
void setText(NoScrollComboBox *combo, const QString &text) const;
void setText(QLineEdit *lineEdit, const QString &text) const;
void setLocations(const QStringList &locations);
void setRegions(const QStringList &regions);
void updateLocationName();
void onSongUpdated(const QString &song);
void onLocationChanged(const QString &location);
void onRegionChanged(const QString &region);
void onLocationNameChanged(const QString &locationName);
void onWeatherChanged(const QString &weather);
void onTypeChanged(const QString &type);

View File

@ -114,6 +114,9 @@ const QMap<ProjectIdentifier, QPair<QString, QString>> ProjectConfig::defaultIde
{ProjectIdentifier::define_map_empty, {"define_map_empty", "MAP_UNDEFINED"}},
{ProjectIdentifier::define_map_section_prefix, {"define_map_section_prefix", "MAPSEC_"}},
{ProjectIdentifier::define_map_section_empty, {"define_map_section_empty", "NONE"}},
{ProjectIdentifier::define_region_prefix, {"define_region_prefix", "REGION_"}},
{ProjectIdentifier::define_region_empty, {"define_region_empty", "NONE"}},
{ProjectIdentifier::define_region_count, {"define_region_count", "REGIONS_COUNT"}},
{ProjectIdentifier::define_species_prefix, {"define_species_prefix", "SPECIES_"}},
{ProjectIdentifier::define_species_empty, {"define_species_empty", "NONE"}},
// Regex
@ -150,6 +153,7 @@ const QMap<ProjectFilePath, QPair<QString, QString>> ProjectConfig::defaultPaths
{ProjectFilePath::json_wild_encounters, { "json_wild_encounters", "src/data/wild_encounters.json"}},
{ProjectFilePath::json_heal_locations, { "json_heal_locations", "src/data/heal_locations.json"}},
{ProjectFilePath::json_region_map_entries, { "json_region_map_entries", "src/data/region_map/region_map_sections.json"}},
{ProjectFilePath::json_region_entries, { "json_region_entries", "src/data/region_map/regions.json"}},
{ProjectFilePath::json_region_porymap_cfg, { "json_region_porymap_cfg", "src/data/region_map/porymap_config.json"}},
{ProjectFilePath::tilesets_headers, { "tilesets_headers", "src/data/tilesets/headers.h"}},
{ProjectFilePath::tilesets_graphics, { "tilesets_graphics", "src/data/tilesets/graphics.h"}},

View File

@ -3,6 +3,7 @@
MapHeader::MapHeader(const MapHeader& other) : MapHeader() {
m_song = other.m_song;
m_location = other.m_location;
m_region = other.m_region;
m_requiresFlash = other.m_requiresFlash;
m_weather = other.m_weather;
m_type = other.m_type;
@ -20,6 +21,7 @@ MapHeader &MapHeader::operator=(const MapHeader &other) {
// repeatedly (but for now at least that's not a big issue).
setSong(other.m_song);
setLocation(other.m_location);
setRegion((other.m_region));
setRequiresFlash(other.m_requiresFlash);
setWeather(other.m_weather);
setType(other.m_type);
@ -48,6 +50,22 @@ void MapHeader::setLocation(const QString &location) {
emit modified();
}
void MapHeader::setRegion(const QString &region) {
if (m_region == region)
return;
m_region = region;
emit regionChanged(m_region);
emit modified();
}
void MapHeader::setUsesMultiRegion(bool usesMultiRegion) {
if (m_usesMultiRegion == usesMultiRegion)
return;
m_usesMultiRegion = usesMultiRegion;
emit usesMultiRegionChanged(m_usesMultiRegion);
emit modified();
}
void MapHeader::setRequiresFlash(bool requiresFlash) {
if (m_requiresFlash == requiresFlash)
return;

View File

@ -218,7 +218,7 @@ bool Project::load() {
&& readSongNames()
&& readMapGroups()
&& readHealLocations();
this->usingMultiRegion = readRegionEntries();
if (success) {
// No need to do this if something failed to load.
// (and in fact we shouldn't, because they contain
@ -445,6 +445,14 @@ bool Project::loadMapData(Map* map) {
map->header()->setType(ParseUtil::jsonToQString(mapObj.take("map_type")));
map->header()->setShowsLocationName(ParseUtil::jsonToBool(mapObj.take("show_map_name")));
map->header()->setBattleScene(ParseUtil::jsonToQString(mapObj.take("battle_scene")));
if (this->usingMultiRegion) {
if (mapObj.contains("region")) {
map->header()->setRegion(ParseUtil::jsonToQString(mapObj.take("region")));
} else {
map->header()->setRegion(getEmptyRegionName());
}
}
if (projectConfig.mapAllowFlagsEnabled) {
map->header()->setAllowsBiking(ParseUtil::jsonToBool(mapObj.take("allow_cycling")));
@ -1369,6 +1377,11 @@ bool Project::saveMap(Map *map, bool skipLayout) {
mapObj["layout"] = map->layoutId();
mapObj["music"] = map->header()->song();
mapObj["region_map_section"] = map->header()->location();
if (this->usingMultiRegion) {
if (map->header()->region() != getEmptyRegionName()) {
mapObj["region"] = map->header()->region();
}
}
mapObj["requires_flash"] = map->header()->requiresFlash();
mapObj["weather"] = map->header()->weather();
mapObj["map_type"] = map->header()->type();
@ -2169,6 +2182,8 @@ bool Project::isIdentifierUnique(const QString &identifier) const {
return false;
if (this->mapSectionIdNames.contains(identifier))
return false;
if (this->regionIdNames.contains(identifier))
return false;
if (this->tilesetLabelsOrdered.contains(identifier))
return false;
if (this->mapLayouts.contains(identifier))
@ -2227,6 +2242,8 @@ void Project::initNewMapSettings() {
this->newMapSettings.header.setSong(this->defaultSong);
this->newMapSettings.header.setLocation(this->mapSectionIdNames.value(0, "0"));
this->newMapSettings.header.setRegion(this->regionIdNames.value(0, "0"));
this->newMapSettings.header.setUsesMultiRegion(this->usingMultiRegion);
this->newMapSettings.header.setRequiresFlash(false);
this->newMapSettings.header.setWeather(this->weatherNames.value(0, "0"));
this->newMapSettings.header.setType(this->mapTypes.value(0, "0"));
@ -2651,6 +2668,71 @@ bool Project::readRegionMapSections() {
return true;
}
bool Project::readRegionEntries()
{
this->regionData.clear();
this->regionIdNames.clear();
this->regionIdNamesSaveOrder.clear();
const QString defaultName = getEmptyRegionName();
const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_region_prefix);
QJsonDocument doc;
const QString filepath = projectConfig.getFilePath(ProjectFilePath::json_region_entries);
QString error;
if (!parser.tryParseJsonFile(&doc, filepath, &error)) {
logWarn(QString("Failed to read region entries from '%1': %2").arg(filepath).arg(error));
return false;
}
watchFile(filepath);
// Make sure the default name is present in the list.
if (!this->regionIdNames.contains(defaultName)) {
this->regionIdNames.append(defaultName);
}
QJsonObject regionsGlobalObj = doc.object();
QJsonArray regions = regionsGlobalObj.take("regions").toArray();
logInfo(QString("[REGION] Loading %1 regions from %2").arg(regions.size()).arg(filepath));
for (int i = 0; i < regions.size(); i++) {
QJsonObject regionObj = regions.at(i).toObject();
// For each region, "id" is the only required field. This is the field we use to display the region names in various drop-downs.
QString idField = "id";
const QString idName = ParseUtil::jsonToQString(regionObj.take(idField));
if (!idName.startsWith(requiredPrefix)) {
logWarn(QString("Ignoring data for region '%1' in '%2'. IDs must start with the prefix '%3'").arg(idName).arg(filepath).arg(requiredPrefix));
continue;
}
logInfo(QString("[REGION] Region ID: %1").arg(idName));
this->regionIdNames.append(idName);
this->regionIdNamesSaveOrder.append(idName);
// Regions in the json file have additional data for generating region name strings.
RegionData region;
if (regionObj.contains("name")) {
region.displayName = ParseUtil::jsonToQString(regionObj.take("name"));
} else {
QString suffix = idName.mid(requiredPrefix.length());
QString normalizedName = suffix.toLower();
if (!normalizedName.isEmpty()) {
normalizedName[0] = normalizedName[0].toUpper();
}
region.displayName = normalizedName;
}
if (regionObj.contains("text")) {
region.displayText = ParseUtil::jsonToQString(regionObj.take("text"));
} else {
region.displayText = idName.mid(requiredPrefix.length());
}
this->regionData.insert(idName, region);
}
Util::numericalModeSort(this->regionIdNames);
return true;
}
void Project::setRegionMapEntries(const QHash<QString, MapSectionEntry> &entries) {
for (auto it = this->locationData.keyBegin(); it != this->locationData.keyEnd(); it++) {
this->locationData[*it].map = entries.value(*it);
@ -2672,6 +2754,10 @@ QString Project::getEmptyMapsecName() {
return projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_empty);
}
QString Project::getEmptyRegionName() {
return projectConfig.getIdentifier(ProjectIdentifier::define_region_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_region_empty);
}
QString Project::getMapGroupPrefix() {
// We could expose this to users, but it's never enforced so it probably won't affect anyone.
return QStringLiteral("gMapGroup_");

View File

@ -16,6 +16,7 @@ MapHeaderForm::MapHeaderForm(QWidget *parent)
connect(ui->comboBox_Song, &QComboBox::currentTextChanged, this, &MapHeaderForm::onSongUpdated);
connect(ui->comboBox_Location, &QComboBox::currentTextChanged, this, &MapHeaderForm::onLocationChanged);
connect(ui->comboBox_Region, &QComboBox::currentTextChanged, this, &MapHeaderForm::onRegionChanged);
connect(ui->comboBox_Weather, &QComboBox::currentTextChanged, this, &MapHeaderForm::onWeatherChanged);
connect(ui->comboBox_Type, &QComboBox::currentTextChanged, this, &MapHeaderForm::onTypeChanged);
connect(ui->comboBox_BattleScene, &QComboBox::currentTextChanged, this, &MapHeaderForm::onBattleSceneChanged);
@ -69,6 +70,10 @@ void MapHeaderForm::setProject(Project * project, bool allowChanges) {
ui->comboBox_Location->clear();
ui->comboBox_Location->addItems(m_project->locationNames());
const QSignalBlocker b_Regions(ui->comboBox_Region);
ui->comboBox_Region->clear();
ui->comboBox_Region->addItems(m_project->regionIdNames);
// Hide config-specific settings
bool hasFlags = projectConfig.mapAllowFlagsEnabled;
@ -83,8 +88,13 @@ void MapHeaderForm::setProject(Project * project, bool allowChanges) {
ui->spinBox_FloorNumber->setVisible(floorNumEnabled);
ui->label_FloorNumber->setVisible(floorNumEnabled);
// Enable/disable region combo box
ui->label_Region->setVisible(m_project->usingMultiRegion);
ui->comboBox_Region->setVisible(m_project->usingMultiRegion);
// If the project changes any of the displayed data, update it accordingly.
connect(m_project, &Project::mapSectionIdNamesChanged, this, &MapHeaderForm::setLocations);
connect(m_project, &Project::regionIdNamesChanged, this, &MapHeaderForm::setRegions);
connect(m_project, &Project::mapSectionDisplayNameChanged, this, &MapHeaderForm::updateLocationName);
}
@ -96,6 +106,14 @@ void MapHeaderForm::setLocations(const QStringList &locations) {
ui->comboBox_Location->setTextItem(before);
}
void MapHeaderForm::setRegions(const QStringList &regions) {
const QSignalBlocker b(ui->comboBox_Region);
const QString before = ui->comboBox_Region->currentText();
ui->comboBox_Region->clear();
ui->comboBox_Region->addItems(regions);
ui->comboBox_Region->setTextItem(before);
}
// Assign a MapHeader that the form will keep in sync with the UI.
void MapHeaderForm::setHeader(MapHeader *header) {
if (m_header == header)
@ -114,6 +132,7 @@ void MapHeaderForm::setHeader(MapHeader *header) {
// If the MapHeader is changed externally (for example, with the scripting API) update the UI accordingly
connect(m_header, &MapHeader::songChanged, this, &MapHeaderForm::setSong);
connect(m_header, &MapHeader::locationChanged, this, &MapHeaderForm::setLocation);
connect(m_header, &MapHeader::regionChanged, this, &MapHeaderForm::setRegion);
connect(m_header, &MapHeader::requiresFlashChanged, this, &MapHeaderForm::setRequiresFlash);
connect(m_header, &MapHeader::weatherChanged, this, &MapHeaderForm::setWeather);
connect(m_header, &MapHeader::typeChanged, this, &MapHeaderForm::setType);
@ -136,6 +155,7 @@ void MapHeaderForm::clear() {
void MapHeaderForm::setHeaderData(const MapHeader &header) {
setSong(header.song());
setLocation(header.location());
setRegion(header.region());
setRequiresFlash(header.requiresFlash());
setWeather(header.weather());
setType(header.type());
@ -156,6 +176,7 @@ MapHeader MapHeaderForm::headerData() const {
MapHeader header;
header.setSong(song());
header.setLocation(location());
header.setRegion(region());
header.setRequiresFlash(requiresFlash());
header.setWeather(weather());
header.setType(type());
@ -175,6 +196,7 @@ void MapHeaderForm::updateLocationName() {
// Set data in UI
void MapHeaderForm::setSong(const QString &song) { setText(ui->comboBox_Song, song); }
void MapHeaderForm::setLocation(const QString &location) { setText(ui->comboBox_Location, location); }
void MapHeaderForm::setRegion(const QString &region) { setText(ui->comboBox_Region, region); }
void MapHeaderForm::setLocationName(const QString &locationName) { setText(ui->lineEdit_LocationName, locationName); }
void MapHeaderForm::setRequiresFlash(bool requiresFlash) { ui->checkBox_RequiresFlash->setChecked(requiresFlash); }
void MapHeaderForm::setWeather(const QString &weather) { setText(ui->comboBox_Weather, weather); }
@ -199,6 +221,7 @@ void MapHeaderForm::setText(QLineEdit *lineEdit, const QString &text) const {
// Read data from UI
QString MapHeaderForm::song() const { return ui->comboBox_Song->currentText(); }
QString MapHeaderForm::location() const { return ui->comboBox_Location->currentText(); }
QString MapHeaderForm::region() const { return ui->comboBox_Region->currentText(); }
QString MapHeaderForm::locationName() const { return ui->lineEdit_LocationName->text(); }
bool MapHeaderForm::requiresFlash() const { return ui->checkBox_RequiresFlash->isChecked(); }
QString MapHeaderForm::weather() const { return ui->comboBox_Weather->currentText(); }
@ -225,6 +248,7 @@ void MapHeaderForm::onLocationChanged(const QString &location) {
if (m_header) m_header->setLocation(location);
updateLocationName();
}
void MapHeaderForm::onRegionChanged(const QString &region) { if (m_header) m_header->setRegion(region); }
void MapHeaderForm::onLocationNameChanged(const QString &locationName) {
if (m_project && m_allowProjectChanges) {
// The location name is actually part of the project, not the map header.