diff --git a/CHANGELOG.md b/CHANGELOG.md index c6df5f22..43a04860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,11 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp The **"Breaking Changes"** listed below are changes that have been made in the decompilation projects (e.g. pokeemerald), which porymap requires in order to work properly. If porymap is used on a project that is not up-to-date with the breaking changes, then porymap will likely break or behave improperly. ## [Unreleased] +### Breaking Changes +- Proper support for pokefirered's clone objects was added, which requires the changes made in [pokefirered/#484](https://github.com/pret/pokefirered/pull/484). + ### Added +- Add Copy/Paste for metatiles in the Tileset Editor. - Add ability to set the opacity of the scripting overlay. - Add ability to get/set map header properties and read tile pixel data via the API. - Add button to copy the full metatile label to the clipboard in the Tileset Editor. @@ -16,12 +20,15 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - If an object event is inanimate, it will always render using its first frame. - Only log "Unknown custom script function" when a registered script function is not present in any script. - Unused metatile attribute bits that are set are preserved instead of being cleared. +- The wild encounter editor is automatically disabled if the encounter JSON data cannot be read +- Overhauled the region map editor, adding support for tilemaps, and significant customization. Also now supports pokefirered. ### Fixed - Fix cursor tile outline not updating at the end of a dragged selection. - Fix cursor tile and player view outlines exiting map bounds while painting. - Fix cursor tile and player view outlines not updating immediately when toggled in Collision view. - Fix selected space not updating while painting in Collision view. +- Fix the map music dropdown being empty when importing a map from Advance Map. ## [4.5.0] - 2021-12-26 ### Added diff --git a/docsrc/manual/editing-map-events.rst b/docsrc/manual/editing-map-events.rst index ca0b4e41..22e30983 100644 --- a/docsrc/manual/editing-map-events.rst +++ b/docsrc/manual/editing-map-events.rst @@ -60,7 +60,7 @@ Object events are typically used for NPCs (non-player-characters). More technic Object Event Properties Id - This is the local id of the object in the map. Some script values use this local id to specify object when using scripting commands such as `applymovement`. + This is the local id of the object in the map. Some script values use this local id to specify an object when using scripting commands such as `applymovement`. Sprite The sprite that is used by the object. @@ -83,8 +83,27 @@ Trainer Type Sight Radius or Berry Tree ID If the object is a trainer, this property control how many tiles the trainer can see to spot the player for battle. If the object is a berry tree, this specifies the global id of the berry tree. Each berry tree in the game has a unique berry tree id. -In Connection - Exclusive to pokefirered. Used to replace objects that are visible in a map's connection with their corresponding object on the connecting map. When checked, these objects will make odd use of other fields; its trainer type value will be the connecting map number, its Sight Radius / Berry Tree Id will be the connecting map group, and its z coordinate will be the object's local id on the connecting map. +Clone Object Events +------------------- + +Clone Object events are a special type of object that inherits its properties from another Object event. They are used in-game to load objects that are visible in the connecting area of adjacent maps. The targeted object to clone is specified by id and map name. If the targeted object does not exist, or it's also a clone, the sprite for graphics id 0 will be displayed instead. Double-clicking on a Clone Object will open the targeted map with the targeted object selected. This event type is exclusive to pokefirered projects; the code to process them does not exist in pokeemerald/pokeruby. + +.. figure:: images/editing-map-events/event-clone-object.png + :alt: Clone Object Event Properties + + Clone Object Event Properties + +Id + This is the local id of the object in the map. Some script values use this local id to specify an object when using scripting commands such as `applymovement`. + +Sprite + The sprite that is used by the object. Clone Objects inherit their sprite from the targeted object, so this cannot be edited. This field is not actually read by the game. + +Target Local Id + The local id of the object to be cloned. + +Target Map + The name of the map the object to be cloned is on. .. _event-warps: diff --git a/docsrc/manual/images/editing-map-events/event-clone-object.png b/docsrc/manual/images/editing-map-events/event-clone-object.png new file mode 100644 index 00000000..442ec822 Binary files /dev/null and b/docsrc/manual/images/editing-map-events/event-clone-object.png differ diff --git a/docsrc/manual/images/region-map-editor/new-configure-window.png b/docsrc/manual/images/region-map-editor/new-configure-window.png new file mode 100644 index 00000000..e6e7eea8 Binary files /dev/null and b/docsrc/manual/images/region-map-editor/new-configure-window.png differ diff --git a/docsrc/manual/images/region-map-editor/rme-config-properties.png b/docsrc/manual/images/region-map-editor/rme-config-properties.png new file mode 100644 index 00000000..1e1676ab Binary files /dev/null and b/docsrc/manual/images/region-map-editor/rme-config-properties.png differ diff --git a/docsrc/manual/images/region-map-editor/rme-main-window.png b/docsrc/manual/images/region-map-editor/rme-main-window.png new file mode 100644 index 00000000..8a622943 Binary files /dev/null and b/docsrc/manual/images/region-map-editor/rme-main-window.png differ diff --git a/docsrc/manual/images/region-map-editor/rme-new-entries-tab.png b/docsrc/manual/images/region-map-editor/rme-new-entries-tab.png new file mode 100644 index 00000000..14838bba Binary files /dev/null and b/docsrc/manual/images/region-map-editor/rme-new-entries-tab.png differ diff --git a/docsrc/manual/images/region-map-editor/rme-new-layout-tab.png b/docsrc/manual/images/region-map-editor/rme-new-layout-tab.png new file mode 100644 index 00000000..e2db56d2 Binary files /dev/null and b/docsrc/manual/images/region-map-editor/rme-new-layout-tab.png differ diff --git a/docsrc/manual/region-map-editor.rst b/docsrc/manual/region-map-editor.rst index 418812e3..e6629b96 100644 --- a/docsrc/manual/region-map-editor.rst +++ b/docsrc/manual/region-map-editor.rst @@ -2,24 +2,73 @@ The Region Map Editor ********************* -This is where you edit the region map for your game. To open the region map -editor, navigate to *Tools -> Region Map Editor* from porymap's main window. +This is where you edit the region maps for your game. You are able to edit the +background tilemap, the layout of map sections, and the array of map section entries +which determines the dimensions of each section. -.. note:: - The region map editor is currently only available for pokeemerald and pokeruby. +To open the region map editor, navigate to *Tools -> Region Map Editor* from +porymap's main window. There is also a keyboard shortcut which is by default ``Ctrl+M``. -When you first open the region map editor, your window will look like this: +When you first open the region map editor, you will need to configure porymap to +read your region map data. There are defaults for every base game project available +which should be sufficient for most users. -.. figure:: images/region-map-editor/rme-new-window.png - :scale: 75% +.. figure:: images/region-map-editor/new-configure-window.png :align: center + :width: 75% :alt: RME Window + Region Maps Configurator + +Porymap supports multiple region maps for any project. +By default, pokeemerald and pokefirered use this feature. +For a more custom region map, you can use the *Add Region Map...* button to +create a new region map configuration from scratch. You can also double-click on any existing +region map in the list to bring this window up to make changes. + +.. figure:: images/region-map-editor/rme-config-properties.png + :align: center + :width: 50% + :alt: RME Config Prop + + Region Map Properties Window + +This window has many options for users to define: + +.. csv-table:: + :header: Field,Explanation,Restrictions + :widths: 10, 30, 20 + + alias,something for porymap to distinguish between your maps,unique & valid json string + **Tilemap Properties**,, + format,format of the tiles,Plain *or* 4bpp *or* 8bpp + width,width *in tiles* of the tilemap,16 *or* 32 *or* 64 *or* 128 + height,height *in tiles* of the tilemap,valid corresponding height based on width + tileset path,the relative path to the tile image from project root,valid filepath string + tilemap path,the relative path to the tilemap binary from project root,valid filepath string + palette path,*optional* relative path to ``.pal`` file from project root,valid filepath string + **Layout Properties**,*can be unchecked for maps without layouts*, + format,the format to read the layout file,C array *or* binary + layout path,the relative path from project root to layout file,valid filepath string + width,the width of the layout,non-negative integer + left offset,the position on the tilemap which defines layout x=0,width + left offset < tilemap width + height,the height of the layout,non-negative integer + top offset,the position on the tilemap which defines layout y=0,height + top offset < tilemap height + +When you are finished configuring your region maps, you can select *OK*. This will +display the main editor window. + +.. figure:: images/region-map-editor/rme-main-window.png + :align: center + :width: 75% + :alt: RME Config Prop + Region Map Editor Window -This window is split vertically--the region map editing is done at the top, -while the zoomed-in city maps are edited at the bottom. You can use the -sliders to zoom in and out on each of the view panes. You will notice +This window has a combobox labeled "Region" which you can use to select the current +region map you want to edit. + +You will notice that there are three different tabs above the image of the region map (:ref:`Background Image `, :ref:`Map Layout `, @@ -39,24 +88,13 @@ are unhappy with what you have done, you can undo (``Ctrl+Z`` or *Edit -> Undo*) and redo (``Ctrl+Y`` or *Edit -> Redo*) your changes. Right-clicking on the map image will select the tile under your mouse from the tile selector. +If your tilemap format is not "Plain", then you can also select the palette, +h-flip, and v-flip of any tile you are painting with. + If you want to clear the background image, *Edit -> Clear Background Image* will set all tiles to the first tile in the tile selector. -.. figure:: images/region-map-editor/rme-painting-image.gif - :scale: 75% - :align: center - :alt: RME Paint - - Drawing on the Region Map Image - -It is likely that you will want to use your own tiles for your region map. You -can import a tile image by navigating to *Tools -> Import Region Map Image Tiles*. -There are strict requirements for your region map tile image. It must (1) be -indexed with a 256 color palette\*, (2) be composed of 8x8 pixel tiles, (3) have 256 -or fewer tiles. - -\* While the region map tile image requires a 256-color palette, the image only -uses the 32 colors beginning at index 112 in the palette. +You can use the sliders to zoom in and out on each of the view panes. .. _map-layout-tab: @@ -67,9 +105,9 @@ The layout tab is where map sections are placed on the region map. When the player looks at the region map in-game, the layout determines the map under the cursor. -.. figure:: images/region-map-editor/rme-layout-tab.png - :scale: 75% +.. figure:: images/region-map-editor/rme-new-layout-tab.png :align: center + :width: 75% :alt: RME Layout RME Layout Tab @@ -78,29 +116,15 @@ To modify the region map layout, select a position by clicking on the map image and higlighting a single square. The "Map Section" combobox will be populated with all of the map sections defined in ``include/constants/region_map_sections.h``. Select the map section you want to associate with the selected position on the -region map. To change the popup name of the map section when you enter the map, -type it into the "Map Name" box. The popup name is tied to the map section, so -each layout square with the same map section will share a name. +region map. -If you want to start from a blank layout, *Edit -> Clear Map Layout* will set -all layout squares to the value of ``MAPSEC_NONE``. +There are a couple of tools which make editing multiple layout squares simultaneously easier. -When adding new region map sections, the layout will be affected. This is -because the layout is stored as a binary file and uses the raw value of each -map section. In order to fix your layout to account for this, you can swap two -values for the entire layout with *Edit -> Swap*. +*Edit -> Clear Map Layout* will set all squares in the layout to ``MAPSEC_NONE``. -In this example, ``MAPSEC_NEW_MAPSEC`` is inserted before ``MAPSEC_NONE``, and -therefore the layout will link the original value of ``MAPSEC_NONE`` to the new -map section ``MAPSEC_NEW_MAPSEC``. Instances of ``MAPSEC_NEW_MAPSEC`` are swapped -with ``MAPSEC_NONE``. +*Edit -> Swap Layout Sections...* will exchange two layout sections with each other. -.. figure:: images/region-map-editor/rme-layout-swap.gif - :scale: 75% - :align: center - :alt: RME Swap - - Swapping Map Sections +*Edit -> Replace Layout Section...* will replace all instances of one section with another. The "Delete Square" button simply resets a single layout square to ``MAPSEC_NONE``. @@ -111,10 +135,10 @@ Map Entries Tab A region map entry is the area on the region map that spans an entire map section. This determines, for example, where the player's head appears on the region map -in-game. Entries are stored at ``src/data/region_map/region_map_entries.h``. +in-game. Entries are stored in ``src/data/region_map/region_map_sections.json``. -.. figure:: images/region-map-editor/rme-entries-tab.png - :scale: 75% +.. figure:: images/region-map-editor/rme-new-entries-tab.png + :width: 75% :align: center :alt: RME Entries @@ -126,29 +150,8 @@ You can also drag the entry around the map. The "x" and "y" values correspond t the position of the entry's top-left square on the region map. The "Dimensions" "width" and "height" spinboxes will change the size of the map entry. -City Maps ---------- -In the bottom half of the region map editor window, city maps can be edited. -You paint on this the same way you paint on the region map background image. - -.. figure:: images/region-map-editor/rme-painting-city.gif - :scale: 60% - :align: center - :alt: City Paint - - Drawing on the City Map - -To use custom tiles, there is a tile image importer under -*Tools -> Import City Map Image Tiles*. These images must (1) be indexed with a -16 color palette, (2) be made up of 8x8 pixel tiles, (3) have 256 or fewer tiles. - -You can add a new city map by pressing the |new-city-map-button| button. - -.. |new-city-map-button| - image:: images/region-map-editor/rme-new-city-map-button.png - -Currently, it is not possible to associate a city map to a region map location, -but that functionality will be added in a future update. +To change the popup name of the map section when you enter the map, type it +into the "Map Name" box. diff --git a/docsrc/manual/settings-and-options.rst b/docsrc/manual/settings-and-options.rst index a3e7d48e..5aec32fc 100644 --- a/docsrc/manual/settings-and-options.rst +++ b/docsrc/manual/settings-and-options.rst @@ -39,10 +39,10 @@ determined by this file. ``use_custom_border_size``, 0, project, yes, Whether to allow variable border sizes ``enable_event_weather_trigger``, 1 if not ``pokefirered``, project, yes, Allows adding Weather Trigger events ``enable_event_secret_base``, 1 if not ``pokefirered``, project, yes, Allows adding Secret Base events + ``enable_event_clone_object``, 1 if ``pokefirered``, project, yes, Allows adding Clone Object events ``enable_hidden_item_quantity``, 1 if ``pokefirered``, project, yes, Adds ``Quantity`` to Hidden Item events ``enable_hidden_item_requires_itemfinder``, 1 if ``pokefirered``, project, yes, Adds ``Requires Itemfinder`` to Hidden Item events ``enable_heal_location_respawn_data``, 1 if ``pokefirered``, project, yes, Adds ``Respawn Map`` and ``Respawn NPC`` to Heal Location events - ``enable_object_event_in_connection``, 1 if ``pokefirered``, project, yes, Adds ``In Connection`` to Object events ``enable_floor_number``, 1 if ``pokefirered``, project, yes, Adds ``Floor Number`` to map headers ``create_map_text_file``, 1 if not ``pokeemerald``, project, yes, A ``text.inc`` or ``text.pory`` file will be created for any new map ``enable_triple_layer_metatiles``, 0, project, yes, Enables triple-layer metatiles (See https://github.com/pret/pokeemerald/wiki/Triple-layer-metatiles) diff --git a/forms/regionmapeditor.ui b/forms/regionmapeditor.ui index 88856e07..68f31641 100644 --- a/forms/regionmapeditor.ui +++ b/forms/regionmapeditor.ui @@ -6,8 +6,8 @@ 0 0 - 829 - 644 + 957 + 535 @@ -15,98 +15,58 @@ - - - - - - - 30 - 50 - - - - - 30 - 160 - - - - Qt::StrongFocus - - - 10 - - - 100 - - - 30 - - - Qt::Vertical - - - QSlider::NoTicks - - - 1 - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 30 - 0 - - - - - 30 - 160 - - - - Qt::StrongFocus - - - 10 - - - 100 - - - 30 - - - Qt::Vertical - - - QSlider::NoTicks - - - 1 - - - - + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Region + + + + + + + QComboBox::AdjustToContents + + + + + + + Qt::Horizontal + + + + 169 + 20 + + + + + + - + @@ -158,12 +118,916 @@ + + + + + + Qt::Horizontal + + + false + + + + + 393 + 251 + + + + 0 + + + + Background Image + + + + + + + 1 + 1 + + + + true + + + + + 0 + 0 + 466 + 351 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 166 + 16 + + + + + + + + + 0 + 0 + + + + false + + + false + + + QAbstractScrollArea::AdjustIgnored + + + QGraphicsView::NoDrag + + + + + + + Qt::Vertical + + + + 16 + 166 + + + + + + + + Qt::Vertical + + + + 16 + 166 + + + + + + + + Qt::Horizontal + + + + 166 + 16 + + + + + + + + + + + + + Map Layout + + + + + + + 1 + 0 + + + + true + + + + + 0 + 0 + 466 + 351 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + false + + + false + + + QAbstractScrollArea::AdjustIgnored + + + QGraphicsView::NoDrag + + + + + + + Qt::Horizontal + + + + 166 + 16 + + + + + + + + Qt::Vertical + + + + 16 + 166 + + + + + + + + Qt::Horizontal + + + + 166 + 16 + + + + + + + + Qt::Vertical + + + + 16 + 166 + + + + + + + + + + + + + Map Entries + + + + + + + 1 + 0 + + + + true + + + + + 0 + 0 + 466 + 351 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + false + + + false + + + QAbstractScrollArea::AdjustIgnored + + + QGraphicsView::NoDrag + + + + + + + Qt::Horizontal + + + + 166 + 16 + + + + + + + + Qt::Vertical + + + + 16 + 166 + + + + + + + + Qt::Horizontal + + + + 166 + 16 + + + + + + + + Qt::Vertical + + + + 16 + 166 + + + + + + + + + + + + + + + 320 + 200 + + + + 0 + + + + + + + + 0 + 0 + + + + + 162 + 162 + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustIgnored + + + true + + + Qt::AlignHCenter|Qt::AlignTop + + + + true + + + + 8 + 0 + 278 + 342 + + + + + 0 + 0 + + + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + + 0 + 0 + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustIgnored + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + QFrame::Panel + + + QFrame::Sunken + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + h-flip + + + + + + + v-flip + + + + + + + 15 + + + + + + + palette + + + + + + + + + + + + + + false + + + + 0 + 0 + + + + + 300 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + 1 + + + + + + + + + Layer + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QLayout::SetNoConstraint + + + + + + 0 + 0 + + + + <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is display when the player enters it.</p></body></html> + + + true + + + + + + + Map Section + + + + + + + + + + 0 + 0 + + + + Delete Square + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + Layout Height + + + + + + + Layout Width + + + + + + + + + + + + + + + + true + + + + 0 + 0 + + + + + 300 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + 1 + + + + + + QLayout::SetNoConstraint + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + width + + + + + + + + + + + + + height + + + + + + + + + + true + + + + 0 + 0 + + + + <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is display when the player enters it.</p></body></html> + + + true + + + + + + + Dimensions + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + x + + + + + + + + + + + + + y + + + + + + + + + + Location + + + + + + + Map Section + + + + + + + Map Name + + + + + + + + + + + + Add + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + - + 30 - 0 + 50 @@ -195,1140 +1059,21 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + - - - - Qt::Vertical - - - false - - - - Qt::Horizontal - - - false - - - - - 393 - 251 - - - - 0 - - - - Background Image - - - - - - - 1 - 1 - - - - true - - - - - 0 - 0 - 350 - 225 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 166 - 16 - - - - - - - - - 0 - 0 - - - - false - - - false - - - QAbstractScrollArea::AdjustIgnored - - - QGraphicsView::NoDrag - - - - - - - Qt::Vertical - - - - 16 - 166 - - - - - - - - Qt::Vertical - - - - 16 - 166 - - - - - - - - Qt::Horizontal - - - - 166 - 16 - - - - - - - - - - - - - Map Layout - - - - - - - 1 - 0 - - - - true - - - - - 0 - 0 - 350 - 225 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - false - - - false - - - QAbstractScrollArea::AdjustIgnored - - - QGraphicsView::NoDrag - - - - - - - Qt::Horizontal - - - - 166 - 16 - - - - - - - - Qt::Vertical - - - - 16 - 166 - - - - - - - - Qt::Horizontal - - - - 166 - 16 - - - - - - - - Qt::Vertical - - - - 16 - 166 - - - - - - - - - - - - - Map Entries - - - - - - - 1 - 0 - - - - true - - - - - 0 - 0 - 350 - 225 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - false - - - false - - - QAbstractScrollArea::AdjustIgnored - - - QGraphicsView::NoDrag - - - - - - - Qt::Horizontal - - - - 166 - 16 - - - - - - - - Qt::Vertical - - - - 16 - 166 - - - - - - - - Qt::Horizontal - - - - 166 - 16 - - - - - - - - Qt::Vertical - - - - 16 - 166 - - - - - - - - - - - - - - - 320 - 200 - - - - 0 - - - - - - - - 0 - 0 - - - - - 162 - 162 - - - - Qt::ScrollBarAlwaysOn - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustIgnored - - - true - - - Qt::AlignHCenter|Qt::AlignTop - - - - true - - - - 8 - 0 - 278 - 262 - - - - - 0 - 0 - - - - - QLayout::SetDefaultConstraint - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - true - - - - 0 - 0 - - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustIgnored - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - false - - - - 0 - 0 - - - - - 300 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - 1 - - - - - - QLayout::SetNoConstraint - - - - - Map Section - - - - - - - - 0 - 0 - - - - <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is display when the player enters it.</p></body></html> - - - true - - - QComboBox::NoInsert - - - - - - - Map Name - - - - - - - true - - - - - - - City Map - - - - - - - <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example. it determines whether biking or running is allowed.</p></body></html> - - - true - - - - - - - - - Delete Square - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - true - - - - 0 - 0 - - - - - 300 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - 1 - - - - - - QLayout::SetNoConstraint - - - - - Map Section - - - - - - - true - - - - 0 - 0 - - - - <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is display when the player enters it.</p></body></html> - - - true - - - QComboBox::NoInsert - - - - - - - Location - - - - - - - Dimensions - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - x - - - - - - - - - - - - - y - - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - width - - - - - - - - - - - - - height - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - Qt::Horizontal - - - false - - - - - QLayout::SetDefaultConstraint - - - - - - - City Map: - - - - - - - - - - - 0 - 0 - - - - <html><head/><body><p>Add a new city map.</p></body></html> - - - - - - - :/icons/add.ico - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - 1 - 0 - - - - - 102 - 102 - - - - true - - - - - 0 - 0 - 441 - 230 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 166 - 16 - - - - - - - - Qt::Vertical - - - - 16 - 166 - - - - - - - - - 0 - 0 - - - - false - - - false - - - QAbstractScrollArea::AdjustIgnored - - - QGraphicsView::NoDrag - - - - - - - Qt::Vertical - - - - 16 - 166 - - - - - - - - Qt::Horizontal - - - - 166 - 16 - - - - - - - - - - - - - - 0 - 0 - - - - - 82 - 82 - - - - Qt::ScrollBarAlwaysOn - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustIgnored - - - true - - - Qt::AlignHCenter|Qt::AlignTop - - - - true - - - - 8 - 0 - 255 - 274 - - - - - 0 - 0 - - - - - QLayout::SetDefaultConstraint - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 40 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - true - - - - 0 - 0 - - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustIgnored - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - @@ -1336,7 +1081,7 @@ 0 0 - 829 + 957 22 @@ -1345,25 +1090,26 @@ File + + + Edit - - - + + + Tools - - @@ -1378,25 +1124,9 @@ Ctrl+S - - - Undo - - - Ctrl+Z - - - - - Redo - - - Ctrl+Y - - - Resize + Resize Tilemap Ctrl+R @@ -1409,7 +1139,7 @@ - Swap... + Swap Layout Sections... @@ -1432,7 +1162,34 @@ Import City Map Image Tiles... + + + Save All + + + + + Clear Map Entries + + + + + Update Config... + + + + + Replace Layout Section... + + + + + NoScrollComboBox + QWidget +
noscrollcombobox.h
+
+
diff --git a/forms/regionmappropertiesdialog.ui b/forms/regionmappropertiesdialog.ui new file mode 100644 index 00000000..80e7020a --- /dev/null +++ b/forms/regionmappropertiesdialog.ui @@ -0,0 +1,706 @@ + + + RegionMapPropertiesDialog + + + + 0 + 0 + 564 + 902 + + + + + 0 + 0 + + + + Region Map Properties + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + alias + + + + + + + A nickname for this region map that will differentiate it from others (should be unique). + + + + + + + color: #ff5c33 + + + + + + + + + + Tilemap Properties + + + + + + format + + + + + + + <html><head/><body><p>The tilemap format can be either:</p><p>1) Plain (tilemap is a list of 8-bit tile indexes into the tileset)</p><p>2) 4BPP (tilemap entries are 16 bits, with x/y-flip, and palette index for 16 16-color palettes)</p><p>3) 8BPP (tilemap entries are 16 bits, with x/y-flip, single 256-color palette)</p></body></html> + + + + plain + + + + + 4bpp + + + + + 8bpp + + + + + + + + color: #ff5c33 + + + + + + + + + + width + + + + + + + color: #ff5c33 + + + + + + + + + + height + + + + + + + + 0 + 0 + + + + + 75 + 0 + + + + The height of the tilemap + + + 255 + + + + + + + tileset path + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 350 + 0 + + + + <html><head/><body><p>Path to the tileset image (.png) relative to the project root.</p></body></html> + + + + + + + ... + + + + + + + + + + color: #ff5c33 + + + + + + + + + + tilemap path + + + + + + + <html><head/><body><p>Path to the tilemap binary relative to the project root.</p></body></html> + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + ... + + + + + + + + + + color: #ff5c33 + + + + + + + + + + palette path + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <html><head/><body><p>(optional) Path to the .pal JASC palette file</p></body></html> + + + + + + + ... + + + + + + + + + + color: #ff5c33 + + + + + + + + + + + 0 + 0 + + + + + 75 + 0 + + + + <html><head/><body><p>The width of the tilemap</p></body></html> + + + 255 + + + + + + + + + + Layout Properties + + + true + + + + + + format + + + + + + + <html><head/><body><p>The format of the layout file, can be a C array in a text file with &quot;MAPSEC_&quot;-prefixed constants, or a binary file where each byte is a value of some &quot;MAPSEC_&quot;-prefixed constant.</p></body></html> + + + + C array + + + + + binary + + + + + + + + color: #ff5c33 + + + + + + + + + + layout path + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <html><head/><body><p>The path to the layout file, relative to the project root.</p></body></html> + + + + + + + ... + + + + + + + + + + color: #ff5c33 + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + width + + + + + + + + 0 + 0 + + + + + 75 + 0 + + + + <html><head/><body><p>The layout width.</p></body></html> + + + 0 + + + 255 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + left offset + + + + + + + <html><head/><body><p>The offset from the left of the tilemap where the layout starts.</p><p>(ie, coordinate (0,0) in the layout would be (offset left, offset top) in the tilemap).</p></body></html> + + + 255 + + + + + + + Qt::Horizontal + + + + 85 + 2 + + + + + + + + + + + color: #ff5c33 + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + height + + + + + + + + 0 + 0 + + + + + 75 + 0 + + + + <html><head/><body><p>The layout height.</p></body></html> + + + 0 + + + 255 + + + + + + + Qt::Horizontal + + + + 87 + 13 + + + + + + + + top offset + + + + + + + <html><head/><body><p>The offset from the top of the tilemap where the layout starts.</p><p>(ie, coordinate (0,0) in the layout would be (offset left, offset top) in the tilemap).</p></body></html> + + + + + + + Qt::Horizontal + + + + 87 + 13 + + + + + + + + + + + color: #ff5c33 + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + RegionMapPropertiesDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + RegionMapPropertiesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index 6fcfaf27..e746ffba 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -567,6 +567,8 @@ Edit + + @@ -663,6 +665,22 @@ Import Secondary Metatiles from Advance Map 1.92... + + + Copy + + + Ctrl+C + + + + + Paste + + + Ctrl+V + + diff --git a/include/config.h b/include/config.h index a48c7e45..c5c0e66b 100644 --- a/include/config.h +++ b/include/config.h @@ -148,7 +148,7 @@ public: this->enableHiddenItemQuantity = false; this->enableHiddenItemRequiresItemfinder = false; this->enableHealLocationRespawnData = false; - this->enableObjectEventInConnection = false; + this->enableEventCloneObject = false; this->enableFloorNumber = false; this->createMapTextFile = false; this->enableTripleLayerMetatiles = false; @@ -178,8 +178,8 @@ public: bool getHiddenItemRequiresItemfinderEnabled(); void setHealLocationRespawnDataEnabled(bool enable); bool getHealLocationRespawnDataEnabled(); - void setObjectEventInConnectionEnabled(bool enable); - bool getObjectEventInConnectionEnabled(); + void setEventCloneObjectEnabled(bool enable); + bool getEventCloneObjectEnabled(); void setFloorNumberEnabled(bool enable); bool getFloorNumberEnabled(); void setCreateMapTextFileEnabled(bool enable); @@ -206,7 +206,7 @@ private: bool enableHiddenItemQuantity; bool enableHiddenItemRequiresItemfinder; bool enableHealLocationRespawnData; - bool enableObjectEventInConnection; + bool enableEventCloneObject; bool enableFloorNumber; bool createMapTextFile; bool enableTripleLayerMetatiles; diff --git a/include/core/event.h b/include/core/event.h index cce095d0..7a82d5cd 100644 --- a/include/core/event.h +++ b/include/core/event.h @@ -14,6 +14,7 @@ class EventType { public: static QString Object; + static QString CloneObject; static QString Warp; static QString Trigger; static QString WeatherTrigger; @@ -23,6 +24,16 @@ public: static QString HealLocation; }; +class EventGroup +{ +public: + static QString Object; + static QString Warp; + static QString Coord; + static QString Bg; + static QString Heal; +}; + class DraggablePixmapItem; class Project; class Event @@ -68,6 +79,7 @@ public: static Event* createNewEvent(QString, QString, Project*); static Event* createNewObjectEvent(Project*); + static Event* createNewCloneObjectEvent(Project*, QString); static Event* createNewWarpEvent(QString); static Event* createNewHealLocationEvent(QString); static Event* createNewTriggerEvent(Project*); @@ -75,8 +87,12 @@ public: static Event* createNewSignEvent(Project*); static Event* createNewHiddenItemEvent(Project*); static Event* createNewSecretBaseEvent(Project*); + static bool isValidType(QString event_type); + static QString typeToGroup(QString event_type); + static int getIndexOffset(QString group_type); OrderedJson::object buildObjectEventJSON(); + OrderedJson::object buildCloneObjectEventJSON(const QMap &); OrderedJson::object buildWarpEventJSON(const QMap &); OrderedJson::object buildTriggerEventJSON(); OrderedJson::object buildWeatherTriggerEventJSON(); @@ -86,7 +102,7 @@ public: void setPixmapFromSpritesheet(QImage, int, int, bool); int getPixelX(); int getPixelY(); - QMap getExpectedFields(); + QSet getExpectedFields(); void readCustomValues(QJsonObject values); void addCustomValuesTo(OrderedJson::object *obj); void setFrameFromMovement(QString); diff --git a/include/core/metatile.h b/include/core/metatile.h index 7c744dc4..ef8ce0eb 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -54,4 +54,18 @@ public: static int getAttributesSize(BaseGameVersion version); }; +inline bool operator==(const Metatile &a, const Metatile &b) { + return a.behavior == b.behavior && + a.layerType == b.layerType && + a.encounterType == b.encounterType && + a.terrainType == b.terrainType && + a.unusedAttributes == b.unusedAttributes && + a.label == b.label && + a.tiles == b.tiles; +} + +inline bool operator!=(const Metatile &a, const Metatile &b) { + return !(operator==(a, b)); +} + #endif // METATILE_H diff --git a/include/core/regionmap.h b/include/core/regionmap.h index 03059266..6116849a 100644 --- a/include/core/regionmap.h +++ b/include/core/regionmap.h @@ -3,7 +3,6 @@ #define REGIONMAP_H #include "map.h" -#include "project.h" #include "tilemaptileselector.h" #include "history.h" @@ -15,138 +14,183 @@ #include #include -enum RegionMapEditorBox { - BackgroundImage = 1, - CityMapImage = 2, -}; +#include +using std::shared_ptr; -class RegionMapHistoryItem { -public: - int which; - int mapWidth = 0; - int mapHeight = 0; - QVector tiles; - QString cityMap; - RegionMapHistoryItem(int which, QVector tiles, QString cityMap) { - this->which = which; - this->tiles = tiles; - this->cityMap = cityMap; - } - RegionMapHistoryItem(int which, QVector tiles, int width, int height) { - this->which = which; - this->tiles = tiles; - this->mapWidth = width; - this->mapHeight = height; - } - ~RegionMapHistoryItem() {} -}; +class Project; -class RegionMapEntry +struct LayoutSquare { -public: - RegionMapEntry()=default; - RegionMapEntry(int x_, int y_, int width_, int height_, QString name_) { - this-> x = x_; - this-> y = y_; - this-> width = width_; - this-> height = height_; - this-> name = name_; - } + LayoutSquare() : map_section("MAPSEC_NONE"), x(-1), y(-1), has_map(false) {} + QString map_section; int x; int y; - int width; - int height; - QString name; - - void setX(int); - void setY(int); - void setWidth(int); - void setHeight(int); + bool has_map; }; -class RegionMapSquare +struct MapSectionEntry { -public: - int x = -1; - int y = -1; - uint8_t tile_img_id = 0x00; - uint8_t secid = 0x00; - bool has_map = false; - bool has_city_map = false; - bool duplicated = false; - QString map_name; - QString mapsec; - QString city_map_name; + QString name = ""; + int x = 0; + int y = 0; + int width = 1; + int height = 1; + bool valid = false; }; -class RegionMap +class RegionMap : public QObject { + Q_OBJECT public: - RegionMap() = default; + RegionMap() = delete; + RegionMap(Project *); + + ~RegionMap() {} Project *project = nullptr; - QVector map_squares; - History history; + bool loadMapData(poryjson::Json); + bool loadTilemap(poryjson::Json); + bool loadLayout(poryjson::Json); + bool loadEntries(); - QMap sMapNamesMap; - QMap mapSecToMapEntry; - QVector sMapNames; - - const int padLeft = 1; - const int padRight = 3; - const int padTop = 2; - const int padBottom = 3; - - bool init(Project*); - - bool readBkgImgBin(); - bool readLayout(); + void setEntries(tsl::ordered_map *entries) { this->region_map_entries = entries; } + void setEntries(tsl::ordered_map entries) { *(this->region_map_entries) = entries; } + void clearEntries() { this->region_map_entries->clear(); } + MapSectionEntry getEntry(QString section); + void setEntry(QString section, MapSectionEntry entry); + void removeEntry(QString section); void save(); - void saveTileImages(); - void saveBkgImgBin(); + void saveTilemap(); void saveLayout(); - void saveOptions(int id, QString sec, QString name, int x, int y); - void resize(int width, int height); + void resizeTilemap(int width, int height, bool update = true); void resetSquare(int index); void clearLayout(); void clearImage(); - void replaceSectionId(unsigned oldId, unsigned newId); + void replaceSection(QString oldSection, QString newSection); + void swapSections(QString secA, QString secB); - int width(); - int height(); - QSize imgSize(); + unsigned getTileId(int index); + shared_ptr getTile(int index); unsigned getTileId(int x, int y); - int getMapSquareIndex(int x, int y); - QString pngPath(); - void setTemporaryPngPath(QString); - QString cityTilesPath(); - void setTemporaryCityTilesPath(QString); + shared_ptr getTile(int x, int y); + bool squareHasMap(int index); + QString squareMapSection(int index); + void setSquareMapSection(int index, QString section); + int squareX(int index); + int squareY(int index); + bool squareInLayout(int x, int y); + int firstLayoutIndex() { return this->offset_left + this->offset_top * this->tilemap_width; } - QVector getTiles(); - void setTiles(QVector tileIds); + void setTileId(int index, unsigned id); + void setTile(int index, TilemapTile &tile); + void setTileData(int index, unsigned id, bool hFlip, bool vFlip, int palette); + int getMapSquareIndex(int x, int y); + + QString getAlias() { return this->alias; } + poryjson::Json::object config(); + + QString palPath(); + QString pngPath(); + QString entriesPath() { return this->entries_path; } + + QByteArray getTilemap(); + void setTilemap(QByteArray newTilemap); + + QList getLayout(QString layer); + void setLayout(QString layer, QList layout); + + bool layoutEnabled() { return this->layout_format != LayoutFormat::None; } + + QMap> getAllLayouts(); + void setAllLayouts(QMap> newLayouts); + + QStringList getLayers() { return this->layout_layers; } + void setLayer(QString layer) { this->current_layer = layer; } + QString getLayer() { return this->current_layer; } QString fixCase(QString); + int padLeft() { return this->offset_left; } + int padTop() { return this->offset_top; } + int padRight() { return this->tilemap_width - this->layout_width - this->offset_left; } + int padBottom() { return this->tilemap_height - this->layout_height - this->offset_top; } + + int tilemapWidth() { return this->tilemap_width; } + int tilemapHeight() { return this->tilemap_height; } + int tilemapSize() { return this->tilemap_width * this->tilemap_height; } + int tilemapBytes(); + + int layoutWidth() { return this->layout_width; } + int layoutHeight() { return this->layout_height; } + void setLayoutDimensions(int width, int height, bool update = true); + + int tilemapToLayoutIndex(int index); + + TilemapFormat tilemapFormat() { return this->tilemap_format; } + + int pixelWidth() { return this->tilemap_width * 8; } + int pixelHeight() { return this->tilemap_height * 8; } + + QString fullPath(QString local); + + void commit(QUndoCommand *command); + QUndoStack editHistory; + + void undo(); + void redo(); + + void emitDisplay(); + +signals: + void mapNeedsDisplaying(); + private: - int layout_width_; - int layout_height_; - int img_width_; - int img_height_; + // TODO: defaults needed? + tsl::ordered_map *region_map_entries = nullptr; - QString region_map_png_path; - QString region_map_bin_path; - QString region_map_entries_path; - QString region_map_layout_bin_path; - QString city_map_tiles_path; + QString alias = ""; - bool region_map_png_needs_saving = false; - bool city_map_png_needs_saving = false; + int tilemap_width; + int tilemap_height; - int img_index_(int x, int y); - int layout_index_(int x, int y); + int layout_width; + int layout_height; + + int offset_left; + int offset_top; + + TilemapFormat tilemap_format; + + enum class LayoutFormat { None, Binary, CArray }; + LayoutFormat layout_format; + + QString tileset_path; + QString tilemap_path; + QString palette_path = ""; + + QString entries_path; + QString layout_path; + + QString layout_array_label; + bool layout_uses_layers = false; + QStringList layout_constants; + QString layout_qualifiers; + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QVector> tilemap; +#else + QList> tilemap; +#endif + + QStringList layout_layers; + QString current_layer; + QMap> layouts; + + int get_tilemap_index(int x, int y); + int get_layout_index(int x, int y); }; #endif // REGIONMAP_H diff --git a/include/core/regionmapeditcommands.h b/include/core/regionmapeditcommands.h new file mode 100644 index 00000000..16cd6486 --- /dev/null +++ b/include/core/regionmapeditcommands.h @@ -0,0 +1,169 @@ +#pragma once +#ifndef REGIONMAPEDITCOMMANDS_H +#define REGIONMAPEDITCOMMANDS_H + +#include "regionmap.h" + +#include +#include + +class RegionMap; + +enum RMCommandId { + ID_EditTilemap = 0, + ID_EditLayout, + ID_ResizeLayout, + ID_EditEntry, + ID_RemoveEntry, + ID_AddEntry, + ID_ResizeTilemap, + ID_ClearEntries, +}; + + +/// Implements a command to commit tilemap paint actions +class EditTilemap : public QUndoCommand { +public: + EditTilemap(RegionMap *map, QByteArray oldTilemap, QByteArray newTilemap, unsigned actionId, QUndoCommand *parent = nullptr); + + void undo() override; + void redo() override; + + bool mergeWith(const QUndoCommand *command) override; + int id() const override { return RMCommandId::ID_EditTilemap; } + +protected: + RegionMap *map; + + QByteArray oldTilemap; + QByteArray newTilemap; + + unsigned actionId; +}; + + +/// Edit region map section layout +class EditLayout : public QUndoCommand { +public: + EditLayout(RegionMap *map, QString layer, int index, QList oldLayout, QList newLayout, QUndoCommand *parent = nullptr); + + void undo() override; + void redo() override; + + bool mergeWith(const QUndoCommand *command) override; + int id() const override { return RMCommandId::ID_EditLayout; } + +private: + RegionMap *map; + + int index; + QString layer; + QList oldLayout; + QList newLayout; +}; + + +/// Edit Layout Dimensions +class ResizeLayout : public QUndoCommand { +public: + ResizeLayout(RegionMap *map, int oldWidth, int oldHeight, int newWidth, int newHeight, + QMap> oldLayouts, QMap> newLayouts, QUndoCommand *parent = nullptr); + + void undo() override; + void redo() override; + + bool mergeWith(const QUndoCommand *command) override; + int id() const override { return RMCommandId::ID_ResizeLayout; } + +private: + RegionMap *map; + + int oldWidth; + int oldHeight; + int newWidth; + int newHeight; + QMap> oldLayouts; + QMap> newLayouts; +}; + + +/// Edit Entry Value +class EditEntry : public QUndoCommand { +public: + EditEntry(RegionMap *map, QString section, MapSectionEntry oldEntry, MapSectionEntry newEntry, QUndoCommand *parent = nullptr); + + void undo() override; + void redo() override; + + bool mergeWith(const QUndoCommand *command) override; + int id() const override { return RMCommandId::ID_EditEntry; } + +protected: + RegionMap *map; + + QString section; + MapSectionEntry oldEntry; + MapSectionEntry newEntry; +}; + + +/// Remove Entry +class RemoveEntry : public EditEntry { +public: + RemoveEntry(RegionMap *map, QString section, MapSectionEntry oldEntry, MapSectionEntry newEntry, QUndoCommand *parent = nullptr); + + void undo() override; + void redo() override; + + int id() const override { return RMCommandId::ID_RemoveEntry; } +}; + + +/// Add Entry +class AddEntry : public EditEntry { +public: + AddEntry(RegionMap *map, QString section, MapSectionEntry oldEntry, MapSectionEntry newEntry, QUndoCommand *parent = nullptr); + + void undo() override; + void redo() override; + + int id() const override { return RMCommandId::ID_AddEntry; } +}; + + +/// ResizeTilemap +class ResizeTilemap : public EditTilemap { +public: + ResizeTilemap(RegionMap *map, QByteArray oldTilemap, QByteArray newTilemap, + int oldWidth, int oldHeight, int newWidth, int newHeight, QUndoCommand *parent = nullptr); + + void undo() override; + void redo() override; + + int id() const override { return RMCommandId::ID_ResizeTilemap; } + +private: + int oldWidth; + int oldHeight; + int newWidth; + int newHeight; +}; + + +/// ClearEntries +class ClearEntries : public QUndoCommand { +public: + ClearEntries(RegionMap *map, tsl::ordered_map, QUndoCommand *parent = nullptr); + + void undo() override; + void redo() override; + + bool mergeWith(const QUndoCommand *command) override { return false; } + int id() const override { return RMCommandId::ID_ClearEntries; } + +private: + RegionMap *map; + tsl::ordered_map entries; +}; + +#endif // REGIONMAPEDITCOMMANDS_H diff --git a/include/core/tile.h b/include/core/tile.h index b97c1775..1a862987 100644 --- a/include/core/tile.h +++ b/include/core/tile.h @@ -22,4 +22,15 @@ public: static int getIndexInTileset(int); }; +inline bool operator==(const Tile &a, const Tile &b) { + return a.tileId == b.tileId && + a.xflip == b.xflip && + a.yflip == b.yflip && + a.palette == b.palette; +} + +inline bool operator!=(const Tile &a, const Tile &b) { + return !(operator==(a, b)); +} + #endif // TILE_H diff --git a/include/editor.h b/include/editor.h index f17eb771..11efabda 100644 --- a/include/editor.h +++ b/include/editor.h @@ -210,7 +210,7 @@ signals: void selectedObjectsChanged(); void loadMapRequested(QString, QString); void wildMonDataChanged(); - void warpEventDoubleClicked(QString mapName, QString warpNum); + void warpEventDoubleClicked(QString, QString, QString); void currentMetatilesSelectionChanged(); void mapRulerStatusChanged(const QString &); void editedMapData(); diff --git a/include/mainwindow.h b/include/mainwindow.h index 043d421e..e330a20d 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -212,7 +212,7 @@ private slots: void on_action_Reload_Project_triggered(); void on_mapList_activated(const QModelIndex &index); void on_action_Save_Project_triggered(); - void openWarpMap(QString map_name, QString warp_num); + void openWarpMap(QString map_name, QString event_id, QString event_group); void duplicate(); void setClipboardData(poryjson::Json::object); @@ -345,7 +345,7 @@ private: QPointer regionMapEditor = nullptr; QPointer shortcutsEditor = nullptr; QPointer mapImageExporter = nullptr; - QPointer newmapprompt = nullptr; + QPointer newMapPrompt = nullptr; QPointer preferenceEditor = nullptr; FilterChildrenProxyModel *mapListProxyModel; QStandardItemModel *mapListModel; @@ -418,6 +418,7 @@ private: void initShortcuts(); void initExtraShortcuts(); void setProjectSpecificUIVisibility(); + void setWildEncountersUIEnabled(bool enabled); void loadUserSettings(); void applyMapListFilter(QString filterText); void restoreWindowState(); diff --git a/include/project.h b/include/project.h index ec1ffe6b..97730481 100644 --- a/include/project.h +++ b/include/project.h @@ -9,6 +9,7 @@ #include "wildmoninfo.h" #include "parseutil.h" #include "orderedjson.h" +#include "regionmap.h" #include #include @@ -60,7 +61,7 @@ public: QMap mapSectionNameToValue; QMap mapSectionValueToName; QMap eventGraphicsMap; - QStringList gfxNames; + QMap gfxDefines; QStringList songNames; QStringList itemNames; QStringList flagNames; @@ -244,6 +245,7 @@ signals: void reloadProject(); void uncheckMonitorFilesAction(); void mapCacheCleared(); + void disableWildEncountersUI(); }; #endif // PROJECT_H diff --git a/include/ui/neweventtoolbutton.h b/include/ui/neweventtoolbutton.h index 1168d447..fa2405df 100644 --- a/include/ui/neweventtoolbutton.h +++ b/include/ui/neweventtoolbutton.h @@ -11,6 +11,7 @@ public: explicit NewEventToolButton(QWidget *parent = nullptr); QString getSelectedEventType(); QAction *newObjectAction; + QAction *newCloneObjectAction; QAction *newWarpAction; QAction *newHealLocationAction; QAction *newTriggerAction; @@ -20,6 +21,7 @@ public: QAction *newSecretBaseAction; public slots: void newObject(); + void newCloneObject(); void newWarp(); void newHealLocation(); void newTrigger(); diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 1dee23f7..182364d2 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -6,6 +6,8 @@ #include "regionmaplayoutpixmapitem.h" #include "regionmapentriespixmapitem.h" #include "regionmap.h" +#include "orderedjson.h" +#include "project.h" #include #include @@ -24,14 +26,9 @@ public: explicit RegionMapEditor(QWidget *parent = 0, Project *pro = nullptr); ~RegionMapEditor(); - RegionMap *region_map; - - bool loadRegionMapData(); - bool loadCityMaps(); - void setCurrentSquareOptions(); + bool load(); void onRegionMapTileSelectorSelectedTileChanged(unsigned id); - void onCityMapTileSelectorSelectedTileChanged(unsigned id); void onRegionMapTileSelectorHoveredTileChanged(unsigned id); void onRegionMapTileSelectorHoveredTileCleared(); @@ -42,10 +39,7 @@ public: void onRegionMapEntriesSelectedTileChanged(QString) {}; void onRegionMapEntryDragged(int, int); - void undo(); - void redo(); - - void resize(int width, int height); + void resizeTilemap(int width, int height); QObjectList shortcutableObjects() const; @@ -56,14 +50,19 @@ private: Ui::RegionMapEditor *ui; Project *project; - History history; + RegionMap *region_map = nullptr; + tsl::ordered_map region_maps; - int currIndex; - unsigned selectedCityTile; - unsigned selectedImageTile; + poryjson::Json rmConfigJson; + + bool configSaved = false; + + QUndoGroup history; + + int currIndex = 0; + unsigned selectedImageTile = 0; QString activeEntry; - bool hasUnsavedChanges = false; bool cityMapFirstDraw = true; bool regionMapFirstDraw = true; bool entriesFirstDraw = true; @@ -72,19 +71,32 @@ private: double initialScale = 30.0; QGraphicsScene *scene_region_map_image = nullptr; - QGraphicsScene *scene_city_map_image = nullptr; QGraphicsScene *scene_region_map_layout = nullptr; QGraphicsScene *scene_region_map_entries = nullptr; QGraphicsScene *scene_region_map_tiles = nullptr; - QGraphicsScene *scene_city_map_tiles = nullptr; TilemapTileSelector *mapsquare_selector_item = nullptr; - TilemapTileSelector *city_map_selector_item = nullptr; RegionMapEntriesPixmapItem *region_map_entries_item = nullptr; RegionMapLayoutPixmapItem *region_map_layout_item = nullptr; RegionMapPixmapItem *region_map_item = nullptr; - CityMapPixmapItem *city_map_item = nullptr; + + bool reload(); + bool setup(); + void clear(); + + bool saveRegionMap(RegionMap *map); + void saveConfig(); + bool loadRegionMapEntries(); + bool saveRegionMapEntries(); + tsl::ordered_map region_map_entries; + + bool buildConfigDialog(); + poryjson::Json configRegionMapDialog(); + poryjson::Json buildDefaultJson(); + bool verifyConfig(poryjson::Json cfg); + + bool modified(); void initShortcuts(); void displayRegionMap(); @@ -94,47 +106,46 @@ private: void displayRegionMapLayoutOptions(); void updateRegionMapLayoutOptions(int index); void displayRegionMapTileSelector(); - void displayCityMapTileSelector(); - void displayCityMap(QString name); + void updateLayerDisplayed(); void displayRegionMapEntryOptions(); void updateRegionMapEntryOptions(QString); - void importTileImage(bool city = false); - - bool createCityMap(QString name); - bool tryInsertNewMapEntry(QString); + void setRegionMap(RegionMap *map); void restoreWindowState(); void closeEvent(QCloseEvent* event); private slots: void on_action_RegionMap_Save_triggered(); - void on_action_RegionMap_Undo_triggered(); - void on_action_RegionMap_Redo_triggered(); + void on_actionSave_All_triggered(); void on_action_RegionMap_Resize_triggered(); void on_action_RegionMap_ClearImage_triggered(); void on_action_RegionMap_ClearLayout_triggered(); + void on_action_RegionMap_ClearEntries_triggered(); void on_action_Swap_triggered(); - void on_action_Import_RegionMap_ImageTiles_triggered(); - void on_action_Import_CityMap_ImageTiles_triggered(); + void on_action_Replace_triggered(); + void on_action_Configure_triggered(); void on_tabWidget_Region_Map_currentChanged(int); void on_pushButton_RM_Options_delete_clicked(); void on_comboBox_RM_ConnectedMap_textActivated(const QString &); void on_comboBox_RM_Entry_MapSection_textActivated(const QString &); + void on_comboBox_regionSelector_textActivated(const QString &); + void on_comboBox_layoutLayer_textActivated(const QString &); void on_spinBox_RM_Entry_x_valueChanged(int); void on_spinBox_RM_Entry_y_valueChanged(int); void on_spinBox_RM_Entry_width_valueChanged(int); void on_spinBox_RM_Entry_height_valueChanged(int); - void on_pushButton_CityMap_add_clicked(); + void on_pushButton_entryActivate_clicked(); + void on_spinBox_RM_LayoutWidth_valueChanged(int); + void on_spinBox_RM_LayoutHeight_valueChanged(int); + void on_spinBox_tilePalette_valueChanged(int); + void on_checkBox_tileHFlip_stateChanged(int); + void on_checkBox_tileVFlip_stateChanged(int); void on_verticalSlider_Zoom_Map_Image_valueChanged(int); void on_verticalSlider_Zoom_Image_Tiles_valueChanged(int); - void on_verticalSlider_Zoom_City_Map_valueChanged(int); - void on_verticalSlider_Zoom_City_Tiles_valueChanged(int); - void on_comboBox_CityMap_picker_currentTextChanged(const QString &); void on_lineEdit_RM_MapName_textEdited(const QString &); void onHoveredRegionMapTileChanged(int x, int y); void onHoveredRegionMapTileCleared(); void mouseEvent_region_map(QGraphicsSceneMouseEvent *event, RegionMapPixmapItem *item); - void mouseEvent_city_map(QGraphicsSceneMouseEvent *event, CityMapPixmapItem *item); }; #endif // REGIONMAPEDITOR_H diff --git a/include/ui/regionmappixmapitem.h b/include/ui/regionmappixmapitem.h index 2f8bc9af..7740fa86 100644 --- a/include/ui/regionmappixmapitem.h +++ b/include/ui/regionmappixmapitem.h @@ -17,12 +17,14 @@ public: this->tile_selector = tile_selector; setAcceptHoverEvents(true); } - RegionMap *region_map; + RegionMap *region_map = nullptr; TilemapTileSelector *tile_selector; virtual void paint(QGraphicsSceneMouseEvent *); + virtual void fill(QGraphicsSceneMouseEvent *); virtual void select(QGraphicsSceneMouseEvent *); virtual void draw(); + void floodFill(int x, int y, std::shared_ptr oldTile, std::shared_ptr newTile); signals: void mouseEvent(QGraphicsSceneMouseEvent *, RegionMapPixmapItem *); diff --git a/include/ui/regionmappropertiesdialog.h b/include/ui/regionmappropertiesdialog.h new file mode 100644 index 00000000..6097d842 --- /dev/null +++ b/include/ui/regionmappropertiesdialog.h @@ -0,0 +1,47 @@ +#ifndef REGIONMAPPROPERTIESDIALOG_H +#define REGIONMAPPROPERTIESDIALOG_H + +#include "orderedjson.h" + +#include +#include + +class Project; + +namespace Ui { +class RegionMapPropertiesDialog; +} + +class RegionMapPropertiesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit RegionMapPropertiesDialog(QWidget *parent = nullptr); + ~RegionMapPropertiesDialog(); + + void setProject(Project *project) { this->project = project; } + + void setProperties(poryjson::Json object); + poryjson::Json saveToJson(); + + virtual void accept() override; + +private: + Ui::RegionMapPropertiesDialog *ui; + Project *project = nullptr; + + void hideMessages(); + + QString browse(QString filter, QFileDialog::FileMode mode); + +private slots: + void on_browse_tilesetImagePath_clicked(); + void on_browse_tilemapBinPath_clicked(); + void on_browse_tilemapPalettePath_clicked(); + void on_browse_layoutPath_clicked(); + + //void on_buttonBox_accepted(); +}; + +#endif // REGIONMAPPROPERTIESDIALOG_H diff --git a/include/ui/tilemaptileselector.h b/include/ui/tilemaptileselector.h index 95650e4b..f68a6974 100644 --- a/include/ui/tilemaptileselector.h +++ b/include/ui/tilemaptileselector.h @@ -1,29 +1,165 @@ +#pragma once #ifndef TILEMAPTILESELECTOR_H #define TILEMAPTILESELECTOR_H #include "selectablepixmapitem.h" +#include "paletteutil.h" + +#include +using std::shared_ptr; + +enum class TilemapFormat { Plain, BPP_4, BPP_8 }; + +class TilemapTile { + unsigned raw_ = 0; + unsigned id_ = 0; + bool hFlip_ = false; + bool vFlip_ = false; + int palette_ = 0; + +protected: + TilemapTile(unsigned raw, unsigned id, bool hFlip, bool vFlip, int palette) : raw_(raw), id_(id), hFlip_(hFlip), vFlip_(vFlip), palette_(palette) {} + virtual ~TilemapTile() {} + +public: + TilemapTile()=default; + + TilemapTile(TilemapTile &other) { + this->raw_ = other.raw(); + this->id_ = other.id(); + this->hFlip_ = other.hFlip(); + this->vFlip_ = other.vFlip(); + this->palette_ = other.palette(); + } + + TilemapTile &operator=(const TilemapTile &other) { + this->raw_ = other.raw(); + this->id_ = other.id(); + this->hFlip_ = other.hFlip(); + this->vFlip_ = other.vFlip(); + this->palette_ = other.palette(); + return *this; + } + + virtual void copy(TilemapTile &other) { + this->raw_ = other.raw(); + this->id_ = other.id(); + this->hFlip_ = other.hFlip(); + this->vFlip_ = other.vFlip(); + this->palette_ = other.palette(); + } + + virtual unsigned raw() const { return this->raw_; } + virtual unsigned id() const { return this->id_; } + virtual bool hFlip() const { return this->hFlip_; } + virtual bool vFlip() const { return this->vFlip_; } + virtual int palette() const { return this->palette_; } + + virtual void setId(unsigned id) { this->id_ = id; } + virtual void setHFlip(bool hFlip) { this->hFlip_ = hFlip; } + virtual void setVFlip(bool vFlip) { this->vFlip_ = vFlip; } + virtual void setPalette(int palette) { this->palette_ = palette; } + + bool operator==(const TilemapTile& other) { + return (this->raw() == other.raw()); + } + + virtual QString info() const { + return QString("Tile: 0x") + QString("%1 ").arg(this->id(), 4, 16, QChar('0')).toUpper(); + } +}; + +class PlainTile : public TilemapTile { +public: + PlainTile(unsigned raw) : TilemapTile(raw, raw, false, false, 0) {} + + ~PlainTile() {} + + virtual unsigned raw () const override { return id(); } +}; + +class BPP4Tile : public TilemapTile { +public: + BPP4Tile(unsigned raw) : TilemapTile( + raw, + raw & 0x3ff, // tileId + !!(raw & 0x0400), // hFlip + !!(raw & 0x0800), // vFlip + (raw & 0xf000) >> 12 // palette + ) {} + + ~BPP4Tile() {} + + virtual unsigned raw () const override { + return (id()) | (hFlip() << 10) | (vFlip() << 11) | (palette() << 12); + } + + virtual QString info() const override { + return TilemapTile::info() + QString("hFlip: %1 vFlip: %2 palette: %3").arg(this->hFlip()).arg(this->vFlip()).arg(this->palette()); + } +}; + +class BPP8Tile : public TilemapTile { +public: + BPP8Tile(unsigned raw) : TilemapTile( + raw, + raw & 0x3ff, // tileId + !!(raw & 0x0400), // hFlip + !!(raw & 0x0800), // vFlip + 0 // palette + ) {} + + ~BPP8Tile() {} + + virtual unsigned raw () const override { + return (id()) | (hFlip() << 10) | (vFlip() << 11); + } + + virtual QString info() const override { + return TilemapTile::info() + QString("hFlip: %1 vFlip: %2").arg(this->hFlip()).arg(this->vFlip()); + } +}; class TilemapTileSelector: public SelectablePixmapItem { Q_OBJECT public: - TilemapTileSelector(QPixmap pixmap_): SelectablePixmapItem(8, 8, 1, 1) { - this->tilemap = pixmap_; - this->setPixmap(this->tilemap); - this->numTilesWide = tilemap.width() / 8; + TilemapTileSelector(QString tilesetFilepath, TilemapFormat format, QString palFilepath): SelectablePixmapItem(8, 8, 1, 1) { + this->tileset = QImage(tilesetFilepath); + this->format = format; + bool err; + if (!palFilepath.isEmpty()) { + this->palette = PaletteUtil::parse(palFilepath, &err); + } + this->setPixmap(QPixmap::fromImage(this->tileset)); + this->numTilesWide = this->tileset.width() / 8; this->selectedTile = 0x00; setAcceptHoverEvents(true); } void draw(); - void select(unsigned tileId); - unsigned getSelectedTile(); + QImage setPalette(int index); int pixelWidth; int pixelHeight; + void select(unsigned tileId); unsigned selectedTile = 0; - QPixmap tilemap; - QImage tileImg(unsigned tileId); + void selectVFlip(bool hFlip) { this->tile_hFlip = hFlip; } + bool tile_hFlip = false; + + void selectHFlip(bool vFlip) { this->tile_vFlip = vFlip; } + bool tile_vFlip = false; + + void selectPalette(int palette) { + this->tile_palette = palette; + this->draw(); + } + int tile_palette = 0; + + QImage tileset; + TilemapFormat format = TilemapFormat::Plain; + QList palette; + QImage tileImg(shared_ptr tile); protected: void mousePressEvent(QGraphicsSceneMouseEvent*); diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 832246aa..301a56b1 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -41,7 +41,7 @@ public: void updateMap(Map *map); void updateTilesets(QString primaryTilsetLabel, QString secondaryTilesetLabel); bool selectMetatile(uint16_t metatileId); - uint16_t getSelectedMetatile(); + uint16_t getSelectedMetatileId(); void setMetatileLabel(QString label); QObjectList shortcutableObjects() const; @@ -105,6 +105,10 @@ private slots: void on_copyButton_metatileLabel_clicked(); + void on_actionCopy_triggered(); + + void on_actionPaste_triggered(); + private: void initUi(); void setMetatileBehaviors(); @@ -129,6 +133,7 @@ private: void closeEvent(QCloseEvent*); void countMetatileUsage(); void countTileUsage(); + bool replaceMetatile(uint16_t metatileId, Metatile * src); Ui::TilesetEditor *ui; History metatileHistory; @@ -139,6 +144,7 @@ private: Project *project = nullptr; Map *map = nullptr; Metatile *metatile = nullptr; + Metatile *copiedMetatile = nullptr; int paletteId; bool tileXFlip; bool tileYFlip; diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index 432241e7..e306727d 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -13,7 +13,7 @@ public: void draw(); bool select(uint16_t metatileId); void setTilesets(Tileset*, Tileset*, bool draw = true); - uint16_t getSelectedMetatile(); + uint16_t getSelectedMetatileId(); void updateSelectedMetatile(); QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId); diff --git a/porymap.pro b/porymap.pro index b73c4ee6..89808d2c 100644 --- a/porymap.pro +++ b/porymap.pro @@ -12,7 +12,7 @@ TARGET = porymap TEMPLATE = app RC_ICONS = resources/icons/porymap-icon-2.ico ICON = resources/icons/porymap.icns -QMAKE_CXXFLAGS += -std=c++11 -Wall +QMAKE_CXXFLAGS += -std=c++17 -Wall QMAKE_TARGET_BUNDLE_PREFIX = com.pret SOURCES += src/core/block.cpp \ @@ -33,6 +33,7 @@ SOURCES += src/core/block.cpp \ src/core/wildmoninfo.cpp \ src/core/editcommands.cpp \ src/lib/orderedjson.cpp \ + src/core/regionmapeditcommands.cpp \ src/mainwindow_scriptapi.cpp \ src/ui/aboutporymap.cpp \ src/ui/draggablepixmapitem.cpp \ @@ -77,6 +78,7 @@ SOURCES += src/core/block.cpp \ src/ui/shortcutseditor.cpp \ src/ui/multikeyedit.cpp \ src/ui/preferenceeditor.cpp \ + src/ui/regionmappropertiesdialog.cpp \ src/config.cpp \ src/editor.cpp \ src/main.cpp \ @@ -105,6 +107,7 @@ HEADERS += include/core/block.h \ include/core/regionmap.h \ include/core/wildmoninfo.h \ include/core/editcommands.h \ + include/core/regionmapeditcommands.h \ include/lib/orderedmap.h \ include/lib/orderedjson.h \ include/ui/aboutporymap.h \ @@ -151,6 +154,7 @@ HEADERS += include/core/block.h \ include/ui/shortcutseditor.h \ include/ui/multikeyedit.h \ include/ui/preferenceeditor.h \ + include/ui/regionmappropertiesdialog.h \ include/config.h \ include/editor.h \ include/mainwindow.h \ @@ -169,11 +173,13 @@ FORMS += forms/mainwindow.ui \ forms/newtilesetdialog.ui \ forms/mapimageexporter.ui \ forms/shortcutseditor.ui \ - forms/preferenceeditor.ui + forms/preferenceeditor.ui \ + forms/regionmappropertiesdialog.ui RESOURCES += \ resources/images.qrc \ - resources/themes.qrc + resources/themes.qrc \ + resources/text.qrc INCLUDEPATH += include INCLUDEPATH += include/core diff --git a/resources/icons/empty_cross_cursor.ico b/resources/icons/empty_cross_cursor.ico new file mode 100644 index 00000000..01b03b54 Binary files /dev/null and b/resources/icons/empty_cross_cursor.ico differ diff --git a/resources/icons/pencil_bw.ico b/resources/icons/pencil_bw.ico new file mode 100644 index 00000000..7d79243b Binary files /dev/null and b/resources/icons/pencil_bw.ico differ diff --git a/resources/images/Entities_16x16.png b/resources/images/Entities_16x16.png index 2d35d31d..6c18122a 100644 Binary files a/resources/images/Entities_16x16.png and b/resources/images/Entities_16x16.png differ diff --git a/resources/text.qrc b/resources/text.qrc new file mode 100644 index 00000000..211598e4 --- /dev/null +++ b/resources/text.qrc @@ -0,0 +1,7 @@ + + + text/region_map_default_emerald.json + text/region_map_default_firered.json + text/region_map_default_ruby.json + + diff --git a/resources/text/region_map_default_emerald.json b/resources/text/region_map_default_emerald.json new file mode 100644 index 00000000..aafd7312 --- /dev/null +++ b/resources/text/region_map_default_emerald.json @@ -0,0 +1,33 @@ +{ + "region_maps": [ + { + "alias": "hoenn", + "tilemap": { + "width": 64, + "height": 64, + "format": "plain", + "tileset_path": "graphics/pokenav/region_map.png", + "tilemap_path": "graphics/pokenav/region_map_map.bin" + }, + "layout": { + "width": 28, + "height": 15, + "offset_left": 1, + "offset_top": 2, + "format": "C array", + "path": "src/data/region_map/region_map_layout.h" + } + }, + { + "alias": "pokedex area screen", + "tilemap": { + "width": 32, + "height": 32, + "format": "8bpp", + "tileset_path": "graphics/pokedex/region_map.png", + "tilemap_path": "graphics/pokedex/region_map.bin" + }, + "layout": null + } + ] +} diff --git a/resources/text/region_map_default_firered.json b/resources/text/region_map_default_firered.json new file mode 100644 index 00000000..bf1cb179 --- /dev/null +++ b/resources/text/region_map_default_firered.json @@ -0,0 +1,80 @@ +{ + "region_maps": [ + { + "alias": "kanto", + "tilemap": { + "width": 30, + "height": 20, + "format": "4bpp", + "tileset_path": "graphics/region_map/region_map.png", + "tilemap_path": "graphics/region_map/kanto.bin", + "palette": "graphics/region_map/region_map.pal" + }, + "layout": { + "width": 22, + "height": 15, + "offset_left": 4, + "offset_top": 4, + "format": "C array", + "path": "src/data/region_map/region_map_layout_kanto.h" + } + }, + { + "alias": "sevii_123", + "tilemap": { + "width": 30, + "height": 20, + "format": "4bpp", + "tileset_path": "graphics/region_map/region_map.png", + "tilemap_path": "graphics/region_map/sevii_123.bin", + "palette": "graphics/region_map/region_map.pal" + }, + "layout": { + "width": 22, + "height": 15, + "offset_left": 4, + "offset_top": 4, + "format": "C array", + "path": "src/data/region_map/region_map_layout_sevii_123.h" + } + }, + { + "alias": "sevii_45", + "tilemap": { + "width": 30, + "height": 20, + "format": "4bpp", + "tileset_path": "graphics/region_map/region_map.png", + "tilemap_path": "graphics/region_map/sevii_45.bin", + "palette": "graphics/region_map/region_map.pal" + }, + "layout": { + "width": 22, + "height": 15, + "offset_left": 4, + "offset_top": 4, + "format": "C array", + "path": "src/data/region_map/region_map_layout_sevii_45.h" + } + }, + { + "alias": "sevii_67", + "tilemap": { + "width": 30, + "height": 20, + "format": "4bpp", + "tileset_path": "graphics/region_map/region_map.png", + "tilemap_path": "graphics/region_map/sevii_67.bin", + "palette": "graphics/region_map/region_map.pal" + }, + "layout": { + "width": 22, + "height": 15, + "offset_left": 4, + "offset_top": 4, + "format": "C array", + "path": "src/data/region_map/region_map_layout_sevii_67.h" + } + } + ] +} diff --git a/resources/text/region_map_default_ruby.json b/resources/text/region_map_default_ruby.json new file mode 100644 index 00000000..4fcaa555 --- /dev/null +++ b/resources/text/region_map_default_ruby.json @@ -0,0 +1,22 @@ +{ + "region_maps": [ + { + "alias": "hoenn", + "tilemap": { + "width": 64, + "height": 64, + "format": "plain", + "tileset_path": "graphics/pokenav/region_map.png", + "tilemap_path": "graphics/pokenav/region_map_map.bin" + }, + "layout": { + "width": 28, + "height": 15, + "offset_left": 1, + "offset_top": 2, + "format": "C array", + "path": "src/data/region_map/region_map_layout.h" + } + } + ] +} diff --git a/src/config.cpp b/src/config.cpp index f9597b4e..32cf00e7 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -522,11 +522,11 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { if (!ok) { logWarn(QString("Invalid config value for enable_heal_location_respawn_data: '%1'. Must be 0 or 1.").arg(value)); } - } else if (key == "enable_object_event_in_connection") { + } else if (key == "enable_event_clone_object") { bool ok; - this->enableObjectEventInConnection = value.toInt(&ok); + this->enableEventCloneObject = value.toInt(&ok); if (!ok) { - logWarn(QString("Invalid config value for enable_object_event_in_connection: '%1'. Must be 0 or 1.").arg(value)); + logWarn(QString("Invalid config value for enable_event_clone_object: '%1'. Must be 0 or 1.").arg(value)); } } else if (key == "enable_floor_number") { bool ok; @@ -570,7 +570,7 @@ void ProjectConfig::setUnreadKeys() { if (!readKeys.contains("enable_hidden_item_quantity")) this->enableHiddenItemQuantity = isPokefirered; if (!readKeys.contains("enable_hidden_item_requires_itemfinder")) this->enableHiddenItemRequiresItemfinder = isPokefirered; if (!readKeys.contains("enable_heal_location_respawn_data")) this->enableHealLocationRespawnData = isPokefirered; - if (!readKeys.contains("enable_object_event_in_connection")) this->enableObjectEventInConnection = isPokefirered; + if (!readKeys.contains("enable_event_clone_object")) this->enableEventCloneObject = isPokefirered; if (!readKeys.contains("enable_floor_number")) this->enableFloorNumber = isPokefirered; if (!readKeys.contains("create_map_text_file")) this->createMapTextFile = (this->baseGameVersion != BaseGameVersion::pokeemerald); } @@ -587,7 +587,7 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("enable_hidden_item_quantity", QString::number(this->enableHiddenItemQuantity)); map.insert("enable_hidden_item_requires_itemfinder", QString::number(this->enableHiddenItemRequiresItemfinder)); map.insert("enable_heal_location_respawn_data", QString::number(this->enableHealLocationRespawnData)); - map.insert("enable_object_event_in_connection", QString::number(this->enableObjectEventInConnection)); + map.insert("enable_event_clone_object", QString::number(this->enableEventCloneObject)); map.insert("enable_floor_number", QString::number(this->enableFloorNumber)); map.insert("create_map_text_file", QString::number(this->createMapTextFile)); map.insert("enable_triple_layer_metatiles", QString::number(this->enableTripleLayerMetatiles)); @@ -628,7 +628,7 @@ void ProjectConfig::onNewConfigFileCreated() { this->enableHiddenItemQuantity = isPokefirered; this->enableHiddenItemRequiresItemfinder = isPokefirered; this->enableHealLocationRespawnData = isPokefirered; - this->enableObjectEventInConnection = isPokefirered; + this->enableEventCloneObject = isPokefirered; this->enableFloorNumber = isPokefirered; this->createMapTextFile = (this->baseGameVersion != BaseGameVersion::pokeemerald); this->useEncounterJson = true; @@ -739,13 +739,13 @@ bool ProjectConfig::getHealLocationRespawnDataEnabled() { return this->enableHealLocationRespawnData; } -void ProjectConfig::setObjectEventInConnectionEnabled(bool enable) { - this->enableObjectEventInConnection = enable; +void ProjectConfig::setEventCloneObjectEnabled(bool enable) { + this->enableEventCloneObject = enable; this->save(); } -bool ProjectConfig::getObjectEventInConnectionEnabled() { - return this->enableObjectEventInConnection; +bool ProjectConfig::getEventCloneObjectEnabled() { + return this->enableEventCloneObject; } void ProjectConfig::setFloorNumberEnabled(bool enable) { diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index b4c96883..f755da6e 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -9,18 +9,16 @@ int getEventTypeMask(QList events) { int eventTypeMask = 0; for (auto event : events) { - if (event->get("event_type") == EventType::Object) { + QString groupType = event->get("event_group_type"); + if (groupType == EventGroup::Object) { eventTypeMask |= IDMask_EventType_Object; - } else if (event->get("event_type") == EventType::Warp) { + } else if (groupType == EventGroup::Warp) { eventTypeMask |= IDMask_EventType_Warp; - } else if (event->get("event_type") == EventType::Trigger || - event->get("event_type") == EventType::WeatherTrigger) { + } else if (groupType == EventGroup::Coord) { eventTypeMask |= IDMask_EventType_Trigger; - } else if (event->get("event_type") == EventType::Sign || - event->get("event_type") == EventType::HiddenItem || - event->get("event_type") == EventType::SecretBase) { + } else if (groupType == EventGroup::Bg) { eventTypeMask |= IDMask_EventType_BG; - } else if (event->get("event_type") == EventType::HealLocation) { + } else if (groupType == EventGroup::Heal) { eventTypeMask |= IDMask_EventType_Heal; } } diff --git a/src/core/event.cpp b/src/core/event.cpp index 75656fc4..df247559 100644 --- a/src/core/event.cpp +++ b/src/core/event.cpp @@ -3,14 +3,33 @@ #include "project.h" #include "config.h" +QString EventGroup::Object = "object_event_group"; +QString EventGroup::Warp = "warp_event_group"; +QString EventGroup::Heal = "heal_event_group"; +QString EventGroup::Coord = "coord_event_group"; +QString EventGroup::Bg = "bg_event_group"; + QString EventType::Object = "event_object"; +QString EventType::CloneObject = "event_clone_object"; QString EventType::Warp = "event_warp"; QString EventType::Trigger = "event_trigger"; QString EventType::WeatherTrigger = "event_weather_trigger"; QString EventType::Sign = "event_sign"; QString EventType::HiddenItem = "event_hidden_item"; QString EventType::SecretBase = "event_secret_base"; -QString EventType::HealLocation = "event_heal_location"; +QString EventType::HealLocation = "event_healspot"; + +const QMap EventTypeTable = { + {EventType::Object, EventGroup::Object}, + {EventType::CloneObject, EventGroup::Object}, + {EventType::Warp, EventGroup::Warp}, + {EventType::Trigger, EventGroup::Coord}, + {EventType::WeatherTrigger, EventGroup::Coord}, + {EventType::Sign, EventGroup::Bg}, + {EventType::HiddenItem, EventGroup::Bg}, + {EventType::SecretBase, EventGroup::Bg}, + {EventType::HealLocation, EventGroup::Heal}, +}; Event::Event() : spriteWidth(16), @@ -41,6 +60,8 @@ Event* Event::createNewEvent(QString event_type, QString map_name, Project *proj if (event_type == EventType::Object) { event = createNewObjectEvent(project); event->setFrameFromMovement(event->get("movement_type")); + } else if (event_type == EventType::CloneObject) { + event = createNewCloneObjectEvent(project, map_name); } else if (event_type == EventType::Warp) { event = createNewWarpEvent(map_name); } else if (event_type == EventType::HealLocation) { @@ -60,6 +81,8 @@ Event* Event::createNewEvent(QString event_type, QString map_name, Project *proj event = new Event; } + event->put("event_type", event_type); + event->put("event_group_type", typeToGroup(event_type)); event->setX(0); event->setY(0); return event; @@ -68,13 +91,8 @@ Event* Event::createNewEvent(QString event_type, QString map_name, Project *proj Event* Event::createNewObjectEvent(Project *project) { Event *event = new Event; - event->put("event_group_type", "object_event_group"); - event->put("event_type", EventType::Object); - event->put("sprite", project->gfxNames.first()); + event->put("sprite", project->gfxDefines.keys().first()); event->put("movement_type", project->movementTypes.first()); - if (projectConfig.getObjectEventInConnectionEnabled()) { - event->put("in_connection", false); - } event->put("radius_x", 0); event->put("radius_y", 0); event->put("script_label", "NULL"); @@ -86,11 +104,18 @@ Event* Event::createNewObjectEvent(Project *project) return event; } +Event* Event::createNewCloneObjectEvent(Project *project, QString map_name) +{ + Event *event = new Event; + event->put("sprite", project->gfxDefines.keys().first()); + event->put("target_local_id", 1); + event->put("target_map", map_name); + return event; +} + Event* Event::createNewWarpEvent(QString map_name) { Event *event = new Event; - event->put("event_group_type", "warp_event_group"); - event->put("event_type", EventType::Warp); event->put("destination_warp", 0); event->put("destination_map_name", map_name); event->put("elevation", 0); @@ -100,8 +125,6 @@ Event* Event::createNewWarpEvent(QString map_name) Event* Event::createNewHealLocationEvent(QString map_name) { Event *event = new Event; - event->put("event_group_type", "heal_event_group"); - event->put("event_type", EventType::HealLocation); event->put("loc_name", QString(Map::mapConstantFromName(map_name)).remove(0,4)); event->put("id_name", map_name.replace(QRegularExpression("([a-z])([A-Z])"), "\\1_\\2").toUpper()); event->put("elevation", 3); @@ -115,8 +138,6 @@ Event* Event::createNewHealLocationEvent(QString map_name) Event* Event::createNewTriggerEvent(Project *project) { Event *event = new Event; - event->put("event_group_type", "coord_event_group"); - event->put("event_type", EventType::Trigger); event->put("script_label", "NULL"); event->put("script_var", project->varNames.first()); event->put("script_var_value", "0"); @@ -127,8 +148,6 @@ Event* Event::createNewTriggerEvent(Project *project) Event* Event::createNewWeatherTriggerEvent(Project *project) { Event *event = new Event; - event->put("event_group_type", "coord_event_group"); - event->put("event_type", EventType::WeatherTrigger); event->put("weather", project->coordEventWeatherNames.first()); event->put("elevation", 0); return event; @@ -137,8 +156,6 @@ Event* Event::createNewWeatherTriggerEvent(Project *project) Event* Event::createNewSignEvent(Project *project) { Event *event = new Event; - event->put("event_group_type", "bg_event_group"); - event->put("event_type", EventType::Sign); event->put("player_facing_direction", project->bgEventFacingDirections.first()); event->put("script_label", "NULL"); event->put("elevation", 0); @@ -148,8 +165,6 @@ Event* Event::createNewSignEvent(Project *project) Event* Event::createNewHiddenItemEvent(Project *project) { Event *event = new Event; - event->put("event_group_type", "bg_event_group"); - event->put("event_type", EventType::HiddenItem); event->put("item", project->itemNames.first()); event->put("flag", project->flagNames.first()); event->put("elevation", 3); @@ -165,8 +180,6 @@ Event* Event::createNewHiddenItemEvent(Project *project) Event* Event::createNewSecretBaseEvent(Project *project) { Event *event = new Event; - event->put("event_group_type", "bg_event_group"); - event->put("event_type", EventType::SecretBase); event->put("secret_base_id", project->secretBaseIds.first()); event->put("elevation", 0); return event; @@ -182,81 +195,76 @@ int Event::getPixelY() return (this->y() * 16) - qMax(0, this->spriteHeight - 16); } -static QMap expectedObjectFields { - {"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}, +const QSet expectedObjectFields = { + "graphics_id", + "elevation", + "movement_type", + "movement_range_x", + "movement_range_y", + "trainer_type", + "trainer_sight_or_berry_tree_id", + "script", + "flag", }; -static QMap expectedWarpFields { - {"x", true}, - {"y", true}, - {"elevation", true}, - {"dest_map", true}, - {"dest_warp_id", true}, +const QSet expectedCloneObjectFields = { + "type", + "graphics_id", + "target_local_id", + "target_map", }; -static QMap expectedTriggerFields { - {"type", true}, - {"x", true}, - {"y", true}, - {"elevation", true}, - {"var", true}, - {"var_value", true}, - {"script", true}, +const QSet expectedWarpFields = { + "elevation", + "dest_map", + "dest_warp_id", }; -static QMap expectedWeatherTriggerFields { - {"type", true}, - {"x", true}, - {"y", true}, - {"elevation", true}, - {"weather", true}, +const QSet expectedTriggerFields = { + "type", + "elevation", + "var", + "var_value", + "script", }; -static QMap expectedSignFields { - {"type", true}, - {"x", true}, - {"y", true}, - {"elevation", true}, - {"player_facing_dir", true}, - {"script", true}, +const QSet expectedWeatherTriggerFields = { + "type", + "elevation", + "weather", }; -static QMap expectedHiddenItemFields { - {"type", true}, - {"x", true}, - {"y", true}, - {"elevation", true}, - {"item", true}, - {"flag", true}, +const QSet expectedSignFields = { + "type", + "elevation", + "player_facing_dir", + "script", }; -static QMap expectedSecretBaseFields { - {"type", true}, - {"x", true}, - {"y", true}, - {"elevation", true}, - {"secret_base_id", true}, +const QSet expectedHiddenItemFields = { + "type", + "elevation", + "item", + "flag", }; -QMap Event::getExpectedFields() +const QSet expectedSecretBaseFields = { + "type", + "elevation", + "secret_base_id", +}; + +QSet Event::getExpectedFields() { QString type = this->get("event_type"); - QMap expectedFields = QMap(); + QSet expectedFields = QSet(); if (type == EventType::Object) { expectedFields = expectedObjectFields; - if (projectConfig.getObjectEventInConnectionEnabled()) { - expectedFields.insert("in_connection", true); + if (projectConfig.getEventCloneObjectEnabled()) { + expectedFields.insert("type"); } + } else if (type == EventType::CloneObject) { + expectedFields = expectedCloneObjectFields; } else if (type == EventType::Warp) { expectedFields = expectedWarpFields; } else if (type == EventType::Trigger) { @@ -268,23 +276,24 @@ QMap Event::getExpectedFields() } else if (type == EventType::HiddenItem) { expectedFields = expectedHiddenItemFields; if (projectConfig.getHiddenItemQuantityEnabled()) { - expectedFields.insert("quantity", true); + expectedFields.insert("quantity"); } if (projectConfig.getHiddenItemRequiresItemfinderEnabled()) { - expectedFields.insert("underfoot", true); + expectedFields.insert("underfoot"); } } else if (type == EventType::SecretBase) { expectedFields = expectedSecretBaseFields; } + expectedFields << "x" << "y"; return expectedFields; }; void Event::readCustomValues(QJsonObject values) { this->customValues.clear(); - QMap expectedValues = this->getExpectedFields(); + QSet expectedFields = this->getExpectedFields(); for (QString key : values.keys()) { - if (!expectedValues.contains(key)) { + if (!expectedFields.contains(key)) { this->customValues[key] = values[key].toString(); } } @@ -301,24 +310,38 @@ void Event::addCustomValuesTo(OrderedJson::object *obj) OrderedJson::object Event::buildObjectEventJSON() { - OrderedJson::object eventObj; - eventObj["graphics_id"] = this->get("sprite"); - if (projectConfig.getObjectEventInConnectionEnabled()) { - eventObj["in_connection"] = this->getInt("in_connection") > 0 || this->get("in_connection") == "TRUE"; + OrderedJson::object objectObj; + if (projectConfig.getEventCloneObjectEnabled()) { + objectObj["type"] = "object"; } - eventObj["x"] = this->getS16("x"); - eventObj["y"] = this->getS16("y"); - eventObj["elevation"] = this->getInt("elevation"); - eventObj["movement_type"] = this->get("movement_type"); - eventObj["movement_range_x"] = this->getInt("radius_x"); - eventObj["movement_range_y"] = this->getInt("radius_y"); - eventObj["trainer_type"] = this->get("trainer_type"); - eventObj["trainer_sight_or_berry_tree_id"] = this->get("sight_radius_tree_id"); - eventObj["script"] = this->get("script_label"); - eventObj["flag"] = this->get("event_flag"); - this->addCustomValuesTo(&eventObj); + objectObj["graphics_id"] = this->get("sprite"); + objectObj["x"] = this->getS16("x"); + objectObj["y"] = this->getS16("y"); + objectObj["elevation"] = this->getInt("elevation"); + objectObj["movement_type"] = this->get("movement_type"); + objectObj["movement_range_x"] = this->getInt("radius_x"); + objectObj["movement_range_y"] = this->getInt("radius_y"); + objectObj["trainer_type"] = this->get("trainer_type"); + objectObj["trainer_sight_or_berry_tree_id"] = this->get("sight_radius_tree_id"); + objectObj["script"] = this->get("script_label"); + objectObj["flag"] = this->get("event_flag"); + this->addCustomValuesTo(&objectObj); - return eventObj; + return objectObj; +} + +OrderedJson::object Event::buildCloneObjectEventJSON(const QMap &mapNamesToMapConstants) +{ + OrderedJson::object cloneObj; + cloneObj["type"] = "clone"; + cloneObj["graphics_id"] = this->get("sprite"); + cloneObj["x"] = this->getS16("x"); + cloneObj["y"] = this->getS16("y"); + cloneObj["target_local_id"] = this->getInt("target_local_id"); + cloneObj["target_map"] = mapNamesToMapConstants.value(this->get("target_map")); + this->addCustomValuesTo(&cloneObj); + + return cloneObj; } OrderedJson::object Event::buildWarpEventJSON(const QMap &mapNamesToMapConstants) @@ -442,3 +465,16 @@ void Event::setFrameFromMovement(QString facingDir) { this->hFlip = true; } } + +// All event groups excepts warps have IDs that start at 1 +int Event::getIndexOffset(QString group_type) { + return (group_type == EventGroup::Warp) ? 0 : 1; +} + +bool Event::isValidType(QString event_type) { + return EventTypeTable.contains(event_type); +} + +QString Event::typeToGroup(QString event_type) { + return EventTypeTable.value(event_type, QString()); +} diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 4302147b..f81547ec 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -34,6 +34,9 @@ QString ParseUtil::readTextFile(const QString &path) { return QString(); } QTextStream in(&file); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + in.setCodec("UTF-8"); +#endif // Qt6 defaults to UTF-8, but setCodec is renamed to setEncoding QString text = ""; while (!in.atEnd()) { text += in.readLine() + '\n'; diff --git a/src/core/regionmap.cpp b/src/core/regionmap.cpp index f8dbcac1..483b2719 100644 --- a/src/core/regionmap.cpp +++ b/src/core/regionmap.cpp @@ -1,8 +1,10 @@ #include "regionmap.h" #include "regionmapeditor.h" #include "paletteutil.h" +#include "project.h" #include "log.h" #include "config.h" +#include "regionmapeditcommands.h" #include #include @@ -11,398 +13,701 @@ #include #include -static bool ensureRegionMapFileExists(QString filepath) { - if (!QFile::exists(filepath)) { - logError(QString("Region map file does not exist: %1").arg(filepath)); - return false; - } - return true; +using std::make_shared; + + + +RegionMap::RegionMap(Project *project) { + this->project = project; } -bool RegionMap::init(Project *pro) { - QString path = pro->root; - this->project = pro; +bool RegionMap::loadMapData(poryjson::Json data) { + poryjson::Json::object mapObject = data.object_items(); - QSize dimensions = porymapConfig.getRegionMapDimensions(); - img_width_ = dimensions.width(); - img_height_ = dimensions.height(); + this->alias = mapObject["alias"].string_value(); - layout_width_ = img_width_ - this->padLeft - this->padRight; - layout_height_ = img_height_ - this->padTop - this->padBottom; + poryjson::Json tilemapJson = mapObject["tilemap"]; + poryjson::Json layoutJson = mapObject["layout"]; - region_map_bin_path = path + "/graphics/pokenav/region_map_map.bin"; - region_map_png_path = path + "/graphics/pokenav/region_map.png"; - region_map_entries_path = path + "/src/data/region_map/region_map_entries.h"; - region_map_layout_bin_path = path + "/graphics/pokenav/region_map_section_layout.bin"; - city_map_tiles_path = path + "/graphics/pokenav/zoom_tiles.png"; - bool allFilesExist = ensureRegionMapFileExists(region_map_bin_path) - && ensureRegionMapFileExists(region_map_png_path) - && ensureRegionMapFileExists(region_map_entries_path) - && ensureRegionMapFileExists(region_map_layout_bin_path) - && ensureRegionMapFileExists(city_map_tiles_path); + this->tilemap.clear(); + this->layout_layers.clear(); + this->layouts.clear(); - return allFilesExist - && readBkgImgBin() - && readLayout(); + return loadTilemap(tilemapJson) && loadLayout(layoutJson); +} + +int RegionMap::tilemapBytes() { + // bytes per tile multiplier + int multiplier = 1; + + switch (tilemap_format) { + case TilemapFormat::Plain: + multiplier = 1; + break; + case TilemapFormat::BPP_4: + multiplier = 2; + break; + case TilemapFormat::BPP_8: + multiplier = 2; + break; + } + + return tilemapSize() * multiplier; +} + +bool RegionMap::loadTilemap(poryjson::Json tilemapJson) { + bool errored = false; + + poryjson::Json::object tilemapObject = tilemapJson.object_items(); + + this->tilemap_width = tilemapObject["width"].int_value(); + this->tilemap_height = tilemapObject["height"].int_value(); + + + QString tilemapFormat = tilemapObject["format"].string_value(); + QMap formatsMap = { {"plain", TilemapFormat::Plain}, {"4bpp", TilemapFormat::BPP_4}, {"8bpp", TilemapFormat::BPP_8} }; + this->tilemap_format = formatsMap[tilemapFormat]; + + this->tileset_path = tilemapObject["tileset_path"].string_value(); + this->tilemap_path = tilemapObject["tilemap_path"].string_value(); + + if (tilemapObject.contains("palette")) { + this->palette_path = tilemapObject["palette"].string_value(); + } + + QFile tilemapFile(fullPath(this->tilemap_path)); + if (!tilemapFile.open(QIODevice::ReadOnly)) { + logError(QString("Failed to open region map tilemap file %1.").arg(tilemap_path)); + return false; + } + + if (tilemapFile.size() < tilemapBytes()) { + logError(QString("The region map tilemap at %1 is too small.").arg(tilemap_path)); + return false; + } + + QByteArray newTilemap = tilemapFile.readAll(); + this->setTilemap(newTilemap); + + tilemapFile.close(); + + return !errored; +} + +bool RegionMap::loadLayout(poryjson::Json layoutJson) { + if (layoutJson.is_null()) { + this->layout_format = LayoutFormat::None; + return true; + } + + this->layout_constants.clear(); + + poryjson::Json::object layoutObject = layoutJson.object_items(); + + QString layoutFormat = layoutObject["format"].string_value(); + QMap layoutFormatMap = { {"binary", LayoutFormat::Binary}, {"C array", LayoutFormat::CArray} }; + this->layout_format = layoutFormatMap[layoutFormat]; + + this->layout_path = layoutObject["path"].string_value(); + this->layout_width = layoutObject["width"].int_value(); + this->layout_height = layoutObject["height"].int_value(); + + this->offset_left = layoutObject["offset_left"].int_value(); + this->offset_top = layoutObject["offset_top"].int_value(); + + bool errored = false; + + switch (this->layout_format) { + case LayoutFormat::Binary: + { + // only one layer supported for binary layouts + QFile binFile(fullPath(this->layout_path)); + if (!binFile.open(QIODevice::ReadOnly)) { + logError(QString("Failed to read region map layout binary file %1").arg(this->layout_path)); + return false; + } + QByteArray mapBinData = binFile.readAll(); + binFile.close(); + + if (mapBinData.size() != this->layout_width * this->layout_height) { + logError("Region map layout file size does not match given dimensions for " + this->alias); + return false; + } + + // for layouts with only a single layer, it is called main + this->layout_layers.append("main"); + QList layout; + + for (int y = 0; y < this->layout_height; y++) { + for (int x = 0; x < this->layout_width; x++) { + int bin_index = x + y * this->layout_width; + uint8_t square_section_id = mapBinData.at(bin_index); + QString square_section_name = project->mapSectionValueToName.value(square_section_id); + + LayoutSquare square; + square.map_section = square_section_name; + square.has_map = (square_section_name != "MAPSEC_NONE" && !square_section_name.isEmpty()); + square.x = x; + square.y = y; + + layout.append(square); + } + } + setLayout("main", layout); + break; + } + case LayoutFormat::CArray: + { + ParseUtil parser; + QString text = parser.readTextFile(fullPath(this->layout_path)); + + QRegularExpression re("(?static)?\\s?(?const)?\\s?(?[A-Za-z0-9_]+)?\\s+(?