diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f71ab5c5..ea31470d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,32 +9,26 @@ on: tags: - '*' pull_request: - branches: - - master # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: - build-qt5-linux: + build-linux: + strategy: + matrix: + qtversion: [5.14.2, 6.8.2] runs-on: ubuntu-latest steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v1 - with: - path: ../Qt - key: ${{ runner.os }}-QtCache + - uses: actions/checkout@v4 - name: Install Qt - uses: jurplel/install-qt-action@v2 + uses: jurplel/install-qt-action@v4 with: - version: '5.14.2' - modules: 'qtwidgets qtqml qtcharts' - cached: ${{ steps.cache-qt.outputs.cache-hit }} + version: ${{ matrix.qtversion }} + modules: 'qtcharts' + cache: 'true' - name: Configure run: qmake porymap.pro @@ -43,24 +37,22 @@ jobs: run: make build-macos: - runs-on: macos-latest + strategy: + matrix: + os: [macos-latest, macos-13] + runs-on: ${{ matrix.os }} + env: + BUILD_NAME: porymap-${{ matrix.os }}-${{ github.ref_name }} steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v1 - with: - path: ../Qt - key: ${{ runner.os }}-QtCache + - uses: actions/checkout@v4 - name: Install Qt - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: - version: '6.7.*' + version: '6.8.2' modules: 'qtcharts' - cached: ${{ steps.cache-qt.outputs.cache-hit }} + cache: 'true' - name: Configure run: qmake -config release porymap.pro @@ -75,19 +67,19 @@ jobs: - name: Prep Release Directory if: startsWith(github.ref, 'refs/tags/') run: | - mkdir porymap-macOS-${{ github.ref_name }} - cp porymap.dmg porymap-macOS-${{ github.ref_name }}/porymap.dmg - cp RELEASE-README.txt porymap-macOS-${{ github.ref_name }}/README.txt + mkdir $BUILD_NAME + cp porymap.dmg $BUILD_NAME/porymap.dmg + cp RELEASE-README.txt $BUILD_NAME/README.txt - name: Bundle Release Directory if: startsWith(github.ref, 'refs/tags/') - run: zip -r porymap-macOS-${{ github.ref_name }}.zip porymap-macOS-${{ github.ref_name }} + run: zip -r $BUILD_NAME.zip $BUILD_NAME - name: Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: - files: porymap-macOS-${{ github.ref_name }}.zip + files: $BUILD_NAME.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -95,7 +87,7 @@ jobs: runs-on: windows-latest steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: dsaltares/fetch-gh-release-asset@master if: steps.cache-static-qt.outputs.cache-hit != 'true' @@ -153,7 +145,7 @@ jobs: run: powershell.exe -Command "Compress-Archive -Path porymap-windows-${{ github.ref_name }} -DestinationPath porymap-windows-${{ github.ref_name }}.zip" - name: Create Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: files: porymap-windows-${{ github.ref_name }}.zip diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d9fd720..abdb4d9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,50 +8,121 @@ The **"Breaking Changes"** listed below are changes that have been made in the d ## [Unreleased] ### Added -- Redesigned the Connections tab, adding a number of new features including the option to open or display diving maps and a list UI for easier edit access. +- Redesigned the map list, adding new features including opening/editing layouts with no associated map, editing the names of map groups, rearranging maps and map groups, and hiding empty folders. +- Add a drop-down for changing the layout of the currently opened map. +- Add an option to duplicate maps/layouts. +- Redesigned the Connections tab, adding new features including the option to open or display diving maps and a list UI for easier edit access. - Add a `Close Project` option -- Add charts to the `Wild Pokémon` tab that show species and level distributions. +- Add a search button to the `Wild Pokémon` tab that shows the encounter data for a species across all maps. +- Add charts to the `Wild Pokémon` tab that show species and level distributions for the current map. - Add options for customizing the map grid under `View -> Grid Settings`. +- Add an option to display Event sprites while editing the map. +- Add an option to display a dividing line between tilesets in the Tileset Editor. +- Add an input field to the Tileset Editor for editing the full metatile attributes value directly, including unused bits. - An alert will be displayed when attempting to open a seemingly invalid project. - Add support for defining project values with `enum` where `#define` was expected. +- Add support for referring to object events and warps with named IDs, rather than referring to them with their index number. +- Add a setting to specify the tile values to use for the unused metatile layer. +- Add a setting to specify the maximum number of events in a group. A warning will be shown if too many events are added. +- Add a setting to customize the size and position of the player view distance. +- Add `onLayoutOpened` to the scripting API. +- Add a splash loading screen for project openings. ### Changed +- `Change Dimensions` now has an interactive resizing rectangle. +- Redesigned the new map dialog, including better error checking and a collapsible section for header data. +- New maps/layouts are no longer saved automatically, and can be fully discarded by closing without saving. +- Map groups and ``MAPSEC`` names specified when creating a new map will be added automatically if they don't already exist. +- Custom fields in JSON files that Porymap writes are no longer discarded. - Edits to map connections now have Undo/Redo and can be viewed in exported timelapses. - Changes to the "Mirror to Connecting Maps" setting will now be saved between sessions. - A notice will be displayed when attempting to open the "Dynamic" map, rather than nothing happening. - The base game version is now auto-detected if the project name contains only one of "emerald", "firered/leafgreen", or "ruby/sapphire". -- The max encounter rate is now read from the project, rather than assuming the default value from RSE. - It's now possible to cancel quitting if there are unsaved changes in sub-windows. - The triple-layer metatiles setting can now be set automatically using a project constant. -- `Export Map Stitch Image` now shows a preview of the full image, not just the current map. +- `Export Map Stitch Image` and `Export Map Timelapse Image` now show a preview of the full image/gif, not just the current map. +- `Custom Attributes` tables now display numbers using spin boxes. The `type` column was removed, because `value`'s type is now obvious. +- Unrecognized map names in Event or Connections data will no longer be overwritten. +- It's now possible to click on an event's sprite even if a different event's rectangle is overlapping it. The old selection behavior is available via a new setting. +- Reduced diff noise when saving maps. +- Map names and ``MAP_NAME`` constants are no longer required to match. +- Porymap will no longer overwrite ``include/constants/map_groups.h`` or ``include/constants/layouts.h``. +- Primary/secondary metatile images are now kept on separate rows, rather than blending together if the primary size is not divisible by 8. +- The prompt to reload the project when a file has changed will now only appear when Porymap is the active application. +- `Script` dropdowns now autocomplete only with scripts from the current map, rather than every script in the project. The old behavior is available via a new setting. +- `Script` dropdowns now update automatically if the current map's scripts file is edited. +- The options for `Encounter Type` and `Terrain Type` in the Tileset Editor are not hardcoded anymore, they're now read from the project. +- The `symbol_wild_encounters` setting was replaced; this value is now read from the project. +- The max encounter rate is now read from the project, rather than assuming the default value from RSE. +- `MAP_OFFSET_W` and `MAP_OFFSET_H` (used to limit the maximum map size) are now read from the project. +- The rendered area of the map border is now limited to the maximum player view distance (prior to this it included two extra rows on the top and bottom). +- An error message will now be shown when Porymap is unable to save changes (e.g. if Porymap doesn't have write permissions for your project). +- A project may now be opened even if it has no maps or map groups. A minimum of one map layout is required. +- The file extensions that are expected for `.png` and `.pal` data files and the extensions outputted when creating a new tileset can now be customized. +- Miscellaneous performance improvements, especially for opening projects. ### Fixed - Fix `Add Region Map...` not updating the region map settings file. - Fix some crashes on invalid region map tilesets. - Improve error reporting for invalid region map editor settings. +- Fix the region map editor's palette resetting between region maps. +- Fix the region map editor's h-flip and v-flip settings being swapped. - Fix config files being written before the project is opened successfully. - Fix the map and other project info still displaying if a new project fails to open. - Fix unsaved changes being ignored when quitting (such as with Cmd+Q on macOS). -- Fix selections with multiple Events not always clearing when making a new selection. +- Fix selections with multiple events not always clearing when making a new selection. +- Fix the new event button not updating correctly when selecting object events. +- Fix duplicated `Hidden Item` events not copying the `Requires Itemfinder` field. +- Fix event sprites disappearing in certain areas outside the map boundaries. +- Fix deselecting an event still allowing you to drag the event around. +- Fix events rendering on top of the ruler at very high y values. +- Fix new map names not appearing in event dropdowns that have already been populated. - Fix `About porymap` opening a new window each time it's activated. - Fix the `Edit History` window not raising to the front when reactivated. - New maps are now always inserted in map dropdowns at the correct position, rather than at the bottom of the list until the project is reloaded. +- Fix species on the wild pokémon tab retaining icons from previously-opened projects. - Fix invalid species names clearing from wild pokémon data when revisited. -- Fix editing wild pokémon data not marking the map as edited. +- Fix editing wild pokémon data not marking the map as unsaved. +- Fix editing an event's `Custom Attributes` not marking the map as unsaved. - Fix changes to map connections not marking connected maps as unsaved. - Fix numerous issues related to connecting a map to itself. - Fix incorrect map connections getting selected when opening a map by double-clicking a map connection. - Fix a visual issue when quickly dragging map connections around. - Fix map connections rendering incorrectly if their direction name was unknown. - Fix map connections rendering incorrectly if their dimensions were smaller than the border draw distance. +- Fix metatile/collision selection images skewing off-center after opening a map from the Connections tab. - Fix the map list filter retaining text between project open/close. - Fix the map list mishandling value gaps when sorting by Area. - Fix a freeze on startup if project values are defined with mismatched parentheses. - Fix stitched map images sometimes rendering garbage - Fix the `Reset` button on `Export Map Timelapse Image` not resetting the Timelapse settings. +- Fix events in exported map stitch images being occluded by neighboring maps. +- Fix the map connections in exported map images coming from the map currently open in the editor, rather than the map shown in the export window. +- Fix crash when exporting a map stitch image if a map fails to load. +- Fix possible crash when exporting a timelapse that has events edit history. +- Fix exported timelapses excluding pasted events and certain map size changes. +- Fix exporting a timelapse sometimes altering the state of the current map's edit history. - Stop sliders in the Palette Editor from creating a bunch of edit history when used. - Fix scrolling on some containers locking up when the mouse stops over a spin box or combo box. +- Fix the selection index for some combo boxes differing from their displayed text. - Fix some file dialogs returning to an incorrect window when closed. +- Fix bug where reloading a layout would overwrite all unsaved changes. +- Fix bug where layout json and blockdata could be saved separately leading to inconsistent data. +- Fix crash when saving tilesets with fewer palettes than the maximum. +- Fix projects not opening on Windows if the project filepath contains certain characters. +- Fix custom project filepaths not converting Windows file separators. +- Fix exported tile images containing garbage pixels after the end of the tiles. +- Fix fully transparent pixels rendering with the incorrect color. +- Fix the values for some config fields shuffling their order every save. +- Fix `key`s in `Custom Attributes` disappearing if given an empty name or the name of an existing field. +- Fix some problems with tileset detection when importing maps from AdvanceMap. +- Fix certain input fields allowing invalid identifiers, like names starting with numbers. +- Fix crash in the Shortcuts Editor when applying changes after closing certain windows. +- Fix the Shortcuts Editor clearing shortcuts after selecting them. +- Fix `Display Metatile Usage Counts` sometimes changing the counts after repeated use. +- The Metatile / Tile usage counts in the Tileset Editor now update to reflect changes. +- Fix regression that stopped the map zoom from centering on the cursor. +- Fix `Open Map Scripts` not working on maps with a `shared_scripts_map` field. ## [5.4.1] - 2024-03-21 ### Fixed diff --git a/docs/manual/creating-new-maps.html b/docs/manual/creating-new-maps.html index 516cdc8c..180b57e5 100644 --- a/docs/manual/creating-new-maps.html +++ b/docs/manual/creating-new-maps.html @@ -456,7 +456,7 @@ in order to add a new map to the folder.

Name
The name of the new map. This cannot be changed in porymap.
Group
-
Which map group the new map will beling to. This cannot be changed in porymap.
+
Which map group the new map will belong to. This cannot be changed in porymap.
Map Width
The width (in metatiles) of the map. This can be changed in porymap.
Map Height
@@ -538,4 +538,4 @@ in order to add a new map to the folder.

- \ No newline at end of file + diff --git a/docsrc/manual/creating-new-maps.rst b/docsrc/manual/creating-new-maps.rst index be660e37..4da20776 100644 --- a/docsrc/manual/creating-new-maps.rst +++ b/docsrc/manual/creating-new-maps.rst @@ -31,7 +31,7 @@ Name The name of the new map. This cannot be changed in porymap. Group - Which map group the new map will beling to. This cannot be changed in porymap. + Which map group the new map will belong to. This cannot be changed in porymap. Map Width The width (in metatiles) of the map. This can be changed in porymap. diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index 30f0acc8..93715d86 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -36,22 +36,20 @@ The filepath that Porymap expects for each file can be overridden on the ``Files data/tilesets/metatiles.inc, yes, yes, ``tilesets_metatiles_asm``, only if ``tilesets_headers`` can't be found data/tilesets/[primary|secondary]/\*, yes, yes, ``data_tilesets_folders``, default tileset data location src/data/wild_encounters.json, yes, yes, ``json_wild_encounters``, optional (only required to use Wild Pokémon tab) + src/data/heal_locations.json, yes, yes, ``json_heal_locations``, src/data/object_events/object_event_graphics_info_pointers.h, yes, no, ``data_obj_event_gfx_pointers``, src/data/object_events/object_event_graphics_info.h, yes, no, ``data_obj_event_gfx_info``, src/data/object_events/object_event_pic_tables.h, yes, no, ``data_obj_event_pic_tables``, src/data/object_events/object_event_graphics.h, yes, no, ``data_obj_event_gfx``, src/data/graphics/pokemon.h, yes, no, ``data_pokemon_gfx``, for pokemon sprite icons - src/data/heal_locations.h, yes, yes, ``data_heal_locations``, src/data/region_map/region_map_sections.json, yes, yes, ``json_region_map_entries``, src/data/region_map/porymap_config.json, yes, yes, ``json_region_porymap_cfg``, include/constants/global.h, yes, no, ``constants_global``, reads ``define_obj_event_count`` - include/constants/map_groups.h, no, yes, ``constants_map_groups``, include/constants/items.h, yes, no, ``constants_items``, for Hidden Item events include/constants/flags.h, yes, no, ``constants_flags``, for Object and Hidden Item events include/constants/vars.h, yes, no, ``constants_vars``, for Trigger events include/constants/weather.h, yes, no, ``constants_weather``, for map weather and Weather Triggers include/constants/songs.h, yes, no, ``constants_songs``, for map music - include/constants/heal_locations.h, yes, yes, ``constants_heal_locations``, include/constants/pokemon.h, yes, no, ``constants_pokemon``, reads ``define_min_level`` and ``define_max_level`` include/constants/map_types.h, yes, no, ``constants_map_types``, include/constants/trainer_types.h, yes, no, ``constants_trainer_types``, for Object events @@ -59,7 +57,6 @@ The filepath that Porymap expects for each file can be overridden on the ``Files include/constants/event_object_movement.h, yes, no, ``constants_obj_event_movement``, include/constants/event_objects.h, yes, no, ``constants_obj_events``, include/constants/event_bg.h, yes, no, ``constants_event_bg``, - include/constants/region_map_sections.h, yes, no, ``constants_region_map_sections``, include/constants/metatile_labels.h, yes, yes, ``constants_metatile_labels``, include/constants/metatile_behaviors.h, yes, no, ``constants_metatile_behaviors``, include/constants/species.h, yes, no, ``constants_metatile_behaviors``, for the Wild Pokémon tab @@ -86,14 +83,9 @@ In addition to these files, there are some specific symbol and macro names that ``symbol_facing_directions``, ``gInitialMovementTypeFacingDirections``, to set sprite frame for Object Events based on movement type ``symbol_obj_event_gfx_pointers``, ``gObjectEventGraphicsInfoPointers``, to map Object Event graphics IDs to graphics data ``symbol_pokemon_icon_table``, ``gMonIconTable``, to map species constants to icon images - ``symbol_wild_encounters``, ``gWildMonHeaders``, output as the ``label`` property for the top-level wild ecounters JSON object - ``symbol_heal_locations_type``, ``struct HealLocation``, the type for the Heal Locations table - ``symbol_heal_locations``, ``sHealLocations``, the default Heal Locations table name when ``Respawn Map/NPC`` is disabled - ``symbol_spawn_points``, ``sSpawnPoints``, the default Heal Locations table name when ``Respawn Map/NPC`` is enabled - ``symbol_spawn_maps``, ``u16 sWhiteoutRespawnHealCenterMapIdxs``, the type and table name for Heal Location ``Respawn Map`` values - ``symbol_spawn_npcs``, ``u8 sWhiteoutRespawnHealerNpcIds``, the type and table name for Heal Location ``Respawn NPC`` values ``symbol_attribute_table``, ``sMetatileAttrMasks``, optionally read to get settings on ``Tilesets`` tab ``symbol_tilesets_prefix``, ``gTileset_``, for new tileset names and to extract base tileset names + ``symbol_dynamic_map_name``, ``Dynamic``, reserved map name to display for ``define_map_dynamic`` ``define_obj_event_count``, ``OBJECT_EVENT_TEMPLATES_COUNT``, to limit total Object Events ``define_min_level``, ``MIN_LEVEL``, minimum wild encounters level ``define_max_level``, ``MAX_LEVEL``, maximum wild encounters level @@ -105,6 +97,8 @@ In addition to these files, there are some specific symbol and macro names that ``define_pals_total``, ``NUM_PALS_TOTAL``, ``define_tiles_per_metatile``, ``NUM_TILES_PER_METATILE``, to determine if triple-layer metatiles are in use. Values other than 8 or 12 are ignored ``define_map_size``, ``MAX_MAP_DATA_SIZE``, to limit map dimensions + ``define_map_offset_width``, ``MAP_OFFSET_W``, to limit map dimensions + ``define_map_offset_height``, ``MAP_OFFSET_H``, to limit map dimensions ``define_mask_metatile``, ``MAPGRID_METATILE_ID_MASK``, optionally read to get settings on ``Maps`` tab ``define_mask_collision``, ``MAPGRID_COLLISION_MASK``, optionally read to get settings on ``Maps`` tab ``define_mask_elevation``, ``MAPGRID_ELEVATION_MASK``, optionally read to get settings on ``Maps`` tab @@ -115,15 +109,15 @@ In addition to these files, there are some specific symbol and macro names that ``define_attribute_terrain``, ``METATILE_ATTRIBUTE_TERRAIN``, name used to extract setting from ``symbol_attribute_table`` ``define_attribute_encounter``, ``METATILE_ATTRIBUTE_ENCOUNTER_TYPE``, name used to extract setting from ``symbol_attribute_table`` ``define_metatile_label_prefix``, ``METATILE_``, expected prefix for metatile label macro names - ``define_heal_locations_prefix``, ``HEAL_LOCATION_``, output as prefix for Heal Location IDs if ``Respawn Map/NPC`` is disabled - ``define_spawn_prefix``, ``SPAWN_``, output as prefix for Heal Location IDs if ``Respawn Map/NPC`` is enabled + ``define_heal_locations_prefix``, ``HEAL_LOCATION_``, default prefix for heal location macro names + ``define_layout_prefix``, ``LAYOUT_``, default prefix for layout macro names ``define_map_prefix``, ``MAP_``, expected prefix for map macro names ``define_map_dynamic``, ``DYNAMIC``, macro name after prefix for Dynamic maps ``define_map_empty``, ``UNDEFINED``, macro name after prefix for empty maps ``define_map_section_prefix``, ``MAPSEC_``, expected prefix for location macro names ``define_map_section_empty``, ``NONE``, macro name after prefix for empty region map sections - ``define_map_section_count``, ``COUNT``, macro name after prefix for total number of region map sections ``define_species_prefix``, ``SPECIES_``, expected prefix for species macro names + ``define_species_empty``, ``NONE``, macro name after prefix for the default species ``regex_behaviors``, ``\bMB_``, regex to find metatile behavior macro names ``regex_obj_event_gfx``, ``\bOBJ_EVENT_GFX_``, regex to find Object Event graphics ID macro names ``regex_items``, ``\bITEM_(?!(B_)?USE_)``, regex to find item macro names @@ -138,3 +132,7 @@ In addition to these files, there are some specific symbol and macro names that ``regex_sign_facing_directions``, ``\bBG_EVENT_PLAYER_FACING_``, regex to find sign facing direction macro names ``regex_trainer_types``, ``\bTRAINER_TYPE_``, regex to find trainer type macro names ``regex_music``, ``\b(SE|MUS)_``, regex to find music macro names + ``regex_gbapal``, ``\.gbapal(\.[\w]+)?$``, regex to get the expected file extension for ``.pal`` data files + ``regex_bpp``, ``\.[\d]+bpp(\.[\w]+)?$``, regex to get the expected file extension for ``.png`` data files + ``pals_output_extension``, ``.gbapal``, the file extension to output for a new tileset's palette data files + ``tiles_output_extension``, ``.4bpp.lz``, the file extension to output for a new tileset's tiles image data file diff --git a/docsrc/manual/scripting-capabilities.rst b/docsrc/manual/scripting-capabilities.rst index fe47f67f..0651f7d9 100644 --- a/docsrc/manual/scripting-capabilities.rst +++ b/docsrc/manual/scripting-capabilities.rst @@ -158,6 +158,13 @@ Callbacks :param mapName: the name of the opened map :type mapName: string +.. js:function:: onLayoutOpened(layoutName) + + Called when a layout is opened, either by selecting a new map/layout in the map list or swapping the layout for the current map. + + :param layoutName: the name of the opened layout + :type layoutName: string + .. js:function:: onBlockChanged(x, y, prevBlock, newBlock) Called when a block is changed on the map. For example, this is called when a user paints a new tile or changes the collision property of a block. @@ -197,7 +204,7 @@ Callbacks Called when the mouse exits the map. -.. js:function:: onMapResized(oldWidth, oldHeight, newWidth, newHeight) +.. js:function:: onMapResized(oldWidth, oldHeight, delta) Called when the dimensions of the map are changed. @@ -205,10 +212,8 @@ Callbacks :type oldWidth: number :param oldHeight: the height of the map before the change :type oldHeight: number - :param newWidth: the width of the map after the change - :type newWidth: number - :param newHeight: the height of the map after the change - :type newHeight: number + :param delta: the amount the map size changed in each direction. The object's shape is ``{left, right, top, bottom}`` + :type prevBlock: delta .. js:function:: onBorderResized(oldWidth, oldHeight, newWidth, newHeight) @@ -1995,7 +2000,28 @@ All utility functions are callable via the global ``utility`` object. Gets the list of map names. - :returns: the list of map names + :returns: the list of map names (e.g. `PetalburgCity`) + :rtype: array + +.. js:function:: utility.getMapConstants() + + Gets the list of map IDs (e.g. `MAP_PETALBURG_CITY`) + + :returns: the list of map IDs + :rtype: array + +.. js:function:: utility.getLayoutNames() + + Gets the list of layout names. + + :returns: the list of layout names (e.g. `PetalburgCity_Layout`) + :rtype: array + +.. js:function:: utility.getLayoutConstants() + + Gets the list of layout IDs (e.g. `LAYOUT_PETALBURG_CITY`) + + :returns: the list of layout IDs :rtype: array .. js:function:: utility.getTilesetNames() diff --git a/forms/aboutporymap.ui b/forms/aboutporymap.ui index f823396c..fc777020 100644 --- a/forms/aboutporymap.ui +++ b/forms/aboutporymap.ui @@ -1,117 +1,92 @@ AboutPorymap - + 0 0 - 582 - 438 + 388 + 121 About Porymap - - - - - - - Arial - 22 - 75 - true - false - true - - - - Porymap - - - Qt::RichText - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - Arial - 12 - - - - Qt::AlignCenter - - - - - - - - Arial - - - - Map editor for pokeemerald, pokefirered and pokeruby. - - - Qt::AlignCenter - - - - - - - <html><head/><body><p>Official Documentation: <a href="https://huderlem.github.io/porymap/"><span style=" text-decoration: underline; color:#0069d9;">https://huderlem.github.io/porymap/</span></a></p></body></html> - - - Qt::AlignCenter - - - true - - - - - - - Qt::Horizontal - - - - - - - true - - - - - - - - - 0 - 0 - 582 - 22 - - - - + + + + + + Arial + 22 + true + false + true + + + + Porymap + + + Qt::TextFormat::RichText + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 0 + 0 + + + + + Arial + 12 + + + + Qt::AlignmentFlag::AlignCenter + + + Qt::TextInteractionFlag::TextSelectableByMouse + + + + + + + + Arial + + + + Map editor for pokeemerald, pokefirered and pokeruby. + + + Qt::AlignmentFlag::AlignCenter + + + + + + + <html><head/><body><p>Official Documentation: <a href="https://huderlem.github.io/porymap/"><span style=" text-decoration: underline; color:#0069d9;">https://huderlem.github.io/porymap/</span></a></p></body></html> + + + Qt::AlignmentFlag::AlignCenter + + + true + + + + diff --git a/forms/connectionslistitem.ui b/forms/connectionslistitem.ui index bf04e8be..116da983 100644 --- a/forms/connectionslistitem.ui +++ b/forms/connectionslistitem.ui @@ -6,7 +6,7 @@ 0 0 - 178 + 188 157 @@ -20,7 +20,7 @@ .ConnectionsListItem { border-width: 1px; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel @@ -65,7 +65,7 @@ - Remove this connection. + <html><head/><body><p>Remove this connection.</p></body></html> ... @@ -79,28 +79,28 @@ - Where the connected map should be positioned relative to the current map. + <html><head/><body><p>Where the connected map should be positioned relative to the current map.</p></body></html> - The name of the map to connect to the current map. + <html><head/><body><p>The name of the map to connect to the current map.</p></body></html> - The number of spaces to move the connected map perpendicular to its connected direction. + <html><head/><body><p>The number of spaces to move the connected map perpendicular to its connected direction.</p></body></html> - Open the connected map. + <html><head/><body><p>Open the connected map.</p></body></html> ... diff --git a/forms/customattributesdialog.ui b/forms/customattributesdialog.ui new file mode 100644 index 00000000..90dfba6e --- /dev/null +++ b/forms/customattributesdialog.ui @@ -0,0 +1,172 @@ + + + CustomAttributesDialog + + + + 0 + 0 + 410 + 192 + + + + Add New Custom Attribute + + + + + + + 0 + + + 0 + + + + + Name + + + + + + + <html><head/><body><p>The key name for the new JSON field</p></body></html> + + + true + + + + + + + Type + + + + + + + <html><head/><body><p>The data type for the new JSON field</p></body></html> + + + + + + + Value + + + + + + + + 0 + 0 + + + + <html><head/><body><p>The value for the new JSON field</p></body></html> + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + + + + + + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+
+ + +
diff --git a/forms/customattributesframe.ui b/forms/customattributesframe.ui new file mode 100644 index 00000000..87619e63 --- /dev/null +++ b/forms/customattributesframe.ui @@ -0,0 +1,89 @@ + + + CustomAttributesFrame + + + + 0 + 0 + 400 + 300 + + + + + + + Custom Attributes + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Add + + + + + + + false + + + Delete + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + CustomAttributesTable + QTableWidget +
customattributestable.h
+
+
+ + +
diff --git a/forms/customscriptseditor.ui b/forms/customscriptseditor.ui index cc7dd4f1..7db3b208 100644 --- a/forms/customscriptseditor.ui +++ b/forms/customscriptseditor.ui @@ -6,7 +6,7 @@ 0 0 - 540 + 582 355
@@ -33,25 +33,34 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised - QFrame::StyledPanel - - - QFrame::Raised + QFrame::Shape::StyledPanel + + 6 + + + 6 + + + 6 + + + 6 + - Create a new Porymap script file with a default template + <html><head/><body><p>Create a new Porymap script file with a default template</p></body></html> Create New Script... @@ -65,7 +74,7 @@ - Add an existing script file to the list below + <html><head/><body><p>Add an existing script file to the list below</p></body></html> Load Script... @@ -79,7 +88,7 @@ - Refresh all loaded scripts to account for any recent edits + <html><head/><body><p>Refresh all loaded scripts to account for any recent edits</p></body></html> Refresh Scripts @@ -91,9 +100,9 @@ - + - Qt::Horizontal + Qt::Orientation::Horizontal @@ -103,35 +112,33 @@ + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + ... + + + + :/icons/help.ico:/icons/help.ico + + + - - - - <html><head/><body><p><a href="https://huderlem.github.io/porymap/manual/scripting-capabilities.html"><span style=" text-decoration: underline; color:#0069d9;">Help</span></a></p></body></html> - - - true - - - - - - - Qt::Vertical - - - QSizePolicy::Minimum - - - - 20 - 5 - - - - @@ -142,32 +149,32 @@ - QAbstractItemView::NoEditTriggers + QAbstractItemView::EditTrigger::NoEditTriggers false - QAbstractItemView::DragOnly + QAbstractItemView::DragDropMode::DragOnly - Qt::IgnoreAction + Qt::DropAction::IgnoreAction - QAbstractItemView::ExtendedSelection + QAbstractItemView::SelectionMode::ExtendedSelection - Qt::ElideLeft + Qt::TextElideMode::ElideLeft - QListView::Free + QListView::Movement::Free - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/forms/eventpropertiesframe.ui b/forms/eventpropertiesframe.ui deleted file mode 100644 index 4629438f..00000000 --- a/forms/eventpropertiesframe.ui +++ /dev/null @@ -1,319 +0,0 @@ - - - EventPropertiesFrame - - - - 0 - 0 - 284 - 146 - - - - - 0 - 0 - - - - - 284 - 90 - - - - Frame - - - Qt::LeftToRight - - - QFrame::Box - - - QFrame::Raised - - - 1 - - - - - - - 0 - 0 - - - - - - - - 0 - 0 - - - - - 64 - 64 - - - - QFrame::Box - - - QFrame::Sunken - - - - - - false - - - Qt::AlignCenter - - - -1 - - - - - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - - - The index of the event currently being inspected. - - - 255 - - - - - - - - 0 - 0 - - - - Id - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - X - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::StrongFocus - - - <html><head/><body><p>The X coordinate of this object.</p></body></html> - - - -32768 - - - 32767 - - - - - - - - - - - Y - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::StrongFocus - - - <html><head/><body><p>The Y coordinate of this object.</p></body></html> - - - -32768 - - - 32767 - - - - - - - - - - - Z - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::StrongFocus - - - <html><head/><body><p>The elevation of this object.</p></body></html> - - - 15 - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - QFormLayout::WrapLongRows - - - 9 - - - 0 - - - 0 - - - - - Sprite - - - comboBox_sprite - - - - - - - true - - - Qt::StrongFocus - - - <html><head/><body><p>The sprite graphics to use for this object.</p></body></html> - - - true - - - - - - 25 - - - QComboBox::AdjustToContentsOnFirstShow - - - - - - - - - - - NoScrollComboBox - QComboBox -
noscrollcombobox.h
-
- - NoScrollSpinBox - QSpinBox -
noscrollspinbox.h
-
-
- - spinBox_x - spinBox_y - spinBox_z - comboBox_sprite - - - -
diff --git a/forms/loadingscreen.ui b/forms/loadingscreen.ui new file mode 100644 index 00000000..4ed06b9f --- /dev/null +++ b/forms/loadingscreen.ui @@ -0,0 +1,157 @@ + + + LoadingScreen + + + Qt::WindowModality::ApplicationModal + + + + 0 + 0 + 366 + 255 + + + + BusyCursor + + + Qt::ContextMenuPolicy::NoContextMenu + + + Form + + + + + + + 20 + true + + + + porymap + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 12 + + + + Version X.x.x + + + Qt::AlignmentFlag::AlignCenter + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Raised + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + 64 + 64 + + + + IMAGE + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + + + + Loading.... + + + + + + + + + + + diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 60dc95e7..c75c8328 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 1287 - 903 + 1298 + 963
@@ -30,9 +30,9 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - + true @@ -42,168 +42,162 @@ 0 - - - 0 - - - 0 - - - 0 - - - 3 - - - 0 - - - - - 0 - - - 3 - - - 3 - - - 3 - - - - - true - - - <html><head/><body><p>Sort map list</p></body></html> - - - - :/icons/sort_alphabet.ico:/icons/sort_alphabet.ico - - - - 16 - 16 - - - - QToolButton::InstantPopup - - - Qt::ToolButtonIconOnly - - - true - - - Qt::NoArrow - - - - - - - <html><head/><body><p>Expand all map folders</p></body></html> - - - - - - - :/icons/expand_all.ico:/icons/expand_all.ico - - - QToolButton::InstantPopup - - - true - - - - - - - <html><head/><body><p>Collapse all map list folders</p></body></html> - - - - - - - :/icons/collapse_all.ico:/icons/collapse_all.ico - - - QToolButton::InstantPopup - - - true - - - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 12 - 20 - - - - - - - - true - - - - - - Filter maps... - - - true - - - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectItems - - - false - - - - + + 0 + + + + Groups + + + + 0 + + + 0 + + + 0 + + + 3 + + + 0 + + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + QAbstractItemView::SelectionMode::SingleSelection + + + QAbstractItemView::SelectionBehavior::SelectItems + + + false + + + + + + + + Locations + + + + 0 + + + 0 + + + 0 + + + 3 + + + 0 + + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + QAbstractItemView::SelectionMode::SingleSelection + + + QAbstractItemView::SelectionBehavior::SelectItems + + + false + + + + + + + + Layouts + + + + 0 + + + 0 + + + 0 + + + 3 + + + 0 + + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + QAbstractItemView::SelectionMode::SingleSelection + + + QAbstractItemView::SelectionBehavior::SelectItems + + + false + + + false + + + + +
@@ -236,7 +230,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -266,9 +260,6 @@ false - - - 0 @@ -288,7 +279,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -298,10 +289,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised 1 @@ -325,10 +316,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -377,10 +368,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -401,10 +392,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -585,7 +576,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -608,10 +599,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -658,6 +649,9 @@ Metatiles + + QLayout::SizeConstraint::SetNoConstraint + 3 @@ -685,17 +679,17 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal - + - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -719,7 +713,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -756,10 +750,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain true @@ -769,7 +763,7 @@ 0 0 - 423 + 424 79 @@ -792,7 +786,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -814,17 +808,17 @@ <html><head/><body><p>The border is a 2x2 metatile which is repeated outside of the map layout's boundary. Draw on this border area to modify it.</p></body></html> - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::Horizontal + Qt::Orientation::Horizontal @@ -875,10 +869,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain true @@ -888,7 +882,7 @@ 0 0 - 423 + 424 79 @@ -911,7 +905,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -936,17 +930,17 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::Horizontal + Qt::Orientation::Horizontal @@ -970,19 +964,19 @@ - Qt::ScrollBarAlwaysOn + Qt::ScrollBarPolicy::ScrollBarAlwaysOn - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored true - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop @@ -992,7 +986,7 @@ 8 0 - 411 + 412 446 @@ -1021,7 +1015,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1043,20 +1037,20 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1069,7 +1063,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1087,7 +1081,7 @@ - + @@ -1096,23 +1090,23 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised - + Primary Tileset - + - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus <html><head/><body><p>Primary Tileset</p><p>Defines the first 0x200 metatiles available for the map.</p></body></html> @@ -1122,17 +1116,17 @@ - + Secondary Tileset - + - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus <html><head/><body><p>Secondary Tileset</p><p>Defines the second 0x200 metatiles available for the map.</p></body></html> @@ -1142,6 +1136,34 @@ + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Raised + + + + 2 + + + 2 + + + + + Layout + + + + + + + + + @@ -1162,7 +1184,7 @@ - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint 3 @@ -1193,8 +1215,8 @@ 0 0 - 427 - 594 + 428 + 633 @@ -1216,7 +1238,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1241,17 +1263,17 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff
- Qt::Horizontal + Qt::Orientation::Horizontal @@ -1264,7 +1286,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1290,7 +1312,7 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal
@@ -1306,17 +1328,17 @@ 50
- Qt::Horizontal + Qt::Orientation::Horizontal
- QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -1342,7 +1364,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1403,7 +1425,7 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true @@ -1413,8 +1435,8 @@ 0 0 - 382 - 699 + 383 + 744 @@ -1436,10 +1458,10 @@ <html><head/><body><p>No prefabs have been created for the currently-used tilesets. Create some by using the button above!</p><p>Prefabs are &quot;prefabricated&quot; metatile selections that are used for easy selecting of complicated map structures. For example, a useful prefab could be a building or tree formation, which would otherwise be annoying to paint with the regular metatile picker.</p></body></html> - Qt::RichText + Qt::TextFormat::RichText - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop true @@ -1459,7 +1481,7 @@ - + 1 @@ -1474,7 +1496,7 @@ 1 - + true @@ -1512,10 +1534,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised 0 @@ -1552,12 +1574,12 @@ :/icons/add.ico:/icons/add.ico - Qt::ToolButtonTextBesideIcon + Qt::ToolButtonStyle::ToolButtonTextBesideIcon - + 0 @@ -1575,7 +1597,7 @@ :/icons/delete.ico:/icons/delete.ico - Qt::ToolButtonTextBesideIcon + Qt::ToolButtonStyle::ToolButtonTextBesideIcon false @@ -1585,7 +1607,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1620,7 +1642,7 @@ There are no events on the current map. - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -1686,7 +1708,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1701,13 +1723,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -1715,7 +1737,7 @@ 0 0 100 - 30 + 16 @@ -1780,7 +1802,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1795,13 +1817,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -1809,7 +1831,7 @@ 0 0 100 - 30 + 16 @@ -1874,7 +1896,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1889,13 +1911,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -1903,7 +1925,7 @@ 0 0 100 - 30 + 16 @@ -1974,7 +1996,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1989,13 +2011,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -2003,7 +2025,7 @@ 0 0 100 - 30 + 16 @@ -2017,9 +2039,9 @@ - + - Healspots + Heal Locations @@ -2066,9 +2088,9 @@ - + - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2081,23 +2103,23 @@
- + - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop - + 0 0 100 - 30 + 16 @@ -2137,13 +2159,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -2195,7 +2217,7 @@ 0 - + false @@ -2206,322 +2228,82 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised - - - QFormLayout::FieldsStayAtSizeHint - - - 12 - + 9 - - - - Song - - - - - - - <html><head/><body><p>The default background music for this map.</p></body></html> - - - true - - - - - - - Location - - - - - - - <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is display when the player enters it.</p></body></html> - - - true - - - - - - - Requires Flash - - - - - - - <html><head/><body><p>Whether or not the map is dark and requires Flash to illuminate.</p></body></html> - - - - - - - - - - Weather - - - - - - - <html><head/><body><p>The default weather for this map.</p></body></html> - - - true - - - - - - - Type - - - - - - - <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example. it determines whether biking or running is allowed.</p></body></html> - - - true - - - - - - - Battle scene - - - - - - - <html><head/><body><p>Determines the type of battle scene graphics to use.</p></body></html> - - - true - - - - - - - Show Location Name - - - - - - - <html><head/><body><p>Whether or not to display the location name when the player enters the map.</p></body></html> - - - - - - - - - - Allow Running - - - - - - - <html><head/><body><p>Allows the player to use Running Shoes</p></body></html> - - - - - - - - - - Allow Biking - - - - - - - <html><head/><body><p>Allows the player to use a Bike</p></body></html> - - - - - - - - - - Allow Dig & Escape Rope - - - - - - - <html><head/><body><p>Allows the player to use Dig or Escape Rope</p></body></html> - - - - - - - - - - Floor Number - - - - - - - <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> - - - -128 - - - 127 - - - - + - QFrame::StyledPanel + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Plain - - - - - Custom Fields - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - - 0 + + true + + + + + 0 + 0 + 1011 + 806 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + - - 0 + + QFrame::Shape::StyledPanel - - 0 + + QFrame::Shadow::Raised - - 0 + + + + + + Qt::Orientation::Vertical - - - - Add - - - - - - - Delete - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - Custom fields will be added to the map.json file for the current map. - - - false - - - false - - - true - - - false - - - true - - - false - - - - Type + + + 1 + 767 + - - - - Key - - - - - Value - - - - - + + + + @@ -2549,10 +2331,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -2564,10 +2346,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -2595,7 +2377,7 @@ - If enabled, connections will automatically be updated on the connected map. + <html><head/><body><p>If enabled, the connected Emerge and/or Dive maps will be displayed with an opacity set using the slider.</p></body></html> Mirror to Connecting Maps @@ -2632,7 +2414,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2644,8 +2426,11 @@ + + false + - Open the selected Dive Map + <html><head/><body><p>Open the selected Dive Map</p></body></html> ... @@ -2659,7 +2444,7 @@ - If enabled, the connected Emerge and/or Dive maps will be displayed with an opacity set using the slider. + <html><head/><body><p>If enabled, the connected Emerge and/or Dive maps will be displayed with an opacity set using the slider.</p></body></html> Show Emerge/Dive Maps @@ -2717,7 +2502,7 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2733,7 +2518,7 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2765,7 +2550,7 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2778,8 +2563,11 @@ + + false + - Open the selected Emerge Map + <html><head/><body><p>Open the selected Emerge Map</p></body></html> ... @@ -2796,9 +2584,9 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - + 0 @@ -2812,22 +2600,22 @@ false - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - QGraphicsView::NoDrag + QGraphicsView::DragMode::NoDrag - QGraphicsView::AnchorUnderMouse + QGraphicsView::ViewportAnchor::AnchorUnderMouse - QGraphicsView::AnchorUnderMouse + QGraphicsView::ViewportAnchor::AnchorUnderMouse @@ -2838,10 +2626,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -2858,10 +2646,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff true @@ -2871,8 +2659,8 @@ 0 0 - 365 - 651 + 204 + 16 @@ -2894,7 +2682,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -2922,19 +2710,19 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -2945,9 +2733,12 @@ - + + + false + - QComboBox::AdjustToContents + QComboBox::SizeAdjustPolicy::AdjustToContents @@ -2988,7 +2779,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -3003,6 +2794,10 @@ Summary Chart... + + + :/icons/chart_bar.ico:/icons/chart_bar.ico + @@ -3012,6 +2807,17 @@ + + + + ... + + + + :/icons/magnifier.ico:/icons/magnifier.ico + + + @@ -3040,8 +2846,8 @@ 0 0 - 1287 - 22 + 1298 + 37 @@ -3057,6 +2863,7 @@ + @@ -3081,7 +2888,10 @@ + + + @@ -3097,7 +2907,10 @@ + + + @@ -3266,45 +3079,6 @@ Ctrl+T - - - true - - - - :/icons/sort_alphabet.ico:/icons/sort_alphabet.ico - - - Sort by &Area - - - - - true - - - - :/icons/sort_number.ico:/icons/sort_number.ico - - - Sort by &Group - - - Sort by Group - - - - - true - - - - :/icons/sort_map.ico:/icons/sort_map.ico - - - Sort by &Layout - - About Porymap... @@ -3412,7 +3186,7 @@ Check for Updates... - QAction::ApplicationSpecificRole + QAction::MenuRole::ApplicationSpecificRole @@ -3434,6 +3208,17 @@ Show Dive/Emerge Map + + + true + + + false + + + Show Events in Map View + + true @@ -3453,6 +3238,16 @@ Grid Settings... + + + New Layout... + + + + + Duplicate Current Map/Layout... + +
@@ -3479,13 +3274,35 @@ MapView - QWidget + QGraphicsView
mapview.h
+ + ConnectionsView + QGraphicsView +
graphicsview.h
+
+ + MapTree + QTreeView +
maplistmodels.h
+
NoScrollGraphicsView QGraphicsView -
mapview.h
+
graphicsview.h
+
+ + MapListToolBar + QFrame +
maplisttoolbar.h
+ 1 +
+ + CustomAttributesFrame + QFrame +
customattributesframe.h
+ 1
diff --git a/forms/mapheaderform.ui b/forms/mapheaderform.ui new file mode 100644 index 00000000..d4fc36f3 --- /dev/null +++ b/forms/mapheaderform.ui @@ -0,0 +1,252 @@ + + + MapHeaderForm + + + + 0 + 0 + 407 + 380 + + + + Qt::FocusPolicy::ClickFocus + + + Form + + + + QFormLayout::FieldGrowthPolicy::FieldsStayAtSizeHint + + + + + Song + + + + + + + <html><head/><body><p>The default background music for this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Location + + + + + + + <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Requires Flash + + + + + + + <html><head/><body><p>If checked, the player will need to use Flash to see fully on this map.</p></body></html> + + + + + + + + + + Weather + + + + + + + <html><head/><body><p>The default weather on this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Type + + + + + + + <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example, underground type maps will have a special transition effect when the player enters/exits the map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Battle Scene + + + + + + + <html><head/><body><p>This field is used to help determine what graphics to use in the background of battles on this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Show Location Name + + + + + + + <html><head/><body><p>If checked, a map name popup will appear when the player enters this map. The name that appears on this popup depends on the Location field.</p></body></html> + + + + + + + + + + Allow Running + + + + + + + <html><head/><body><p>If checked, the player will be allowed to run on this map.</p></body></html> + + + + + + + + + + Allow Biking + + + + + + + <html><head/><body><p>If checked, the player will be allowed to get on their bike on this map.</p></body></html> + + + + + + + + + + Allow Dig & Escape Rope + + + + + + + <html><head/><body><p>If checked, the player will be allowed to use Dig or Escape Rope on this map.</p></body></html> + + + + + + + + + + Floor Number + + + + + + + <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> + + + + + + + Location Name + + + + + + + <html><head/><body><p>The name that will be displayed in-game for this Location. This name will be shared with any other map that has the same Location.</p></body></html> + + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+ + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
+
+ + +
diff --git a/forms/mapimageexporter.ui b/forms/mapimageexporter.ui index 933aab67..f2f4c09a 100644 --- a/forms/mapimageexporter.ui +++ b/forms/mapimageexporter.ui @@ -7,7 +7,7 @@ 0 0 817 - 535 + 556 @@ -25,11 +25,14 @@ 0 + + Qt::FocusPolicy::ClickFocus + - + Map @@ -37,6 +40,9 @@ + + QComboBox::InsertPolicy::NoInsert + QComboBox::SizeAdjustPolicy::AdjustToContents @@ -163,7 +169,7 @@ - + Collision @@ -188,7 +194,10 @@ - + + + Qt::FocusPolicy::StrongFocus + @@ -214,7 +223,10 @@ - + + + Qt::FocusPolicy::StrongFocus + @@ -250,9 +262,22 @@ - + + + <html><head/><body><p>If checked, the image in the preview window will not be recreated when the settings are changed.</p></body></html> + - Preview actual size + Disable preview updates + + + + + + + <html><head/><body><p>If checked, the image shown in the preview window will not scale to fit into the available space. The aspect ratio of the image will never change.</p></body></html> + + + Disable preview scaling @@ -263,6 +288,9 @@ Reset + + false + @@ -283,6 +311,9 @@ Cancel + + false +
@@ -290,6 +321,9 @@ Save + + false + @@ -323,16 +357,22 @@ + + QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents + true + + Qt::AlignmentFlag::AlignCenter + 0 0 469 - 464 + 485 @@ -398,6 +438,11 @@ QComboBox
noscrollcombobox.h
+ + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
diff --git a/forms/maplisttoolbar.ui b/forms/maplisttoolbar.ui new file mode 100644 index 00000000..07878f0a --- /dev/null +++ b/forms/maplisttoolbar.ui @@ -0,0 +1,175 @@ + + + MapListToolBar + + + + 0 + 0 + 274 + 32 + + + + Form + + + + 0 + + + 3 + + + 3 + + + 0 + + + 3 + + + + + <html><head/><body><p>Add a new folder to the list.</p></body></html> + + + + + + + :/icons/folder_add.ico:/icons/folder_add.ico + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + Hide empty folders in the list. + + + + :/icons/folder_eye_open.ico + :/icons/folder_eye_closed.ico:/icons/folder_eye_open.ico + + + true + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + <html><head/><body><p>Expand all folders in the list.</p></body></html> + + + + + + + :/icons/expand_all.ico:/icons/expand_all.ico + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + <html><head/><body><p>Collapse all folders in the list.</p></body></html> + + + + + + + :/icons/collapse_all.ico:/icons/collapse_all.ico + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + <html><head/><body><p>If enabled, folders may be renamed and items in the list may be rearranged.</p></body></html> + + + + + + + :/icons/lock_edit.ico + :/icons/unlock_edit.ico:/icons/lock_edit.ico + + + true + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Preferred + + + + 12 + 19 + + + + + + + + true + + + + + + Filter... + + + true + + + + + + + + + + diff --git a/forms/newdefinedialog.ui b/forms/newdefinedialog.ui new file mode 100644 index 00000000..c816c6e3 --- /dev/null +++ b/forms/newdefinedialog.ui @@ -0,0 +1,89 @@ + + + NewDefineDialog + + + + 0 + 0 + 252 + 124 + + + + true + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Name + + + + + + + true + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + + + + + + + + Value + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + + diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui new file mode 100644 index 00000000..980b5351 --- /dev/null +++ b/forms/newlayoutdialog.ui @@ -0,0 +1,174 @@ + + + NewLayoutDialog + + + + 0 + 0 + 264 + 173 + + + + New Layout Options + + + true + + + true + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + true + + + + + 0 + 0 + 240 + 109 + + + + + 10 + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + + + + <html><head/><body><p>The constant that will be used to refer to this layout. It cannot be the same as any other existing layout.</p></body></html> + + + LAYOUT_MY_NEW_LAYOUT + + + true + + + + + + + Layout Name + + + + + + + Layout ID + + + + + + + <html><head/><body><p>The name of the new layout. The name cannot be the same as any other existing layout.</p></body></html> + + + MyNewLayout + + + true + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + + + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::Reset + + + + + + + + NewLayoutForm + QWidget +
newlayoutform.h
+ 1 +
+
+ + +
diff --git a/forms/newlayoutform.ui b/forms/newlayoutform.ui new file mode 100644 index 00000000..bc3e7502 --- /dev/null +++ b/forms/newlayoutform.ui @@ -0,0 +1,244 @@ + + + NewLayoutForm + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Map Dimensions + + + + + + + 0 + 0 + + + + Width + + + + + + + <html><head/><body><p>Width (in metatiles) of the new map.</p></body></html> + + + 1 + + + + + + + + 0 + 0 + + + + Height + + + + + + + <html><head/><body><p>Height (in metatiles) of the new map.</p></body></html> + + + 1 + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + + + + Border Dimensions + + + + + + + 0 + 0 + + + + Width + + + + + + + <html><head/><body><p>Width (in metatiles) of the new map's border.</p></body></html> + + + 1 + + + + + + + <html><head/><body><p>Height (in metatiles) of the new map's border.</p></body></html> + + + 1 + + + + + + + + 0 + 0 + + + + Height + + + + + + + + + + Tilesets + + + + + + Primary + + + + + + + <html><head/><body><p>The primary tileset for the new map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + Secondary + + + + + + + <html><head/><body><p>The secondary tileset for the new map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+ + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
+
+ + +
diff --git a/forms/newlocationdialog.ui b/forms/newlocationdialog.ui new file mode 100644 index 00000000..285e2704 --- /dev/null +++ b/forms/newlocationdialog.ui @@ -0,0 +1,89 @@ + + + NewLocationDialog + + + + 0 + 0 + 252 + 124 + + + + true + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Location ID + + + + + + + true + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + + + + + + + + Location Name + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + + diff --git a/forms/newmapconnectiondialog.ui b/forms/newmapconnectiondialog.ui index 9b3a3b6e..85aeec72 100644 --- a/forms/newmapconnectiondialog.ui +++ b/forms/newmapconnectiondialog.ui @@ -17,10 +17,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -45,7 +45,7 @@ - The name of the map to connect to the current map. + <html><head/><body><p>The name of the map to connect to the current map.</p></body></html> @@ -59,7 +59,7 @@ - Where the connected map should be positioned relative to the current map. + <html><head/><body><p>Where the connected map should be positioned relative to the current map.</p></body></html> @@ -82,10 +82,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui new file mode 100644 index 00000000..604800f8 --- /dev/null +++ b/forms/newmapdialog.ui @@ -0,0 +1,253 @@ + + + NewMapDialog + + + + 0 + 0 + 559 + 614 + + + + New Map Options + + + true + + + true + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents + + + true + + + + + 0 + 0 + 535 + 550 + + + + + 10 + + + + + Map Name + + + + + + + + QLayout::SizeConstraint::SetMinAndMaxSize + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + Layout ID + + + + + + + Map Group + + + + + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> + + + + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + + + + <html><head/><body><p>The name of the group this map will be added to.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Can Fly To + + + + + + + <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> + + + MyNewMap + + + true + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::Reset + + + + + + + + NewLayoutForm + QWidget +
newlayoutform.h
+ 1 +
+ + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+
+ + +
diff --git a/forms/newmapgroupdialog.ui b/forms/newmapgroupdialog.ui new file mode 100644 index 00000000..d475b665 --- /dev/null +++ b/forms/newmapgroupdialog.ui @@ -0,0 +1,79 @@ + + + NewMapGroupDialog + + + + 0 + 0 + 252 + 87 + + + + true + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Map Group Name + + + + + + + true + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + + diff --git a/forms/newmappopup.ui b/forms/newmappopup.ui deleted file mode 100644 index b102b1cc..00000000 --- a/forms/newmappopup.ui +++ /dev/null @@ -1,414 +0,0 @@ - - - NewMapPopup - - - - 0 - 0 - 410 - 621 - - - - New Map Options - - - - - - - false - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - QLayout::SetDefaultConstraint - - - 12 - - - - - Name - - - - - - - <html><head/><body><p>The name of the new map. If the name is invalid (red), it will be replaced with the default name of a new map.</p></body></html> - - - true - - - - - - - Group - - - - - - - <html><head/><body><p>New map group.</p></body></html> - - - true - - - - - - - Map Width - - - - - - - <html><head/><body><p>Width (in blocks) of the new map.</p></body></html> - - - 200 - - - - - - - Map Height - - - - - - - <html><head/><body><p>Height (in blocks) of the new map.</p></body></html> - - - 200 - - - - - - - Border Width - - - - - - - <html><head/><body><p>Width (in blocks) of the new map's border.</p></body></html> - - - 255 - - - - - - - Border Height - - - - - - - <html><head/><body><p>Height (in blocks) of the new map's border.</p></body></html> - - - 255 - - - - - - - Primary Tileset - - - - - - - <html><head/><body><p>The primary tileset for the new map.</p></body></html> - - - true - - - - - - - Secondary Tileset - - - - - - - <html><head/><body><p>The secondary tileset for the new map.</p></body></html> - - - true - - - - - - - Type - - - - - - - <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example. it determines whether biking or running is allowed.</p></body></html> - - - true - - - - - - - Location - - - - - - - <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.</p></body></html> - - - true - - - - - - - Song - - - - - - - <html><head/><body><p>The default background music for this map.</p></body></html> - - - true - - - - - - - Can Fly To - - - - - - - <html><head/><body><p>Whether to add a heal location to the new map.</p></body></html> - - - - - - - - - - Show Location Name - - - - - - - <html><head/><body><p>Whether or not to display the location name when the player enters the map.</p></body></html> - - - - - - - - - - Allow Running - - - - - - - <html><head/><body><p>Allows the player to use Running Shoes</p></body></html> - - - - - - - - - - Allow Biking - - - - - - - <html><head/><body><p>Allows the player to use a Bike</p></body></html> - - - - - - - - - - Allow Dig & Escape Rope - - - - - - - <html><head/><body><p>Allows the player to use Dig or Escape Rope</p></body></html> - - - - - - - - - - Floor Number - - - - - - - <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> - - - 127 - - - - - - - - - - Accept - - - - - - - true - - - - 0 - 0 - - - - false - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - false - - - ! - - - - - - - - 0 - 0 - - - - color: rgb(255, 0, 0) - - - - - - - - - - - - - - - 0 - 0 - 410 - 21 - - - - - - - - NoScrollComboBox - QComboBox -
noscrollcombobox.h
-
-
- - -
diff --git a/forms/newtilesetdialog.ui b/forms/newtilesetdialog.ui index c582eba0..66c701cd 100644 --- a/forms/newtilesetdialog.ui +++ b/forms/newtilesetdialog.ui @@ -6,238 +6,151 @@ 0 0 - 400 - 216 + 450 + 209
- - - 0 - 0 - - Add new Tileset - - - - 0 - 0 - 400 - 216 - - - - - 0 - 0 - - - - - 10 - - - 10 - - - 10 - - - 10 - - - 6 - - - - - Qt::Vertical + + true + + + + + + + 0 - - - 20 - 40 - + + 0 - - - - - - - 0 - 0 - + + 0 - - - 380 - 161 - + + 0 - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 0 - 0 - 380 - 155 - - - - - 6 + + + + Name - - 10 + + + + + + true - - 10 + + + + + + false - - 10 + + color: rgb(255, 0, 0) - - - - Name - - + + + + + + + + + Type + + + + + + + false + + + + Primary + - - - - true - - + + + Secondary + - - - - Type - - - - - - - - Primary - - - - - Secondary - - - - - - - - Path - - - - - - - false - - - true - - - - - - - Symbol Name - - - - - - - false - - - true - - - - - - - Checkerboard Fill - - - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - false - - - - - +
+
+ + + + Checkerboard Fill + + + + + + + <html><head/><body><p>If enabled, the metatiles in the new tileset will be filled alternately with tile 1 or tile 2. If not enabled, all the metatiles will be filled with tile 0.</p></body></html> + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + +
+
+
+ + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+
- - - buttonBox - accepted() - NewTilesetDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - NewTilesetDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - +
diff --git a/forms/preferenceeditor.ui b/forms/preferenceeditor.ui index a7009e6e..83de6f18 100644 --- a/forms/preferenceeditor.ui +++ b/forms/preferenceeditor.ui @@ -6,8 +6,8 @@ 0 0 - 522 - 493 + 530 + 432 @@ -19,205 +19,302 @@ 9 - - - Miscellaneous + + + 0 - - - - - If checked, a prompt to reload your project will appear if relevant project files are edited - - - Monitor project files - - - - - - - If checked, Porymap will automatically open your most recently opened project on startup - - - Open recent project on launch - - - - - - - If checked, Porymap will automatically alert you on startup if a new release is available - - - Automatically check for updates - - - - - - - - - - - 0 - 0 - - - - Application Theme - - - - - - - Preferred Text Editor - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - true - - - - - 0 - 0 - 492 - 327 - + + + General + + + + 12 + + + 12 + + + 12 + + + + + <html><head/><body><p>If checked, a prompt to reload your project will appear if relevant project files are edited</p></body></html> - - - QLayout::SetMinimumSize - - - - - <html><head/><body><p>When this command is set a button will appear next to the <span style=" font-weight:600; font-style:italic;">Script</span> combo-box in the <span style=" font-weight:600; font-style:italic;">Events</span> tab which executes this command.<span style=" font-weight:600;"> %F</span> will be substituted with the file path of the script and <span style=" font-weight:600;">%L</span> will be substituted with the line number of the script in that file. <span style=" font-weight:600;">%F </span><span style=" font-style:italic;">must</span> be given if <span style=" font-weight:600;">%L</span> is given. If <span style=" font-weight:600;">%F</span> is <span style=" font-style:italic;">not</span> given then the script's file path will be added to the end of the command. If the script can't be found then the current map's scripts file is opened.</p></body></html> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - - - - - Goto Line Command - - - - - + + Monitor project files + + + + + + + <html><head/><body><p>If checked, Porymap will automatically open your most recently opened project on startup</p></body></html> + + + Open recent project on launch + + + + + + + <html><head/><body><p>If checked, Porymap will automatically alert you on startup if a new release is available</p></body></html> + + + Automatically check for updates + + + + + + + + 0 + 0 + + + + Application Theme + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + Events + + + + 12 + + + 12 + + + 12 + + + + + <html><head/><body><p>If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted.</p></body></html> + + + Disable warning when deleting events with IDs + + + + + + + <html><head/><body><p>If checked, the list of suggestions when typing in an Event's Script field will include all global script labels in the project. Enabling this setting will make Porymap's startup slower.</p></body></html> + + + Autocomplete Script labels using all possible scripts + + + + + + + Selection Mode + + + + - The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH). + <html><head/><body><p>If enabled, an event can be selected by clicking directly on the opaque pixels of its sprite. This may be preferable when events are overlapping.</p></body></html> - - e.g. code %D - - - true - - - - - - <html><head/><body><p>This is the command that is executed when clicking <span style=" font-weight:600; font-style:italic;">Open Project in Text Editor</span> in the <span style=" font-weight:600; font-style:italic;">Tools</span> menu. <span style=" font-weight:600;">%D</span> will be substituted with the project's root directory. If <span style=" font-weight:600;">%D</span> is <span style=" font-style:italic;">not</span> specified then the project directory will be added to the end of the command.</p></body></html> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true + Select by clicking on sprite - - - - Open Directory Command - - - - - + + - The shell command for your preferred text editor to open a file to a specific line number (possibly an absolute path if the program doesn't exist in your PATH). + <html><head/><body><p>If enabled, an event can be selected by clicking anywhere within its sprite dimensions. This may be preferable for events with small or mostly transparent sprites.</p></body></html> - - e.g. code --goto %F:%L - - - true + + Select by clicking within bounding rectangle - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 15 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + Text Editor + + + + 12 + + + 12 + + + 12 + + + + + QFrame::Shape::NoFrame + + + true + + + + + 0 + 0 + 476 + 343 + + + + + QLayout::SizeConstraint::SetMinimumSize + + + + + <html><head/><body><p>When this command is set a button will appear next to the <span style=" font-weight:600; font-style:italic;">Script</span> combo-box in the <span style=" font-weight:600; font-style:italic;">Events</span> tab which executes this command.<span style=" font-weight:600;"> %F</span> will be substituted with the file path of the script and <span style=" font-weight:600;">%L</span> will be substituted with the line number of the script in that file. <span style=" font-weight:600;">%F </span><span style=" font-style:italic;">must</span> be given if <span style=" font-weight:600;">%L</span> is given. If <span style=" font-weight:600;">%F</span> is <span style=" font-style:italic;">not</span> given then the script's file path will be added to the end of the command. If the script can't be found then the current map's scripts file is opened.</p></body></html> + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + true + + + + + + + Goto Line Command + + + + + + + <html><head/><body><p>The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH).</p></body></html> + + + e.g. code %D + + + true + + + + + + + <html><head/><body><p>This is the command that is executed when clicking <span style=" font-weight:600; font-style:italic;">Open Project in Text Editor</span> in the <span style=" font-weight:600; font-style:italic;">Tools</span> menu. <span style=" font-weight:600;">%D</span> will be substituted with the project's root directory. If <span style=" font-weight:600;">%D</span> is <span style=" font-style:italic;">not</span> specified then the project directory will be added to the end of the command.</p></body></html> + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + true + + + + + + + Open Directory Command + + + + + + + <html><head/><body><p>The shell command for your preferred text editor to open a file to a specific line number (possibly an absolute path if the program doesn't exist in your PATH).</p></body></html> + + + e.g. code --goto %F:%L + + + true + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Fixed + + + + 20 + 15 + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index dc6d730e..c840d331 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -6,8 +6,8 @@ 0 0 - 631 - 600 + 642 + 609 @@ -38,8 +38,8 @@ 0 0 - 559 - 568 + 570 + 680 @@ -52,7 +52,7 @@ - Whether map script files should prefer using .pory + <html><head/><body><p>Whether map script files should prefer using .pory</p></body></html> Use Poryscript @@ -61,11 +61,24 @@ + + <html><head/><body><p>If enabled, Porymap will display wild encounter data on the Wild Pokémon tab.</p></body></html> + Show Wild Encounter Tables + + + + <html><head/><body><p>If enabled, Porymap will not discard data like &quot;connections_include_order&quot; or &quot;name_clone&quot;, which serve no purpose other than recreating the original game.</p></body></html> + + + Preserve data only needed to match the original game + + + @@ -81,7 +94,7 @@ ... - + :/icons/folder.ico:/icons/folder.ico @@ -89,7 +102,7 @@ - Restore the data in the prefabs file to the version defaults. Will create a new file if one doesn't exist. + <html><head/><body><p>Restore the data in the prefabs file to the version defaults. Will create a new file if one doesn't exist.</p></body></html> Import Defaults @@ -99,7 +112,7 @@ - The file that will be used to populate the Prefabs tab + <html><head/><body><p>The file that will be used to populate the Prefabs tab</p></body></html> prefabs.json @@ -125,7 +138,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -138,7 +151,7 @@ - The image sheet that will be used to represent elevation and collision on the Collision tab + <html><head/><body><p>The image sheet that will be used to represent elevation and collision on the Collision tab</p></body></html> true @@ -151,7 +164,7 @@ ... - + :/icons/folder.ico:/icons/folder.ico @@ -166,7 +179,7 @@ - The maximum collision value represented with an icon on the image sheet + <html><head/><body><p>The maximum collision value represented with an icon on the image sheet</p></body></html> @@ -187,17 +200,17 @@ - The maximum elevation value represented with an icon on the image sheet + <html><head/><body><p>The maximum elevation value represented with an icon on the image sheet</p></body></html> - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Maximum + QSizePolicy::Policy::Maximum @@ -235,7 +248,7 @@ ... - + :/icons/folder.ico:/icons/folder.ico @@ -260,7 +273,7 @@ - The icon that will be displayed on the Wild Pokémon tab for the above species + <html><head/><body><p>The icon that will be displayed on the Wild Pokémon tab for the above species</p></body></html> true @@ -270,16 +283,101 @@ + + + + Player View Distance + + + + + + + + North + + + + + + + South + + + + + + + <html><head/><body><p>The distance (in pixels) that a player is able to see North of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 160 pixel tall GBA screen.</p></body></html> + + + 0 + + + + + + + <html><head/><body><p>The distance (in pixels) that a player is able to see South of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 160 pixel tall GBA screen.</p></body></html> + + + 0 + + + + + + + + + + + West + + + + + + + East + + + + + + + <html><head/><body><p>The distance (in pixels) that a player is able to see West of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 240 pixel wide GBA screen.</p></body></html> + + + 0 + + + + + + + <html><head/><body><p>The distance (in pixels) that a player is able to see East of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 240 pixel wide GBA screen.</p></body></html> + + + 0 + + + + + + + + .QFrame { border: 1px solid red; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -287,7 +385,6 @@ 12 - 75 true @@ -319,7 +416,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -338,7 +435,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -369,8 +466,8 @@ 0 0 - 559 - 548 + 570 + 622 @@ -380,37 +477,6 @@ Map Data Defaults - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - The default metatile value that will be used to fill new maps - - - 0x - - - 16 - - - @@ -418,6 +484,50 @@ + + + + Width + + + + + + + <html><head/><body><p>The default elevation that will be used to fill new maps</p></body></html> + + + + + + + <html><head/><body><p>Whether a separate text.inc or text.pory file will be created for new maps, alongside the scripts file</p></body></html> + + + Create separate text file + + + + + + + <html><head/><body><p>The default layout width for new maps</p></body></html> + + + 1 + + + + + + + <html><head/><body><p>The default layout height for new maps</p></body></html> + + + 1 + + + @@ -425,13 +535,10 @@ - - - - Whether a separate text.inc or text.pory file will be created for new maps, alongside the scripts file - + + - Create separate text file + Collision @@ -442,6 +549,20 @@ + + + + <html><head/><body><p>The default metatile value that will be used to fill new maps</p></body></html> + + + + + + + <html><head/><body><p>The default collision that will be used to fill new maps</p></body></html> + + + @@ -461,7 +582,7 @@ - A comma-separated list of metatile values that will be used to fill new map borders + <html><head/><body><p>A comma-separated list of metatile values that will be used to fill new map borders</p></body></html> @@ -482,54 +603,30 @@ 0 - + - The default metatile value that will be used for the top-left border metatile on new maps. - - - 0x - - - 16 + <html><head/><body><p>The default metatile value that will be used for the top-left border metatile on new maps.</p></body></html> - + - The default metatile value that will be used for the top-right border metatile on new maps. - - - 0x - - - 16 + <html><head/><body><p>The default metatile value that will be used for the top-right border metatile on new maps.</p></body></html> - + - The default metatile value that will be used for the bottom-left border metatile on new maps. - - - 0x - - - 16 + <html><head/><body><p>The default metatile value that will be used for the bottom-left border metatile on new maps.</p></body></html> - + - The default metatile value that will be used for the bottom-right border metatile on new maps. - - - 0x - - - 16 + <html><head/><body><p>The default metatile value that will be used for the bottom-right border metatile on new maps.</p></body></html> @@ -537,24 +634,28 @@ - - - - The default elevation that will be used to fill new maps - + + + + + 0 + + + 0 + + + 0 + + + 0 + + - - + + - Collision - - - - - - - The default collision that will be used to fill new maps + Height @@ -567,10 +668,10 @@ .QFrame { border: 1px solid red; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -578,7 +679,6 @@ 12 - 75 true @@ -604,9 +704,9 @@ - + - The mask used to read/write metatile IDs in map data. + <html><head/><body><p>The mask used to read/write metatile IDs in map data.</p></body></html> @@ -618,9 +718,9 @@ - + - The mask used to read/write collision values in map data. + <html><head/><body><p>The mask used to read/write collision values in map data.</p></body></html> @@ -632,9 +732,9 @@ - + - The mask used to read/write elevation values in map data. + <html><head/><body><p>The mask used to read/write elevation values in map data.</p></body></html> @@ -663,7 +763,7 @@ - Whether "Allow Running", "Allow Biking" and "Allow Dig & Escape Rope" are default options for Map Headers + <html><head/><body><p>Whether &quot;Allow Running&quot;, &quot;Allow Biking&quot; and &quot;Allow Dig &amp; Escape Rope&quot; are default options for Map Headers</p></body></html> Enable 'Allow Running/Biking/Escaping' @@ -673,7 +773,7 @@ - Whether "Floor Number" is a default option for Map Headers + <html><head/><body><p>Whether &quot;Floor Number&quot; is a default option for Map Headers</p></body></html> Enable 'Floor Number' @@ -683,7 +783,7 @@ - Whether the dimensions of the border can be changed. If not set, all borders are 2x2 + <html><head/><body><p>Whether the dimensions of the border can be changed. If not set, all borders are 2x2</p></body></html> Enable Custom Border Size @@ -693,7 +793,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -712,7 +812,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -743,8 +843,8 @@ 0 0 - 559 - 568 + 570 + 798 @@ -762,7 +862,11 @@ - + + + <html><head/><body><p>The default primary tileset to use for new maps/layouts.</p></body></html> + + @@ -772,7 +876,91 @@ - + + + <html><head/><body><p>The default secondary tileset to use for new maps/layouts.</p></body></html> + + + + + + + + + + Transparent Pixel Rendering + + + + + + <html><head/><body><p>Fully transparent pixels will be rendered as black pixels (the Pokémon games do this by default)</p></body></html> + + + Render as black + + + + + + + <html><head/><body><p>Fully transparent pixels will be rendered using the first palette color (this the default behavior for the GBA)</p></body></html> + + + Render using first palette color + + + + + + + + + + Unused Layer Rendering + + + + + + Normal + + + + + + + <html><head/><body><p>This raw tile value will be used to fill the unused bottom layer of Normal metatiles</p></body></html> + + + + + + + Covered + + + + + + + <html><head/><body><p>This raw tile value will be used to fill the unused top layer of Covered metatiles</p></body></html> + + + + + + + Split + + + + + + + <html><head/><body><p>This raw tile value will be used to fill the unused middle layer of Split metatiles</p></body></html> + + @@ -783,10 +971,10 @@ .QFrame { border: 1px solid red; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -794,7 +982,6 @@ 12 - 75 true @@ -813,24 +1000,21 @@ - + - The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled. + <html><head/><body><p>The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled.</p></body></html> - + - The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled. + <html><head/><body><p>The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled.</p></body></html> - - The number of bytes used per metatile for metatile attributes - Attributes size (in bytes) @@ -839,10 +1023,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Maximum + QSizePolicy::Policy::Maximum @@ -861,15 +1045,18 @@ + + <html><head/><body><p>If checked, metatiles will be interpreted as having 3 layers of 4 tiles each (12 tiles total) as opposed to the default 2 layers of 4 tiles each (8 total).</p></body></html> + Enable Triple Layer Metatiles - + - The mask used to read/write Terrain Type from the metatile's attributes data. If 0, this attribute is disabled. + <html><head/><body><p>The mask used to read/write Terrain Type from the metatile's attributes data. If 0, this attribute is disabled.</p></body></html> @@ -894,9 +1081,9 @@ - + - The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled. + <html><head/><body><p>The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled.</p></body></html> @@ -909,6 +1096,9 @@ + + <html><head/><body><p>The number of bytes each metatile has for metatile attributes. This is the metadata about each metatile like behvior, layer type, etc.</p></body></html> + false @@ -924,10 +1114,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::MinimumExpanding + QSizePolicy::Policy::MinimumExpanding @@ -949,7 +1139,7 @@ - Whether the C data outputted for new tilesets will include the "callback" field + <html><head/><body><p>Whether the C data outputted for new tilesets will include the &quot;callback&quot; field</p></body></html> Output 'callback' field @@ -959,7 +1149,7 @@ - Whether the C data outputted for new tilesets will include the "isCompressed" field + <html><head/><body><p>Whether the C data outputted for new tilesets will include the &quot;isCompressed&quot; field</p></body></html> Output 'isCompressed' field @@ -975,7 +1165,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1006,24 +1196,63 @@ 0 0 - 559 - 788 + 570 + 927 + + + + Tab Icon + + + + + + false + + + ... + + + + :/icons/folder.ico:/icons/folder.ico + + + + + + + false + + + <html><head/><body><p>The image file path to use for the icon of the Events tab.</p></body></html> + + + true + + + + + + + <html><head/><body><p>The icon that will be displayed for the Events tab in the editor. If 'Automatic' is chosen, the icon will be a random player character from the project's base game version. If 'Custom' is chosen an image file path may be specified.</p></body></html> + + + false + + + + + + Default Icons - - - - Triggers - - - @@ -1031,47 +1260,24 @@ - - + + - The icon that will be used to represent Warp events + <html><head/><body><p>The icon that will be used to represent BG events</p></body></html> true - - - - The icon that will be used to represent Healspot events - - - true - - - - - + + - BGs + ... - - - - - - Healspots - - - - - - - The icon that will be used to represent Object events that don't have their own sprite - - - true + + + :/icons/folder.ico:/icons/folder.ico @@ -1082,34 +1288,17 @@ - - - - The icon that will be used to represent Trigger events - - - true - - - - - - - The icon that will be used to represent BG events - - - true - - - - - + + - ... + BGs - - - :/icons/folder.ico:/icons/folder.ico + + + + + + Triggers @@ -1119,19 +1308,35 @@ ... - + :/icons/folder.ico:/icons/folder.ico - - + + - ... + Heal Locations - - - :/icons/folder.ico:/icons/folder.ico + + + + + + <html><head/><body><p>The icon that will be used to represent Object events that don't have their own sprite</p></body></html> + + + true + + + + + + + <html><head/><body><p>The icon that will be used to represent Warp events</p></body></html> + + + true @@ -1141,22 +1346,53 @@ ... - + :/icons/folder.ico:/icons/folder.ico - - + + + + <html><head/><body><p>The icon that will be used to represent Heal Location events</p></body></html> + + + true + + + + + ... - + :/icons/folder.ico:/icons/folder.ico + + + + ... + + + + :/icons/folder.ico:/icons/folder.ico + + + + + + + <html><head/><body><p>The icon that will be used to represent Trigger events</p></body></html> + + + true + + + @@ -1169,13 +1405,13 @@ - Remove the current text from the list + <html><head/><body><p>Remove the current text from the list</p></body></html> ... - + :/icons/delete.ico:/icons/delete.ico @@ -1193,7 +1429,7 @@ - If checked, Warp Events will not display a warning about incompatible metatile behaviors + <html><head/><body><p>If checked, Warp Events will not display a warning about incompatible metatile behaviors</p></body></html> Disable Warning @@ -1204,15 +1440,15 @@ - + - Metatile Behaviors on this list won't trigger warnings for Warp Events + <html><head/><body><p>Metatile Behaviors on this list won't trigger warnings for Warp Events</p></body></html> true - Qt::NoTextInteraction + Qt::TextInteractionFlag::NoTextInteraction Use the dropbown and buttons to add behaviors to the list... @@ -1222,13 +1458,13 @@ - Add the current text to the list + <html><head/><body><p>Add the current text to the list</p></body></html> ... - + :/icons/add.ico:/icons/add.ico @@ -1242,10 +1478,10 @@ .QFrame { border: 1px solid red; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -1253,7 +1489,6 @@ 12 - 75 true @@ -1265,6 +1500,29 @@ + + + + + + + Maximum Events per Event group + + + + + + + <html><head/><body><p>Maps cannot have more than this number of events in each event group. Object events are additionally limited by 'define_obj_event_count' on the Identifiers tab.</p></body></html> + + + 1 + + + + + + @@ -1316,7 +1574,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1335,7 +1593,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1366,28 +1624,25 @@ 0 0 - 559 - 490 + 570 + 499 - - - - - <html><head/><body><p><a href="https://huderlem.github.io/porymap/manual/project-files.html#files"><span style=" text-decoration: underline; color:#0069d9;">Help</span></a></p></body></html> + + + + + Qt::Orientation::Horizontal - - Qt::RichText + + + 40 + 20 + - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - true - - + - + @@ -1418,8 +1673,8 @@ 0 0 - 533 - 440 + 544 + 341 @@ -1439,6 +1694,34 @@ + + + + <html><head/><body><p>Add additional C files containing #defines or enums. These will be used to resolve unknown symbols during project launch.</p></body></html> + + + Add Global Constants File... + + + + :/icons/add.ico:/icons/add.ico + + + + + + + ... + + + + :/icons/help.ico:/icons/help.ico + + + + + + @@ -1460,28 +1743,23 @@ 0 0 - 559 - 490 + 570 + 499 - - - + + + - <html><head/><body><p><a href="https://huderlem.github.io/porymap/manual/project-files.html#identifiers"><span style=" text-decoration: underline; color:#0069d9;">Help</span></a></p></body></html> + ... - - Qt::RichText - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - true + + + :/icons/help.ico:/icons/help.ico - + @@ -1512,8 +1790,8 @@ 0 0 - 533 - 440 + 544 + 425 @@ -1533,6 +1811,36 @@ + + + + <html><head/><body><p>Add an additional #define name and expression. This may be used to evaluate other #defines during project launch.</p></body></html> + + + Add Global Constant... + + + + :/icons/add.ico:/icons/add.ico + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + @@ -1544,7 +1852,7 @@ - QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::RestoreDefaults @@ -1563,13 +1871,21 @@
noscrollspinbox.h
- UIntHexSpinBox - QWidget + NoScrollTextEdit + QTextEdit +
noscrolltextedit.h
+
+ + UIntSpinBox + QAbstractSpinBox
uintspinbox.h
+ + UIntHexSpinBox + UIntSpinBox +
uintspinbox.h
+
- - - + diff --git a/forms/regionmapeditor.ui b/forms/regionmapeditor.ui index 17cff63e..66787b7b 100644 --- a/forms/regionmapeditor.ui +++ b/forms/regionmapeditor.ui @@ -18,10 +18,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -46,14 +46,14 @@ - QComboBox::AdjustToContents + QComboBox::SizeAdjustPolicy::AdjustToContents - Qt::Horizontal + Qt::Orientation::Horizontal @@ -83,7 +83,7 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus 10 @@ -95,10 +95,10 @@ 30 - Qt::Vertical + Qt::Orientation::Vertical - QSlider::NoTicks + QSlider::TickPosition::NoTicks 1 @@ -108,7 +108,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -123,7 +123,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal false @@ -160,7 +160,7 @@ 0 0 466 - 351 + 336 @@ -182,7 +182,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -207,17 +207,17 @@ false - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - QGraphicsView::NoDrag + QGraphicsView::DragMode::NoDrag - Qt::Vertical + Qt::Orientation::Vertical @@ -230,7 +230,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -243,7 +243,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -281,7 +281,7 @@ 0 0 466 - 351 + 336 @@ -315,17 +315,17 @@ false - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - QGraphicsView::NoDrag + QGraphicsView::DragMode::NoDrag - Qt::Horizontal + Qt::Orientation::Horizontal @@ -338,7 +338,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -351,7 +351,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -364,7 +364,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -402,7 +402,7 @@ 0 0 466 - 351 + 336 @@ -436,17 +436,17 @@ false - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - QGraphicsView::NoDrag + QGraphicsView::DragMode::NoDrag - Qt::Horizontal + Qt::Orientation::Horizontal @@ -459,7 +459,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -472,7 +472,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -485,7 +485,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -529,19 +529,19 @@ - Qt::ScrollBarAlwaysOn + Qt::ScrollBarPolicy::ScrollBarAlwaysOn - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored true - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop @@ -552,7 +552,7 @@ 8 0 278 - 342 + 327 @@ -563,7 +563,7 @@ - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint 0 @@ -583,7 +583,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -596,7 +596,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -618,20 +618,20 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - Qt::Horizontal + Qt::Orientation::Horizontal @@ -648,10 +648,10 @@ - QFrame::Panel + QFrame::Shape::Panel - QFrame::Sunken + QFrame::Shadow::Sunken @@ -681,7 +681,7 @@ - + 15 @@ -719,10 +719,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised 1 @@ -747,7 +747,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -760,10 +760,10 @@ - QLayout::SetNoConstraint + QLayout::SizeConstraint::SetNoConstraint - + 0 @@ -771,10 +771,10 @@ - <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> + <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.</p></body></html> - - true + + false @@ -803,7 +803,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -816,10 +816,10 @@ - + - + @@ -862,10 +862,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised 1 @@ -874,15 +874,15 @@ - QLayout::SetNoConstraint + QLayout::SizeConstraint::SetNoConstraint - + - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -893,10 +893,10 @@ - + - + @@ -909,7 +909,7 @@ - + true @@ -922,25 +922,25 @@ <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 + + false - - + + Dimensions - + - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -951,10 +951,10 @@ - + - + @@ -966,7 +966,7 @@ - + Location @@ -980,16 +980,6 @@ - - - - Map Name - - - - - - @@ -1002,7 +992,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1037,7 +1027,7 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus 10 @@ -1049,10 +1039,10 @@ 30 - Qt::Vertical + Qt::Orientation::Vertical - QSlider::NoTicks + QSlider::TickPosition::NoTicks 1 @@ -1062,7 +1052,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1082,7 +1072,7 @@ 0 0 957 - 22 + 37 @@ -1180,9 +1170,14 @@ NoScrollComboBox - QWidget + QComboBox
noscrollcombobox.h
+ + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
diff --git a/forms/regionmappropertiesdialog.ui b/forms/regionmappropertiesdialog.ui index 80e7020a..88b465f4 100644 --- a/forms/regionmappropertiesdialog.ui +++ b/forms/regionmappropertiesdialog.ui @@ -21,7 +21,7 @@
- QFormLayout::AllNonFixedFieldsGrow + QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow @@ -33,7 +33,7 @@ - A nickname for this region map that will differentiate it from others (should be unique). + <html><head/><body><p>A nickname for this region map that will differentiate it from others (should be unique).</p></body></html> @@ -131,7 +131,7 @@
- The height of the tilemap + <html><head/><body><p>The height of the tilemap</p></body></html> 255 @@ -148,10 +148,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -218,10 +218,10 @@ <html><head/><body><p>Path to the tilemap binary relative to the project root.</p></body></html> - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -269,10 +269,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -392,10 +392,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -487,7 +487,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -517,7 +517,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -590,7 +590,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -617,7 +617,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -646,7 +646,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -659,10 +659,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/forms/resizelayoutpopup.ui b/forms/resizelayoutpopup.ui new file mode 100644 index 00000000..c98fe10f --- /dev/null +++ b/forms/resizelayoutpopup.ui @@ -0,0 +1,276 @@ + + + ResizeLayoutPopup + + + + 0 + 0 + 598 + 378 + + + + Dialog + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Resize Layout + + + Qt::AlignCenter + + + 8 + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + + + true + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Width + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 64 + 0 + + + + + + + + Height + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 64 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Border Width + + + + + + + + 64 + 0 + + + + + + + + Border Height + + + + + + + + 64 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
+
+ + + + buttonBox + accepted() + ResizeLayoutPopup + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ResizeLayoutPopup + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index c144e5f2..1ff9d0fb 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -6,12 +6,12 @@ 0 0 - 727 - 700 + 733 + 784
- Qt::ClickFocus + Qt::FocusPolicy::ClickFocus Tileset Editor @@ -21,7 +21,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal false @@ -34,10 +34,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -58,15 +58,15 @@ true - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop 0 0 - 223 - 593 + 239 + 659 @@ -88,17 +88,17 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::Vertical + Qt::Orientation::Vertical @@ -121,7 +121,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -129,10 +129,10 @@
- QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -162,14 +162,14 @@
- QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised - QLayout::SetMinimumSize + QLayout::SizeConstraint::SetMinimumSize 0 @@ -195,6 +195,70 @@ false + + + + true + + + + + + + + 0 + 0 + + + + + 185 + 0 + + + + + + + + Metatile Label (Optional) + + + + + + + Bottom/Top + + + + + + + Terrain Type + + + + + + + Layer Type + + + + + + + + 0 + 0 + + + + QComboBox::InsertPolicy::NoInsert + + + @@ -216,10 +280,10 @@
- Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff
@@ -231,17 +295,23 @@ 0
+ + QComboBox::InsertPolicy::NoInsert +
- - + + - Qt::Vertical + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Maximum - 20 - 1 + 10 + 20 @@ -254,16 +324,33 @@ 0
- -
- - - - Metatile Label (Optional) + + QComboBox::InsertPolicy::NoInsert - + + + + Metatile Behavior + + + + + + + Raw Attributes Value + + + + + + + Encounter Type + + + + <html><head/><body><p>Copies the full metatile label to the clipboard.</p></body></html> @@ -277,89 +364,21 @@ - - - - - 0 - 0 - - - - - 185 - 0 - - - - - - - - - 0 - 0 - - - - - - - - Layer Type - - - - - - - true - - - - - - - Metatile Behavior - - - - - + + - Qt::Horizontal - - - QSizePolicy::Maximum + Qt::Orientation::Vertical - 10 - 20 + 20 + 1 - - - - Encounter Type - - - - - - - Terrain Type - - - - - - - Bottom/Top - - + +
@@ -399,7 +418,7 @@ - Qt::LeftToRight + Qt::LayoutDirection::LeftToRight @@ -423,10 +442,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed @@ -451,10 +470,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -497,13 +516,13 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff @@ -513,7 +532,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -535,15 +554,15 @@ true - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop 0 0 - 455 - 232 + 445 + 237 @@ -562,17 +581,17 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::Vertical + Qt::Orientation::Vertical @@ -589,7 +608,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -604,8 +623,8 @@ 0 0 - 727 - 22 + 733 + 37 @@ -647,6 +666,8 @@ + + @@ -799,6 +820,22 @@ Ctrl+G + + + true + + + Show Tileset Divider + + + + + true + + + Show Raw Metatile Attributes + + @@ -811,6 +848,16 @@ QGraphicsView
mapview.h
+ + UIntSpinBox + QAbstractSpinBox +
uintspinbox.h
+
+ + UIntHexSpinBox + UIntSpinBox +
uintspinbox.h
+
diff --git a/forms/wildmonchart.ui b/forms/wildmonchart.ui index 488e066e..05c0ba44 100644 --- a/forms/wildmonchart.ui +++ b/forms/wildmonchart.ui @@ -145,7 +145,7 @@ false - QComboBox::AdjustToMinimumContentsLength + QComboBox::AdjustToMinimumContentsLengthWithIcon 8 @@ -197,7 +197,7 @@
- + true @@ -237,6 +237,11 @@ QGraphicsView
QtCharts
+ + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
diff --git a/forms/wildmonsearch.ui b/forms/wildmonsearch.ui new file mode 100644 index 00000000..117f7242 --- /dev/null +++ b/forms/wildmonsearch.ui @@ -0,0 +1,94 @@ + + + WildMonSearch + + + + 0 + 0 + 547 + 329 + + + + Wild Pokémon Search + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + :/images/pokemon_icon_placeholder.png + + + Qt::AlignmentFlag::AlignCenter + + + + + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + + + + 4 + + + + + + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+
+ + + + +
diff --git a/include/config.h b/include/config.h index b42bd4f8..2e99dc3e 100644 --- a/include/config.h +++ b/include/config.h @@ -11,27 +11,23 @@ #include #include #include +#include #include "events.h" +#include "gridsettings.h" -static const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_VERSION); +extern const QVersionNumber porymapVersion; -// In both versions the default new map border is a generic tree -#define DEFAULT_BORDER_RSE (QList{0x1D4, 0x1D5, 0x1DC, 0x1DD}) -#define DEFAULT_BORDER_FRLG (QList{0x14, 0x15, 0x1C, 0x1D}) +// Distance in pixels from the edge of a GBA screen (240x160) to the center 16x16 pixels. +#define GBA_H_DIST_TO_CENTER ((240-16)/2) +#define GBA_V_DIST_TO_CENTER ((160-16)/2) #define CONFIG_BACKWARDS_COMPATABILITY -enum MapSortOrder { - Group = 0, - Area = 1, - Layout = 2, -}; - class KeyValueConfigBase { public: - void save(); + bool save(); void load(); virtual ~KeyValueConfigBase(); virtual void reset() = 0; @@ -41,9 +37,11 @@ protected: virtual QMap getKeyValueMap() = 0; virtual void init() = 0; virtual void setUnreadKeys() = 0; - bool getConfigBool(QString key, QString value); - int getConfigInteger(QString key, QString value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0); - uint32_t getConfigUint32(QString key, QString value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0); + + static bool getConfigBool(const QString &key, const QString &value); + static int getConfigInteger(const QString &key, const QString &value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0); + static uint32_t getConfigUint32(const QString &key, const QString &value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0); + static QColor getConfigColor(const QString &key, const QString &value, const QColor &defaultValue = Qt::black); }; class PorymapConfig: public KeyValueConfigBase @@ -56,7 +54,9 @@ public: this->recentProjects.clear(); this->projectManuallyClosed = false; this->reopenOnLaunch = true; - this->mapSortOrder = MapSortOrder::Group; + this->mapListTab = 0; + this->mapListEditGroupsEnabled = false; + this->mapListHideEmptyEnabled.clear(); this->prettyCursors = true; this->mirrorConnectingMaps = true; this->showDiveEmergeMaps = false; @@ -74,19 +74,28 @@ public: this->showGrid = false; this->showTilesetEditorMetatileGrid = false; this->showTilesetEditorLayerGrid = true; + this->showTilesetEditorDivider = false; + this->showTilesetEditorRawAttributes = false; this->monitorFiles = true; this->tilesetCheckerboardFill = true; + this->newMapHeaderSectionExpanded = false; this->theme = "default"; this->wildMonChartTheme = ""; this->textEditorOpenFolder = ""; this->textEditorGotoLine = ""; this->paletteEditorBitDepth = 24; this->projectSettingsTab = 0; + this->loadAllEventScripts = false; this->warpBehaviorWarningDisabled = false; + this->eventDeleteWarningDisabled = false; + this->eventOverlayEnabled = false; this->checkForUpdates = true; this->lastUpdateCheckTime = QDateTime(); this->lastUpdateCheckVersion = porymapVersion; this->rateLimitTimes.clear(); + this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape; + this->shownInGameReloadMessage = false; + this->gridSettings = GridSettings(); } void addRecentProject(QString project); void setRecentProjects(QStringList projects); @@ -107,7 +116,9 @@ public: bool reopenOnLaunch; bool projectManuallyClosed; - MapSortOrder mapSortOrder; + int mapListTab; + bool mapListEditGroupsEnabled; + QMap mapListHideEmptyEnabled; bool prettyCursors; bool mirrorConnectingMaps; bool showDiveEmergeMaps; @@ -125,20 +136,31 @@ public: bool showGrid; bool showTilesetEditorMetatileGrid; bool showTilesetEditorLayerGrid; + bool showTilesetEditorDivider; + bool showTilesetEditorRawAttributes; bool monitorFiles; bool tilesetCheckerboardFill; + bool newMapHeaderSectionExpanded; QString theme; QString wildMonChartTheme; QString textEditorOpenFolder; QString textEditorGotoLine; int paletteEditorBitDepth; int projectSettingsTab; + bool loadAllEventScripts; bool warpBehaviorWarningDisabled; + bool eventDeleteWarningDisabled; + bool eventOverlayEnabled; bool checkForUpdates; QDateTime lastUpdateCheckTime; QVersionNumber lastUpdateCheckVersion; QMap rateLimitTimes; + QGraphicsPixmapItem::ShapeMode eventSelectionShapeMode; QByteArray wildMonChartGeometry; + QByteArray newMapDialogGeometry; + QByteArray newLayoutDialogGeometry; + bool shownInGameReloadMessage; + GridSettings gridSettings; protected: virtual QString getConfigFilepath() override; @@ -148,8 +170,8 @@ protected: virtual void setUnreadKeys() override {}; private: - QString stringFromByteArray(QByteArray); - QByteArray bytesFromString(QString); + QString stringFromByteArray(const QByteArray&); + QByteArray bytesFromString(const QString&); QStringList recentProjects; QByteArray mainWindowGeometry; @@ -183,14 +205,9 @@ enum ProjectIdentifier { symbol_facing_directions, symbol_obj_event_gfx_pointers, symbol_pokemon_icon_table, - symbol_wild_encounters, - symbol_heal_locations_type, - symbol_heal_locations, - symbol_spawn_points, - symbol_spawn_maps, - symbol_spawn_npcs, symbol_attribute_table, symbol_tilesets_prefix, + symbol_dynamic_map_name, define_obj_event_count, define_min_level, define_max_level, @@ -202,6 +219,8 @@ enum ProjectIdentifier { define_pals_total, define_tiles_per_metatile, define_map_size, + define_map_offset_width, + define_map_offset_height, define_mask_metatile, define_mask_collision, define_mask_elevation, @@ -213,14 +232,14 @@ enum ProjectIdentifier { define_attribute_encounter, define_metatile_label_prefix, define_heal_locations_prefix, - define_spawn_prefix, + define_layout_prefix, define_map_prefix, define_map_dynamic, define_map_empty, define_map_section_prefix, define_map_section_empty, - define_map_section_count, define_species_prefix, + define_species_empty, regex_behaviors, regex_obj_event_gfx, regex_items, @@ -235,6 +254,12 @@ enum ProjectIdentifier { regex_sign_facing_directions, regex_trainer_types, regex_music, + regex_encounter_types, + regex_terrain_types, + regex_gbapal, + regex_bpp, + pals_output_extension, + tiles_output_extension, }; enum ProjectFilePath { @@ -246,6 +271,7 @@ enum ProjectFilePath { json_map_groups, json_layouts, json_wild_encounters, + json_heal_locations, json_region_map_entries, json_region_porymap_cfg, tilesets_headers, @@ -259,15 +285,12 @@ enum ProjectFilePath { data_obj_event_pic_tables, data_obj_event_gfx, data_pokemon_gfx, - data_heal_locations, constants_global, - constants_map_groups, constants_items, constants_flags, constants_vars, constants_weather, constants_songs, - constants_heal_locations, constants_pokemon, constants_map_types, constants_trainer_types, @@ -275,7 +298,6 @@ enum ProjectFilePath { constants_obj_event_movement, constants_obj_events, constants_event_bg, - constants_region_map_sections, constants_metatile_labels, constants_metatile_behaviors, constants_species, @@ -302,20 +324,31 @@ public: this->defaultMetatileId = 1; this->defaultElevation = 3; this->defaultCollision = 0; + this->defaultMapSize = QSize(20,20); this->defaultPrimaryTileset = "gTileset_General"; this->prefabFilepath = QString(); this->prefabImportPrompted = false; this->tilesetsHaveCallback = true; this->tilesetsHaveIsCompressed = true; + this->setTransparentPixelsBlack = true; + this->preserveMatchingOnlyData = false; this->filePaths.clear(); this->eventIconPaths.clear(); this->pokemonIconPaths.clear(); + this->eventsTabIconPath = QString(); this->collisionSheetPath = QString(); - this->collisionSheetWidth = 2; - this->collisionSheetHeight = 16; + this->collisionSheetSize = QSize(2, 16); + this->playerViewDistance = QMargins(GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER, GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER); this->blockMetatileIdMask = 0x03FF; this->blockCollisionMask = 0x0C00; this->blockElevationMask = 0xF000; + this->unusedTileNormal = 0x3014; + this->unusedTileCovered = 0x0000; + this->unusedTileSplit = 0x0000; + this->maxEventsPerGroup = 255; + this->forcedMajorVersion = 0; + this->globalConstantsFilepaths.clear(); + this->globalConstants.clear(); this->identifiers.clear(); this->readKeys.clear(); } @@ -324,13 +357,16 @@ public: static const QStringList versionStrings; static BaseGameVersion stringToBaseGameVersion(const QString &string); + static QString getPlayerIconPath(BaseGameVersion baseGameVersion, int character); + static QIcon getPlayerIcon(BaseGameVersion baseGameVersion, int character); + void reset(BaseGameVersion baseGameVersion); void setFilePath(ProjectFilePath pathId, const QString &path); void setFilePath(const QString &pathId, const QString &path); QString getCustomFilePath(ProjectFilePath pathId); QString getCustomFilePath(const QString &pathId); QString getFilePath(ProjectFilePath pathId); - void setIdentifier(ProjectIdentifier id, const QString &text); + void setIdentifier(ProjectIdentifier id, QString text); void setIdentifier(const QString &id, const QString &text); QString getCustomIdentifier(ProjectIdentifier id); QString getCustomIdentifier(const QString &id); @@ -343,7 +379,7 @@ public: QString getEventIconPath(Event::Group group); void setPokemonIconPath(const QString &species, const QString &path); QString getPokemonIconPath(const QString &species); - QHash getPokemonIconPaths(); + QMap getPokemonIconPaths(); BaseGameVersion baseGameVersion; QString projectDir; @@ -361,6 +397,7 @@ public: uint16_t defaultMetatileId; uint16_t defaultElevation; uint16_t defaultCollision; + QSize defaultMapSize; QList newMapBorderMetatileIds; QString defaultPrimaryTileset; QString defaultSecondaryTileset; @@ -368,6 +405,8 @@ public: bool prefabImportPrompted; bool tilesetsHaveCallback; bool tilesetsHaveIsCompressed; + bool setTransparentPixelsBlack; + bool preserveMatchingOnlyData; int metatileAttributesSize; uint32_t metatileBehaviorMask; uint32_t metatileTerrainTypeMask; @@ -376,11 +415,19 @@ public: uint16_t blockMetatileIdMask; uint16_t blockCollisionMask; uint16_t blockElevationMask; + uint16_t unusedTileNormal; + uint16_t unusedTileCovered; + uint16_t unusedTileSplit; bool mapAllowFlagsEnabled; + QString eventsTabIconPath; QString collisionSheetPath; - int collisionSheetWidth; - int collisionSheetHeight; - QSet warpBehaviors; + QSize collisionSheetSize; + QMargins playerViewDistance; + QList warpBehaviors; + int maxEventsPerGroup; + int forcedMajorVersion; + QStringList globalConstantsFilepaths; + QMap globalConstants; protected: virtual QString getConfigFilepath() override; @@ -394,7 +441,7 @@ private: QMap identifiers; QMap filePaths; QMap eventIconPaths; - QHash pokemonIconPaths; + QMap pokemonIconPaths; }; extern ProjectConfig projectConfig; @@ -406,7 +453,7 @@ public: reset(); } virtual void reset() override { - this->recentMap = QString(); + this->recentMapOrLayout = QString(); this->useEncounterJson = true; this->customScripts.clear(); this->readKeys.clear(); @@ -418,7 +465,7 @@ public: QList getCustomScriptsEnabled(); QString projectDir; - QString recentMap; + QString recentMapOrLayout; bool useEncounterJson; protected: diff --git a/include/core/advancemapparser.h b/include/core/advancemapparser.h new file mode 100644 index 00000000..c3cb76cb --- /dev/null +++ b/include/core/advancemapparser.h @@ -0,0 +1,18 @@ +#ifndef ADVANCEMAPPARSER_H +#define ADVANCEMAPPARSER_H + +#include +#include +#include + +class Project; +class Layout; +class Metatile; + +namespace AdvanceMapParser { + Layout *parseLayout(const QString &filepath, bool *error, const Project *project); + QList parseMetatiles(const QString &filepath, bool *error, bool primaryTileset); + QList parsePalette(const QString &filepath, bool *error); +}; + +#endif // ADVANCEMAPPARSER_H diff --git a/include/core/editcommands.h b/include/core/editcommands.h index 40f0cd62..5dc4bf0c 100644 --- a/include/core/editcommands.h +++ b/include/core/editcommands.h @@ -8,12 +8,13 @@ #include #include #include +#include -class MapPixmapItem; class Map; +class Layout; class Blockdata; class Event; -class DraggablePixmapItem; +class EventPixmapItem; class Editor; enum CommandId { @@ -24,9 +25,9 @@ enum CommandId { ID_PaintCollision, ID_BucketFillCollision, ID_MagicFillCollision, - ID_ResizeMap, + ID_ResizeLayout, ID_PaintBorder, - ID_ScriptEditMap, + ID_ScriptEditLayout, ID_EventMove, ID_EventShift, ID_EventCreate, @@ -46,11 +47,18 @@ enum CommandId { #define IDMask_EventType_Trigger (1 << 11) #define IDMask_EventType_Heal (1 << 12) +#define IDMask_ConnectionDirection_Up (1 << 8) +#define IDMask_ConnectionDirection_Down (1 << 9) +#define IDMask_ConnectionDirection_Left (1 << 10) +#define IDMask_ConnectionDirection_Right (1 << 11) +#define IDMask_ConnectionDirection_Dive (1 << 12) +#define IDMask_ConnectionDirection_Emerge (1 << 13) + /// Implements a command to commit metatile paint actions /// onto the map using the pencil tool. class PaintMetatile : public QUndoCommand { public: - PaintMetatile(Map *map, + PaintMetatile(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent = nullptr); @@ -61,7 +69,7 @@ public: int id() const override { return CommandId::ID_PaintMetatile; } private: - Map *map; + Layout *layout; Blockdata newMetatiles; Blockdata oldMetatiles; @@ -75,10 +83,10 @@ private: /// on the metatile collision and elevation. class PaintCollision : public PaintMetatile { public: - PaintCollision(Map *map, + PaintCollision(Layout *layout, const Blockdata &oldCollision, const Blockdata &newCollision, unsigned actionId, QUndoCommand *parent = nullptr) - : PaintMetatile(map, oldCollision, newCollision, actionId, parent) { + : PaintMetatile(layout, oldCollision, newCollision, actionId, parent) { setText("Paint Collision"); } @@ -90,7 +98,7 @@ public: /// Implements a command to commit paint actions on the map border. class PaintBorder : public QUndoCommand { public: - PaintBorder(Map *map, + PaintBorder(Layout *layout, const Blockdata &oldBorder, const Blockdata &newBorder, unsigned actionId, QUndoCommand *parent = nullptr); @@ -101,7 +109,7 @@ public: int id() const override { return CommandId::ID_PaintBorder; } private: - Map *map; + Layout *layout; Blockdata newBorder; Blockdata oldBorder; @@ -115,10 +123,10 @@ private: /// with the bucket tool onto the map. class BucketFillMetatile : public PaintMetatile { public: - BucketFillMetatile(Map *map, + BucketFillMetatile(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent = nullptr) - : PaintMetatile(map, oldMetatiles, newMetatiles, actionId, parent) { + : PaintMetatile(layout, oldMetatiles, newMetatiles, actionId, parent) { setText("Bucket Fill Metatiles"); } @@ -131,10 +139,10 @@ public: /// on the metatile collision and elevation. class BucketFillCollision : public PaintCollision { public: - BucketFillCollision(Map *map, + BucketFillCollision(Layout *layout, const Blockdata &oldCollision, const Blockdata &newCollision, QUndoCommand *parent = nullptr) - : PaintCollision(map, oldCollision, newCollision, -1, parent) { + : PaintCollision(layout, oldCollision, newCollision, -1, parent) { setText("Flood Fill Collision"); } @@ -148,10 +156,10 @@ public: /// with the bucket or paint tool onto the map. class MagicFillMetatile : public PaintMetatile { public: - MagicFillMetatile(Map *map, + MagicFillMetatile(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent = nullptr) - : PaintMetatile(map, oldMetatiles, newMetatiles, actionId, parent) { + : PaintMetatile(layout, oldMetatiles, newMetatiles, actionId, parent) { setText("Magic Fill Metatiles"); } @@ -163,10 +171,10 @@ public: /// Implements a command to commit magic fill collision actions. class MagicFillCollision : public PaintCollision { public: - MagicFillCollision(Map *map, + MagicFillCollision(Layout *layout, const Blockdata &oldCollision, const Blockdata &newCollision, QUndoCommand *parent = nullptr) - : PaintCollision(map, oldCollision, newCollision, -1, parent) { + : PaintCollision(layout, oldCollision, newCollision, -1, parent) { setText("Magic Fill Collision"); } @@ -179,7 +187,7 @@ public: /// Implements a command to commit metatile shift actions. class ShiftMetatiles : public QUndoCommand { public: - ShiftMetatiles(Map *map, + ShiftMetatiles(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent = nullptr); @@ -190,7 +198,7 @@ public: int id() const override { return CommandId::ID_ShiftMetatiles; } private: - Map *map; + Layout *layout= nullptr; Blockdata newMetatiles; Blockdata oldMetatiles; @@ -201,9 +209,9 @@ private: /// Implements a command to commit a map or border resize action. -class ResizeMap : public QUndoCommand { +class ResizeLayout : public QUndoCommand { public: - ResizeMap(Map *map, QSize oldMapDimensions, QSize newMapDimensions, + ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QMargins newLayoutMargins, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, @@ -213,15 +221,14 @@ public: void redo() override; bool mergeWith(const QUndoCommand *) override { return false; } - int id() const override { return CommandId::ID_ResizeMap; } + int id() const override { return CommandId::ID_ResizeLayout; } private: - Map *map; + Layout *layout = nullptr; - int oldMapWidth; - int oldMapHeight; - int newMapWidth; - int newMapHeight; + int oldLayoutWidth; + int oldLayoutHeight; + QMargins newLayoutMargins; int oldBorderWidth; int oldBorderHeight; @@ -351,10 +358,10 @@ public: /// Implements a command to commit map edits from the scripting API. /// The scripting api can edit map/border blocks and dimensions. -class ScriptEditMap : public QUndoCommand { +class ScriptEditLayout : public QUndoCommand { public: - ScriptEditMap(Map *map, - QSize oldMapDimensions, QSize newMapDimensions, + ScriptEditLayout(Layout *layout, + QSize oldLayoutDimensions, QSize newLayoutDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, @@ -364,10 +371,10 @@ public: void redo() override; bool mergeWith(const QUndoCommand *) override { return false; } - int id() const override { return CommandId::ID_ScriptEditMap; } + int id() const override { return CommandId::ID_ScriptEditLayout; } private: - Map *map; + Layout *layout = nullptr; Blockdata newMetatiles; Blockdata oldMetatiles; @@ -375,10 +382,10 @@ private: Blockdata newBorder; Blockdata oldBorder; - int oldMapWidth; - int oldMapHeight; - int newMapWidth; - int newMapHeight; + int oldLayoutWidth; + int oldLayoutHeight; + int newLayoutWidth; + int newLayoutHeight; int oldBorderWidth; int oldBorderHeight; @@ -400,7 +407,7 @@ public: void redo() override; bool mergeWith(const QUndoCommand *command) override; - int id() const override { return CommandId::ID_MapConnectionMove; } + int id() const override; private: MapConnection *connection; @@ -421,7 +428,7 @@ public: void undo() override; void redo() override; - int id() const override { return CommandId::ID_MapConnectionChangeDirection; } + int id() const override; private: QPointer connection; @@ -443,7 +450,7 @@ public: void undo() override; void redo() override; - int id() const override { return CommandId::ID_MapConnectionChangeMap; } + int id() const override; private: QPointer connection; @@ -465,7 +472,7 @@ public: void undo() override; void redo() override; - int id() const override { return CommandId::ID_MapConnectionAdd; } + int id() const override; private: Map *map = nullptr; @@ -485,7 +492,7 @@ public: void undo() override; void redo() override; - int id() const override { return CommandId::ID_MapConnectionRemove; } + int id() const override; private: Map *map = nullptr; diff --git a/include/core/events.h b/include/core/events.h index c0a5a625..76a58dcf 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -10,7 +10,7 @@ #include #include "orderedjson.h" -using OrderedJson = poryjson::Json; +#include "parseutil.h" class Project; @@ -19,7 +19,7 @@ class EventFrame; class ObjectFrame; class CloneObjectFrame; class WarpFrame; -class DraggablePixmapItem; +class EventPixmapItem; class Event; class ObjectEvent; @@ -34,23 +34,6 @@ class HiddenItemEvent; class SecretBaseEvent; class HealLocationEvent; -class EventVisitor { -public: - virtual void nothing() { } - virtual void visitObject(ObjectEvent *) = 0; - virtual void visitTrigger(TriggerEvent *) = 0; - virtual void visitSign(SignEvent *) = 0; -}; - -struct EventGraphics -{ - QImage spritesheet; - int spriteWidth; - int spriteHeight; - bool inanimate; -}; - - /// /// Event base class -- purely virtual /// @@ -65,11 +48,7 @@ public: Event& operator=(const Event &other) = delete; protected: - Event() { - this->spriteWidth = 16; - this->spriteHeight = 16; - this->usingSprite = false; - } + Event() {} // public enums & static methods public: @@ -79,7 +58,6 @@ public: Trigger, WeatherTrigger, Sign, HiddenItem, SecretBase, HealLocation, - Generic, None, }; @@ -92,9 +70,13 @@ public: None, }; - // all event groups except warps have IDs that start at 1 + // Normally we refer to events using their index in the list of that group's events. + // Object events often get referred to with a special "local ID", which is really just the index + 1. + // We use this local ID number in the index spinner for object events instead of the actual index. + // This distinction is only really important for object and warp events, because these are normally + // the only two groups of events that need to be explicitly referred to. static int getIndexOffset(Event::Group group) { - return (group == Event::Group::Warp) ? 0 : 1; + return (group == Event::Group::Object) ? 1 : 0; } static Event::Group typeToGroup(Event::Type type) { @@ -120,20 +102,16 @@ public: static Event* create(Event::Type type); - static QMap icons; - // standard public methods public: - virtual Event *duplicate() = 0; + virtual Event *duplicate() const = 0; void setMap(Map *newMap) { this->map = newMap; } Map *getMap() const { return this->map; } void modify(); - virtual void accept(EventVisitor *) { } - void setX(int newX) { this->x = newX; } void setY(int newY) { this->y = newY; } void setZ(int newZ) { this->elevation = newZ; } @@ -144,8 +122,8 @@ public: int getZ() const { return this->elevation; } int getElevation() const { return this->elevation; } - int getPixelX() const { return (this->x * 16) - qMax(0, (this->spriteWidth - 16) / 2); } - int getPixelY() const { return (this->y * 16) - qMax(0, this->spriteHeight - 16); } + int getPixelX() const { return (this->x * 16) - qMax(0, (pixmap.width() - 16) / 2); } + int getPixelY() const { return (this->y * 16) - qMax(0, pixmap.height() - 16); } virtual EventFrame *getEventFrame(); virtual EventFrame *createEventFrame() = 0; @@ -160,35 +138,34 @@ public: virtual void setDefaultValues(Project *project); virtual QSet getExpectedFields() = 0; - void readCustomValues(QJsonObject values); - void addCustomValuesTo(OrderedJson::object *obj); - const QMap getCustomValues() { return this->customValues; } - void setCustomValues(const QMap newCustomValues) { this->customValues = newCustomValues; } - virtual void loadPixmap(Project *project); + virtual QStringList getScripts() const { return QStringList(); } + + QJsonObject getCustomAttributes() const { return this->customAttributes; } + void setCustomAttributes(const QJsonObject &newCustomAttributes) { this->customAttributes = newCustomAttributes; } + + virtual QPixmap loadPixmap(Project *project); void setPixmap(QPixmap newPixmap) { this->pixmap = newPixmap; } - QPixmap getPixmap() { return this->pixmap; } + QPixmap getPixmap() const { return this->pixmap; } - void setPixmapItem(DraggablePixmapItem *item); - DraggablePixmapItem *getPixmapItem() { return this->pixmapItem; } + void setPixmapItem(EventPixmapItem *item); + EventPixmapItem *getPixmapItem() const { return this->pixmapItem; } - void setUsingSprite(bool newUsingSprite) { this->usingSprite = newUsingSprite; } - bool getUsingSprite() const { return this->usingSprite; } - - void setSpriteWidth(int newSpriteWidth) { this->spriteWidth = newSpriteWidth; } - int getspriteWidth() const { return this->spriteWidth; } - - void setSpriteHeight(int newSpriteHeight) { this->spriteHeight = newSpriteHeight; } - int getspriteHeight() const { return this->spriteHeight; } + void setUsesDefaultPixmap(bool newUsesDefaultPixmap) { this->usesDefaultPixmap = newUsesDefaultPixmap; } + bool getUsesDefaultPixmap() const { return this->usesDefaultPixmap; } int getEventIndex(); - static QString eventGroupToString(Event::Group group); - static QString eventTypeToString(Event::Type type); - static Event::Type eventTypeFromString(QString type); - static void clearIcons(); - static void setIcons(); + void setIdName(QString newIdName) { this->idName = newIdName; } + QString getIdName() const { return this->idName; } + + static QString groupToString(Event::Group group); + static QString typeToString(Event::Type type); + static QString typeToJsonKey(Event::Type type); + static Event::Type typeFromJsonKey(QString type); + static QList types(); + static QList groups(); // protected attributes protected: @@ -202,16 +179,23 @@ protected: int y = 0; int elevation = 0; - int spriteWidth = 16; - int spriteHeight = 16; - bool usingSprite = false; + bool usesDefaultPixmap = true; - QMap customValues; + // Some events can have an associated #define name that should be unique to this event. + // e.g. object events can have a 'LOCALID', or Heal Locations have a 'HEAL_LOCATION' id. + // When deleting events like this we want to warn the user that the #define may also be deleted. + QString idName; + + QJsonObject customAttributes; QPixmap pixmap; - DraggablePixmapItem *pixmapItem = nullptr; + EventPixmapItem *pixmapItem = nullptr; QPointer eventFrame; + + static QString readString(QJsonObject *object, const QString &key) { return ParseUtil::jsonToQString(object->take(key)); } + static int readInt(QJsonObject *object, const QString &key) { return ParseUtil::jsonToInt(object->take(key)); } + static bool readBool(QJsonObject *object, const QString &key) { return ParseUtil::jsonToBool(object->take(key)); } }; @@ -227,9 +211,7 @@ public: } virtual ~ObjectEvent() {} - virtual Event *duplicate() override; - - virtual void accept(EventVisitor *visitor) override { visitor->visitObject(this); } + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; @@ -240,35 +222,33 @@ public: virtual QSet getExpectedFields() override; - virtual void loadPixmap(Project *project) override; + virtual QStringList getScripts() const override { return {getScript()}; } + + virtual QPixmap loadPixmap(Project *project) override; void setGfx(QString newGfx) { this->gfx = newGfx; } - QString getGfx() { return this->gfx; } + QString getGfx() const { return this->gfx; } void setMovement(QString newMovement) { this->movement = newMovement; } - QString getMovement() { return this->movement; } + QString getMovement() const { return this->movement; } void setRadiusX(int newRadiusX) { this->radiusX = newRadiusX; } - int getRadiusX() { return this->radiusX; } + int getRadiusX() const { return this->radiusX; } void setRadiusY(int newRadiusY) { this->radiusY = newRadiusY; } - int getRadiusY() { return this->radiusY; } + int getRadiusY() const { return this->radiusY; } void setTrainerType(QString newTrainerType) { this->trainerType = newTrainerType; } - QString getTrainerType() { return this->trainerType; } + QString getTrainerType() const { return this->trainerType; } void setSightRadiusBerryTreeID(QString newValue) { this->sightRadiusBerryTreeID = newValue; } - QString getSightRadiusBerryTreeID() { return this->sightRadiusBerryTreeID; } + QString getSightRadiusBerryTreeID() const { return this->sightRadiusBerryTreeID; } void setScript(QString newScript) { this->script = newScript; } - QString getScript() { return this->script; } + QString getScript() const { return this->script; } void setFlag(QString newFlag) { this->flag = newFlag; } - QString getFlag() { return this->flag; } - -public: - void setFrameFromMovement(QString movement); - void setPixmapFromSpritesheet(EventGraphics * gfx); + QString getFlag() const { return this->flag; } protected: @@ -300,7 +280,7 @@ public: } virtual ~CloneObjectEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; @@ -311,17 +291,17 @@ public: virtual QSet getExpectedFields() override; - virtual void loadPixmap(Project *project) override; + virtual QPixmap loadPixmap(Project *project) override; void setTargetMap(QString newTargetMap) { this->targetMap = newTargetMap; } - QString getTargetMap() { return this->targetMap; } + QString getTargetMap() const { return this->targetMap; } - void setTargetID(int newTargetID) { this->targetID = newTargetID; } - int getTargetID() { return this->targetID; } + void setTargetID(QString newTargetID) { this->targetID = newTargetID; } + QString getTargetID() const { return this->targetID; } private: QString targetMap; - int targetID = 0; + QString targetID; }; @@ -338,7 +318,7 @@ public: } virtual ~WarpEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; @@ -350,16 +330,18 @@ public: virtual QSet getExpectedFields() override; void setDestinationMap(QString newDestinationMap) { this->destinationMap = newDestinationMap; } - QString getDestinationMap() { return this->destinationMap; } + QString getDestinationMap() const { return this->destinationMap; } void setDestinationWarpID(QString newDestinationWarpID) { this->destinationWarpID = newDestinationWarpID; } - QString getDestinationWarpID() { return this->destinationWarpID; } + QString getDestinationWarpID() const { return this->destinationWarpID; } void setWarningEnabled(bool enabled); + bool getWarningEnabled() const { return this->warningEnabled; } private: QString destinationMap; QString destinationWarpID; + bool warningEnabled = false; }; @@ -373,7 +355,7 @@ public: CoordEvent() : Event() {} virtual ~CoordEvent() {} - virtual Event *duplicate() override = 0; + virtual Event *duplicate() const override = 0; virtual EventFrame *createEventFrame() override = 0; @@ -399,9 +381,7 @@ public: } virtual ~TriggerEvent() {} - virtual Event *duplicate() override; - - virtual void accept(EventVisitor *visitor) override { visitor->visitTrigger(this); } + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; @@ -412,14 +392,16 @@ public: virtual QSet getExpectedFields() override; + virtual QStringList getScripts() const override { return {getScriptLabel()}; } + void setScriptVar(QString newScriptVar) { this->scriptVar = newScriptVar; } - QString getScriptVar() { return this->scriptVar; } + QString getScriptVar() const { return this->scriptVar; } void setScriptVarValue(QString newScriptVarValue) { this->scriptVarValue = newScriptVarValue; } - QString getScriptVarValue() { return this->scriptVarValue; } + QString getScriptVarValue() const { return this->scriptVarValue; } void setScriptLabel(QString newScriptLabel) { this->scriptLabel = newScriptLabel; } - QString getScriptLabel() { return this->scriptLabel; } + QString getScriptLabel() const { return this->scriptLabel; } private: QString scriptVar; @@ -441,7 +423,7 @@ public: } virtual ~WeatherTriggerEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; @@ -453,7 +435,7 @@ public: virtual QSet getExpectedFields() override; void setWeather(QString newWeather) { this->weather = newWeather; } - QString getWeather() { return this->weather; } + QString getWeather() const { return this->weather; } private: QString weather; @@ -472,7 +454,7 @@ public: } virtual ~BGEvent() {} - virtual Event *duplicate() override = 0; + virtual Event *duplicate() const override = 0; virtual EventFrame *createEventFrame() override = 0; @@ -497,9 +479,7 @@ public: } virtual ~SignEvent() {} - virtual Event *duplicate() override; - - virtual void accept(EventVisitor *visitor) override { visitor->visitSign(this); } + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; @@ -510,11 +490,13 @@ public: virtual QSet getExpectedFields() override; + virtual QStringList getScripts() const override { return {getScriptLabel()}; } + void setFacingDirection(QString newFacingDirection) { this->facingDirection = newFacingDirection; } - QString getFacingDirection() { return this->facingDirection; } + QString getFacingDirection() const { return this->facingDirection; } void setScriptLabel(QString newScriptLabel) { this->scriptLabel = newScriptLabel; } - QString getScriptLabel() { return this->scriptLabel; } + QString getScriptLabel() const { return this->scriptLabel; } private: QString facingDirection; @@ -534,7 +516,7 @@ public: } virtual ~HiddenItemEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; @@ -546,16 +528,16 @@ public: virtual QSet getExpectedFields() override; void setItem(QString newItem) { this->item = newItem; } - QString getItem() { return this->item; } + QString getItem() const { return this->item; } void setFlag(QString newFlag) { this->flag = newFlag; } - QString getFlag() { return this->flag; } + QString getFlag() const { return this->flag; } void setQuantity(int newQuantity) { this->quantity = newQuantity; } - int getQuantity() { return this->quantity; } + int getQuantity() const { return this->quantity; } void setUnderfoot(bool newUnderfoot) { this->underfoot = newUnderfoot; } - bool getUnderfoot() { return this->underfoot; } + bool getUnderfoot() const { return this->underfoot; } private: QString item; @@ -579,7 +561,7 @@ public: } virtual ~SecretBaseEvent() {} - virtual Event *duplicate() override; + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; @@ -591,7 +573,7 @@ public: virtual QSet getExpectedFields() override; void setBaseID(QString newBaseID) { this->baseID = newBaseID; } - QString getBaseID() { return this->baseID; } + QString getBaseID() const { return this->baseID; } private: QString baseID; @@ -611,56 +593,35 @@ public: } virtual ~HealLocationEvent() {} - virtual Event *duplicate() override { return nullptr; } + virtual Event *duplicate() const override; virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(QJsonObject, Project *) override { return false; } + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; - virtual QSet getExpectedFields() override { return QSet(); } + virtual QSet getExpectedFields() override; - void setIndex(int newIndex) { this->index = newIndex; } - int getIndex() { return this->index; } + void setHostMapName(QString newHostMapName) { this->hostMapName = newHostMapName; } + QString getHostMapName() const; - void setLocationName(QString newLocationName) { this->locationName = newLocationName; } - QString getLocationName() { return this->locationName; } + void setRespawnMapName(QString newRespawnMapName) { this->respawnMapName = newRespawnMapName; } + QString getRespawnMapName() const { return this->respawnMapName; } - void setIdName(QString newIdName) { this->idName = newIdName; } - QString getIdName() { return this->idName; } - - void setRespawnMap(QString newRespawnMap) { this->respawnMap = newRespawnMap; } - QString getRespawnMap() { return this->respawnMap; } - - void setRespawnNPC(uint8_t newRespawnNPC) { this->respawnNPC = newRespawnNPC; } - uint8_t getRespawnNPC() { return this->respawnNPC; } + void setRespawnNPC(QString newRespawnNPC) { this->respawnNPC = newRespawnNPC; } + QString getRespawnNPC() const { return this->respawnNPC; } private: - int index = -1; - QString locationName; - QString idName; - QString respawnMap; - uint8_t respawnNPC = 0; + QString respawnMapName; + QString respawnNPC; + QString hostMapName; // Only needed if the host map fails to load. }; - -/// -/// Keeps track of scripts -/// -class ScriptTracker : public EventVisitor { -public: - virtual void visitObject(ObjectEvent *object) override { this->scripts << object->getScript(); }; - virtual void visitTrigger(TriggerEvent *trigger) override { this->scripts << trigger->getScriptLabel(); }; - virtual void visitSign(SignEvent *sign) override { this->scripts << sign->getScriptLabel(); }; - - QStringList getScripts() { return this->scripts; } - -private: - QStringList scripts; -}; - +inline uint qHash(const Event::Group &key, uint seed = 0) { + return qHash(static_cast(key), seed); +} #endif // EVENTS_H diff --git a/include/core/heallocation.h b/include/core/heallocation.h deleted file mode 100644 index 533f44b5..00000000 --- a/include/core/heallocation.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#ifndef HEALLOCATION_H -#define HEALLOCATION_H - -#include -#include - -class Event; - -class HealLocation { - -public: - HealLocation()=default; - HealLocation(QString, QString, int, int16_t, int16_t, QString = "", uint8_t = 1); - friend QDebug operator<<(QDebug debug, const HealLocation &hl); - -public: - QString idName; - QString mapName; - int index; - int16_t x; - int16_t y; - QString respawnMap; - uint8_t respawnNPC; - static HealLocation fromEvent(Event *); -}; - -#endif // HEALLOCATION_H diff --git a/include/core/map.h b/include/core/map.h index 2333c862..2ba172ce 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -7,11 +7,13 @@ #include "maplayout.h" #include "tileset.h" #include "events.h" +#include "mapheader.h" #include #include #include #include +#include #include #define DEFAULT_BORDER_WIDTH 2 @@ -21,11 +23,7 @@ #define MAX_BORDER_WIDTH 255 #define MAX_BORDER_HEIGHT 255 -// Number of metatiles to draw out from edge of map. Could allow modification of this in the future. -// porymap will reflect changes to it, but the value is hard-coded in the projects at the moment -#define BORDER_DISTANCE 7 - -class MapPixmapItem; +class LayoutPixmapItem; class CollisionPixmapItem; class BorderMetatilesPixmapItem; @@ -34,117 +32,120 @@ class Map : public QObject Q_OBJECT public: explicit Map(QObject *parent = nullptr); + explicit Map(const Map &other, QObject *parent = nullptr); ~Map(); public: - QString name; - QString constantName; - QString song; - QString layoutId; - QString location; - bool requiresFlash; - QString weather; - QString type; - bool show_location; - bool allowRunning; - bool allowBiking; - bool allowEscaping; - int floorNumber = 0; - QString battle_scene; - QString sharedEventsMap = ""; - QString sharedScriptsMap = ""; - QStringList scriptsFileLabels; - QMap customHeaders; - MapLayout *layout; - bool isPersistedToFile = true; - bool hasUnsavedDataChanges = false; - bool needsLayoutDir = true; - bool needsHealLocation = false; - bool scriptsLoaded = false; - QImage collision_image; - QPixmap collision_pixmap; - QImage image; - QPixmap pixmap; + void setName(const QString &mapName) { m_name = mapName; } + QString name() const { return m_name; } - QMap> events; - QList ownedEvents; // for memory management + void setConstantName(const QString &constantName) { m_constantName = constantName; } + QString constantName() const { return m_constantName; } - QList metatileLayerOrder; - QList metatileLayerOpacity; + static QString mapConstantFromName(const QString &name); + QString expectedConstantName() const { return Map::mapConstantFromName(m_name); } - void setName(QString mapName); - static QString mapConstantFromName(QString mapName, bool includePrefix = true); - int getWidth(); - int getHeight(); - int getBorderWidth(); - int getBorderHeight(); - QPixmap render(bool ignoreCache = false, MapLayout *fromLayout = nullptr, QRect bounds = QRect(0, 0, -1, -1)); - QPixmap renderCollision(bool ignoreCache); - bool mapBlockChanged(int i, const Blockdata &cache); - bool borderBlockChanged(int i, const Blockdata &cache); - void cacheBlockdata(); - void cacheCollision(); - bool getBlock(int x, int y, Block *out); - void setBlock(int x, int y, Block block, bool enableScriptCallback = false); - void setBlockdata(Blockdata blockdata, bool enableScriptCallback = false); - uint16_t getBorderMetatileId(int x, int y); - void setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback = false); - void setBorderBlockData(Blockdata blockdata, bool enableScriptCallback = false); - void floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); - void _floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); - void magicFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); - QList getAllEvents() const; - QStringList getScriptLabels(Event::Group group = Event::Group::None); + void setLayout(Layout *layout); + Layout* layout() const { return m_layout; } + + int getWidth() const; + int getHeight() const; + int getBorderWidth() const; + int getBorderHeight() const; + + void setHeader(const MapHeader &header) { *m_header = header; } + MapHeader* header() const { return m_header; } + + void setSharedEventsMap(const QString &sharedEventsMap) { m_sharedEventsMap = sharedEventsMap; } + void setSharedScriptsMap(const QString &sharedScriptsMap); + + QString sharedEventsMap() const { return m_sharedEventsMap; } + QString sharedScriptsMap() const { return m_sharedScriptsMap; } + + void setNeedsHealLocation(bool needsHealLocation) { m_needsHealLocation = needsHealLocation; } + void setIsPersistedToFile(bool persistedToFile) { m_isPersistedToFile = persistedToFile; } + void setHasUnsavedDataChanges(bool unsavedDataChanges) { m_hasUnsavedDataChanges = unsavedDataChanges; } + + bool needsHealLocation() const { return m_needsHealLocation; } + bool isPersistedToFile() const { return m_isPersistedToFile; } + bool hasUnsavedDataChanges() const { return m_hasUnsavedDataChanges; } + + void resetEvents(); + QList getEvents(Event::Group group = Event::Group::None) const; + Event* getEvent(Event::Group group, int index) const; + Event* getEvent(Event::Group group, const QString &idName) const; + QStringList getEventIdNames(Event::Group group) const; + int getNumEvents(Event::Group group = Event::Group::None) const; void removeEvent(Event *); void addEvent(Event *); + int getIndexOfEvent(Event *) const; + bool hasEvent(Event *) const; + + QStringList getScriptLabels(Event::Group group = Event::Group::None); + QString getScriptsFilePath() const; + void openScript(const QString &label); + void deleteConnections(); - QList getConnections() const; + QList getConnections() const { return m_connections; } + MapConnection* getConnection(const QString &direction) const; void removeConnection(MapConnection *); void addConnection(MapConnection *); void loadConnection(MapConnection *); - QRect getConnectionRect(const QString &direction, MapLayout *fromLayout = nullptr); - QPixmap renderConnection(const QString &direction, MapLayout *fromLayout = nullptr); - QPixmap renderBorder(bool ignoreCache = false); - void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); - void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); - void clearBorderCache(); - void cacheBorder(); - bool hasUnsavedChanges(); - bool isWithinBounds(int x, int y); - bool isWithinBorderBounds(int x, int y); - void openScript(QString label); - QString getScriptsFilePath() const; + QRect getConnectionRect(const QString &direction, Layout *fromLayout = nullptr) const; + QPixmap renderConnection(const QString &direction, Layout *fromLayout = nullptr); - MapPixmapItem *mapItem = nullptr; - void setMapItem(MapPixmapItem *item) { mapItem = item; } - - CollisionPixmapItem *collisionItem = nullptr; - void setCollisionItem(CollisionPixmapItem *item) { collisionItem = item; } - - BorderMetatilesPixmapItem *borderItem = nullptr; - void setBorderItem(BorderMetatilesPixmapItem *item) { borderItem = item; } - - QUndoStack editHistory; + QUndoStack* editHistory() const { return m_editHistory; } + void commit(QUndoCommand*); void modify(); - void clean(); + void setClean(); + bool hasUnsavedChanges() const; void pruneEditHistory(); + void setCustomAttributes(const QJsonObject &attributes) { m_customAttributes = attributes; } + QJsonObject customAttributes() const { return m_customAttributes; } + private: - void setNewDimensionsBlockdata(int newWidth, int newHeight); - void setNewBorderDimensionsBlockdata(int newWidth, int newHeight); + QString m_name; + QString m_constantName; + QString m_sharedEventsMap = ""; + QString m_sharedScriptsMap = ""; + + QStringList m_scriptLabels; + QJsonObject m_customAttributes; + + MapHeader *m_header = nullptr; + Layout *m_layout = nullptr; + + bool m_isPersistedToFile = true; + bool m_hasUnsavedDataChanges = false; + bool m_needsHealLocation = false; + bool m_scriptsLoaded = false; + + QMap> m_events; + QSet m_ownedEvents; // for memory management + + QList m_metatileLayerOrder; + QList m_metatileLayerOpacity; + void trackConnection(MapConnection*); // MapConnections in 'ownedConnections' but not 'connections' persist in the edit history. - QList connections; - QSet ownedConnections; + QList m_connections; + QSet m_ownedConnections; + + QPointer m_editHistory; + QPointer m_scriptFileWatcher; + + void invalidateScripts(); signals: void modified(); + void scriptsModified(); void mapDimensionsChanged(const QSize &size); - void mapNeedsRedrawing(); void openScriptRequested(QString label); void connectionAdded(MapConnection*); void connectionRemoved(MapConnection*); + void layoutChanged(); }; #endif // MAP_H diff --git a/include/core/mapconnection.h b/include/core/mapconnection.h index 21c00ac6..5df071bd 100644 --- a/include/core/mapconnection.h +++ b/include/core/mapconnection.h @@ -5,6 +5,7 @@ #include #include #include +#include class Project; class Map; @@ -26,13 +27,22 @@ public: QString direction() const { return m_direction; } void setDirection(const QString &direction, bool mirror = true); + bool isCardinal() const { return isCardinal(m_direction); } + bool isHorizontal() const { return isHorizontal(m_direction); } + bool isVertical() const { return isVertical(m_direction); } + bool isDiving() const { return isDiving(m_direction); } + int offset() const { return m_offset; } void setOffset(int offset, bool mirror = true); + QJsonObject customData() const { return m_customData; } + void setCustomData(const QJsonObject &customData) { m_customData = customData; } + MapConnection* findMirror(); MapConnection* createMirror(); - QPixmap getPixmap(); + QPixmap render() const; + QPoint relativePos(bool clipped = false) const; static QPointer project; static const QMap oppositeDirections; @@ -49,6 +59,7 @@ private: QString m_targetMapName; QString m_direction; int m_offset; + QJsonObject m_customData; void markMapEdited(); Map* getMap(const QString& mapName) const; diff --git a/include/core/mapheader.h b/include/core/mapheader.h new file mode 100644 index 00000000..2058280a --- /dev/null +++ b/include/core/mapheader.h @@ -0,0 +1,83 @@ +#ifndef MAPHEADER_H +#define MAPHEADER_H + +#include + +class MapHeader : public QObject +{ + Q_OBJECT +public: + MapHeader(QObject *parent = nullptr) : QObject(parent) {}; + ~MapHeader() {}; + MapHeader(const MapHeader& other); + MapHeader& operator=(const MapHeader& other); + bool operator==(const MapHeader& other) const { + return m_song == other.m_song + && m_location == other.m_location + && m_requiresFlash == other.m_requiresFlash + && m_weather == other.m_weather + && m_type == other.m_type + && m_showsLocationName == other.m_showsLocationName + && m_allowsRunning == other.m_allowsRunning + && m_allowsBiking == other.m_allowsBiking + && m_allowsEscaping == other.m_allowsEscaping + && m_floorNumber == other.m_floorNumber + && m_battleScene == other.m_battleScene; + } + bool operator!=(const MapHeader& other) const { + return !(operator==(other)); + } + + void setSong(const QString &song); + void setLocation(const QString &location); + void setRequiresFlash(bool requiresFlash); + void setWeather(const QString &weather); + void setType(const QString &type); + void setShowsLocationName(bool showsLocationName); + void setAllowsRunning(bool allowsRunning); + void setAllowsBiking(bool allowsBiking); + void setAllowsEscaping(bool allowsEscaping); + void setFloorNumber(int floorNumber); + void setBattleScene(const QString &battleScene); + + QString song() const { return m_song; } + QString location() const { return m_location; } + bool requiresFlash() const { return m_requiresFlash; } + QString weather() const { return m_weather; } + QString type() const { return m_type; } + bool showsLocationName() const { return m_showsLocationName; } + bool allowsRunning() const { return m_allowsRunning; } + bool allowsBiking() const { return m_allowsBiking; } + bool allowsEscaping() const { return m_allowsEscaping; } + int floorNumber() const { return m_floorNumber; } + QString battleScene() const { return m_battleScene; } + +signals: + void songChanged(QString); + void locationChanged(QString); + void requiresFlashChanged(bool); + void weatherChanged(QString); + void typeChanged(QString); + void showsLocationNameChanged(bool); + void allowsRunningChanged(bool); + void allowsBikingChanged(bool); + void allowsEscapingChanged(bool); + void floorNumberChanged(int); + void battleSceneChanged(QString); + void modified(); + +private: + QString m_song; + QString m_location; + bool m_requiresFlash = false; + QString m_weather; + QString m_type; + bool m_showsLocationName = false; + bool m_allowsRunning = false; + bool m_allowsBiking = false; + bool m_allowsEscaping = false; + int m_floorNumber = 0; + QString m_battleScene; +}; + +#endif // MAPHEADER_H diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 13215fff..3e67af18 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -7,41 +7,152 @@ #include #include #include +#include -class MapLayout { +class Map; +class LayoutPixmapItem; +class CollisionPixmapItem; +class BorderMetatilesPixmapItem; + +class Layout : public QObject { + Q_OBJECT public: - MapLayout() {} - static QString layoutConstantFromName(QString mapName); + Layout() {} + Layout(const Layout &other); + + static QString layoutConstantFromName(const QString &name); + + bool hasUnsavedDataChanges = false; + QString id; QString name; + QString newFolderPath; + int width; int height; int border_width; int border_height; + QString border_path; QString blockdata_path; + QString tileset_primary_label; QString tileset_secondary_label; + Tileset *tileset_primary = nullptr; Tileset *tileset_secondary = nullptr; + + QJsonObject customData; + Blockdata blockdata; + + QImage image; + QPixmap pixmap; QImage border_image; QPixmap border_pixmap; + QImage collision_image; + QPixmap collision_pixmap; + Blockdata border; Blockdata cached_blockdata; Blockdata cached_collision; Blockdata cached_border; struct { Blockdata blocks; - QSize mapDimensions; + QSize layoutDimensions; Blockdata border; QSize borderDimensions; } lastCommitBlocks; // to track map changes - int getWidth(); - int getHeight(); - int getBorderWidth(); - int getBorderHeight(); + QList metatileLayerOrder; + QList metatileLayerOpacity; + + LayoutPixmapItem *layoutItem = nullptr; + CollisionPixmapItem *collisionItem = nullptr; + BorderMetatilesPixmapItem *borderItem = nullptr; + + QUndoStack editHistory; + + // to simplify new layout settings transfer between functions + struct Settings { + QString id; + QString name; + // The name of a new layout's folder in `data/layouts/` is not always the same as the layout's name + // (e.g. the majority of the default layouts use the name of their associated map). + QString folderName; + int width; + int height; + int borderWidth; + int borderHeight; + QString primaryTilesetLabel; + QString secondaryTilesetLabel; + }; + Settings settings() const; + + Layout *copy() const; + void copyFrom(const Layout *other); + + int getWidth() const { return width; } + int getHeight() const { return height; } + int getBorderWidth() const { return border_width; } + int getBorderHeight() const { return border_height; } + QMargins getBorderMargins() const; + QRect getVisibleRect() const; + + bool isWithinBounds(int x, int y) const; + bool isWithinBounds(const QRect &rect) const; + bool isWithinBorderBounds(int x, int y) const; + + bool getBlock(int x, int y, Block *out); + void setBlock(int x, int y, Block block, bool enableScriptCallback = false); + void setBlockdata(Blockdata blockdata, bool enableScriptCallback = false); + + void adjustDimensions(const QMargins &margins, bool setNewBlockdata = true); + void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true); + void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); + + void cacheBlockdata(); + void cacheCollision(); + void clearBorderCache(); + void cacheBorder(); + + bool hasUnsavedChanges() const; + + bool save(const QString &root); + bool saveBorder(const QString &root); + bool saveBlockdata(const QString &root); + + bool layoutBlockChanged(int i, const Blockdata &cache); + + uint16_t getBorderMetatileId(int x, int y); + void setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback = false); + void setBorderBlockData(Blockdata blockdata, bool enableScriptCallback = false); + + void floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); + void _floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); + void magicFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); + + QPixmap render(bool ignoreCache = false, Layout *fromLayout = nullptr, QRect bounds = QRect(0, 0, -1, -1)); + QPixmap renderCollision(bool ignoreCache); + // QPixmap renderConnection(MapConnection, Layout *); + QPixmap renderBorder(bool ignoreCache = false); + + QPixmap getLayoutItemPixmap(); + + void setLayoutItem(LayoutPixmapItem *item) { layoutItem = item; } + void setCollisionItem(CollisionPixmapItem *item) { collisionItem = item; } + void setBorderItem(BorderMetatilesPixmapItem *item) { borderItem = item; } + +private: + void setNewDimensionsBlockdata(int newWidth, int newHeight); + void setNewBorderDimensionsBlockdata(int newWidth, int newHeight); + bool writeBlockdata(const QString &path, const Blockdata &blockdata) const; + + static int getBorderDrawDistance(int dimension, qreal minimum); + +signals: + void dimensionsChanged(const QSize &size); + void needsRedrawing(); }; #endif // MAPLAYOUT_H diff --git a/include/core/mapparser.h b/include/core/mapparser.h deleted file mode 100644 index 21e3074e..00000000 --- a/include/core/mapparser.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef MAPPARSER_H -#define MAPPARSER_H - -#include "maplayout.h" -#include "project.h" -#include -#include - -class MapParser -{ -public: - MapParser(); - MapLayout *parse(QString filepath, bool *error, Project *project); -}; - -#endif // MAPPARSER_H diff --git a/include/core/metatile.h b/include/core/metatile.h index 0bb55af9..a1c805d1 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -11,27 +11,6 @@ class Project; -enum { - METATILE_LAYER_MIDDLE_TOP, - METATILE_LAYER_BOTTOM_MIDDLE, - METATILE_LAYER_BOTTOM_TOP, - NUM_METATILE_LAYER_TYPES -}; - -enum { - ENCOUNTER_NONE, - ENCOUNTER_LAND, - ENCOUNTER_WATER, - NUM_METATILE_ENCOUNTER_TYPES -}; - -enum { - TERRAIN_NONE, - TERRAIN_GRASS, - TERRAIN_WATER, - TERRAIN_WATERFALL, - NUM_METATILE_TERRAIN_TYPES -}; class Metatile { @@ -41,6 +20,13 @@ public: Metatile &operator=(const Metatile &other) = default; Metatile(const int numTiles); + enum LayerType { + Normal, + Covered, + Split, + Count + }; + enum Attr { Behavior, TerrainType, diff --git a/include/core/metatileparser.h b/include/core/metatileparser.h deleted file mode 100644 index b85e5b36..00000000 --- a/include/core/metatileparser.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#ifndef METATILEPARSER_H -#define METATILEPARSER_H - -#include "metatile.h" -#include - -namespace MetatileParser { - QList parse(QString filepath, bool *error, bool primaryTileset); -} - -#endif // METATILEPARSER_H diff --git a/include/core/paletteutil.h b/include/core/paletteutil.h index ce221026..34e9ae3f 100644 --- a/include/core/paletteutil.h +++ b/include/core/paletteutil.h @@ -7,7 +7,7 @@ namespace PaletteUtil { QList parse(QString filepath, bool *error); - void writeJASC(QString filepath, QVector colors, int offset, int nColors); + bool writeJASC(const QString &filepath, const QVector &colors, int offset, int nColors); } #endif // PALETTEUTIL_H diff --git a/include/core/parseutil.h b/include/core/parseutil.h index e3df8375..2edb9f78 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -2,9 +2,9 @@ #ifndef PARSEUTIL_H #define PARSEUTIL_H -#include "heallocation.h" #include "log.h" #include "orderedjson.h" +#include "orderedmap.h" #include #include @@ -43,26 +43,35 @@ class ParseUtil { public: ParseUtil(); - void set_root(const QString &dir); - static QString readTextFile(const QString &path); - void invalidateTextFile(const QString &path); + + void setRoot(const QString &dir) { this->root = dir; } + void setUpdatesSplashScreen(bool updates) { this->updatesSplashScreen = updates; } + + static QString readTextFile(const QString &path, QString *error = nullptr); + QString loadTextFile(const QString &path, QString *error = nullptr); + + bool cacheFile(const QString &path, QString *error = nullptr); + void clearFileCache() { this->fileCache.clear(); } static int textFileLineCount(const QString &path); QList parseAsm(const QString &filename); QStringList readCArray(const QString &filename, const QString &label); QMap readCArrayMulti(const QString &filename); - QMap readNamedIndexCArray(const QString &text, const QString &label); + QMap readNamedIndexCArray(const QString &text, const QString &label, QString *error = nullptr); QString readCIncbin(const QString &text, const QString &label); QMap readCIncbinMulti(const QString &filepath); QStringList readCIncbinArray(const QString &filename, const QString &label); - QMap readCDefinesByRegex(const QString &filename, const QStringList ®exList); - QMap readCDefinesByName(const QString &filename, const QStringList &names); - QStringList readCDefineNames(const QString &filename, const QStringList ®exList); - QMap> readCStructs(const QString &, const QString & = "", const QHash = { }); + QHash readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); + QHash readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); + QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); + void loadGlobalCDefinesFromFile(const QString &filename, QString *error = nullptr); + void loadGlobalCDefines(const QMap &defines); + void loadGlobalCDefines(const QHash &defines); + void resetCDefines(); + OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); - bool tryParseJsonFile(QJsonDocument *out, const QString &filepath); - bool tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath); - bool ensureFieldsExist(const QJsonObject &obj, const QList &fields); + bool tryParseJsonFile(QJsonDocument *out, const QString &filepath, QString *error = nullptr); + bool tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath, QString *error = nullptr); // Returns the 1-indexed line number for the definition of scriptLabel in the scripts file at filePath. // Returns 0 if a definition for scriptLabel cannot be found. @@ -77,35 +86,52 @@ public: static QString removeLineComments(QString text, const QStringList &commentSymbols); static QStringList splitShellCommand(QStringView command); - static int gameStringToInt(QString gameString, bool * ok = nullptr); - static bool gameStringToBool(QString gameString, bool * ok = nullptr); - static QString jsonToQString(QJsonValue value, bool * ok = nullptr); - static int jsonToInt(QJsonValue value, bool * ok = nullptr); - static bool jsonToBool(QJsonValue value, bool * ok = nullptr); + static int gameStringToInt(const QString &gameString, bool * ok = nullptr); + static bool gameStringToBool(const QString &gameString, bool * ok = nullptr); + static QString jsonToQString(const QJsonValue &value, bool * ok = nullptr); + static int jsonToInt(const QJsonValue &value, bool * ok = nullptr); + static bool jsonToBool(const QJsonValue &value, bool * ok = nullptr); private: QString root; QString text; QString file; QString curDefine; + QHash fileCache; QHash errorMap; - int evaluateDefine(const QString&, const QString &, QMap*, QMap*); - QList tokenizeExpression(QString, QMap*, QMap*); + + // The maps of define names to values/expressions that are available while parsing C defines. + // As the parser reads and evaluates more defines it will update these maps accordingly. + QHash knownDefineValues; + QHash knownDefineExpressions; + + // Maps of special define names to values/expressions that take precedence over defines encountered while parsing. + // Some (like 'TRUE'/'FALSE') are always present in these maps, others may be specified by the user with 'loadGlobalCDefines' / 'loadGlobalCDefinesFromFile'. + QHash globalDefineValues; + QHash globalDefineExpressions; + + bool updatesSplashScreen = false; + + int evaluateDefine(const QString &identifier, bool *ok = nullptr); + int evaluateExpression(const QString &expression); + QList tokenizeExpression(QString expression); QList generatePostfix(const QList &tokens); int evaluatePostfix(const QList &postfix); void recordError(const QString &message); void recordErrors(const QStringList &errors); void logRecordedErrors(); QString createErrorMessage(const QString &message, const QString &expression); + void updateSplashScreen(QString path); struct ParsedDefines { - QMap expressions; // Map of all define names encountered to their expressions + QHash expressions; // Map of all define names encountered to their expressions QStringList filteredNames; // List of define names that matched the search text, in the order that they were encountered }; - ParsedDefines readCDefines(const QString &filename, const QStringList &filterList, bool useRegex); - QMap evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex); - bool defineNameMatchesFilter(const QString &name, const QStringList &filterList) const; - bool defineNameMatchesFilter(const QString &name, const QList &filterList) const; + ParsedDefines readCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); + QHash evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); + bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; + bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; + QString pathWithRoot(const QString &path); static const QRegularExpression re_incScriptLabel; static const QRegularExpression re_globalIncScriptLabel; diff --git a/include/core/regionmap.h b/include/core/regionmap.h index bc05a84f..c8afb1b2 100644 --- a/include/core/regionmap.h +++ b/include/core/regionmap.h @@ -33,7 +33,6 @@ struct LayoutSquare struct MapSectionEntry { - QString name = ""; int x = 0; int y = 0; int width = 1; @@ -57,8 +56,8 @@ public: bool loadLayout(poryjson::Json); bool loadEntries(); - void setEntries(tsl::ordered_map *entries) { this->region_map_entries = entries; } - void setEntries(tsl::ordered_map entries) { *(this->region_map_entries) = entries; } + void setEntries(QHash *entries) { this->region_map_entries = entries; } + void setEntries(const QHash &entries) { *(this->region_map_entries) = entries; } void clearEntries() { this->region_map_entries->clear(); } MapSectionEntry getEntry(QString section); void setEntry(QString section, MapSectionEntry entry); @@ -114,8 +113,6 @@ public: 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; } @@ -149,14 +146,12 @@ public: const QString section_prefix; const QString default_map_section; - const QString count_map_section; signals: void mapNeedsDisplaying(); private: - // TODO: defaults needed? - tsl::ordered_map *region_map_entries = nullptr; + QHash *region_map_entries = nullptr; QString alias = ""; diff --git a/include/core/regionmapeditcommands.h b/include/core/regionmapeditcommands.h index 69bea251..e47fdb7b 100644 --- a/include/core/regionmapeditcommands.h +++ b/include/core/regionmapeditcommands.h @@ -64,9 +64,9 @@ private: /// Edit Layout Dimensions -class ResizeLayout : public QUndoCommand { +class ResizeRMLayout : public QUndoCommand { public: - ResizeLayout(RegionMap *map, int oldWidth, int oldHeight, int newWidth, int newHeight, + ResizeRMLayout(RegionMap *map, int oldWidth, int oldHeight, int newWidth, int newHeight, QMap> oldLayouts, QMap> newLayouts, QUndoCommand *parent = nullptr); void undo() override; @@ -153,7 +153,7 @@ private: /// ClearEntries class ClearEntries : public QUndoCommand { public: - ClearEntries(RegionMap *map, tsl::ordered_map, QUndoCommand *parent = nullptr); + ClearEntries(RegionMap *map, QHash, QUndoCommand *parent = nullptr); void undo() override; void redo() override; @@ -163,7 +163,7 @@ public: private: RegionMap *map; - tsl::ordered_map entries; + QHash entries; }; #endif // REGIONMAPEDITCOMMANDS_H diff --git a/include/core/tile.h b/include/core/tile.h index 5d85066a..b58a187d 100644 --- a/include/core/tile.h +++ b/include/core/tile.h @@ -19,6 +19,8 @@ public: uint16_t rawValue() const; static int getIndexInTileset(int); + + static const uint16_t maxValue; }; inline bool operator==(const Tile &a, const Tile &b) { diff --git a/include/core/tileset.h b/include/core/tileset.h index bb7631d7..abb31ab1 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -38,8 +38,6 @@ public: QList> palettes; QList> palettePreviews; - bool hasUnsavedTilesImage; - static Tileset* getMetatileTileset(int, Tileset*, Tileset*); static Tileset* getTileTileset(int, Tileset*, Tileset*); static Metatile* getMetatile(int, Tileset*, Tileset*); @@ -56,9 +54,24 @@ public: static QHash getHeaderMemberMap(bool usingAsm); static QString getExpectedDir(QString tilesetName, bool isSecondary); QString getExpectedDir(); - bool appendToHeaders(QString root, QString friendlyName, bool usingAsm); - bool appendToGraphics(QString root, QString friendlyName, bool usingAsm); - bool appendToMetatiles(QString root, QString friendlyName, bool usingAsm); + + bool load(); + bool loadMetatiles(); + bool loadMetatileAttributes(); + bool loadTilesImage(QImage *importedImage = nullptr); + bool loadPalettes(); + + bool save(); + bool saveMetatileAttributes(); + bool saveMetatiles(); + bool saveTilesImage(); + bool savePalettes(); + + bool appendToHeaders(const QString &filepath, const QString &friendlyName, bool usingAsm); + bool appendToGraphics(const QString &filepath, const QString &friendlyName, bool usingAsm); + bool appendToMetatiles(const QString &filepath, const QString &friendlyName, bool usingAsm); + + void setTilesImage(const QImage &image); void setMetatiles(const QList &metatiles); void addMetatile(Metatile* metatile); @@ -67,11 +80,12 @@ public: Metatile* metatileAt(unsigned int i) const { return m_metatiles.at(i); } void clearMetatiles(); - void resizeMetatiles(unsigned int newNumMetatiles); + void resizeMetatiles(int newNumMetatiles); int numMetatiles() const { return m_metatiles.length(); } private: QList m_metatiles; + bool m_hasUnsavedTilesImage = false; }; #endif // TILESET_H diff --git a/include/core/utility.h b/include/core/utility.h new file mode 100644 index 00000000..09caebce --- /dev/null +++ b/include/core/utility.h @@ -0,0 +1,16 @@ +#pragma once +#ifndef UTILITY_H +#define UTILITY_H + +#include + +namespace Util { + void numericalModeSort(QStringList &list); + int roundUp(int numToRound, int multiple); + QString toDefineCase(QString input); + QString toHexString(uint32_t value, int minLength = 0); + QString toHtmlParagraph(const QString &text); + Qt::Orientations getOrientation(bool xflip, bool yflip); +} + +#endif // UTILITY_H diff --git a/include/core/validator.h b/include/core/validator.h new file mode 100644 index 00000000..46b916ce --- /dev/null +++ b/include/core/validator.h @@ -0,0 +1,69 @@ +#ifndef VALIDATOR_H +#define VALIDATOR_H + +/* + This file contains our subclasses of QValidator. + + - PrefixValidator is for input widgets that want to enforce a particular prefix. + It differs from a QRegularExpressionValidator with a prefix in the regex because + it will automatically enforce the prefix in fixup() if it isn't present. + It's preferable to QLineEdit's input mask because it won't affect cursor behavior. + + - IdentifierValidator is for validating that input text can be used for the name of an identifier in the project. + (i.e., starts with a letter or underscore, then may continue with letters, numbers, or underscores). + Unless a prefix is specified this is a normal QRegularExpressionValidator, we only have a subclass because we use it so often. + + - UppercaseValidator is just a validator that uppercases input text. +*/ + +#include + +class PrefixValidator : public QRegularExpressionValidator { + Q_OBJECT + +public: + explicit PrefixValidator(const QString &prefix, QObject *parent = nullptr) + : QRegularExpressionValidator(parent), m_prefix(prefix) {}; + explicit PrefixValidator(const QString &prefix, const QRegularExpression &re, QObject *parent = nullptr) + : QRegularExpressionValidator(re, parent), m_prefix(prefix) {}; + ~PrefixValidator() {}; + + virtual QValidator::State validate(QString &input, int &) const override; + virtual void fixup(QString &input) const override; + + QString prefix() const { return m_prefix; } + void setPrefix(const QString &prefix); + + bool isValid(const QString &input) const; + +private: + QString m_prefix; + + bool missingPrefix(const QString &input) const; +}; + +class IdentifierValidator : public PrefixValidator { + Q_OBJECT + +public: + explicit IdentifierValidator(QObject *parent = nullptr) + : PrefixValidator("", re_identifier, parent) {}; + explicit IdentifierValidator(const QString &prefix, QObject *parent = nullptr) + : PrefixValidator(prefix, re_identifier, parent) {}; + ~IdentifierValidator() {}; + + void setAllowEmpty(bool allowEmpty); + +private: + static const QRegularExpression re_identifier; + static const QRegularExpression re_identifierOrEmpty; +}; + +class UppercaseValidator : public QValidator { + virtual QValidator::State validate(QString &input, int &) const override { + input = input.toUpper(); + return QValidator::Acceptable; + } +}; + +#endif // VALIDATOR_H diff --git a/include/core/wildmoninfo.h b/include/core/wildmoninfo.h index 6c93d0d2..a3665b7d 100644 --- a/include/core/wildmoninfo.h +++ b/include/core/wildmoninfo.h @@ -3,34 +3,43 @@ #define GUARD_WILDMONINFO_H #include -#include "orderedmap.h" +#include "orderedjson.h" -struct WildPokemon { - int minLevel = 5; - int maxLevel = 5; - QString species = "SPECIES_NONE"; +class WildPokemon { +public: + WildPokemon(); + WildPokemon(int minLevel, int maxLevel, const QString &species); + + int minLevel; + int maxLevel; + QString species; + OrderedJson::object customData; }; struct WildMonInfo { bool active = false; int encounterRate = 0; QVector wildPokemon; + OrderedJson::object customData; }; struct WildPokemonHeader { - tsl::ordered_map wildMons; + OrderedMap wildMons; + OrderedJson::object customData; }; struct EncounterField { QString name; // Ex: "fishing_mons" QVector encounterRates; - tsl::ordered_map> groups; // Ex: "good_rod", {2, 3, 4} + OrderedMap> groups; // Ex: "good_rod", {2, 3, 4} + OrderedJson::object customData; }; typedef QVector EncounterFields; void setDefaultEncounterRate(QString fieldName, int rate); -WildMonInfo getDefaultMonInfo(EncounterField field); +WildMonInfo getDefaultMonInfo(const EncounterField &field); +QVector getWildEncounterPercentages(const EncounterField &field); void combineEncounters(WildMonInfo &to, WildMonInfo from); #endif // GUARD_WILDMONINFO_H diff --git a/include/editor.h b/include/editor.h index 88eec307..432084e8 100644 --- a/include/editor.h +++ b/include/editor.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "mapconnection.h" #include "metatileselector.h" @@ -21,7 +22,7 @@ #include "divingmappixmapitem.h" #include "currentselectedmetatilespixmapitem.h" #include "collisionpixmapitem.h" -#include "mappixmapitem.h" +#include "layoutpixmapitem.h" #include "settings.h" #include "gridsettings.h" #include "movablerect.h" @@ -29,7 +30,7 @@ #include "mapruler.h" #include "encountertablemodel.h" -class DraggablePixmapItem; +class EventPixmapItem; class MetatilesPixmapItem; class Editor : public QObject @@ -45,19 +46,32 @@ public: public: Ui::MainWindow* ui; - QObject *parent = nullptr; + QPointer project = nullptr; - Map *map = nullptr; + QPointer map = nullptr; + QPointer layout = nullptr; + + QUndoGroup editGroup; // Manages the undo history for each map + Settings *settings; GridSettings gridSettings; + void setProject(Project * project); - void saveProject(); - void save(); - void closeProject(); - bool setMap(QString map_name); - void saveUiFields(); + bool saveAll(); + bool saveCurrent(); void saveEncounterTabData(); + + void closeProject(); + + bool setMap(QString map_name); + bool setLayout(QString layoutName); + void unsetMap(); + + Tileset *getCurrentMapPrimaryTileset(); + bool displayMap(); + bool displayLayout(); + void displayMetatileSelector(); void displayMapMetatiles(); void displayMapMovementPermissions(); @@ -75,56 +89,63 @@ public: void updateMapBorder(); void updateMapConnections(); - void setEditingMap(); - void setEditingCollision(); - void setEditingObjects(); - void setEditingConnections(); - void setMapEditingButtonsEnabled(bool enabled); void setConnectionsVisibility(bool visible); void updateDivingMapsVisibility(); void renderDivingConnections(); - void addConnection(MapConnection* connection); + void addNewConnection(const QString &mapName, const QString &direction); + void replaceConnection(const QString &mapName, const QString &direction); void removeConnection(MapConnection* connection); void removeSelectedConnection(); void addNewWildMonGroup(QWidget *window); void deleteWildMonGroup(); + void configureEncounterJSON(QWidget *); EncounterTableModel* getCurrentWildMonTable(); - void updateDiveMap(QString mapName); - void updateEmergeMap(QString mapName); + bool setDivingMapName(const QString &mapName, const QString &direction); + QString getDivingMapName(const QString &direction) const; void setSelectedConnection(MapConnection *connection); + void updatePrimaryTileset(QString tilesetLabel, bool forceLoad = false); void updateSecondaryTileset(QString tilesetLabel, bool forceLoad = false); void toggleBorderVisibility(bool visible, bool enableScriptCallback = true); - void updateCustomMapHeaderValues(QTableWidget *); - void configureEncounterJSON(QWidget *); - Tileset *getCurrentMapPrimaryTileset(); + void updateCustomMapAttributes(); - DraggablePixmapItem *addMapEvent(Event *event); - void selectMapEvent(DraggablePixmapItem *object, bool toggle = false); - DraggablePixmapItem *addNewEvent(Event::Type type); - void updateSelectedEvents(); + EventPixmapItem *addEventPixmapItem(Event *event); + void removeEventPixmapItem(Event *event); + bool canAddEvents(const QList &events); + void selectMapEvent(Event *event, bool toggle = false); + Event *addNewEvent(Event::Type type); + void updateEvents(); void duplicateSelectedEvents(); - void redrawObject(DraggablePixmapItem *item); - QList getObjects(); + void redrawAllEvents(); + void redrawEvents(const QList &events); + void redrawEventPixmapItem(EventPixmapItem *item); + void updateEventPixmapItemZValue(EventPixmapItem *item); + qreal getEventOpacity(const Event *event) const; + + void setPlayerViewRect(const QRectF &rect); void updateCursorRectPos(int x, int y); void setCursorRectVisible(bool visible); + + void onEventDragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition); + void onEventReleased(Event *event, const QPoint &position); void updateWarpEventWarning(Event *event); void updateWarpEventWarnings(); - bool eventLimitReached(Map *, Event::Type); QPointer scene = nullptr; QGraphicsPixmapItem *current_view = nullptr; - QPointer map_item = nullptr; + QPointer map_item = nullptr; QList> connection_items; QMap> diving_map_items; QGraphicsPathItem *connection_mask = nullptr; QPointer collision_item = nullptr; QGraphicsItemGroup *events_group = nullptr; + QList borderItems; QGraphicsItemGroup *mapGrid = nullptr; + QPointer map_ruler = nullptr; + MovableRect *playerViewRect = nullptr; CursorTileRect *cursorMapTileRect = nullptr; - MapRuler *map_ruler = nullptr; QPointer scene_metatiles = nullptr; QPointer scene_current_metatile_selection = nullptr; @@ -136,31 +157,51 @@ public: CurrentSelectedMetatilesPixmapItem *current_metatile_selection_item = nullptr; QPointer movement_permissions_selector_item = nullptr; - QList *selected_events = nullptr; + QList selectedEvents; QPointer selected_connection_item = nullptr; QPointer connection_to_select = nullptr; - QString map_edit_mode = "paint"; - QString obj_edit_mode = "select"; + enum class EditAction { None, Paint, Select, Fill, Shift, Pick, Move }; + EditAction mapEditAction = EditAction::Paint; + EditAction eventEditAction = EditAction::Select; + + enum class EditMode { None, Disabled, Metatiles, Collision, Header, Events, Connections, Encounters }; + void setEditMode(EditMode editMode); + EditMode getEditMode() const { return this->editMode; } + + bool getEditingLayout(); + + void setMapEditingButtonsEnabled(bool enabled); int scaleIndex = 2; qreal collisionOpacity = 0.5; static QList> collisionIcons; - void objectsView_onMousePress(QMouseEvent *event); - - int getBorderDrawDistance(int dimension); - - QUndoGroup editGroup; // Manages the undo history for each map - - bool selectingEvent = false; + int eventShiftActionId = 0; + int eventMoveActionId = 0; + void deleteSelectedEvents(); void shouldReselectEvents(); void scaleMapView(int); static void openInTextEditor(const QString &path, int lineNum = 0); - bool eventLimitReached(Event::Type type); void setCollisionGraphics(); + enum ZValue { + MapBorder = -4, + MapConnectionInactive = -3, + MapConnectionActive = -2, + MapConnectionMask = -1, + + // Event pixmaps set their z value to be their y position on the map. + // Their y value is int16_t, so we have enough space to allocate the + // full range + 1 for the selected event (which should always be on top). + EventMinimum = 1, + EventMaximum = EventMinimum + 0x10000, + + Ruler, + ResizeLayoutPopup + }; + public slots: void openMapScripts() const; void openScript(const QString &scriptLabel) const; @@ -175,6 +216,9 @@ private: const QImage collisionPlaceholder = QImage(":/images/collisions_unknown.png"); QPixmap collisionSheetPixmap; + EditMode editMode = EditMode::None; + + bool save(bool currentOnly); void clearMap(); void clearMetatileSelector(); void clearMovementPermissionSelector(); @@ -188,15 +232,14 @@ private: void clearMapBorder(); void clearMapGrid(); void clearWildMonTables(); + int getSortedItemIndex(QComboBox *combo, QString item); void updateBorderVisibility(); - void disconnectMapConnection(MapConnection *connection); - QPoint getConnectionOrigin(MapConnection *connection); void removeConnectionPixmap(MapConnection *connection); - void updateConnectionPixmap(ConnectionPixmapItem *connectionItem); void displayConnection(MapConnection *connection); void displayDivingConnection(MapConnection *connection); - void setDivingMapName(QString mapName, QString direction); void removeDivingMapPixmap(MapConnection *connection); + void onDivingMapEditingFinished(NoScrollComboBox* combo, const QString &direction); + void updateDivingMapButton(QToolButton* button, const QString &mapName); void updateEncounterFields(EncounterFields newFields); QString getMovementPermissionText(uint16_t collision, uint16_t elevation); QString getMetatileDisplayMessage(uint16_t metatileId); @@ -206,11 +249,11 @@ private: qint64 *pid = nullptr); private slots: - void onMapStartPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); - void onMapEndPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); + void onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); + void onMapEndPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); void setSmartPathCursorMode(QGraphicsSceneMouseEvent *event); void setStraightPathCursorMode(QGraphicsSceneMouseEvent *event); - void mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); + void mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); void mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item); void setSelectedConnectionItem(ConnectionPixmapItem *connectionItem); void onHoveredMovementPermissionChanged(uint16_t, uint16_t); @@ -225,12 +268,12 @@ private slots: void onWheelZoom(int); signals: - void objectsChanged(); + void eventsChanged(); + void openEventMap(Event*); void openConnectedMap(MapConnection*); void wildMonTableOpened(EncounterTableModel*); void wildMonTableClosed(); void wildMonTableEdited(); - void warpEventDoubleClicked(QString, int, Event::Group); void currentMetatilesSelectionChanged(); void mapRulerStatusChanged(const QString &); void tilesetUpdated(QString); diff --git a/include/lib/collapsiblesection.h b/include/lib/collapsiblesection.h new file mode 100644 index 00000000..b74835f4 --- /dev/null +++ b/include/lib/collapsiblesection.h @@ -0,0 +1,69 @@ +/* + Elypson/qt-collapsible-section + (c) 2016 Michael A. Voelkel - michael.alexander.voelkel@gmail.com + + This file is part of Elypson/qt-collapsible section. + + Elypson/qt-collapsible-section is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Elypson/qt-collapsible-section is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Elypson/qt-collapsible-section. If not, see . + + + PORYMAP NOTE: Modified to support having the section expanded by default, to stop the contents + squashing during the collapse animation, and to add some guard rails against crashes. +*/ + +#ifndef COLLAPSIBLESECTION_H +#define COLLAPSIBLESECTION_H + +#include +#include +#include +#include +#include +#include +#include +#include + +class CollapsibleSection : public QWidget +{ + Q_OBJECT + +public: + explicit CollapsibleSection(const QString& title = "", const bool expanded = false, const int animationDuration = 0, QWidget* parent = 0); + + void setContentLayout(QLayout* contentLayout); + void setTitle(const QString &title); + bool isExpanded() const { return this->expanded; } + +public slots: + void toggle(bool collapsed); + +private: + QGridLayout* mainLayout; + QToolButton* toggleButton; + QFrame* headerLine; + QParallelAnimationGroup* toggleAnimation; + QSet sectionAnimations; + QPropertyAnimation* contentAnimation; + QScrollArea* contentArea; + int animationDuration; + int collapsedHeight; + bool expanded; + + void updateToggleButton(); + void updateAnimationTargets(); + int getContentHeight() const; + +}; + +#endif // COLLAPSIBLESECTION_H diff --git a/include/lib/fex/array_value.h b/include/lib/fex/array_value.h index 642cc129..3339d21a 100644 --- a/include/lib/fex/array_value.h +++ b/include/lib/fex/array_value.h @@ -96,6 +96,7 @@ namespace fex case Type::kValuePair: return pair_.first + " = " + pair_.second->ToString() + "\n"; } + return ""; } static ArrayValue Empty() diff --git a/include/lib/fex/lexer.h b/include/lib/fex/lexer.h index 9b22976d..d4d65271 100644 --- a/include/lib/fex/lexer.h +++ b/include/lib/fex/lexer.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace fex { @@ -89,9 +90,7 @@ namespace fex Lexer() = default; ~Lexer() = default; - std::vector LexFile(const std::string &path); - std::vector LexString(const std::string &data); - void LexFileDumpTokens(const std::string &path, const std::string &out); + std::vector LexFile(const QString &path); private: std::vector Lex(); diff --git a/include/lib/fex/parser.h b/include/lib/fex/parser.h index 6a6b9e43..e69b2c40 100644 --- a/include/lib/fex/parser.h +++ b/include/lib/fex/parser.h @@ -9,6 +9,7 @@ #include "array_value.h" #include "define_statement.h" #include "lexer.h" +#include "orderedmap.h" namespace fex { @@ -19,9 +20,9 @@ namespace fex std::vector Parse(std::vector tokens); std::vector ParseTopLevelArrays(std::vector tokens); - std::map ParseTopLevelObjects(std::vector tokens); + tsl::ordered_map ParseTopLevelObjects(std::vector tokens); - std::map ReadDefines(const std::string &filename, std::vector matching); + tsl::ordered_map ReadDefines(const QString &filename, std::vector matching); private: int EvaluateExpression(std::vector tokens); diff --git a/include/lib/fex/parser_util.h b/include/lib/fex/parser_util.h deleted file mode 100644 index 58ff89fc..00000000 --- a/include/lib/fex/parser_util.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef PARSER_UTIL_H -#define PARSER_UTIL_H - -#include -#include - -class ParserUtil -{ -public: - ParserUtil(QString root); - QStringList ReadDefines(QString filename, QString prefix); - QStringList ReadDefinesValueSort(QString filename, QString prefix); - -private: - QString root_; -}; - - -#endif // PARSER_UTIL_H diff --git a/include/lib/orderedjson.h b/include/lib/orderedjson.h index c13ec13c..cb5139d3 100644 --- a/include/lib/orderedjson.h +++ b/include/lib/orderedjson.h @@ -99,7 +99,7 @@ public: // Array and object typedefs typedef QVector array; - typedef tsl::ordered_map object; + typedef OrderedMap object; // Constructors for the various types of JSON value. Json() noexcept; // NUL @@ -132,7 +132,22 @@ public: int>::type = 0> Json(const V & v) : Json(array(v.begin(), v.end())) {} - static const Json fromQJsonValue(QJsonValue value); + static Json fromQJsonValue(const QJsonValue &value); + + static void append(Json::array *array, const QJsonArray &addendum) { + for (const auto &i : addendum) array->push_back(fromQJsonValue(i)); + } + static void append(Json::array *array, const Json::array &addendum) { + for (const auto &i : addendum) array->push_back(i); + } + static void append(Json::object *object, const QJsonObject &addendum) { + for (auto it = addendum.constBegin(); it != addendum.constEnd(); it++) + (*object)[it.key()] = fromQJsonValue(it.value()); + } + static void append(Json::object *object, const Json::object &addendum) { + for (auto it = addendum.cbegin(); it != addendum.cend(); it++) + (*object)[it.key()] = it.value(); + } // This prevents Json(some_pointer) from accidentally producing a bool. Use // Json(bool(some_pointer)) if that behavior is desired. @@ -182,15 +197,15 @@ public: // Parse. If parse fails, return Json() and assign an error message to err. static Json parse(const QString & in, - QString & err, + QString * err = nullptr, JsonParse strategy = JsonParse::STANDARD); static Json parse(const char * in, - QString & err, + QString * err = nullptr, JsonParse strategy = JsonParse::STANDARD) { if (in) { return parse(QString(in), err, strategy); } else { - err = "null input"; + if (err) *err = "null input"; return nullptr; } } @@ -247,4 +262,7 @@ protected: } // namespace poryjson +using OrderedJson = poryjson::Json; +using OrderedJsonDoc = poryjson::JsonDoc; + #endif // ORDERED_JSON_H diff --git a/include/lib/orderedmap.h b/include/lib/orderedmap.h index 40882d9f..33fcfc50 100644 --- a/include/lib/orderedmap.h +++ b/include/lib/orderedmap.h @@ -1977,6 +1977,14 @@ public: size_type erase(const K& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } + + // Naive solution for take, should probably be replaced with one that does a single lookup and no unnecessary insertion. + // We want to mirror the behavior of QMap::take, which returns a default-constructed value if the key is not present. + T take(const key_type& key) { + typename ValueSelect::value_type value = m_ht[key]; + m_ht.erase(key); + return value; + } @@ -2404,4 +2412,7 @@ private: } // end namespace tsl +template +using OrderedMap = tsl::ordered_map; + #endif diff --git a/include/mainwindow.h b/include/mainwindow.h index c69425a2..5de2a50a 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -21,16 +21,18 @@ #include "regionmapeditor.h" #include "mapimageexporter.h" #include "filterchildrenproxymodel.h" -#include "newmappopup.h" -#include "newtilesetdialog.h" +#include "maplistmodels.h" #include "shortcutseditor.h" #include "preferenceeditor.h" #include "projectsettingseditor.h" #include "gridsettings.h" #include "customscriptseditor.h" #include "wildmonchart.h" +#include "wildmonsearch.h" #include "updatepromoter.h" #include "aboutporymap.h" +#include "mapheaderform.h" +#include "newlayoutdialog.h" @@ -50,6 +52,8 @@ public: MainWindow(const MainWindow &) = delete; MainWindow & operator = (const MainWindow &) = delete; + void initialize(); + // Scripting API Q_INVOKABLE QJSValue getBlock(int x, int y); void tryRedrawMapArea(bool forceRedraw); @@ -172,41 +176,37 @@ private slots: void on_action_Open_Project_triggered(); void on_action_Reload_Project_triggered(); void on_action_Close_Project_triggered(); - void on_mapList_activated(const QModelIndex &index); void on_action_Save_Project_triggered(); - void openWarpMap(QString map_name, int event_id, Event::Group event_group); + bool save(bool currentOnly = false); + + void openEventMap(Event *event); void duplicate(); void setClipboardData(poryjson::Json::object); void setClipboardData(QImage); + void setClipboardData(const QString &text); void copy(); void paste(); void onOpenConnectedMap(MapConnection*); - void onMapNeedsRedrawing(); void onTilesetsSaved(QString, QString); - void openNewMapPopupWindow(); - void onNewMapCreated(); - void onMapCacheCleared(); + void onNewMapCreated(Map *newMap, const QString &groupName); + void onNewMapGroupCreated(const QString &groupName); + void onNewMapSectionCreated(const QString &idName); + void onMapSectionDisplayNameChanged(const QString &idName, const QString &displayName); + void onNewLayoutCreated(Layout *layout); + void onNewTilesetCreated(Tileset *tileset); void onMapLoaded(Map *map); - void importMapFromAdvanceMap1_92(); void onMapRulerStatusChanged(const QString &); void applyUserShortcuts(); + void markMapEdited(Map*); + void markLayoutEdited(); - void on_action_NewMap_triggered(); void on_actionNew_Tileset_triggered(); void on_action_Save_triggered(); void on_action_Exit_triggered(); - void on_comboBox_Song_currentTextChanged(const QString &arg1); - void on_comboBox_Location_currentTextChanged(const QString &arg1); - void on_comboBox_Weather_currentTextChanged(const QString &arg1); - void on_comboBox_Type_currentTextChanged(const QString &arg1); - void on_comboBox_BattleScene_currentTextChanged(const QString &arg1); - void on_checkBox_ShowLocation_stateChanged(int selected); - void on_checkBox_AllowRunning_stateChanged(int selected); - void on_checkBox_AllowBiking_stateChanged(int selected); - void on_checkBox_AllowEscaping_stateChanged(int selected); - void on_spinBox_FloorNumber_valueChanged(int offset); + void onLayoutSelectorEditingFinished(); + void on_comboBox_LayoutSelector_currentTextChanged(const QString &text); void on_actionShortcuts_triggered(); void on_actionZoom_In_triggered(); @@ -221,14 +221,10 @@ private slots: void on_actionMove_triggered(); void on_actionMap_Shift_triggered(); - void onDeleteKeyPressed(); - void on_toolButton_deleteObject_clicked(); - - void addNewEvent(Event::Type type); void tryAddEventTab(QWidget * tab); void displayEventTabs(); - void updateSelectedObjects(); - void updateObjects(); + void updateSelectedEvents(); + void updateEvents(); void on_toolButton_Paint_clicked(); void on_toolButton_Select_clicked(); @@ -238,9 +234,6 @@ private slots: void on_toolButton_Shift_clicked(); void onOpenMapListContextMenu(const QPoint &point); - void onAddNewMapToGroupClick(QAction* triggeredAction); - void onAddNewMapToAreaClick(QAction* triggeredAction); - void onAddNewMapToLayoutClick(QAction* triggeredAction); void currentMetatilesSelectionChanged(); void on_action_Export_Map_Image_triggered(); @@ -251,49 +244,41 @@ private slots: void on_pushButton_AddConnection_clicked(); void on_button_OpenDiveMap_clicked(); void on_button_OpenEmergeMap_clicked(); - void on_comboBox_DiveMap_currentTextChanged(const QString &mapName); - void on_comboBox_EmergeMap_currentTextChanged(const QString &mapName); void on_comboBox_PrimaryTileset_currentTextChanged(const QString &arg1); void on_comboBox_SecondaryTileset_currentTextChanged(const QString &arg1); void on_pushButton_ChangeDimensions_clicked(); - void on_checkBox_smartPaths_stateChanged(int selected); - void on_checkBox_Visibility_stateChanged(int selected); - void on_checkBox_ToggleBorder_stateChanged(int selected); void resetMapViewScale(); void on_actionTileset_Editor_triggered(); - void mapSortOrder_changed(QAction *action); - - void on_lineEdit_filterBox_textChanged(const QString &arg1); - void moveEvent(QMoveEvent *event); void closeEvent(QCloseEvent *); void eventTabChanged(int index); - void on_checkBox_MirrorConnections_stateChanged(int selected); void on_actionDive_Emerge_Map_triggered(); + void on_actionShow_Events_In_Map_View_triggered(); void on_groupBox_DiveMapOpacity_toggled(bool on); void on_slider_DiveEmergeMapOpacity_valueChanged(int value); void on_slider_DiveMapOpacity_valueChanged(int value); void on_slider_EmergeMapOpacity_valueChanged(int value); void on_horizontalSlider_CollisionTransparency_valueChanged(int value); - void on_toolButton_ExpandAll_clicked(); - void on_toolButton_CollapseAll_clicked(); + + void mapListShortcut_ToggleEmptyFolders(); + void mapListShortcut_ExpandAll(); + void mapListShortcut_CollapseAll(); + void on_actionAbout_Porymap_triggered(); void on_actionOpen_Log_File_triggered(); void on_actionOpen_Config_Folder_triggered(); - void on_pushButton_AddCustomHeaderField_clicked(); - void on_pushButton_DeleteCustomHeaderField_clicked(); - void on_tableWidget_CustomHeaderFields_cellChanged(int row, int column); void on_horizontalSlider_MetatileZoom_valueChanged(int value); void on_horizontalSlider_CollisionZoom_valueChanged(int value); void on_pushButton_NewWildMonGroup_clicked(); void on_pushButton_DeleteWildMonGroup_clicked(); void on_pushButton_SummaryChart_clicked(); void on_pushButton_ConfigureEncountersJSON_clicked(); + void on_toolButton_WildMonSearch_clicked(); void on_pushButton_CreatePrefab_clicked(); void on_spinBox_SelectedElevation_valueChanged(int elevation); void on_spinBox_SelectedCollision_valueChanged(int collision); @@ -306,91 +291,128 @@ private slots: void reloadScriptEngine(); void on_actionShow_Grid_triggered(); void on_actionGrid_Settings_triggered(); + void openWildMonTable(const QString &mapName, const QString &groupName, const QString &fieldName); public: Ui::MainWindow *ui; QPointer editor = nullptr; +signals: + void mapOpened(Map*); + void layoutOpened(Layout*); + private: QLabel *label_MapRulerStatus = nullptr; QPointer tilesetEditor = nullptr; QPointer regionMapEditor = nullptr; QPointer shortcutsEditor = nullptr; QPointer mapImageExporter = nullptr; - QPointer newMapPrompt = nullptr; QPointer preferenceEditor = nullptr; QPointer projectSettingsEditor = nullptr; QPointer gridSettingsDialog = nullptr; QPointer customScriptsEditor = nullptr; + + QPointer groupListProxyModel = nullptr; + QPointer mapGroupModel = nullptr; + QPointer locationListProxyModel = nullptr; + QPointer mapLocationModel = nullptr; + QPointer layoutListProxyModel = nullptr; + QPointer layoutTreeModel = nullptr; + QPointer updatePromoter = nullptr; QPointer networkAccessManager = nullptr; QPointer aboutWindow = nullptr; QPointer wildMonChart = nullptr; - FilterChildrenProxyModel *mapListProxyModel; - QStandardItemModel *mapListModel; - QList *mapGroupItemsList; - QMap mapListIndexes; - QIcon mapIcon; + QPointer wildMonSearch = nullptr; QAction *undoAction = nullptr; QAction *redoAction = nullptr; + QPointer undoView = nullptr; QAction *copyAction = nullptr; QAction *pasteAction = nullptr; - QMap lastSelectedEvent; + MapHeaderForm *mapHeaderForm = nullptr; + + QMap lastSelectedEvent; bool isProgrammaticEventTabChange; - bool newMapDefaultsSet = false; + bool tilesetNeedsRedraw = false; - bool userSetMap(QString, bool scrollTreeView = false); - bool setMap(QString, bool scrollTreeView = false); + bool setLayout(QString layoutId); + bool setMap(QString); + void unsetMap(); + bool userSetLayout(QString layoutId); + bool userSetMap(QString); void redrawMapScene(); void refreshMapScene(); - bool checkProjectSanity(); + void refreshMetatileViews(); + void refreshCollisionSelector(); + void setLayoutOnlyMode(bool layoutOnly); + + bool isInvalidProject(Project *project); + bool checkProjectSanity(Project *project); + bool checkProjectVersion(Project *project); bool loadProjectData(); bool setProjectUI(); void clearProjectUI(); - void sortMapList(); + + void openEditHistory(); + void openNewMapDialog(); + void openDuplicateMapDialog(const QString &mapName); + NewLayoutDialog* createNewLayoutDialog(const Layout *layoutToCopy = nullptr); + void openNewLayoutDialog(); + void openDuplicateLayoutDialog(const QString &layoutId); + void openDuplicateMapOrLayoutDialog(); + void openNewMapGroupDialog(); + void openNewLocationDialog(); void openSubWindow(QWidget * window); + void scrollMapList(MapTree *list, const QString &itemName); + void scrollMapListToCurrentMap(MapTree *list); + void scrollMapListToCurrentLayout(MapTree *list); + void resetMapListFilters(); + void showFileWatcherWarning(); QString getExistingDirectory(QString); bool openProject(QString dir, bool initial = false); bool closeProject(); + void showRecentError(const QString &baseMessage); void showProjectOpenFailure(); - void saveGlobalConfigs(); + void showMapsExcludedAlert(const QStringList &excludedMapNames); + bool setInitialMap(); - QStandardItem* createMapItem(QString mapName, int groupNum, int inGroupNum); + void saveGlobalConfigs(); + void refreshRecentProjectsMenu(); - void updateMapListIcon(const QString &mapName); + void rebuildMapList_Locations(); + void rebuildMapList_Layouts(); void updateMapList(); + void openMapListItem(const QModelIndex &index); + void onMapListTabChanged(int index); void displayMapProperties(); void checkToolButtons(); - void clickToolButtonFromEditMode(QString editMode); + void clickToolButtonFromEditAction(Editor::EditAction editAction); - void markMapEdited(); - void markMapEdited(Map*); - void showWindowTitle(); + void updateWindowTitle(); void initWindow(); void initCustomUI(); void initExtraSignals(); void initEditor(); void initMiscHeapObjects(); - void initMapSortOrder(); + void initMapList(); void initShortcuts(); void initExtraShortcuts(); void loadUserSettings(); - void applyMapListFilter(QString filterText); void restoreWindowState(); void setTheme(QString); void updateTilesetEditor(); Event::Group getEventGroupFromTabWidget(QWidget *tab); bool closeSupplementaryWindows(); void setWindowDisabled(bool); - + void resetMapCustomAttributesTable(); void initTilesetEditor(); bool initRegionMapEditor(bool silent = false); bool askToFixRegionMapEditor(); @@ -403,19 +425,20 @@ private: double getMetatilesZoomScale(); void redrawMetatileSelection(); void scrollMetatileSelectorToSelection(); + MapListToolBar* getMapListToolBar(int tab); + MapListToolBar* getCurrentMapListToolBar(); + MapTree* getCurrentMapList(); + void setLocationComboBoxes(const QStringList &locations); QObjectList shortcutableObjects() const; void addCustomHeaderValue(QString key, QJsonValue value, bool isNew = false); - int insertTilesetLabel(QStringList * list, QString label); void checkForUpdates(bool requestedByUser); void setDivingMapsVisible(bool visible); -}; -enum MapListUserRoles { - GroupRole = Qt::UserRole + 1, // Used to hold the map group number. - TypeRole, // Used to differentiate between the different layers of the map list tree view. - TypeRole2, // Used for various extra data needed. + void setSmartPathsEnabled(bool enabled); + void setBorderVisibility(bool visible); + void setMirrorConnectionsEnabled(bool enabled); }; // These are namespaced in a struct to avoid colliding with e.g. class Map. @@ -437,4 +460,10 @@ struct MapViewTab { }; }; +struct MapListTab { + enum { + Groups = 0, Locations, Layouts + }; +}; + #endif // MAINWINDOW_H diff --git a/include/project.h b/include/project.h index f9fc704d..6f981a85 100644 --- a/include/project.h +++ b/include/project.h @@ -4,7 +4,6 @@ #include "map.h" #include "blockdata.h" -#include "heallocation.h" #include "wildmoninfo.h" #include "parseutil.h" #include "orderedjson.h" @@ -18,14 +17,11 @@ #include #include -// The displayed name of the special map value used by warps with multiple potential destinations -static QString DYNAMIC_MAP_NAME = "Dynamic"; - class Project : public QObject { Q_OBJECT public: - Project(QWidget *parent = nullptr); + Project(QObject *parent = nullptr); ~Project(); Project(const Project &) = delete; @@ -35,29 +31,24 @@ public: public: QString root; - QStringList groupNames; - QMap mapGroups; - QList groupedMapNames; QStringList mapNames; - QList healLocations; - QMap healLocationNameToValue; + QStringList groupNames; + QMap groupNameToMapNames; + QStringList healLocationSaveOrder; + QMap> healLocations; QMap mapConstantsToMapNames; - QMap mapNamesToMapConstants; - QStringList mapLayoutsTable; - QStringList mapLayoutsTableMaster; QString layoutsLabel; - QMap mapLayouts; - QMap mapLayoutsMaster; - QMap mapSecToMapHoverName; - QMap mapSectionNameToValue; - QMap mapSectionValueToName; - QMap eventGraphicsMap; + QStringList layoutIds; + QStringList layoutIdsMaster; + QMap mapLayouts; + QMap mapLayoutsMaster; QMap gfxDefines; QString defaultSong; QStringList songNames; QStringList itemNames; QStringList flagNames; QStringList varNames; + QStringList speciesNames; QStringList movementTypes; QStringList mapTypes; QStringList mapBattleScenes; @@ -67,14 +58,17 @@ public: QStringList bgEventFacingDirections; QStringList trainerTypes; QStringList globalScriptLabels; + QStringList mapSectionIdNamesSaveOrder; + QStringList mapSectionIdNames; + QMap encounterTypeToName; + QMap terrainTypeToName; QMap> metatileLabelsMap; QMap unusedMetatileLabels; QMap metatileBehaviorMap; QMap metatileBehaviorMapInverse; - QMap facingDirections; ParseUtil parser; QFileSystemWatcher fileWatcher; - QMap modifiedFileTimestamps; + QSet modifiedFiles; bool usingAsmTilesets; QSet disabledSettingsNames; int pokemonMinLevel; @@ -82,30 +76,27 @@ public: int maxEncounterRate; bool wildEncountersLoaded; - void set_root(QString); + void setRoot(const QString&); - void initSignals(); - - void clearMapCache(); + void clearMaps(); void clearTilesetCache(); void clearMapLayouts(); void clearEventGraphics(); - - struct DataQualifiers - { - bool isStatic; - bool isConst; - }; - DataQualifiers getDataQualifiers(QString, QString); - DataQualifiers healLocationDataQualifiers; - QString healLocationsTableName; + void clearHealLocations(); bool sanityCheck(); + int getSupportedMajorVersion(QString *errorOut = nullptr); bool load(); - QMap mapCache; - Map* loadMap(QString); - Map* getMap(QString); + Map* loadMap(const QString &mapName); + + // Note: This does not guarantee the map is loaded. + Map* getMap(const QString &mapName) { return this->maps.value(mapName); } + + bool isMapLoaded(const Map *map) const { return map && isMapLoaded(map->name()); } + bool isMapLoaded(const QString &mapName) const { return this->loadedMapNames.contains(mapName); } + bool isLayoutLoaded(const Layout *layout) const { return layout && isLayoutLoaded(layout->id); } + bool isLayoutLoaded(const QString &layoutId) const { return this->loadedLayoutIds.contains(layoutId); } QMap tilesetCache; Tileset* loadTileset(QString, Tileset *tileset = nullptr); @@ -114,64 +105,81 @@ public: QStringList secondaryTilesetLabels; QStringList tilesetLabelsOrdered; - Blockdata readBlockdata(QString); - bool loadBlockdata(MapLayout*); - bool loadLayoutBorder(MapLayout*); - - void saveTextFile(QString path, QString text); - void appendTextFile(QString path, QString text); - void deleteFile(QString path); + Blockdata readBlockdata(QString, bool *ok = nullptr); + bool loadBlockdata(Layout *); + bool loadLayoutBorder(Layout *); bool readMapGroups(); - Map* addNewMapToGroup(QString, int, Map*, bool, bool); - QString getNewMapName(); - QString getProjectTitle(); + void addNewMapGroup(const QString &groupName); + QString mapNameToMapGroup(const QString &mapName) const; + QString getMapConstant(const QString &mapName, const QString &defaultValue = QString()) const; + QString getMapLayoutId(const QString &mapName, const QString &defaultValue = QString()) const; + QString getMapLocation(const QString &mapName, const QString &defaultValue = QString()) const; - QString readMapLayoutId(QString map_name); - QString readMapLocation(QString map_name); + struct NewMapSettings { + QString name; + QString group; + bool canFlyTo; + Layout::Settings layout; + MapHeader header; + }; + NewMapSettings newMapSettings; + Layout::Settings newLayoutSettings; + + void initNewMapSettings(); + void initNewLayoutSettings(); + + Map *createNewMap(const Project::NewMapSettings &mapSettings, const Map* toDuplicate = nullptr); + Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); + Tileset *createNewTileset(QString name, bool secondary, bool checkerboardFill); + bool isIdentifierUnique(const QString &identifier) const; + bool isValidNewIdentifier(const QString &identifier) const; + QString toUniqueIdentifier(const QString &identifier) const; + QString getProjectTitle() const; + QString getNewHealLocationName(const Map* map) const; bool readWildMonData(); - tsl::ordered_map> wildMonData; + OrderedMap> wildMonData; + QString wildMonTableName; QVector wildMonFields; QVector encounterGroupLabels; - QVector extraEncounterGroups; bool readSpeciesIconPaths(); - QMap speciesToIconPath; + QString getDefaultSpeciesIconPath(const QString &species); + QPixmap getSpeciesIcon(const QString &species); - QSet getTopLevelMapFields(); + bool addNewMapsec(const QString &idName, const QString &displayName = QString()); + void removeMapsec(const QString &idName); + QString getMapsecDisplayName(const QString &idName) const { return this->locationData.value(idName).displayName; } + void setMapsecDisplayName(const QString &idName, const QString &displayName); + + bool hasUnsavedChanges(); + bool hasUnsavedDataChanges = false; + + bool readMapJson(const QString &mapName, QJsonDocument * out); + bool loadMapEvent(Map *map, QJsonObject json, Event::Type defaultType = Event::Type::None); bool loadMapData(Map*); bool readMapLayouts(); - bool loadLayout(MapLayout *); + Layout *loadLayout(QString layoutId); + bool loadLayout(Layout *); bool loadMapLayout(Map*); - bool loadLayoutTilesets(MapLayout*); - void loadTilesetAssets(Tileset*); - void loadTilesetTiles(Tileset*, QImage); - void loadTilesetMetatiles(Tileset*); + bool loadLayoutTilesets(Layout *); + bool loadTilesetAssets(Tileset*); void loadTilesetMetatileLabels(Tileset*); - void loadTilesetPalettes(Tileset*); void readTilesetPaths(Tileset* tileset); - void saveLayoutBlockdata(Map*); - void saveLayoutBorder(Map*); - void writeBlockdata(QString, const Blockdata &); - void saveAllMaps(); - void saveMap(Map*); - void saveAllDataStructures(); - void saveConfig(); - void saveMapLayouts(); - void saveMapGroups(); - void saveWildMonData(); - void saveMapConstantsHeader(); - void saveHealLocations(Map*); - void saveTilesets(Tileset*, Tileset*); - void saveTilesetMetatileLabels(Tileset*, Tileset*); - void saveTilesetMetatileAttributes(Tileset*); - void saveTilesetMetatiles(Tileset*); - void saveTilesetTilesImage(Tileset*); - void saveTilesetPalettes(Tileset*); - void appendTilesetLabel(QString label, QString isSecondaryStr); + bool saveAll(); + bool saveGlobalData(); + bool saveConfig(); + bool saveLayout(Layout *layout); + bool saveMap(Map *map, bool skipLayout = false); + bool saveTextFile(const QString &path, const QString &text); + bool saveRegionMapSections(); + bool saveTilesets(Tileset*, Tileset*); + bool saveTilesetMetatileLabels(Tileset*, Tileset*); + + void appendTilesetLabel(const QString &label, const QString &isSecondaryStr); bool readTilesetLabels(); bool readTilesetMetatileLabels(); bool readRegionMapSections(); @@ -188,7 +196,6 @@ public: bool readBgEventFacingDirections(); bool readTrainerTypes(); bool readMetatileBehaviors(); - bool readHealLocationConstants(); bool readHealLocations(); bool readMiscellaneousConstants(); bool readEventScriptLabels(); @@ -197,9 +204,13 @@ public: bool readEventGraphics(); bool readFieldmapProperties(); bool readFieldmapMasks(); + bool readGlobalConstants(); QMap> readObjEventGfxInfo(); - void setEventPixmap(Event *event, bool forceLoad = false); + QPixmap getEventPixmap(const QString &gfxName, const QString &movementName); + QPixmap getEventPixmap(const QString &gfxName, int frame, bool hFlip); + QPixmap getEventPixmap(Event::Group group); + void loadEventPixmap(Event *event, bool forceLoad = false); QString fixPalettePath(QString path); QString fixGraphicPath(QString path); @@ -207,59 +218,138 @@ public: static QString getScriptFileExtension(bool usePoryScript); QString getScriptDefaultString(bool usePoryScript, QString mapName) const; QStringList getEventScriptsFilePaths() const; + void insertGlobalScriptLabels(QStringList &scriptLabels) const; - QString getDefaultPrimaryTilesetLabel(); - QString getDefaultSecondaryTilesetLabel(); - QString getDynamicMapDefineName(); + QString getDefaultPrimaryTilesetLabel() const; + QString getDefaultSecondaryTilesetLabel() const; void updateTilesetMetatileLabels(Tileset *tileset); QString buildMetatileLabelsText(const QMap defines); QString findMetatileLabelsTileset(QString label); static QString getExistingFilepath(QString filepath); void applyParsedLimits(); + + void setRegionMapEntries(const QHash &entries); + QHash getRegionMapEntries() const; - static int getNumTilesPrimary(); - static int getNumTilesTotal(); - static int getNumMetatilesPrimary(); - static int getNumMetatilesTotal(); - static int getNumPalettesPrimary(); - static int getNumPalettesTotal(); - static int getMaxMapDataSize(); - static int getDefaultMapSize(); - static int getMaxMapWidth(); - static int getMaxMapHeight(); - static int getMapDataSize(int width, int height); - static bool mapDimensionsValid(int width, int height); + QSet getTopLevelMapFields() const; + + int getMapDataSize(int width, int height) const; + int getMaxMapDataSize() const { return this->maxMapDataSize; } + int getMaxMapWidth() const; + int getMaxMapHeight() const; + bool mapDimensionsValid(int width, int height) const; bool calculateDefaultMapSize(); - static int getMaxObjectEvents(); + QSize getDefaultMapSize() const { return this->defaultMapSize; } + QSize getMapSizeAddition() const { return this->mapSizeAddition; } + + int getMaxEvents(Event::Group group) const; + + static QString getEmptyMapDefineName(); + static QString getDynamicMapDefineName(); + static QString getDynamicMapName(); + static QString getEmptySpeciesName(); + static QMargins getMetatileViewDistance(); + static int getNumTilesPrimary() { return num_tiles_primary; } + static int getNumTilesTotal() { return num_tiles_total; } + static int getNumMetatilesPrimary() { return num_metatiles_primary; } + static int getNumMetatilesTotal() { return Block::getMaxMetatileId() + 1; } + static int getNumPalettesPrimary(){ return num_pals_primary; } + static int getNumPalettesTotal() { return num_pals_total; } + static QString getEmptyMapsecName(); + static QString getMapGroupPrefix(); private: - void updateMapLayout(Map*); + QMap modifiedFileTimestamps; + QMap facingDirections; + QHash speciesToIconPath; + QHash maps; - void setNewMapBlockdata(Map* map); - void setNewMapBorder(Map *map); - void setNewMapEvents(Map *map); - void setNewMapConnections(Map *map); + // Fields for preserving top-level JSON data that Porymap isn't expecting. + QJsonObject customLayoutsData; + QJsonObject customMapSectionsData; + QJsonObject customMapGroupsData; + QJsonObject customHealLocationsData; + OrderedJson::object customWildMonData; + OrderedJson::object customWildMonGroupData; + OrderedJson::array extraEncounterGroups; - void saveHealLocationsData(Map *map); - void saveHealLocationsConstants(); + // Maps/layouts represented in these sets have been fully loaded from the project. + // If a valid map name / layout id is not in these sets, a Map / Layout object exists + // for it in Project::maps / Project::mapLayouts, but it has been minimally populated + // (i.e. for a map layout it only has the data read from layouts.json, none of its assets + // have been loaded, and for a map it only has the data needed to identify it in the map + // list, none of the rest of its data in map.json). + QSet loadedMapNames; + QSet loadedLayoutIds; - void ignoreWatchedFileTemporarily(QString filepath); + const QRegularExpression re_gbapalExtension; + const QRegularExpression re_bppExtension; + struct EventGraphics + { + QString filepath; + bool loaded = false; + QImage spritesheet; + int spriteWidth = -1; + int spriteHeight = -1; + bool inanimate = false; + }; + QMap eventGraphicsMap; + + // The extra data that can be associated with each MAPSEC name. + struct LocationData + { + MapSectionEntry map; + QString displayName; + QJsonObject custom; + }; + QHash locationData; + + void setNewLayoutBlockdata(Layout *layout); + void setNewLayoutBorder(Layout *layout); + + void watchFile(const QString &filename); + void watchFiles(const QStringList &filenames); + void ignoreWatchedFileTemporarily(const QString &filepath); + void ignoreWatchedFilesTemporarily(const QStringList &filepaths); + void recordFileChange(const QString &filepath); + void resetFileCache(); + + bool saveMapLayouts(); + bool saveMapGroups(); + bool saveWildMonData(); + bool saveHealLocations(); + bool appendTextFile(const QString &path, const QString &text); + + QString findSpeciesIconPath(const QStringList &names) const; + + int maxObjectEvents; + int maxMapDataSize; + QSize defaultMapSize; + QSize mapSizeAddition; + + // TODO: These really shouldn't be static, they're specific to a single project. + // We're making an assumption here that we only have one project open at a single time + // (which is true, but then if that's the case we should have some global Project instance instead) static int num_tiles_primary; static int num_tiles_total; static int num_metatiles_primary; static int num_pals_primary; static int num_pals_total; - static int max_map_data_size; - static int default_map_size; - static int max_object_events; signals: - void reloadProject(); - void uncheckMonitorFilesAction(); - void mapCacheCleared(); + void fileChanged(const QString &filepath); void mapLoaded(Map *map); + void mapCreated(Map *newMap, const QString &groupName); + void layoutCreated(Layout *newLayout); + void tilesetCreated(Tileset *newTileset); + void mapGroupAdded(const QString &groupName); + void mapSectionAdded(const QString &idName); + void mapSectionDisplayNameChanged(const QString &idName, const QString &displayName); + void mapSectionIdNamesChanged(const QStringList &idNames); + void mapsExcluded(const QStringList &excludedMapNames); + void eventScriptLabelsRead(); }; #endif // PROJECT_H diff --git a/include/scripting.h b/include/scripting.h index c08bbd74..b85e9e26 100644 --- a/include/scripting.h +++ b/include/scripting.h @@ -9,6 +9,8 @@ #include #include +// !! New callback functions or changes to existing callback function names/arguments +// should be synced to resources/text/script_template.txt and docsrc/manual/scripting-capabilities.rst enum CallbackType { OnProjectOpened, OnProjectClosed, @@ -17,6 +19,7 @@ enum CallbackType { OnBlockHoverChanged, OnBlockHoverCleared, OnMapOpened, + OnLayoutOpened, OnMapResized, OnBorderResized, OnMapShifted, @@ -36,6 +39,7 @@ public: static void populateGlobalObject(MainWindow *mainWindow); static QJSEngine *getEngine(); static void invokeAction(int actionIndex); + static void cb_ProjectOpened(QString projectPath); static void cb_ProjectClosed(QString projectPath); static void cb_MetatileChanged(int x, int y, Block prevBlock, Block newBlock); @@ -43,18 +47,21 @@ public: static void cb_BlockHoverChanged(int x, int y); static void cb_BlockHoverCleared(); static void cb_MapOpened(QString mapName); - static void cb_MapResized(int oldWidth, int oldHeight, int newWidth, int newHeight); + static void cb_LayoutOpened(QString layoutName); + static void cb_MapResized(int oldWidth, int oldHeight, const QMargins &delta); static void cb_BorderResized(int oldWidth, int oldHeight, int newWidth, int newHeight); static void cb_MapShifted(int xDelta, int yDelta); static void cb_TilesetUpdated(QString tilesetName); static void cb_MainTabChanged(int oldTab, int newTab); static void cb_MapViewTabChanged(int oldTab, int newTab); static void cb_BorderVisibilityToggled(bool visible); + static bool tryErrorJS(QJSValue js); static QJSValue fromBlock(Block block); static QJSValue fromTile(Tile tile); static Tile toTile(QJSValue obj); static QJSValue dimensions(int width, int height); + static QJSValue margins(const QMargins &margins); static QJSValue position(int x, int y); static const QImage * getImage(const QString &filepath, bool useCache); static QJSValue dialogInput(QJSValue input, bool selectedOk); diff --git a/include/scriptutility.h b/include/scriptutility.h index 0d6fca75..78272eec 100644 --- a/include/scriptutility.h +++ b/include/scriptutility.h @@ -42,6 +42,9 @@ public: Q_INVOKABLE QList getMetatileLayerOpacity(); Q_INVOKABLE void setMetatileLayerOpacity(QList order); Q_INVOKABLE QList getMapNames(); + Q_INVOKABLE QList getMapConstants(); + Q_INVOKABLE QList getLayoutNames(); + Q_INVOKABLE QList getLayoutConstants(); Q_INVOKABLE QList getTilesetNames(); Q_INVOKABLE QList getPrimaryTilesetNames(); Q_INVOKABLE QList getSecondaryTilesetNames(); diff --git a/include/ui/aboutporymap.h b/include/ui/aboutporymap.h index 28b06249..3a760f44 100644 --- a/include/ui/aboutporymap.h +++ b/include/ui/aboutporymap.h @@ -2,18 +2,19 @@ #define ABOUTPORYMAP_H #include -#include -#include +#include namespace Ui { class AboutPorymap; } -class AboutPorymap : public QMainWindow +class AboutPorymap : public QDialog { public: explicit AboutPorymap(QWidget *parent = nullptr); ~AboutPorymap(); + + static QString getVersionString(); private: Ui::AboutPorymap *ui; }; diff --git a/include/ui/bordermetatilespixmapitem.h b/include/ui/bordermetatilespixmapitem.h index f4af8bfe..cf2ba0d4 100644 --- a/include/ui/bordermetatilespixmapitem.h +++ b/include/ui/bordermetatilespixmapitem.h @@ -1,21 +1,21 @@ #ifndef BORDERMETATILESPIXMAPITEM_H #define BORDERMETATILESPIXMAPITEM_H -#include "map.h" +#include "maplayout.h" #include "metatileselector.h" #include class BorderMetatilesPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: - BorderMetatilesPixmapItem(Map *map_, MetatileSelector *metatileSelector) { - this->map = map_; - this->map->setBorderItem(this); + BorderMetatilesPixmapItem(Layout *layout, MetatileSelector *metatileSelector) { + this->layout = layout; + this->layout->setBorderItem(this); this->metatileSelector = metatileSelector; setAcceptHoverEvents(true); } MetatileSelector *metatileSelector; - Map *map; + Layout *layout; void draw(); signals: void hoveredBorderMetatileSelectionChanged(uint16_t); diff --git a/include/ui/collisionpixmapitem.h b/include/ui/collisionpixmapitem.h index c489ddd7..1419f2e4 100644 --- a/include/ui/collisionpixmapitem.h +++ b/include/ui/collisionpixmapitem.h @@ -4,19 +4,20 @@ #include #include "metatileselector.h" -#include "mappixmapitem.h" +#include "movementpermissionsselector.h" +#include "layoutpixmapitem.h" #include "map.h" #include "settings.h" -class CollisionPixmapItem : public MapPixmapItem { +class CollisionPixmapItem : public LayoutPixmapItem { Q_OBJECT public: - CollisionPixmapItem(Map *map, QSpinBox * selectedCollision, QSpinBox * selectedElevation, MetatileSelector *metatileSelector, Settings *settings, qreal *opacity) - : MapPixmapItem(map, metatileSelector, settings){ + CollisionPixmapItem(Layout *layout, QSpinBox * selectedCollision, QSpinBox * selectedElevation, MetatileSelector *metatileSelector, Settings *settings, qreal *opacity) + : LayoutPixmapItem(layout, metatileSelector, settings){ this->selectedCollision = selectedCollision; this->selectedElevation = selectedElevation; this->opacity = opacity; - map->setCollisionItem(this); + layout->setCollisionItem(this); } QSpinBox * selectedCollision; QSpinBox * selectedElevation; diff --git a/include/ui/colorinputwidget.h b/include/ui/colorinputwidget.h index cd871e0b..527794d9 100644 --- a/include/ui/colorinputwidget.h +++ b/include/ui/colorinputwidget.h @@ -2,7 +2,6 @@ #define COLORINPUTWIDGET_H #include -#include namespace Ui { class ColorInputWidget; diff --git a/include/ui/colorpicker.h b/include/ui/colorpicker.h index fa431341..69a6c9b2 100644 --- a/include/ui/colorpicker.h +++ b/include/ui/colorpicker.h @@ -27,7 +27,7 @@ private: QColor color = Qt::white; - void hover(int mouseX, int mouseY); + void hover(const QPoint &pos); }; #endif // COLORPICKER_H diff --git a/include/ui/connectionpixmapitem.h b/include/ui/connectionpixmapitem.h index 62eda6fe..32e309f9 100644 --- a/include/ui/connectionpixmapitem.h +++ b/include/ui/connectionpixmapitem.h @@ -5,26 +5,25 @@ #include #include #include +#include class ConnectionPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: - ConnectionPixmapItem(MapConnection* connection, int originX, int originY); - ConnectionPixmapItem(MapConnection* connection, QPoint origin); + ConnectionPixmapItem(MapConnection* connection); const QPointer connection; - void setOrigin(int x, int y); - void setOrigin(QPoint pos); - void setEditable(bool editable); bool getEditable(); void setSelected(bool selected); - void updatePos(); void render(bool ignoreCache = false); +signals: + void positionChanged(qreal x, qreal y); + private: QPixmap basePixmap; qreal originX; @@ -35,15 +34,20 @@ private: static const int mWidth = 16; static const int mHeight = 16; + void updatePos(); + void updateOrigin(); + void refresh(); + protected: - QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; - void mousePressEvent(QGraphicsSceneMouseEvent*) override; - void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override; + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override; signals: void connectionItemDoubleClicked(MapConnection*); void selectionChanged(bool selected); + void deleteRequested(MapConnection*); }; #endif // CONNECTIONPIXMAPITEM_H diff --git a/include/ui/connectionslistitem.h b/include/ui/connectionslistitem.h index bbe0f2d3..1b9713cf 100644 --- a/include/ui/connectionslistitem.h +++ b/include/ui/connectionslistitem.h @@ -23,7 +23,6 @@ public: explicit ConnectionsListItem(QWidget *parent, MapConnection *connection, const QStringList &mapNames); ~ConnectionsListItem(); - void updateUI(); void setSelected(bool selected); private: @@ -33,20 +32,21 @@ private: bool isSelected = false; unsigned actionId = 0; + void updateUI(); + protected: - void mousePressEvent(QMouseEvent*) override; + virtual void mousePressEvent(QMouseEvent*) override; + virtual bool eventFilter(QObject*, QEvent *event) override; signals: void selected(); - void removed(MapConnection*); void openMapClicked(MapConnection*); -private slots: - void on_comboBox_Direction_currentTextChanged(QString direction); - void on_comboBox_Map_currentTextChanged(QString mapName); - void on_spinBox_Offset_valueChanged(int offset); - void on_button_Delete_clicked(); - void on_button_OpenMap_clicked(); +private: + void commitDirection(); + void commitMap(const QString &mapName); + void commitMove(int offset); + void commitRemove(); }; #endif // CONNECTIONSLISTITEM_H diff --git a/include/ui/currentselectedmetatilespixmapitem.h b/include/ui/currentselectedmetatilespixmapitem.h index 5e4bd275..109f52c5 100644 --- a/include/ui/currentselectedmetatilespixmapitem.h +++ b/include/ui/currentselectedmetatilespixmapitem.h @@ -1,23 +1,24 @@ #ifndef CURRENTSELECTEDMETATILESPIXMAPITEM_H #define CURRENTSELECTEDMETATILESPIXMAPITEM_H -#include "map.h" #include "metatileselector.h" #include +class Layout; + class CurrentSelectedMetatilesPixmapItem : public QGraphicsPixmapItem { public: - CurrentSelectedMetatilesPixmapItem(Map *map, MetatileSelector *metatileSelector) { - this->map = map; + CurrentSelectedMetatilesPixmapItem(Layout *layout, MetatileSelector *metatileSelector) { + this->layout = layout; this->metatileSelector = metatileSelector; } - Map* map = nullptr; + Layout *layout = nullptr; MetatileSelector *metatileSelector; void draw(); - void setMap(Map *map) { this->map = map; } + void setLayout(Layout *layout) { this->layout = layout; } }; -QPixmap drawMetatileSelection(MetatileSelection selection, Map *map); +QPixmap drawMetatileSelection(MetatileSelection selection, Layout *layout); #endif // CURRENTSELECTEDMETATILESPIXMAPITEM_H diff --git a/include/ui/customattributesdialog.h b/include/ui/customattributesdialog.h new file mode 100644 index 00000000..7048e16c --- /dev/null +++ b/include/ui/customattributesdialog.h @@ -0,0 +1,34 @@ +#ifndef CUSTOMATTRIBUTESDIALOG_H +#define CUSTOMATTRIBUTESDIALOG_H + +#include +#include + +#include "customattributestable.h" + +namespace Ui { +class CustomAttributesDialog; +} + +class CustomAttributesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CustomAttributesDialog(CustomAttributesTable *table); + ~CustomAttributesDialog(); + +private: + Ui::CustomAttributesDialog *ui; + CustomAttributesTable *const m_table; + + void setInputType(int inputType); + void onNameChanged(const QString &); + bool validateName(bool allowEmpty = false); + void clickedButton(QAbstractButton *button); + void addNewAttribute(); + QVariant getValue() const; +}; + + +#endif // CUSTOMATTRIBUTESDIALOG_H diff --git a/include/ui/customattributesframe.h b/include/ui/customattributesframe.h new file mode 100644 index 00000000..f19d0601 --- /dev/null +++ b/include/ui/customattributesframe.h @@ -0,0 +1,36 @@ +#ifndef CUSTOMATTRIBUTESFRAME_H +#define CUSTOMATTRIBUTESFRAME_H + +/* + The frame containing the Custom Attributes table and its Add/Delete buttons. + Shared by the map's Header tab and Events. +*/ + +#include "customattributestable.h" + +#include +#include + +namespace Ui { +class CustomAttributesFrame; +} + +class CustomAttributesFrame : public QFrame +{ + Q_OBJECT + +public: + explicit CustomAttributesFrame(QWidget *parent = nullptr); + ~CustomAttributesFrame(); + + CustomAttributesTable* table() const; + +private: + Ui::CustomAttributesFrame *ui; + + void addAttribute(); + void deleteAttribute(); + void updateDeleteButton(); +}; + +#endif // CUSTOMATTRIBUTESFRAME_H diff --git a/include/ui/customattributestable.h b/include/ui/customattributestable.h index f17f4877..780f441d 100644 --- a/include/ui/customattributestable.h +++ b/include/ui/customattributestable.h @@ -1,25 +1,44 @@ #ifndef CUSTOMATTRIBUTESTABLE_H #define CUSTOMATTRIBUTESTABLE_H -#include "events.h" #include -#include +#include #include -class CustomAttributesTable : public QFrame +class CustomAttributesTable : public QTableWidget { -public: - explicit CustomAttributesTable(Event *event, QWidget *parent = nullptr); - ~CustomAttributesTable(); + Q_OBJECT - static const QMap getAttributes(QTableWidget * table); - static QJsonValue pickType(QWidget * parent, bool * ok = nullptr); - static void addAttribute(QTableWidget * table, QString key, QJsonValue value, bool isNew = false); - static bool deleteSelectedAttributes(QTableWidget * table); +public: + explicit CustomAttributesTable(QWidget *parent = nullptr); + ~CustomAttributesTable() {}; + + QJsonObject getAttributes() const; + void setAttributes(const QJsonObject &attributes); + + void addNewAttribute(const QString &key, const QJsonValue &value); + bool deleteSelectedAttributes(); + + bool isEmpty() const; + bool isSelectionEmpty() const; + + QSet keys() const { return m_keys; } + QSet restrictedKeys() const { return m_restrictedKeys; } + void setRestrictedKeys(const QSet &keys) { m_restrictedKeys = keys; } + +signals: + void edited(); + +protected: + virtual void resizeEvent(QResizeEvent *event) override; private: - Event *event; - QTableWidget *table; + QSet m_keys; // All keys currently in the table + QSet m_restrictedKeys; // All keys not allowed in the table + + QPair getAttribute(int row) const; + int addAttribute(const QString &key, const QJsonValue &value); + void removeAttribute(const QString &key); void resizeVertically(); }; diff --git a/include/ui/customscriptseditor.h b/include/ui/customscriptseditor.h index c93c7b7d..8d261570 100644 --- a/include/ui/customscriptseditor.h +++ b/include/ui/customscriptseditor.h @@ -49,6 +49,7 @@ private: void restoreWindowState(); void initShortcuts(); QObjectList shortcutableObjects() const; + void openManual(); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/include/ui/divingmappixmapitem.h b/include/ui/divingmappixmapitem.h index 7eceba07..0a4a32c1 100644 --- a/include/ui/divingmappixmapitem.h +++ b/include/ui/divingmappixmapitem.h @@ -2,6 +2,7 @@ #define DIVINGMAPPIXMAPITEM_H #include "mapconnection.h" +#include "noscrollcombobox.h" #include #include @@ -10,7 +11,7 @@ class DivingMapPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: - DivingMapPixmapItem(MapConnection *connection, QComboBox *combo); + DivingMapPixmapItem(MapConnection *connection, NoScrollComboBox *combo); ~DivingMapPixmapItem(); MapConnection* connection() const { return m_connection; } @@ -18,7 +19,7 @@ public: private: QPointer m_connection; - QPointer m_combo; + QPointer m_combo; void setComboText(const QString &text); static QPixmap getBasePixmap(MapConnection* connection); diff --git a/include/ui/draggablepixmapitem.h b/include/ui/draggablepixmapitem.h deleted file mode 100644 index aeda4daf..00000000 --- a/include/ui/draggablepixmapitem.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef DRAGGABLEPIXMAPITEM_H -#define DRAGGABLEPIXMAPITEM_H - -#include -#include -#include -#include - -#include - -#include "events.h" - -class Editor; - -class DraggablePixmapItem : public QObject, public QGraphicsPixmapItem { - Q_OBJECT -public: - DraggablePixmapItem(QPixmap pixmap): QGraphicsPixmapItem(pixmap) {} - - DraggablePixmapItem(Event *event, Editor *editor) : QGraphicsPixmapItem(event->getPixmap()) { - this->event = event; - event->setPixmapItem(this); - this->editor = editor; - updatePosition(); - } - - Event *event = nullptr; - - void updatePosition(); - void move(int dx, int dy); - void moveTo(const QPoint &pos); - void emitPositionChanged(); - void updatePixmap(); - -private: - Editor *editor = nullptr; - QPoint lastPos; - bool active = false; - bool releaseSelectionQueued = false; - -signals: - void positionChanged(Event *event); - void xChanged(int); - void yChanged(int); - void elevationChanged(int); - void spriteChanged(QPixmap pixmap); - void onPropertyChanged(QString key, QString value); - -public slots: - void set_x(int x) { - event->setX(x); - updatePosition(); - } - void set_y(int y) { - event->setY(y); - updatePosition(); - } - void set_elevation(int z) { - event->setElevation(z); - updatePosition(); - } - -protected: - void mousePressEvent(QGraphicsSceneMouseEvent*); - void mouseMoveEvent(QGraphicsSceneMouseEvent*); - void mouseReleaseEvent(QGraphicsSceneMouseEvent*); - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*); -}; - -#endif // DRAGGABLEPIXMAPITEM_H diff --git a/include/ui/encountertablemodel.h b/include/ui/encountertablemodel.h index b2a39ad8..157e0771 100644 --- a/include/ui/encountertablemodel.h +++ b/include/ui/encountertablemodel.h @@ -14,7 +14,7 @@ class EncounterTableModel : public QAbstractTableModel { Q_OBJECT public: - EncounterTableModel(WildMonInfo monInfo, EncounterFields allFields, int fieldIndex, QObject *parent = nullptr); + EncounterTableModel(const WildMonInfo &monInfo, const EncounterField &field, QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; @@ -28,22 +28,17 @@ public: Slot, Group, Species, MinLevel, MaxLevel, EncounterChance, SlotRatio, EncounterRate, Count }; - WildMonInfo encounterData() const { return this->monInfo; } - EncounterField encounterField() const { return this->encounterFields.at(this->fieldIndex); } - QList percentages() const { return this->slotPercentages; } - void resize(int rows, int cols); + WildMonInfo encounterData() const { return m_monInfo; } + EncounterField encounterField() const { return m_encounterField; } + QVector percentages() const { return m_slotPercentages; } private: - WildMonInfo monInfo; - EncounterFields encounterFields; - int fieldIndex; - - int numRows = 0; - int numCols = 0; - - QVector slotRatios; - QList groupNames; - QList slotPercentages; + int m_numRows = 0; + int m_numCols = 0; + WildMonInfo m_monInfo; + EncounterField m_encounterField; + QMap m_groupNames; + QVector m_slotPercentages; signals: void edited(); diff --git a/include/ui/eventfilters.h b/include/ui/eventfilters.h new file mode 100644 index 00000000..4fc5ee83 --- /dev/null +++ b/include/ui/eventfilters.h @@ -0,0 +1,41 @@ +#include +#include + + + +/// Prevent wheel scroll +class WheelFilter : public QObject { + Q_OBJECT +public: + WheelFilter(QObject *parent) : QObject(parent) {} + virtual ~WheelFilter() {} + bool eventFilter(QObject *obj, QEvent *event) override; +}; + + + +/// Ctrl+Wheel = zoom +class MapSceneEventFilter : public QObject { + Q_OBJECT +protected: + bool eventFilter(QObject *obj, QEvent *event) override; +public: + explicit MapSceneEventFilter(QObject *parent = nullptr) : QObject(parent) {} + +signals: + void wheelZoom(int delta); +public slots: +}; + + + +/// Emits a signal when a window gets activated / regains focus +class ActiveWindowFilter : public QObject { + Q_OBJECT +public: + ActiveWindowFilter(QObject *parent) : QObject(parent) {} + virtual ~ActiveWindowFilter() {} + bool eventFilter(QObject *obj, QEvent *event) override; +signals: + void activated(); +}; diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index c11cf8e6..3858e54e 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -31,8 +31,6 @@ public: void invalidateUi(); void invalidateValues(); - void populateScriptDropdown(NoScrollComboBox * combo, Project * project); - virtual void setActive(bool active); public: @@ -52,10 +50,18 @@ public: QFrame *frame_contents; QVBoxLayout *layout_contents; + CustomAttributesFrame *custom_attributes; + protected: bool populated = false; bool initialized = false; bool connected = false; + QPointer project; + + void populateDropdown(NoScrollComboBox * combo, const QStringList &items); + void populateScriptDropdown(NoScrollComboBox * combo, Project * project); + void populateMapNameDropdown(NoScrollComboBox * combo, Project * project); + void populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group); private: Event *event; @@ -76,6 +82,7 @@ public: virtual void populate(Project *project) override; public: + QLineEdit *line_edit_local_id; NoScrollComboBox *combo_sprite; NoScrollComboBox *combo_movement; NoScrollSpinBox *spinner_radius_x; @@ -106,12 +113,15 @@ public: virtual void populate(Project *project) override; public: + QLineEdit *line_edit_local_id; NoScrollComboBox *combo_sprite; - NoScrollSpinBox *spinner_target_id; + NoScrollComboBox *combo_target_id; NoScrollComboBox *combo_target_map; private: CloneObjectEvent *clone; + + void tryInvalidateIdDropdown(Map *map); }; @@ -129,12 +139,15 @@ public: virtual void populate(Project *project) override; public: + QLineEdit *line_edit_id; NoScrollComboBox *combo_dest_map; NoScrollComboBox *combo_dest_warp; QPushButton *warning; private: WarpEvent *warp; + + void tryInvalidateIdDropdown(Map *map); }; @@ -265,13 +278,16 @@ public: virtual void populate(Project *project) override; public: + QLineEdit *line_edit_id; QFrame *hideable_respawn_map; QFrame *hideable_respawn_npc; NoScrollComboBox *combo_respawn_map; - NoScrollSpinBox *spinner_respawn_npc; + NoScrollComboBox *combo_respawn_npc; private: HealLocationEvent *healLocation; + + void tryInvalidateIdDropdown(Map *map); }; #endif // EVENTRAMES_H diff --git a/include/ui/eventpixmapitem.h b/include/ui/eventpixmapitem.h new file mode 100644 index 00000000..a28232ba --- /dev/null +++ b/include/ui/eventpixmapitem.h @@ -0,0 +1,58 @@ +#ifndef EVENTPIXMAPITEM_H +#define EVENTPIXMAPITEM_H + +#include +#include +#include +#include + +#include + +#include "events.h" + +class Project; + +class EventPixmapItem : public QObject, public QGraphicsPixmapItem { + Q_OBJECT +public: + explicit EventPixmapItem(Event *event); + + void render(Project *project); + + bool isSelected() const { return m_selected; } + void setSelected(bool selected) { m_selected = selected; } + + Event * getEvent() const { return m_event; } + + void move(int dx, int dy); + void moveTo(int x, int y); + void moveTo(const QPoint &pos); + +private: + QPixmap m_basePixmap; + Event *const m_event = nullptr; + QPoint m_lastPos; + bool m_active = false; + bool m_selected = false; + bool m_releaseSelectionQueued = false; + + void updatePixelPosition(); + +signals: + void xChanged(int x); + void yChanged(int y); + void posChanged(int x, int y); + void rendered(const QPixmap &pixmap); + void selected(Event *event, bool toggle); + void dragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition); + void released(Event *event, const QPoint &position); + void doubleClicked(Event *event); + +protected: + virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override { emit doubleClicked(m_event); } +}; + +#endif // EVENTPIXMAPITEM_H diff --git a/include/ui/filterchildrenproxymodel.h b/include/ui/filterchildrenproxymodel.h index b73cbd62..93934a7e 100644 --- a/include/ui/filterchildrenproxymodel.h +++ b/include/ui/filterchildrenproxymodel.h @@ -3,15 +3,30 @@ #include -class FilterChildrenProxyModel : public QSortFilterProxyModel +class NumericSortProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - explicit FilterChildrenProxyModel(QObject *parent = nullptr); -protected: - bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const; + explicit NumericSortProxyModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) {}; +protected: + virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; +}; + +class FilterChildrenProxyModel : public NumericSortProxyModel +{ + Q_OBJECT + +public: + explicit FilterChildrenProxyModel(QObject *parent = nullptr) : NumericSortProxyModel(parent) {}; + void setHideEmpty(bool hidden) { this->hideEmpty = hidden; } + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override; + +private: + bool hideEmpty = false; }; #endif // FILTERCHILDRENPROXYMODEL_H diff --git a/include/ui/graphicsview.h b/include/ui/graphicsview.h index c0d1592c..c587d2a9 100644 --- a/include/ui/graphicsview.h +++ b/include/ui/graphicsview.h @@ -32,24 +32,17 @@ signals: void clicked(QMouseEvent *event); }; -class Editor; - -class GraphicsView : public QGraphicsView +class ConnectionsView : public QGraphicsView { + Q_OBJECT public: - GraphicsView() : QGraphicsView() {} - GraphicsView(QWidget *parent) : QGraphicsView(parent) {} + ConnectionsView(QWidget *parent = nullptr) : QGraphicsView(parent) {} + +signals: + void pressedDelete(); -public: -// GraphicsView_Object object; - Editor *editor; protected: - void mousePressEvent(QMouseEvent *event); - void mouseMoveEvent(QMouseEvent *event); - void mouseReleaseEvent(QMouseEvent *event); - void moveEvent(QMoveEvent *event); + virtual void keyPressEvent(QKeyEvent *event) override; }; -//Q_DECLARE_METATYPE(GraphicsView) - #endif // GRAPHICSVIEW_H diff --git a/include/ui/imageproviders.h b/include/ui/imageproviders.h index fefa5546..806ffd5b 100644 --- a/include/ui/imageproviders.h +++ b/include/ui/imageproviders.h @@ -8,8 +8,8 @@ QImage getCollisionMetatileImage(Block); QImage getCollisionMetatileImage(int, int); -QImage getMetatileImage(uint16_t, Tileset*, Tileset*, QList, QList, bool useTruePalettes = false); -QImage getMetatileImage(Metatile*, Tileset*, Tileset*, QList, QList, bool useTruePalettes = false); +QImage getMetatileImage(uint16_t, Tileset*, Tileset*, const QList&, const QList&, bool useTruePalettes = false); +QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList&, const QList&, bool useTruePalettes = false); QImage getTileImage(uint16_t, Tileset*, Tileset*); QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false); QImage getGreyscaleTileImage(uint16_t tile, Tileset *primaryTileset, Tileset *secondaryTileset); diff --git a/include/ui/mappixmapitem.h b/include/ui/layoutpixmapitem.h similarity index 80% rename from include/ui/mappixmapitem.h rename to include/ui/layoutpixmapitem.h index cd2d335c..08496c54 100644 --- a/include/ui/mappixmapitem.h +++ b/include/ui/layoutpixmapitem.h @@ -1,54 +1,58 @@ #ifndef MAPPIXMAPITEM_H #define MAPPIXMAPITEM_H -#include "map.h" #include "settings.h" #include "metatileselector.h" #include -class MapPixmapItem : public QObject, public QGraphicsPixmapItem { +class Layout; + +class LayoutPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT private: using QGraphicsPixmapItem::paint; public: - enum class PaintMode { - Disabled, - Metatiles, - EventObjects - }; - MapPixmapItem(Map *map_, MetatileSelector *metatileSelector, Settings *settings) { - this->map = map_; - this->map->setMapItem(this); + LayoutPixmapItem(Layout *layout, MetatileSelector *metatileSelector, Settings *settings) { + this->layout = layout; + // this->map->setMapItem(this); this->metatileSelector = metatileSelector; this->settings = settings; - this->paintingMode = PaintMode::Metatiles; - this->lockedAxis = MapPixmapItem::Axis::None; + this->lockedAxis = LayoutPixmapItem::Axis::None; this->prevStraightPathState = false; setAcceptHoverEvents(true); } - MapPixmapItem::PaintMode paintingMode; - Map *map; + + Layout *layout; + MetatileSelector *metatileSelector; + Settings *settings; + bool active; bool has_mouse = false; bool right_click; + int paint_tile_initial_x; int paint_tile_initial_y; bool prevStraightPathState; int straight_path_initial_x; int straight_path_initial_y; + QPoint metatilePos; + enum Axis { None = 0, X, Y }; - MapPixmapItem::Axis lockedAxis; + + LayoutPixmapItem::Axis lockedAxis; + QPoint selection_origin; QList selection; + virtual void paint(QGraphicsSceneMouseEvent*); virtual void floodFill(QGraphicsSceneMouseEvent*); virtual void magicFill(QGraphicsSceneMouseEvent*); @@ -70,26 +74,33 @@ public: QList selectedCollisions, bool fromScriptCall = false); void floodFillSmartPath(int initialX, int initialY, bool fromScriptCall = false); + virtual void pick(QGraphicsSceneMouseEvent*); virtual void select(QGraphicsSceneMouseEvent*); virtual void shift(QGraphicsSceneMouseEvent*); void shift(int xDelta, int yDelta, bool fromScriptCall = false); virtual void draw(bool ignoreCache = false); + void updateMetatileSelection(QGraphicsSceneMouseEvent *event); void paintNormal(int x, int y, bool fromScriptCall = false); void lockNondominantAxis(QGraphicsSceneMouseEvent *event); QPoint adjustCoords(QPoint pos); + void setEditsEnabled(bool enabled) { this->editsEnabled = enabled; } + bool getEditsEnabled() { return this->editsEnabled; } + private: void paintSmartPath(int x, int y, bool fromScriptCall = false); static QList smartPathTable; unsigned actionId_ = 0; + bool editsEnabled = true; + signals: - void startPaint(QGraphicsSceneMouseEvent *, MapPixmapItem *); - void endPaint(QGraphicsSceneMouseEvent *, MapPixmapItem *); - void mouseEvent(QGraphicsSceneMouseEvent *, MapPixmapItem *); + void startPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); + void endPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); + void mouseEvent(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); void hoveredMapMetatileChanged(const QPoint &pos); void hoveredMapMetatileCleared(); diff --git a/include/ui/loadingscreen.h b/include/ui/loadingscreen.h new file mode 100644 index 00000000..ff88e87c --- /dev/null +++ b/include/ui/loadingscreen.h @@ -0,0 +1,43 @@ +#include "qgifimage.h" + +#include +#include +#include + + + +namespace Ui { +class LoadingScreen; +} + +class PorymapLoadingScreen : public QWidget { + + Q_OBJECT + +public: + explicit PorymapLoadingScreen(QWidget *parent = nullptr); + ~PorymapLoadingScreen(); + + void setPixmap(const QPixmap &pixmap); + void showMessage(const QString &text); + void showMessage(const QString &prefix, const QString &text); + void showLoadingMessage(const QString &text); + + void start(); + void stop (); + +private: + void setupUi(); + +public slots: + void updateFrame(); + +private: + Ui::LoadingScreen *ui; + + QGifImage splashImage; + int frame = 0; + QTimer timer; +}; + +extern PorymapLoadingScreen *porysplash; diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h new file mode 100644 index 00000000..fb4f7aa9 --- /dev/null +++ b/include/ui/mapheaderform.h @@ -0,0 +1,87 @@ +#ifndef MAPHEADERFORM_H +#define MAPHEADERFORM_H + +/* + This is the UI class used to edit the fields in a map's header. + It's intended to be used anywhere the UI needs to present an editor for a map's header, + e.g. for the current map in the main editor or in the new map dialog. +*/ + +#include +#include + +#include "mapheader.h" +#include "project.h" +#include "noscrollcombobox.h" + +namespace Ui { +class MapHeaderForm; +} + +class MapHeaderForm : public QWidget +{ + Q_OBJECT + +public: + explicit MapHeaderForm(QWidget *parent = nullptr); + ~MapHeaderForm(); + + void setProject(Project * project, bool allowProjectChanges = true); + void clear(); + + void setHeader(MapHeader *header); + void setHeaderData(const MapHeader &header); + MapHeader headerData() const; + + void setSong(const QString &song); + void setLocation(const QString &location); + void setLocationName(const QString &locationName); + void setRequiresFlash(bool requiresFlash); + void setWeather(const QString &weather); + void setType(const QString &type); + void setBattleScene(const QString &battleScene); + void setShowsLocationName(bool showsLocationName); + void setAllowsRunning(bool allowsRunning); + void setAllowsBiking(bool allowsBiking); + void setAllowsEscaping(bool allowsEscaping); + void setFloorNumber(int floorNumber); + + QString song() const; + QString location() const; + QString locationName() const; + bool requiresFlash() const; + QString weather() const; + QString type() const; + QString battleScene() const; + bool showsLocationName() const; + bool allowsRunning() const; + bool allowsBiking() const; + bool allowsEscaping() const; + int floorNumber() const; + +private: + Ui::MapHeaderForm *ui; + QPointer m_header = nullptr; + QPointer m_project = nullptr; + bool m_allowProjectChanges = true; + + void setText(NoScrollComboBox *combo, const QString &text) const; + void setText(QLineEdit *lineEdit, const QString &text) const; + void setLocations(const QStringList &locations); + void updateLocationName(); + + void onSongUpdated(const QString &song); + void onLocationChanged(const QString &location); + void onLocationNameChanged(const QString &locationName); + void onWeatherChanged(const QString &weather); + void onTypeChanged(const QString &type); + void onBattleSceneChanged(const QString &battleScene); + void onRequiresFlashChanged(bool enabled); + void onShowLocationNameChanged(bool enabled); + void onAllowRunningChanged(bool enabled); + void onAllowBikingChanged(bool enabled); + void onAllowEscapingChanged(bool enabled); + void onFloorNumberChanged(int offset); +}; + +#endif // MAPHEADERFORM_H diff --git a/include/ui/mapimageexporter.h b/include/ui/mapimageexporter.h index 82dae443..51c1afe3 100644 --- a/include/ui/mapimageexporter.h +++ b/include/ui/mapimageexporter.h @@ -1,10 +1,9 @@ #ifndef MAPIMAGEEXPORTER_H #define MAPIMAGEEXPORTER_H -#include "map.h" -#include "editor.h" +#include "project.h" -#include +class QGifImage; namespace Ui { class MapImageExporter; @@ -17,21 +16,17 @@ enum ImageExporterMode { }; struct ImageExporterSettings { - bool showObjects = false; - bool showWarps = false; - bool showBGs = false; - bool showTriggers = false; - bool showHealLocations = false; - bool showUpConnections = false; - bool showDownConnections = false; - bool showLeftConnections = false; - bool showRightConnections = false; + QSet showEvents; + QSet showConnections; bool showGrid = false; bool showBorder = false; bool showCollision = false; - bool previewActualSize = false; + bool disablePreviewScaling = false; + bool disablePreviewUpdates = false; int timelapseSkipAmount = 1; int timelapseDelayMs = 200; + // Not exposed as a setting in the UI atm (our color input widget has no alpha channel). + QColor fillColor = Qt::transparent; }; class MapImageExporter : public QDialog @@ -39,56 +34,84 @@ class MapImageExporter : public QDialog Q_OBJECT public: - explicit MapImageExporter(QWidget *parent, Editor *editor, ImageExporterMode mode); + explicit MapImageExporter(QWidget *parent, Project *project, Map *map, ImageExporterMode mode = ImageExporterMode::Normal) + : MapImageExporter(parent, project, map, map->layout(), mode) {}; + explicit MapImageExporter(QWidget *parent, Project *project, Layout *layout, ImageExporterMode mode = ImageExporterMode::Normal) + : MapImageExporter(parent, project, nullptr, layout, mode) {}; ~MapImageExporter(); + ImageExporterMode mode() const { return m_mode; } + + void setMap(Map *map); + void setLayout(Layout *layout); + private: + explicit MapImageExporter(QWidget *parent, Project *project, Map *map, Layout *layout, ImageExporterMode mode); + Ui::MapImageExporter *ui; + Project *m_project = nullptr; + Map *m_map = nullptr; + Layout *m_layout = nullptr; + QGraphicsScene *m_scene = nullptr; + QGifImage *m_timelapseGifImage = nullptr; + QBuffer *m_timelapseBuffer = nullptr; + QMovie *m_timelapseMovie = nullptr; + QGraphicsPixmapItem *m_preview = nullptr; - Map *map = nullptr; - Editor *editor = nullptr; - QGraphicsScene *scene = nullptr; + ImageExporterSettings m_settings; + ImageExporterMode m_mode = ImageExporterMode::Normal; + ImageExporterMode m_originalMode; - QPixmap preview; - - ImageExporterSettings settings; - ImageExporterMode mode = ImageExporterMode::Normal; - - void updatePreview(); + void setModeSpecificUi(); + void setSelectionText(const QString &text); + void updateMapSelection(); + void resetSettings(); + QString getTitle(ImageExporterMode mode); + QString getDescription(ImageExporterMode mode); + void updatePreview(bool forceUpdate = false); void scalePreview(); - void updateShowBorderState(); + bool eventsEnabled(); + void setEventGroupEnabled(Event::Group group, bool enable); + bool connectionsEnabled(); + void setConnectionDirectionEnabled(const QString &dir, bool enable); void saveImage(); - QPixmap getStitchedImage(QProgressDialog *progress, bool includeBorder); - QPixmap getFormattedMapPixmap(Map *map, bool ignoreBorder = false); - bool historyItemAppliesToFrame(const QUndoCommand *command); + QGifImage* createTimelapseGifImage(QProgressDialog *progress); + QPixmap getStitchedImage(QProgressDialog *progress); + QPixmap getFormattedMapPixmap(); + void paintBorder(QPainter *painter, Layout *layout); + void paintCollision(QPainter *painter, Layout *layout); + void paintConnections(QPainter *painter, const Map *map); + void paintEvents(QPainter *painter, const Map *map); + void paintGrid(QPainter *painter, const Layout *layout = nullptr); + QMargins getMargins(const Map *map); + QPixmap getExpandedPixmap(const QPixmap &pixmap, const QSize &targetSize, const QColor &fillColor); + bool currentHistoryAppliesToFrame(QUndoStack *historyStack); protected: virtual void showEvent(QShowEvent *) override; virtual void resizeEvent(QResizeEvent *) override; -private slots: - void on_checkBox_Objects_stateChanged(int state); - void on_checkBox_Warps_stateChanged(int state); - void on_checkBox_BGs_stateChanged(int state); - void on_checkBox_Triggers_stateChanged(int state); - void on_checkBox_HealLocations_stateChanged(int state); - void on_checkBox_AllEvents_stateChanged(int state); - - void on_checkBox_ConnectionUp_stateChanged(int state); - void on_checkBox_ConnectionDown_stateChanged(int state); - void on_checkBox_ConnectionLeft_stateChanged(int state); - void on_checkBox_ConnectionRight_stateChanged(int state); - void on_checkBox_AllConnections_stateChanged(int state); - - void on_checkBox_Elevation_stateChanged(int state); - void on_checkBox_Grid_stateChanged(int state); - void on_checkBox_Border_stateChanged(int state); +private: + void setShowGrid(bool checked); + void setShowBorder(bool checked); + void setShowObjects(bool checked); + void setShowWarps(bool checked); + void setShowBgs(bool checked); + void setShowTriggers(bool checked); + void setShowHealLocations(bool checked); + void setShowAllEvents(bool checked); + void setShowConnectionUp(bool checked); + void setShowConnectionDown(bool checked); + void setShowConnectionLeft(bool checked); + void setShowConnectionRight(bool checked); + void setShowAllConnections(bool checked); + void setShowCollision(bool checked); + void setDisablePreviewScaling(bool checked); + void setDisablePreviewUpdates(bool checked); void on_pushButton_Reset_pressed(); - void on_spinBox_TimelapseDelay_valueChanged(int delayMs); - void on_spinBox_FrameSkip_valueChanged(int skip); - - void on_checkBox_ActualSize_stateChanged(int state); + void on_spinBox_TimelapseDelay_editingFinished(); + void on_spinBox_FrameSkip_editingFinished(); }; #endif // MAPIMAGEEXPORTER_H diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h new file mode 100644 index 00000000..681f0955 --- /dev/null +++ b/include/ui/maplistmodels.h @@ -0,0 +1,156 @@ +#pragma once +#ifndef MAPLISTMODELS_H +#define MAPLISTMODELS_H + +#include +#include +#include +#include +#include + + + +class Project; + +enum MapListUserRoles { + NameRole = Qt::UserRole, // Holds the name of the item in the list + TypeRole, // Used to differentiate between the different layers of the map list tree view. +}; + + + +class MapTree : public QTreeView { + Q_OBJECT +public: + MapTree(QWidget *parent) : QTreeView(parent) { + this->setDropIndicatorShown(true); + this->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + this->setFocusPolicy(Qt::StrongFocus); + this->setContextMenuPolicy(Qt::CustomContextMenu); + } + +protected: + virtual void keyPressEvent(QKeyEvent *event) override; + +public slots: + void removeSelected(); +}; + + + +class GroupNameDelegate : public QStyledItemDelegate { + Q_OBJECT + +public: + GroupNameDelegate(Project *project, QObject *parent = nullptr) : QStyledItemDelegate(parent), project(project) {} + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + +private: + Project *project = nullptr; +}; + + +class MapListModel : public QStandardItemModel { + Q_OBJECT + +public: + MapListModel(Project *project, QObject *parent = nullptr); + ~MapListModel() { } + + void setActiveItem(const QString &itemName) { this->activeItemName = itemName; } + + virtual QStandardItem *insertMapItem(const QString &mapName, const QString &folderName); + virtual QStandardItem *insertMapFolderItem(const QString &folderName); + + virtual QModelIndex indexOf(const QString &itemName) const; + virtual void removeItemAt(const QModelIndex &index); + virtual QStandardItem *itemAt(const QModelIndex &index) const; + virtual QStandardItem *itemAt(const QString &itemName) const; + + virtual QVariant data(const QModelIndex &index, int role) const override; + +protected: + Project *project; + QStandardItem *root = nullptr; + + QString activeItemName; + QString folderTypeName; + bool editable = false; + + QIcon mapGrayIcon; + QIcon mapIcon; + QIcon mapEditedIcon; + QIcon mapOpenedIcon; + QIcon mapFolderIcon; + QIcon emptyMapFolderIcon; + + QMap mapFolderItems; + QMap mapItems; + + virtual QStandardItem *createMapItem(const QString &mapName, QStandardItem *map = nullptr); + virtual QStandardItem *createMapFolderItem(const QString &groupName, QStandardItem *fromItem = nullptr); + virtual void removeItem(QStandardItem *item) = 0; +}; + +class MapGroupModel : public MapListModel { + Q_OBJECT + +public: + MapGroupModel(Project *project, QObject *parent = nullptr); + ~MapGroupModel() { } + + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + Qt::DropActions supportedDropActions() const override; + QStringList mimeTypes() const override; + QMimeData *mimeData(const QModelIndexList &indexes) const override; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + +protected: + void removeItem(QStandardItem *item) override; + +private: + friend class MapTree; + void updateProject(); + +signals: + void dragMoveCompleted(); +}; + + + +class MapLocationModel : public MapListModel { + Q_OBJECT + +public: + MapLocationModel(Project *project, QObject *parent = nullptr); + ~MapLocationModel() {} + + QStandardItem *createMapFolderItem(const QString &folderName, QStandardItem *folder) override; + +protected: + void removeItem(QStandardItem *item) override; +}; + + + +class LayoutTreeModel : public MapListModel { + Q_OBJECT + +public: + LayoutTreeModel(Project *project, QObject *parent = nullptr); + ~LayoutTreeModel() {} + + QVariant data(const QModelIndex &index, int role) const override; + QStandardItem *createMapFolderItem(const QString &folderName, QStandardItem *folder) override; + +protected: + void removeItem(QStandardItem *item) override; +}; + +#endif // MAPLISTMODELS_H diff --git a/include/ui/maplisttoolbar.h b/include/ui/maplisttoolbar.h new file mode 100644 index 00000000..655ce414 --- /dev/null +++ b/include/ui/maplisttoolbar.h @@ -0,0 +1,56 @@ +#ifndef MAPLISTTOOLBAR_H +#define MAPLISTTOOLBAR_H + +#include "maplistmodels.h" +#include "filterchildrenproxymodel.h" + +#include +#include + +namespace Ui { +class MapListToolBar; +} + +class MapListToolBar : public QFrame +{ + Q_OBJECT + +public: + explicit MapListToolBar(QWidget *parent = nullptr); + ~MapListToolBar(); + + MapTree* list() const { return m_list; } + void setList(MapTree *list); + + void setEditsAllowedButtonVisible(bool visible); + void setEditsAllowed(bool allowed); + void toggleEditsAllowed(); + + void setEmptyFoldersVisible(bool visible); + void toggleEmptyFolders(); + + void expandList(); + void collapseList(); + + void applyFilter(const QString &filterText); + void clearFilter(); + void setFilterLocked(bool locked) { m_filterLocked = locked; } + bool isFilterLocked() const { return m_filterLocked; } + + void setSearchFocus(); + +signals: + void filterCleared(MapTree*); + void addFolderClicked(); + void editsAllowedChanged(bool allowed); + void emptyFoldersVisibleChanged(bool visible); + +private: + Ui::MapListToolBar *ui; + QPointer m_list; + bool m_filterLocked = false; + bool m_editsAllowed = false; + bool m_emptyFoldersVisible = true; +}; + +#endif // MAPLISTTOOLBAR_H diff --git a/include/ui/mapsceneeventfilter.h b/include/ui/mapsceneeventfilter.h deleted file mode 100644 index 7de427e3..00000000 --- a/include/ui/mapsceneeventfilter.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef MAPSCENEEVENTFILTER_H -#define MAPSCENEEVENTFILTER_H - -#include - -class MapSceneEventFilter : public QObject -{ - Q_OBJECT -protected: - bool eventFilter(QObject *obj, QEvent *event) override; -public: - explicit MapSceneEventFilter(QObject *parent = nullptr); - -signals: - void wheelZoom(int delta); -public slots: -}; - -#endif // MAPSCENEEVENTFILTER_H diff --git a/include/ui/mapview.h b/include/ui/mapview.h index 9176c825..d53e5cce 100644 --- a/include/ui/mapview.h +++ b/include/ui/mapview.h @@ -5,13 +5,17 @@ #include "graphicsview.h" #include "overlay.h" -class MapView : public GraphicsView +class Editor; + +class MapView : public QGraphicsView { Q_OBJECT public: - MapView() : GraphicsView() {} - MapView(QWidget *parent) : GraphicsView(parent) {} + MapView() : QGraphicsView() {} + MapView(QWidget *parent) : QGraphicsView(parent) {} + + Editor *editor; Overlay * getOverlay(int layer); void clearOverlayMap(); @@ -70,10 +74,14 @@ public: Q_INVOKABLE void addTileImage(int x, int y, QJSValue tileObj, bool setTransparency = false, int layer = 0); Q_INVOKABLE void addMetatileImage(int x, int y, int metatileId, bool setTransparency = false, int layer = 0); +protected: + virtual void drawForeground(QPainter *painter, const QRectF &rect) override; + virtual void keyPressEvent(QKeyEvent*) override; + virtual void moveEvent(QMoveEvent *event) override; private: QMap overlayMap; -protected: - void drawForeground(QPainter *painter, const QRectF &rect); + + void updateScene(); }; #endif // GRAPHICSVIEW_H diff --git a/include/ui/message.h b/include/ui/message.h new file mode 100644 index 00000000..1756c48d --- /dev/null +++ b/include/ui/message.h @@ -0,0 +1,60 @@ +#pragma once +#ifndef MESSAGE_H +#define MESSAGE_H + +/* + These classes are thin wrappers around QMessageBox for convenience. + The base Message class is a regular window-modal QMessageBox with "porymap" as the window title. + + QMessageBox's static functions enforce application modality (among other things), which changes the style of the message boxes on macOS. + With these equivalent static functions we have more control over the appearance and behavior of the window, + and we keep the convenience of not needing to provide all the arguments. + + If more control is needed (like adding custom buttons to the window) use the constructors as you would for a normal QMessageBox. +*/ + +#include + +class Message : public QMessageBox { +public: + Message(QMessageBox::Icon icon, const QString &text, QMessageBox::StandardButtons buttons, QWidget *parent); +}; + +// Basic error message with an 'Ok' button. +class ErrorMessage : public Message { +public: + ErrorMessage(const QString &message, QWidget *parent); + static void show(const QString &message, QWidget *parent); +}; + +// Basic warning message with an 'Ok' button. +class WarningMessage : public Message { +public: + WarningMessage(const QString &message, QWidget *parent); + static void show(const QString &message, QWidget *parent); +}; + +// Basic informational message with a 'Close' button. +class InfoMessage : public Message { +public: + InfoMessage(const QString &message, QWidget *parent); + static void show(const QString &message, QWidget *parent); +}; + +// Basic question message with a 'Yes' and 'No' button. +class QuestionMessage : public Message { +public: + QuestionMessage(const QString &message, QWidget *parent); + static int show(const QString &message, QWidget *parent); +}; + +// Error message directing users to their log file. +// Shows the most recent error as detailed text. +class RecentErrorMessage : public ErrorMessage { +public: + RecentErrorMessage(const QString &message, QWidget *parent); + static void show(const QString &message, QWidget *parent); +}; + + +#endif // MESSAGE_H diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h index ec2c49f4..7bb3f131 100644 --- a/include/ui/metatileselector.h +++ b/include/ui/metatileselector.h @@ -31,19 +31,21 @@ struct MetatileSelection class MetatileSelector: public SelectablePixmapItem { Q_OBJECT public: - MetatileSelector(int numMetatilesWide, Map *map): SelectablePixmapItem(16, 16) { + MetatileSelector(int numMetatilesWide, Layout *layout): SelectablePixmapItem(16, 16) { this->externalSelection = false; this->prefabSelection = false; this->numMetatilesWide = numMetatilesWide; - this->map = map; - this->primaryTileset = map->layout->tileset_primary; - this->secondaryTileset = map->layout->tileset_secondary; + this->layout = layout; + this->primaryTileset = layout->tileset_primary; + this->secondaryTileset = layout->tileset_secondary; this->selection = MetatileSelection{}; this->cellPos = QPoint(-1, -1); setAcceptHoverEvents(true); } - QPoint getSelectionDimensions(); - void draw(); + + QPoint getSelectionDimensions() override; + void draw() override; + bool select(uint16_t metatile); void selectFromMap(uint16_t metatileId, uint16_t collision, uint16_t elevation); void setTilesets(Tileset*, Tileset*); @@ -51,27 +53,31 @@ public: void setPrefabSelection(MetatileSelection selection); void setExternalSelection(int, int, QList, QList>); QPoint getMetatileIdCoordsOnWidget(uint16_t); - void setMap(Map*); + void setLayout(Layout *layout); bool isInternalSelection() const { return (!this->externalSelection && !this->prefabSelection); } + Tileset *primaryTileset; Tileset *secondaryTileset; protected: - void mousePressEvent(QGraphicsSceneMouseEvent*); - void mouseMoveEvent(QGraphicsSceneMouseEvent*); - void mouseReleaseEvent(QGraphicsSceneMouseEvent*); - void hoverMoveEvent(QGraphicsSceneHoverEvent*); - void hoverLeaveEvent(QGraphicsSceneHoverEvent*); + void mousePressEvent(QGraphicsSceneMouseEvent*) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; + void hoverMoveEvent(QGraphicsSceneHoverEvent*) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; + void drawSelection() override; private: + QPixmap basePixmap; bool externalSelection; bool prefabSelection; int numMetatilesWide; - Map *map; + Layout *layout; int externalSelectionWidth; int externalSelectionHeight; QList externalSelectedMetatiles; MetatileSelection selection; QPoint cellPos; + void updateBasePixmap(); void updateSelectedMetatiles(); void updateExternalSelectedMetatiles(); uint16_t getMetatileId(int x, int y) const; @@ -79,6 +85,7 @@ private: bool positionIsValid(const QPoint &pos) const; bool selectionIsValid(); void hoverChanged(); + int numPrimaryMetatilesRounded() const; signals: void hoveredMetatileSelectionChanged(uint16_t); diff --git a/include/ui/montabwidget.h b/include/ui/montabwidget.h index 4b66969c..8292d28e 100644 --- a/include/ui/montabwidget.h +++ b/include/ui/montabwidget.h @@ -27,19 +27,20 @@ public: void copy(int index); void paste(int index); + void setCurrentField(const QString &fieldName); + public slots: void setTabActive(int index, bool active = true); void deactivateTab(int tabIndex); private: - bool eventFilter(QObject *object, QEvent *event); - void actionCopyTab(int index); void actionAddDeleteTab(int index); QVector activeTabs; QVector addDeleteTabButtons; QVector copyTabButtons; + QMap fieldNameToIndex; Editor *editor; }; diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index efd85847..92dd43f7 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -5,34 +5,86 @@ #include #include -class MovableRect : public QGraphicsItem + + +class MovableRect : public QGraphicsRectItem { public: - MovableRect(bool *enabled, int width, int height, QRgb color); - QRectF boundingRect() const override - { + MovableRect(bool *enabled, const QRectF &rect, const QRgb &color); + QRectF boundingRect() const override { qreal penWidth = 4; return QRectF(-penWidth, -penWidth, - 30 * 8 + penWidth * 2, - 20 * 8 + penWidth * 2); + this->rect().width() + penWidth * 2, + this->rect().height() + penWidth * 2); } - void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override - { - if (!(*enabled)) return; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override { + if (!isVisible()) return; painter->setPen(this->color); - painter->drawRect(x() - 2, y() - 2, this->width + 3, this->height + 3); - painter->setPen(QColor(0, 0, 0)); - painter->drawRect(x() - 3, y() - 3, this->width + 5, this->height + 5); - painter->drawRect(x() - 1, y() - 1, this->width + 1, this->height + 1); + painter->drawRect(this->rect() + QMargins(1,1,1,1)); // Fill + painter->setPen(Qt::black); + painter->drawRect(this->rect() + QMargins(2,2,2,2)); // Outer border + painter->drawRect(this->rect()); // Inner border } void updateLocation(int x, int y); - bool *enabled; -private: - int width; - int height; + + void setActive(bool active); + bool getActive() const { return this->active; } + +protected: + bool *enabled = nullptr; + bool active = true; + QRectF baseRect; QRgb color; + + void updateVisibility(); +}; + + + +/// A MovableRect with the addition of being resizable. +class ResizableRect : public QObject, public MovableRect +{ + Q_OBJECT +public: + ResizableRect(QObject *parent, bool *enabled, int width, int height, QRgb color); + + QRectF boundingRect() const override { + return QRectF(this->rect() + QMargins(lineWidth, lineWidth, lineWidth, lineWidth)); + } + + QPainterPath shape() const override { + QPainterPath path; + path.addRect(this->rect() + QMargins(lineWidth, lineWidth, lineWidth, lineWidth)); + path.addRect(this->rect() - QMargins(lineWidth, lineWidth, lineWidth, lineWidth)); + return path; + } + + void updatePosFromRect(QRect newPos); + void setLimit(QRect limit) { this->limit = limit; } + +protected: + void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent *) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + +private: + enum class Edge { None, Left, Right, Top, Bottom, TopLeft, BottomLeft, TopRight, BottomRight }; + ResizableRect::Edge detectEdge(int x, int y); + + // Variables for keeping state of original rect while resizing + ResizableRect::Edge clickedEdge = ResizableRect::Edge::None; + QPointF clickedPos = QPointF(); + QRect clickedRect; + + QRect limit = QRect(); + + int lineWidth = 8; + +signals: + void rectUpdated(QRect rect); }; #endif // MOVABLERECT_H diff --git a/include/ui/multikeyedit.h b/include/ui/multikeyedit.h index 27231330..9a8faa10 100644 --- a/include/ui/multikeyedit.h +++ b/include/ui/multikeyedit.h @@ -37,15 +37,14 @@ signals: void customContextMenuRequested(const QPoint &pos); private: - QVector keySequenceEdit_vec; - QList keySequence_list; // Used to track changes + QVector keySequenceEdits; void addNewKeySequenceEdit(); void alignKeySequencesLeft(); void setFocusToLastNonEmptyKeySequenceEdit(); private slots: - void onEditingFinished(); + void onEditingFinished(QKeySequenceEdit *sender); void showDefaultContextMenu(QLineEdit *lineEdit, const QPoint &pos); }; diff --git a/include/ui/newdefinedialog.h b/include/ui/newdefinedialog.h new file mode 100644 index 00000000..2107dcec --- /dev/null +++ b/include/ui/newdefinedialog.h @@ -0,0 +1,32 @@ +#ifndef NEWDEFINEDIALOG_H +#define NEWDEFINEDIALOG_H + +#include +#include + +namespace Ui { +class NewDefineDialog; +} + +class NewDefineDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewDefineDialog(QWidget *parent = nullptr); + ~NewDefineDialog(); + + virtual void accept() override; + +signals: + void createdDefine(const QString &name, const QString &expression); + +private: + Ui::NewDefineDialog *ui; + + bool validateName(bool allowEmpty = false); + void onNameChanged(const QString &name); + void dialogButtonClicked(QAbstractButton *button); +}; + +#endif // NEWDEFINEDIALOG_H diff --git a/include/ui/neweventtoolbutton.h b/include/ui/neweventtoolbutton.h index 721fe2f2..f956bea9 100644 --- a/include/ui/neweventtoolbutton.h +++ b/include/ui/neweventtoolbutton.h @@ -9,31 +9,20 @@ class NewEventToolButton : public QToolButton Q_OBJECT public: explicit NewEventToolButton(QWidget *parent = nullptr); - Event::Type getSelectedEventType(); - QAction *newObjectAction; - QAction *newCloneObjectAction; - QAction *newWarpAction; - QAction *newHealLocationAction; - QAction *newTriggerAction; - QAction *newWeatherTriggerAction; - QAction *newSignAction; - QAction *newHiddenItemAction; - QAction *newSecretBaseAction; -public slots: - void newObject(); - void newCloneObject(); - void newWarp(); - void newHealLocation(); - void newTrigger(); - void newWeatherTrigger(); - void newSign(); - void newHiddenItem(); - void newSecretBase(); + + Event::Type getSelectedEventType() const { return this->selectedEventType; } + bool selectEventType(Event::Type type); + void setEventTypeVisible(Event::Type type, bool visible); + signals: void newEventAdded(Event::Type); + private: + QMap typeToAction; Event::Type selectedEventType; - void init(); + QMenu* menu; + + void addEventType(Event::Type type); }; #endif // NEWEVENTTOOLBUTTON_H diff --git a/include/ui/newlayoutdialog.h b/include/ui/newlayoutdialog.h new file mode 100644 index 00000000..b1a2c47b --- /dev/null +++ b/include/ui/newlayoutdialog.h @@ -0,0 +1,49 @@ +#ifndef NEWLAYOUTDIALOG_H +#define NEWLAYOUTDIALOG_H + +#include +#include +#include "editor.h" +#include "project.h" +#include "map.h" +#include "mapheaderform.h" +#include "newlayoutform.h" +#include "lib/collapsiblesection.h" + +namespace Ui { +class NewLayoutDialog; +} + +class NewLayoutDialog : public QDialog +{ + Q_OBJECT +public: + explicit NewLayoutDialog(Project *project, QWidget *parent = nullptr); + explicit NewLayoutDialog(Project *project, const Layout *layoutToCopy, QWidget *parent = nullptr); + ~NewLayoutDialog(); + + virtual void accept() override; + +signals: + void applied(const QString &newLayoutId); + +private: + Ui::NewLayoutDialog *ui; + Project *project; + const Layout *layoutToCopy; + + // Each of these validation functions will allow empty names up until `OK` is selected, + // because clearing the text during editing is common and we don't want to flash errors for this. + bool validateLayoutID(bool allowEmpty = false); + bool validateName(bool allowEmpty = false); + + void refresh(); + void saveSettings(); + +private slots: + void dialogButtonClicked(QAbstractButton *button); + void on_lineEdit_Name_textChanged(const QString &); + void on_lineEdit_LayoutID_textChanged(const QString &); +}; + +#endif // NEWLAYOUTDIALOG_H diff --git a/include/ui/newlayoutform.h b/include/ui/newlayoutform.h new file mode 100644 index 00000000..3e77af18 --- /dev/null +++ b/include/ui/newlayoutform.h @@ -0,0 +1,40 @@ +#ifndef NEWLAYOUTFORM_H +#define NEWLAYOUTFORM_H + +#include + +#include "maplayout.h" + +class Project; + +namespace Ui { +class NewLayoutForm; +} + +class NewLayoutForm : public QWidget +{ + Q_OBJECT + +public: + explicit NewLayoutForm(QWidget *parent = nullptr); + ~NewLayoutForm(); + + void initUi(Project *project); + + void setSettings(const Layout::Settings &settings); + Layout::Settings settings() const; + + void setDisabled(bool disabled); + + bool validate(); + +private: + Ui::NewLayoutForm *ui; + Project *m_project; + + bool validateMapDimensions(); + bool validatePrimaryTileset(bool allowEmpty = false); + bool validateSecondaryTileset(bool allowEmpty = false); +}; + +#endif // NEWLAYOUTFORM_H diff --git a/include/ui/newlocationdialog.h b/include/ui/newlocationdialog.h new file mode 100644 index 00000000..a98b2d61 --- /dev/null +++ b/include/ui/newlocationdialog.h @@ -0,0 +1,34 @@ +#ifndef NEWLOCATIONDIALOG_H +#define NEWLOCATIONDIALOG_H + +#include +#include +#include + +#include "project.h" + +namespace Ui { +class NewLocationDialog; +} + +class NewLocationDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewLocationDialog(Project *project = nullptr, QWidget *parent = nullptr); + ~NewLocationDialog(); + + virtual void accept() override; + +private: + Ui::NewLocationDialog *ui; + QPointer project = nullptr; + const QString namePrefix; + + bool validateIdName(bool allowEmpty = false); + void onIdNameChanged(const QString &name); + void dialogButtonClicked(QAbstractButton *button); +}; + +#endif // NEWLOCATIONDIALOG_H diff --git a/include/ui/newmapconnectiondialog.h b/include/ui/newmapconnectiondialog.h index 4781c971..db9eee49 100644 --- a/include/ui/newmapconnectiondialog.h +++ b/include/ui/newmapconnectiondialog.h @@ -20,13 +20,16 @@ public: virtual void accept() override; signals: - void accepted(MapConnection *result); + void newConnectionedAdded(const QString &mapName, const QString &direction); + void connectionReplaced(const QString &mapName, const QString &direction); private: Ui::NewMapConnectionDialog *ui; + Map *m_map; bool mapNameIsValid(); void setWarningVisible(bool visible); + bool askReplaceConnection(MapConnection *connection, const QString &newMapName); }; #endif // NEWMAPCONNECTIONDIALOG_H diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h new file mode 100644 index 00000000..4b560bf1 --- /dev/null +++ b/include/ui/newmapdialog.h @@ -0,0 +1,51 @@ +#ifndef NEWMAPDIALOG_H +#define NEWMAPDIALOG_H + +#include +#include +#include "editor.h" +#include "project.h" +#include "map.h" +#include "mapheaderform.h" +#include "newlayoutform.h" +#include "lib/collapsiblesection.h" + +namespace Ui { +class NewMapDialog; +} + +class NewMapDialog : public QDialog +{ + Q_OBJECT +public: + explicit NewMapDialog(Project *project, QWidget *parent = nullptr); + explicit NewMapDialog(Project *project, const Map *mapToCopy = nullptr, QWidget *parent = nullptr); + explicit NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent = nullptr); + ~NewMapDialog(); + + virtual void accept() override; + +private: + Ui::NewMapDialog *ui; + Project *project; + CollapsibleSection *headerSection; + MapHeaderForm *headerForm; + const Map *mapToCopy; + + // Each of these validation functions will allow empty names up until `OK` is selected, + // because clearing the text during editing is common and we don't want to flash errors for this. + bool validateName(bool allowEmpty = false); + bool validateGroup(bool allowEmpty = false); + bool validateLayoutID(bool allowEmpty = false); + + void refresh(); + void saveSettings(); + +private slots: + void dialogButtonClicked(QAbstractButton *button); + void on_lineEdit_Name_textChanged(const QString &); + void on_comboBox_Group_currentTextChanged(const QString &text); + void on_comboBox_LayoutID_currentTextChanged(const QString &text); +}; + +#endif // NEWMAPDIALOG_H diff --git a/include/ui/newmapgroupdialog.h b/include/ui/newmapgroupdialog.h new file mode 100644 index 00000000..cbf8aa34 --- /dev/null +++ b/include/ui/newmapgroupdialog.h @@ -0,0 +1,36 @@ +#ifndef NEWMAPGROUPDIALOG_H +#define NEWMAPGROUPDIALOG_H + +/* + This is a generic dialog for requesting a new unique name from the user. +*/ + +#include +#include + +class Project; + +namespace Ui { +class NewMapGroupDialog; +} + +class NewMapGroupDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewMapGroupDialog(Project *project = nullptr, QWidget *parent = nullptr); + ~NewMapGroupDialog(); + + virtual void accept() override; + +private: + Ui::NewMapGroupDialog *ui; + Project *project = nullptr; + + bool validateName(bool allowEmpty = false); + void onNameChanged(const QString &name); + void dialogButtonClicked(QAbstractButton *button); +}; + +#endif // NEWMAPGROUPDIALOG_H diff --git a/include/ui/newmappopup.h b/include/ui/newmappopup.h deleted file mode 100644 index 826e1609..00000000 --- a/include/ui/newmappopup.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef NEWMAPPOPUP_H -#define NEWMAPPOPUP_H - -#include -#include -#include "editor.h" -#include "project.h" -#include "map.h" - -namespace Ui { -class NewMapPopup; -} - -class NewMapPopup : public QMainWindow -{ - Q_OBJECT -public: - explicit NewMapPopup(QWidget *parent = nullptr, Project *project = nullptr); - ~NewMapPopup(); - Map *map; - int group; - bool existingLayout; - bool importedMap; - QString layoutId; - void init(); - void init(MapSortOrder type, QVariant data); - void init(MapLayout *); - static void setDefaultSettings(Project *project); - -signals: - void applied(); - -private: - Ui::NewMapPopup *ui; - Project *project; - bool checkNewMapDimensions(); - bool checkNewMapGroup(); - void saveSettings(); - void useLayout(QString layoutId); - void useLayoutSettings(MapLayout *mapLayout); - - struct Settings { - QString group; - int width; - int height; - int borderWidth; - int borderHeight; - QString primaryTilesetLabel; - QString secondaryTilesetLabel; - QString type; - QString location; - QString song; - bool canFlyTo; - bool showLocationName; - bool allowRunning; - bool allowBiking; - bool allowEscaping; - int floorNumber; - }; - static struct Settings settings; - -private slots: - void on_pushButton_NewMap_Accept_clicked(); - void on_lineEdit_NewMap_Name_textChanged(const QString &); -}; - -#endif // NEWMAPPOPUP_H diff --git a/include/ui/newtilesetdialog.h b/include/ui/newtilesetdialog.h index c2563f21..2a5ad14e 100644 --- a/include/ui/newtilesetdialog.h +++ b/include/ui/newtilesetdialog.h @@ -2,7 +2,10 @@ #define NEWTILESETDIALOG_H #include -#include "project.h" +#include + +class Project; +class Tileset; namespace Ui { class NewTilesetDialog; @@ -15,20 +18,20 @@ class NewTilesetDialog : public QDialog public: explicit NewTilesetDialog(Project *project, QWidget *parent = nullptr); ~NewTilesetDialog(); - QString path; - QString fullSymbolName; - QString friendlyName; - bool isSecondary; - bool checkerboardFill; -private slots: - void NameOrSecondaryChanged(); - void SecondaryChanged(); - void FillChanged(); + virtual void accept() override; + +signals: + void applied(Tileset *tileset); private: Ui::NewTilesetDialog *ui; Project *project = nullptr; + const QString symbolPrefix; + + bool validateName(bool allowEmpty = false); + void onNameChanged(const QString &name); + void dialogButtonClicked(QAbstractButton *button); }; #endif // NEWTILESETDIALOG_H diff --git a/include/ui/noscrollcombobox.h b/include/ui/noscrollcombobox.h index 32966b3a..0ae2487c 100644 --- a/include/ui/noscrollcombobox.h +++ b/include/ui/noscrollcombobox.h @@ -18,6 +18,9 @@ public: void setLineEdit(QLineEdit *edit); void setFocusedScrollingEnabled(bool enabled); +signals: + void editingFinished(); + private: void setItem(int index, const QString &text); diff --git a/include/ui/noscrollspinbox.h b/include/ui/noscrollspinbox.h index 0cc11043..0615da5a 100644 --- a/include/ui/noscrollspinbox.h +++ b/include/ui/noscrollspinbox.h @@ -12,6 +12,8 @@ public: void wheelEvent(QWheelEvent *event) override; void focusOutEvent(QFocusEvent *event) override; + void setLineEditEnabled(bool enabled); + unsigned getActionId(); private: diff --git a/include/ui/noscrolltextedit.h b/include/ui/noscrolltextedit.h new file mode 100644 index 00000000..dfc66789 --- /dev/null +++ b/include/ui/noscrolltextedit.h @@ -0,0 +1,25 @@ +#ifndef NOSCROLLTEXTEDIT_H +#define NOSCROLLTEXTEDIT_H + +#include +#include + +class NoScrollTextEdit : public QTextEdit +{ + Q_OBJECT +public: + explicit NoScrollTextEdit(const QString &text, QWidget *parent = nullptr) : QTextEdit(text, parent) { + setFocusPolicy(Qt::StrongFocus); + }; + explicit NoScrollTextEdit(QWidget *parent = nullptr) : NoScrollTextEdit(QString(), parent) {}; + + virtual void wheelEvent(QWheelEvent *event) override { + if (hasFocus()) { + QTextEdit::wheelEvent(event); + } else { + event->ignore(); + } + }; +}; + +#endif // NOSCROLLTEXTEDIT_H diff --git a/include/ui/overlay.h b/include/ui/overlay.h index 8f64d058..86fe0963 100644 --- a/include/ui/overlay.h +++ b/include/ui/overlay.h @@ -50,19 +50,19 @@ private: QColor fillColor; }; -class OverlayImage : public OverlayItem { +class OverlayPixmap : public OverlayItem { public: - OverlayImage(int x, int y, QImage image) { + OverlayPixmap(int x, int y, QPixmap pixmap) { this->x = x; this->y = y; - this->image = image; + this->pixmap = pixmap; } - ~OverlayImage() {} + ~OverlayPixmap() {} virtual void render(QPainter *painter); private: int x; int y; - QImage image; + QPixmap pixmap; }; class Overlay diff --git a/include/ui/prefab.h b/include/ui/prefab.h index 7bd9e0b2..2ac8fa04 100644 --- a/include/ui/prefab.h +++ b/include/ui/prefab.h @@ -20,9 +20,9 @@ struct PrefabItem class Prefab { public: - void initPrefabUI(MetatileSelector *selector, QWidget *prefabWidget, QLabel *emptyPrefabLabel, Map *map); - void addPrefab(MetatileSelection selection, Map *map, QString name); - void updatePrefabUi(Map *map); + void initPrefabUI(MetatileSelector *selector, QWidget *prefabWidget, QLabel *emptyPrefabLabel, Layout *layout); + void addPrefab(MetatileSelection selection, Layout *layout, QString name); + void updatePrefabUi(Layout *layout); bool tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version, QString filepath = ""); private: diff --git a/include/ui/prefabcreationdialog.h b/include/ui/prefabcreationdialog.h index 5748f35a..0821f751 100644 --- a/include/ui/prefabcreationdialog.h +++ b/include/ui/prefabcreationdialog.h @@ -2,10 +2,11 @@ #define PREFABCREATIONDIALOG_H #include "metatileselector.h" -#include "map.h" #include +class Layout; + namespace Ui { class PrefabCreationDialog; } @@ -15,12 +16,12 @@ class PrefabCreationDialog : public QDialog Q_OBJECT public: - explicit PrefabCreationDialog(QWidget *parent, MetatileSelector *metatileSelector, Map *map); + explicit PrefabCreationDialog(QWidget *parent, MetatileSelector *metatileSelector, Layout *layout); ~PrefabCreationDialog(); void savePrefab(); private: - Map *map; + Layout *layout = nullptr; Ui::PrefabCreationDialog *ui; MetatileSelection selection; }; diff --git a/include/ui/preferenceeditor.h b/include/ui/preferenceeditor.h index c59641c2..e4541495 100644 --- a/include/ui/preferenceeditor.h +++ b/include/ui/preferenceeditor.h @@ -23,6 +23,7 @@ public: signals: void preferencesSaved(); void themeChanged(const QString &theme); + void scriptSettingsChanged(bool on); private: Ui::PreferenceEditor *ui; diff --git a/include/ui/projectsettingseditor.h b/include/ui/projectsettingseditor.h index 41affa8b..579aec21 100644 --- a/include/ui/projectsettingseditor.h +++ b/include/ui/projectsettingseditor.h @@ -36,7 +36,7 @@ private: bool projectNeedsReload = false; bool refreshing = false; const QString baseDir; - QHash editedPokemonIconPaths; + QMap editedPokemonIconPaths; QString prevIconSpecies; void initUi(); @@ -65,6 +65,14 @@ private: void updateMaskOverlapWarning(QLabel * warning, QList masks); QStringList getWarpBehaviorsList(); void setWarpBehaviorsList(QStringList list); + void openFilesHelp(); + void openIdentifiersHelp(); + void addNewGlobalConstantsFilepath(); + void addGlobalConstantsFilepath(const QString &filepath); + QStringList getGlobalConstantsFilepaths(); + void addNewGlobalConstant(); + void addGlobalConstant(const QString &name, const QString &expression); + QMap getGlobalConstants(); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 3d889f88..432eaab6 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -54,10 +54,9 @@ private: Project *project; RegionMap *region_map = nullptr; - tsl::ordered_map region_maps; + OrderedMap region_maps; QString configFilepath; - QString mapSectionFilepath; poryjson::Json rmConfigJson; @@ -96,7 +95,7 @@ private: void saveConfig(); bool loadRegionMapEntries(); bool saveRegionMapEntries(); - tsl::ordered_map region_map_entries; + QHash region_map_entries; bool buildConfigDialog(); poryjson::Json configRegionMapDialog(); @@ -117,10 +116,14 @@ private: void displayRegionMapEntryOptions(); void updateRegionMapEntryOptions(QString); void setRegionMap(RegionMap *map); + void setLocations(const QStringList &locations); void restoreWindowState(); void closeEvent(QCloseEvent* event); + void setTileHFlip(bool enabled); + void setTileVFlip(bool enabled); + private slots: void on_action_RegionMap_Save_triggered(); void on_actionSave_All_triggered(); @@ -134,7 +137,7 @@ private slots: 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_RM_Entry_MapSection_currentTextChanged(const QString &); void on_comboBox_regionSelector_textActivated(const QString &); void on_comboBox_layoutLayer_textActivated(const QString &); void on_spinBox_RM_Entry_x_valueChanged(int); @@ -145,11 +148,8 @@ private slots: 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_lineEdit_RM_MapName_textEdited(const QString &); void onHoveredRegionMapTileChanged(int x, int y); void onHoveredRegionMapTileCleared(); void mouseEvent_region_map(QGraphicsSceneMouseEvent *event, RegionMapPixmapItem *item); diff --git a/include/ui/resizelayoutpopup.h b/include/ui/resizelayoutpopup.h new file mode 100644 index 00000000..1f79580e --- /dev/null +++ b/include/ui/resizelayoutpopup.h @@ -0,0 +1,109 @@ +#ifndef RESIZELAYOUTPOPUP_H +#define RESIZELAYOUTPOPUP_H + +#include +#include +#include +#include +#include +#include + +class ResizableRect; +class Editor; +namespace Ui { + class ResizeLayoutPopup; +} + + + +/// Custom scene that paints its background a gray checkered pattern. +/// Additionally there is a definable "valid" area which will paint the checkerboard green inside. +class CheckeredBgScene : public QGraphicsScene { + Q_OBJECT + +public: + CheckeredBgScene(QObject *parent = nullptr); + void setValidRect(int x, int y, int width, int height) { + this->validRect = QRect(x * this->gridSize, y * this->gridSize, width * this->gridSize, height * this->gridSize); + } + void setValidRect(QRect rect) { + this->validRect = rect; + } + QRect getValidRect() { return this->validRect; } + +protected: + void drawBackground(QPainter *painter, const QRectF &rect) override; + +private: + int gridSize = 16; // virtual pixels + QRect validRect = QRect(); +}; + + + +/// PixmapItem subclass which allows for creating a boundary which determine whether +/// the pixmap paints normally or with a black tint. +/// This item is movable and snaps on a 16x16 grid. +class BoundedPixmapItem : public QGraphicsPixmapItem { +public: + BoundedPixmapItem(const QPixmap &pixmap, QGraphicsItem *parent = nullptr); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override; + + void setBoundary(ResizableRect *rect) { this->boundary = rect; } + +protected: + QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + +private: + ResizableRect *boundary = nullptr; + QPointF clickedPos = QPointF(); +}; + + + +/// The main (modal) dialog window for resizing layout and border dimensions. +/// The dialog itself is minimal, and is connected to the parent widget's geometry. +class ResizeLayoutPopup : public QDialog +{ + Q_OBJECT + +public: + ResizeLayoutPopup(QWidget *parent, Editor *editor); + ~ResizeLayoutPopup(); + + void setupLayoutView(); + + void resetPosition(); + + QMargins getResult(); + QSize getBorderResult(); + +protected: + void moveEvent(QMoveEvent *) override { + // Prevent the dialog from being moved + this->resetPosition(); + } + + void resizeEvent(QResizeEvent *) override { + // Prevent the dialog from being resized + this->resetPosition(); + } + +private slots: + void on_spinBox_width_valueChanged(int value); + void on_spinBox_height_valueChanged(int value); + void on_buttonBox_clicked(QAbstractButton *button); + +private: + QWidget *parent = nullptr; + Editor *editor = nullptr; + + Ui::ResizeLayoutPopup *ui; + + ResizableRect *outline = nullptr; + BoundedPixmapItem *layoutPixmap = nullptr; + + QPointer scene = nullptr; +}; + +#endif // RESIZELAYOUTPOPUP_H diff --git a/include/ui/selectablepixmapitem.h b/include/ui/selectablepixmapitem.h index 97a47c43..2681e485 100644 --- a/include/ui/selectablepixmapitem.h +++ b/include/ui/selectablepixmapitem.h @@ -31,9 +31,9 @@ protected: void select(int, int, int, int); void updateSelection(int, int); QPoint getCellPos(QPointF); - void mousePressEvent(QGraphicsSceneMouseEvent*); - void mouseMoveEvent(QGraphicsSceneMouseEvent*); - void mouseReleaseEvent(QGraphicsSceneMouseEvent*); + virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; virtual void drawSelection(); signals: diff --git a/include/ui/shortcut.h b/include/ui/shortcut.h index 8989401d..5fdbfaee 100644 --- a/include/ui/shortcut.h +++ b/include/ui/shortcut.h @@ -49,9 +49,6 @@ public: void setAutoRepeat(bool on); bool autoRepeat() const; - int id() const; - QList ids() const; - inline QWidget *parentWidget() const { return static_cast(QObject::parent()); } diff --git a/include/ui/shortcutseditor.h b/include/ui/shortcutseditor.h index 31b0279e..e44c8896 100644 --- a/include/ui/shortcutseditor.h +++ b/include/ui/shortcutseditor.h @@ -35,9 +35,9 @@ signals: private: Ui::ShortcutsEditor *ui; QWidget *main_container; - QMultiMap labels_objects; + QMultiMap> labels_objects; QHash contexts_layouts; - QHash multiKeyEdits_objects; + QHash> multiKeyEdits_objects; void parseObjectList(const QObjectList &objectList); QString getLabel(const QObject *object) const; diff --git a/include/ui/tilemaptileselector.h b/include/ui/tilemaptileselector.h index 867f6302..155957a6 100644 --- a/include/ui/tilemaptileselector.h +++ b/include/ui/tilemaptileselector.h @@ -5,6 +5,7 @@ #include "selectablepixmapitem.h" #include "paletteutil.h" #include "imageproviders.h" +#include "utility.h" #include using std::shared_ptr; @@ -66,7 +67,7 @@ public: } virtual QString info() const { - return QString("Tile: 0x") + QString("%1 ").arg(this->id(), 4, 16, QChar('0')).toUpper(); + return QString("Tile: %1 ").arg(Util::toHexString(this->id(), 4)); } }; @@ -148,10 +149,10 @@ public: void select(unsigned tileId); unsigned selectedTile = 0; - void selectVFlip(bool hFlip) { this->tile_hFlip = hFlip; } + void selectHFlip(bool hFlip) { this->tile_hFlip = hFlip; } bool tile_hFlip = false; - void selectHFlip(bool vFlip) { this->tile_vFlip = vFlip; } + void selectVFlip(bool vFlip) { this->tile_vFlip = vFlip; } bool tile_vFlip = false; void selectPalette(int palette) { diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 9ce9777d..b6a60a61 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -8,7 +8,9 @@ #include "tileseteditormetatileselector.h" #include "tileseteditortileselector.h" #include "metatilelayersitem.h" -#include "map.h" + +class NoScrollComboBox; +class Layout; namespace Ui { class TilesetEditor; @@ -39,10 +41,10 @@ class TilesetEditor : public QMainWindow Q_OBJECT public: - explicit TilesetEditor(Project*, Map*, QWidget *parent = nullptr); + explicit TilesetEditor(Project *project, Layout *layout, QWidget *parent = nullptr); ~TilesetEditor(); - void update(Map *map, QString primaryTilsetLabel, QString secondaryTilesetLabel); - void updateMap(Map *map); + void update(Layout *layout, QString primaryTilsetLabel, QString secondaryTilesetLabel); + void updateLayout(Layout *layout); void updateTilesets(QString primaryTilsetLabel, QString secondaryTilesetLabel); bool selectMetatile(uint16_t metatileId); uint16_t getSelectedMetatileId(); @@ -56,6 +58,7 @@ public slots: void onSelectedMetatileChanged(uint16_t); private slots: + void onWindowActivated(); void onHoveredMetatileChanged(uint16_t); void onHoveredMetatileCleared(); void onHoveredTileChanged(uint16_t); @@ -68,12 +71,6 @@ private slots: void on_spinBox_paletteSelector_valueChanged(int arg1); - void on_checkBox_xFlip_stateChanged(int arg1); - - void on_checkBox_yFlip_stateChanged(int arg1); - - void on_actionSave_Tileset_triggered(); - void on_actionImport_Primary_Tiles_triggered(); void on_actionImport_Secondary_Tiles_triggered(); @@ -87,21 +84,13 @@ private slots: void on_actionShow_UnusedTiles_toggled(bool checked); void on_actionMetatile_Grid_triggered(bool checked); void on_actionLayer_Grid_triggered(bool checked); + void on_actionShow_Tileset_Divider_triggered(bool checked); void on_actionUndo_triggered(); - void on_actionRedo_triggered(); - void on_comboBox_metatileBehaviors_currentTextChanged(const QString &arg1); - void on_lineEdit_metatileLabel_editingFinished(); - void on_comboBox_layerType_activated(int arg1); - - void on_comboBox_encounterType_activated(int arg1); - - void on_comboBox_terrainType_activated(int arg1); - void on_actionExport_Primary_Tiles_Image_triggered(); void on_actionExport_Secondary_Tiles_Image_triggered(); void on_actionExport_Primary_Metatiles_Image_triggered(); @@ -119,9 +108,7 @@ private slots: void on_horizontalSlider_TilesZoom_valueChanged(int value); private: - void initUi(); - void setAttributesUi(); - void setMetatileLabelValidator(); + void initAttributesUi(); void initMetatileSelector(); void initTileSelector(); void initSelectedTileItem(); @@ -147,6 +134,17 @@ private: bool replaceMetatile(uint16_t metatileId, const Metatile * src, QString label); void commitMetatileChange(Metatile * prevMetatile); void commitMetatileAndLabelChange(Metatile * prevMetatile, QString prevLabel); + uint32_t attributeNameToValue(Metatile::Attr attribute, const QString &text, bool *ok); + void commitAttributeFromComboBox(Metatile::Attr attribute, NoScrollComboBox *combo); + void onRawAttributesEdited(); + void refreshMetatileAttributes(); + void commitMetatileBehavior(); + void commitEncounterType(); + void commitTerrainType(); + void commitLayerType(); + void setRawAttributesVisible(bool visible); + void setXFlip(bool enabled); + void setYFlip(bool enabled); Ui::TilesetEditor *ui; History metatileHistory; @@ -155,7 +153,7 @@ private: MetatileLayersItem *metatileLayersItem = nullptr; PaletteEditor *paletteEditor = nullptr; Project *project = nullptr; - Map *map = nullptr; + Layout *layout = nullptr; Metatile *metatile = nullptr; Metatile *copiedMetatile = nullptr; QString copiedMetatileLabel; @@ -173,6 +171,8 @@ private: bool lockSelection = false; QSet metatileReloadQueue; + bool save(); + signals: void tilesetsSaved(QString, QString); }; diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index f8b6011d..8a3fdfa7 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -3,16 +3,21 @@ #include "selectablepixmapitem.h" #include "tileset.h" -#include "map.h" + +class Layout; class TilesetEditorMetatileSelector: public SelectablePixmapItem { Q_OBJECT public: - TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Map *map); - Map *map = nullptr; - void draw(); + TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout); + Layout *layout = nullptr; + + void draw() override; + void drawMetatile(uint16_t metatileId); + void drawSelectedMetatile(); + bool select(uint16_t metatileId); - void setTilesets(Tileset*, Tileset*, bool draw = true); + void setTilesets(Tileset*, Tileset*); uint16_t getSelectedMetatileId(); void updateSelectedMetatile(); QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId); @@ -22,32 +27,38 @@ public: QVector usedMetatiles; bool selectorShowUnused = false; bool selectorShowCounts = false; - bool showGrid; + bool showGrid = false; + bool showDivider = false; protected: - void mousePressEvent(QGraphicsSceneMouseEvent*); - void mouseMoveEvent(QGraphicsSceneMouseEvent*); - void mouseReleaseEvent(QGraphicsSceneMouseEvent*); - void hoverMoveEvent(QGraphicsSceneHoverEvent*); - void hoverLeaveEvent(QGraphicsSceneHoverEvent*); + void mousePressEvent(QGraphicsSceneMouseEvent*) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; + void hoverMoveEvent(QGraphicsSceneHoverEvent*) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; private: + QImage baseImage; + QPixmap basePixmap; Tileset *primaryTileset = nullptr; Tileset *secondaryTileset = nullptr; - uint16_t selectedMetatile; + uint16_t selectedMetatileId; int numMetatilesWide; int numMetatilesHigh; + void updateBasePixmap(); uint16_t getMetatileId(int x, int y); QPoint getMetatileIdCoords(uint16_t); bool shouldAcceptEvent(QGraphicsSceneMouseEvent*); int numRows(int numMetatiles); int numRows(); void drawGrid(); + void drawDivider(); void drawFilters(); void drawUnused(); void drawCounts(); QImage buildAllMetatilesImage(); QImage buildImage(int metatileIdStart, int numMetatiles); + int numPrimaryMetatilesRounded() const; signals: void hoveredMetatileChanged(uint16_t); diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h index bfbc4946..aa2a1923 100644 --- a/include/ui/tileseteditortileselector.h +++ b/include/ui/tileseteditortileselector.h @@ -33,6 +33,7 @@ public: QVector usedTiles; bool showUnused = false; + bool showDivider = false; protected: void mousePressEvent(QGraphicsSceneMouseEvent*); @@ -61,6 +62,7 @@ private: QPoint getTileCoords(uint16_t); QList getCurPaletteTable(); QList buildSelectedTiles(int, int, QList); + QImage buildImage(int tileIdStart, int numTiles); void drawUnused(); diff --git a/include/ui/wildmonchart.h b/include/ui/wildmonchart.h index 3be7e468..20840c87 100644 --- a/include/ui/wildmonchart.h +++ b/include/ui/wildmonchart.h @@ -78,7 +78,6 @@ private: // As of writing our static Qt build for Windows doesn't include the QtCharts module, so we dummy the class out here. // The charts module is additionally excluded from Windows in porymap.pro -#define DISABLE_CHARTS_MODULE class WildMonChart : public QWidget { diff --git a/include/ui/wildmonsearch.h b/include/ui/wildmonsearch.h new file mode 100644 index 00000000..ccf0fe94 --- /dev/null +++ b/include/ui/wildmonsearch.h @@ -0,0 +1,62 @@ +#ifndef WILDMONSEARCH_H +#define WILDMONSEARCH_H + +#include +#include +#include + +class Project; + +class NumericSortTableItem : public QTableWidgetItem +{ +public: + explicit NumericSortTableItem(const QString &text) : QTableWidgetItem(text) {}; + +protected: + virtual bool operator<(const QTableWidgetItem &other) const override { + QCollator collator; + collator.setNumericMode(true); + return collator.compare(text(), other.text()) < 0; + } +}; + +namespace Ui { +class WildMonSearch; +} + +class WildMonSearch : public QDialog +{ + Q_OBJECT + +public: + explicit WildMonSearch(Project *project, QWidget *parent = nullptr); + ~WildMonSearch(); + + void refresh(); + +signals: + void openWildMonTableRequested(const QString &mapName, const QString &groupName, const QString &fieldName); + +private: + struct RowData { + QString mapName; + QString groupName; + QString fieldName; + QString levelRange; + QString chance; + }; + + Ui::WildMonSearch *ui; + Project *const project; + QMap> percentageStrings; + QMap> resultsCache; + + void addTableEntry(const RowData &rowData); + QList search(const QString &species) const; + void updatePercentageStrings(); + void updateResults(const QString &species); + void cellDoubleClicked(int row, int column); + +}; + +#endif // WILDMONSEARCH_H diff --git a/porymap.pro b/porymap.pro index 51df62f1..ff894f39 100644 --- a/porymap.pro +++ b/porymap.pro @@ -6,8 +6,10 @@ QT += core gui qml network -!win32 { +qtHaveModule(charts) { QT += charts +} else { + warning("Qt module 'charts' not found, disabling chart features.") } greaterThan(QT_MAJOR_VERSION, 4): QT += widgets @@ -18,33 +20,45 @@ RC_ICONS = resources/icons/porymap-icon-2.ico ICON = resources/icons/porymap.icns QMAKE_CXXFLAGS += -std=c++17 -Wall QMAKE_TARGET_BUNDLE_PREFIX = com.pret -VERSION = 5.4.1 + +# Get latest commit hash if we can (to display alongside version information). +win32 { + LATEST_COMMIT = $$system(git rev-parse --short HEAD 2> nul) +} else { + LATEST_COMMIT = $$system(git rev-parse --short HEAD 2>/dev/null) +} + +DEFINES += PORYMAP_LATEST_COMMIT=\\\"$$LATEST_COMMIT\\\" + +VERSION = 6.0.0 DEFINES += PORYMAP_VERSION=\\\"$$VERSION\\\" -SOURCES += src/core/block.cpp \ +SOURCES += src/core/advancemapparser.cpp \ + src/core/block.cpp \ + src/ui/resizelayoutpopup.cpp \ src/core/bitpacker.cpp \ src/core/blockdata.cpp \ src/core/events.cpp \ src/core/filedialog.cpp \ - src/core/heallocation.cpp \ src/core/imageexport.cpp \ src/core/map.cpp \ src/core/mapconnection.cpp \ + src/core/mapheader.cpp \ src/core/maplayout.cpp \ - src/core/mapparser.cpp \ src/core/metatile.cpp \ - src/core/metatileparser.cpp \ src/core/network.cpp \ src/core/paletteutil.cpp \ src/core/parseutil.cpp \ src/core/tile.cpp \ src/core/tileset.cpp \ + src/core/utility.cpp \ + src/core/validator.cpp \ src/core/regionmap.cpp \ src/core/wildmoninfo.cpp \ src/core/editcommands.cpp \ src/lib/fex/lexer.cpp \ src/lib/fex/parser.cpp \ - src/lib/fex/parser_util.cpp \ + src/lib/collapsiblesection.cpp \ src/lib/orderedjson.cpp \ src/core/regionmapeditcommands.cpp \ src/scriptapi/apimap.cpp \ @@ -54,10 +68,12 @@ SOURCES += src/core/block.cpp \ src/ui/aboutporymap.cpp \ src/ui/colorinputwidget.cpp \ src/ui/connectionslistitem.cpp \ + src/ui/customattributesdialog.cpp \ + src/ui/customattributestable.cpp \ src/ui/customscriptseditor.cpp \ src/ui/customscriptslistitem.cpp \ src/ui/divingmappixmapitem.cpp \ - src/ui/draggablepixmapitem.cpp \ + src/ui/eventpixmapitem.cpp \ src/ui/bordermetatilespixmapitem.cpp \ src/ui/collisionpixmapitem.cpp \ src/ui/connectionpixmapitem.cpp \ @@ -70,21 +86,30 @@ SOURCES += src/core/block.cpp \ src/ui/regionmaplayoutpixmapitem.cpp \ src/ui/regionmapentriespixmapitem.cpp \ src/ui/cursortilerect.cpp \ - src/ui/customattributestable.cpp \ + src/ui/customattributesframe.cpp \ src/ui/eventframes.cpp \ + src/ui/eventfilters.cpp \ src/ui/filterchildrenproxymodel.cpp \ + src/ui/maplistmodels.cpp \ + src/ui/maplisttoolbar.cpp \ + src/ui/message.cpp \ src/ui/graphicsview.cpp \ src/ui/imageproviders.cpp \ - src/ui/mappixmapitem.cpp \ + src/ui/layoutpixmapitem.cpp \ src/ui/prefabcreationdialog.cpp \ src/ui/regionmappixmapitem.cpp \ src/ui/citymappixmapitem.cpp \ - src/ui/mapsceneeventfilter.cpp \ + src/ui/mapheaderform.cpp \ src/ui/metatilelayersitem.cpp \ src/ui/metatileselector.cpp \ src/ui/movablerect.cpp \ src/ui/movementpermissionsselector.cpp \ + src/ui/newdefinedialog.cpp \ src/ui/neweventtoolbutton.cpp \ + src/ui/newlayoutdialog.cpp \ + src/ui/newlayoutform.cpp \ + src/ui/newlocationdialog.cpp \ + src/ui/newmapgroupdialog.cpp \ src/ui/noscrollcombobox.cpp \ src/ui/noscrollspinbox.cpp \ src/ui/montabwidget.cpp \ @@ -97,7 +122,7 @@ SOURCES += src/core/block.cpp \ src/ui/tileseteditortileselector.cpp \ src/ui/tilemaptileselector.cpp \ src/ui/regionmapeditor.cpp \ - src/ui/newmappopup.cpp \ + src/ui/newmapdialog.cpp \ src/ui/mapimageexporter.cpp \ src/ui/newtilesetdialog.cpp \ src/ui/flowlayout.cpp \ @@ -109,6 +134,7 @@ SOURCES += src/core/block.cpp \ src/ui/preferenceeditor.cpp \ src/ui/regionmappropertiesdialog.cpp \ src/ui/colorpicker.cpp \ + src/ui/loadingscreen.cpp \ src/config.cpp \ src/editor.cpp \ src/main.cpp \ @@ -118,27 +144,29 @@ SOURCES += src/core/block.cpp \ src/log.cpp \ src/ui/uintspinbox.cpp \ src/ui/updatepromoter.cpp \ - src/ui/wildmonchart.cpp + src/ui/wildmonchart.cpp \ + src/ui/wildmonsearch.cpp -HEADERS += include/core/block.h \ +HEADERS += include/core/advancemapparser.h \ + include/core/block.h \ include/core/bitpacker.h \ include/core/blockdata.h \ include/core/events.h \ include/core/filedialog.h \ - include/core/heallocation.h \ include/core/history.h \ include/core/imageexport.h \ include/core/map.h \ include/core/mapconnection.h \ + include/core/mapheader.h \ include/core/maplayout.h \ - include/core/mapparser.h \ include/core/metatile.h \ - include/core/metatileparser.h \ include/core/network.h \ include/core/paletteutil.h \ include/core/parseutil.h \ include/core/tile.h \ include/core/tileset.h \ + include/core/utility.h \ + include/core/validator.h \ include/core/regionmap.h \ include/core/wildmoninfo.h \ include/core/editcommands.h \ @@ -148,45 +176,57 @@ HEADERS += include/core/block.h \ include/lib/fex/define_statement.h \ include/lib/fex/lexer.h \ include/lib/fex/parser.h \ - include/lib/fex/parser_util.h \ + include/lib/collapsiblesection.h \ include/lib/orderedmap.h \ include/lib/orderedjson.h \ include/ui/aboutporymap.h \ include/ui/connectionslistitem.h \ + include/ui/customattributesdialog.h \ + include/ui/customattributestable.h \ include/ui/customscriptseditor.h \ include/ui/customscriptslistitem.h \ include/ui/divingmappixmapitem.h \ - include/ui/draggablepixmapitem.h \ + include/ui/eventpixmapitem.h \ include/ui/bordermetatilespixmapitem.h \ include/ui/collisionpixmapitem.h \ include/ui/connectionpixmapitem.h \ include/ui/currentselectedmetatilespixmapitem.h \ include/ui/gridsettings.h \ + include/ui/mapheaderform.h \ include/ui/newmapconnectiondialog.h \ include/ui/prefabframe.h \ include/ui/projectsettingseditor.h \ include/ui/regionmaplayoutpixmapitem.h \ include/ui/regionmapentriespixmapitem.h \ include/ui/cursortilerect.h \ - include/ui/customattributestable.h \ + include/ui/customattributesframe.h \ include/ui/eventframes.h \ + include/ui/eventfilters.h \ include/ui/filterchildrenproxymodel.h \ + include/ui/maplistmodels.h \ + include/ui/maplisttoolbar.h \ + include/ui/message.h \ include/ui/graphicsview.h \ include/ui/imageproviders.h \ - include/ui/mappixmapitem.h \ + include/ui/layoutpixmapitem.h \ include/ui/mapview.h \ include/ui/prefabcreationdialog.h \ include/ui/regionmappixmapitem.h \ include/ui/citymappixmapitem.h \ include/ui/colorinputwidget.h \ - include/ui/mapsceneeventfilter.h \ include/ui/metatilelayersitem.h \ include/ui/metatileselector.h \ include/ui/movablerect.h \ include/ui/movementpermissionsselector.h \ + include/ui/newdefinedialog.h \ include/ui/neweventtoolbutton.h \ + include/ui/newlayoutdialog.h \ + include/ui/newlayoutform.h \ + include/ui/newlocationdialog.h \ + include/ui/newmapgroupdialog.h \ include/ui/noscrollcombobox.h \ include/ui/noscrollspinbox.h \ + include/ui/noscrolltextedit.h \ include/ui/montabwidget.h \ include/ui/encountertablemodel.h \ include/ui/encountertabledelegates.h \ @@ -198,7 +238,7 @@ HEADERS += include/core/block.h \ include/ui/tileseteditortileselector.h \ include/ui/tilemaptileselector.h \ include/ui/regionmapeditor.h \ - include/ui/newmappopup.h \ + include/ui/newmapdialog.h \ include/ui/mapimageexporter.h \ include/ui/newtilesetdialog.h \ include/ui/overlay.h \ @@ -211,6 +251,7 @@ HEADERS += include/core/block.h \ include/ui/preferenceeditor.h \ include/ui/regionmappropertiesdialog.h \ include/ui/colorpicker.h \ + include/ui/loadingscreen.h \ include/config.h \ include/editor.h \ include/mainwindow.h \ @@ -221,19 +262,30 @@ HEADERS += include/core/block.h \ include/log.h \ include/ui/uintspinbox.h \ include/ui/updatepromoter.h \ - include/ui/wildmonchart.h + include/ui/wildmonchart.h \ + include/ui/wildmonsearch.h \ + include/ui/resizelayoutpopup.h FORMS += forms/mainwindow.ui \ forms/colorinputwidget.ui \ forms/connectionslistitem.ui \ + forms/customattributesframe.ui \ forms/gridsettingsdialog.ui \ + forms/loadingscreen.ui \ + forms/mapheaderform.ui \ + forms/maplisttoolbar.ui \ + forms/newdefinedialog.ui \ + forms/newlayoutdialog.ui \ + forms/newlayoutform.ui \ + forms/newlocationdialog.ui \ forms/newmapconnectiondialog.ui \ + forms/newmapgroupdialog.ui \ forms/prefabcreationdialog.ui \ forms/prefabframe.ui \ forms/tileseteditor.ui \ forms/paletteeditor.ui \ forms/regionmapeditor.ui \ - forms/newmappopup.ui \ + forms/newmapdialog.ui \ forms/aboutporymap.ui \ forms/newtilesetdialog.ui \ forms/mapimageexporter.ui \ @@ -244,8 +296,11 @@ FORMS += forms/mainwindow.ui \ forms/projectsettingseditor.ui \ forms/customscriptseditor.ui \ forms/customscriptslistitem.ui \ + forms/customattributesdialog.ui \ forms/updatepromoter.ui \ - forms/wildmonchart.ui + forms/wildmonchart.ui \ + forms/wildmonsearch.ui \ + forms/resizelayoutpopup.ui RESOURCES += \ resources/images.qrc \ diff --git a/resources/icons/application_form_edit.ico b/resources/icons/application_form_edit.ico index 7bb403ea..5d9cc7da 100644 Binary files a/resources/icons/application_form_edit.ico and b/resources/icons/application_form_edit.ico differ diff --git a/resources/icons/chart_bar.ico b/resources/icons/chart_bar.ico new file mode 100755 index 00000000..a66163a4 Binary files /dev/null and b/resources/icons/chart_bar.ico differ diff --git a/resources/icons/collapse_all.ico b/resources/icons/collapse_all.ico index f6c7f315..806f2435 100644 Binary files a/resources/icons/collapse_all.ico and b/resources/icons/collapse_all.ico differ diff --git a/resources/icons/connections.ico b/resources/icons/connections.ico new file mode 100644 index 00000000..effb20c9 Binary files /dev/null and b/resources/icons/connections.ico differ diff --git a/resources/icons/expand_all.ico b/resources/icons/expand_all.ico index 0707936c..ca913a13 100644 Binary files a/resources/icons/expand_all.ico and b/resources/icons/expand_all.ico differ diff --git a/resources/icons/folder_add.ico b/resources/icons/folder_add.ico new file mode 100755 index 00000000..d881adf8 Binary files /dev/null and b/resources/icons/folder_add.ico differ diff --git a/resources/icons/folder_closed_map.ico b/resources/icons/folder_closed_map.ico index 27f9810b..d299acb7 100644 Binary files a/resources/icons/folder_closed_map.ico and b/resources/icons/folder_closed_map.ico differ diff --git a/resources/icons/folder_eye_closed.ico b/resources/icons/folder_eye_closed.ico new file mode 100644 index 00000000..354abadb Binary files /dev/null and b/resources/icons/folder_eye_closed.ico differ diff --git a/resources/icons/folder_eye_open.ico b/resources/icons/folder_eye_open.ico new file mode 100644 index 00000000..15e7175f Binary files /dev/null and b/resources/icons/folder_eye_open.ico differ diff --git a/resources/icons/lock_edit.ico b/resources/icons/lock_edit.ico new file mode 100644 index 00000000..f30f4249 Binary files /dev/null and b/resources/icons/lock_edit.ico differ diff --git a/resources/icons/magnifier.ico b/resources/icons/magnifier.ico new file mode 100755 index 00000000..9cbc3618 Binary files /dev/null and b/resources/icons/magnifier.ico differ diff --git a/resources/icons/map_grayed.ico b/resources/icons/map_grayed.ico new file mode 100644 index 00000000..86c3f5fb Binary files /dev/null and b/resources/icons/map_grayed.ico differ diff --git a/resources/icons/minimap.ico b/resources/icons/minimap.ico new file mode 100644 index 00000000..548c7c8f Binary files /dev/null and b/resources/icons/minimap.ico differ diff --git a/resources/icons/player/brendan_em.ico b/resources/icons/player/brendan_em.ico new file mode 100644 index 00000000..2c896c44 Binary files /dev/null and b/resources/icons/player/brendan_em.ico differ diff --git a/resources/icons/player/brendan_rs.ico b/resources/icons/player/brendan_rs.ico new file mode 100644 index 00000000..c3385a4d Binary files /dev/null and b/resources/icons/player/brendan_rs.ico differ diff --git a/resources/icons/player/green.ico b/resources/icons/player/green.ico new file mode 100644 index 00000000..3cdada55 Binary files /dev/null and b/resources/icons/player/green.ico differ diff --git a/resources/icons/player/may_em.ico b/resources/icons/player/may_em.ico new file mode 100644 index 00000000..ffa56bab Binary files /dev/null and b/resources/icons/player/may_em.ico differ diff --git a/resources/icons/player/may_rs.ico b/resources/icons/player/may_rs.ico new file mode 100644 index 00000000..7450d620 Binary files /dev/null and b/resources/icons/player/may_rs.ico differ diff --git a/resources/icons/viewsprites.ico b/resources/icons/player/red.ico similarity index 100% rename from resources/icons/viewsprites.ico rename to resources/icons/player/red.ico diff --git a/resources/icons/ui/dark_branch_closed.png b/resources/icons/ui/dark/branch_closed.png similarity index 100% rename from resources/icons/ui/dark_branch_closed.png rename to resources/icons/ui/dark/branch_closed.png diff --git a/resources/icons/ui/dark_branch_end.png b/resources/icons/ui/dark/branch_end.png similarity index 100% rename from resources/icons/ui/dark_branch_end.png rename to resources/icons/ui/dark/branch_end.png diff --git a/resources/icons/ui/dark_branch_more.png b/resources/icons/ui/dark/branch_more.png similarity index 100% rename from resources/icons/ui/dark_branch_more.png rename to resources/icons/ui/dark/branch_more.png diff --git a/resources/icons/ui/dark_branch_open.png b/resources/icons/ui/dark/branch_open.png similarity index 100% rename from resources/icons/ui/dark_branch_open.png rename to resources/icons/ui/dark/branch_open.png diff --git a/resources/icons/ui/dark_checkbox_checked.png b/resources/icons/ui/dark/checkbox_checked.png similarity index 100% rename from resources/icons/ui/dark_checkbox_checked.png rename to resources/icons/ui/dark/checkbox_checked.png diff --git a/resources/icons/ui/dark_checkbox_checked@2x.png b/resources/icons/ui/dark/checkbox_checked@2x.png similarity index 100% rename from resources/icons/ui/dark_checkbox_checked@2x.png rename to resources/icons/ui/dark/checkbox_checked@2x.png diff --git a/resources/icons/ui/dark_checkbox_checked_disabled.png b/resources/icons/ui/dark/checkbox_checked_disabled.png similarity index 100% rename from resources/icons/ui/dark_checkbox_checked_disabled.png rename to resources/icons/ui/dark/checkbox_checked_disabled.png diff --git a/resources/icons/ui/dark_checkbox_checked_disabled@2x.png b/resources/icons/ui/dark/checkbox_checked_disabled@2x.png similarity index 100% rename from resources/icons/ui/dark_checkbox_checked_disabled@2x.png rename to resources/icons/ui/dark/checkbox_checked_disabled@2x.png diff --git a/resources/icons/ui/dark_checkbox_unchecked.png b/resources/icons/ui/dark/checkbox_unchecked.png similarity index 100% rename from resources/icons/ui/dark_checkbox_unchecked.png rename to resources/icons/ui/dark/checkbox_unchecked.png diff --git a/resources/icons/ui/dark_checkbox_unchecked@2x.png b/resources/icons/ui/dark/checkbox_unchecked@2x.png similarity index 100% rename from resources/icons/ui/dark_checkbox_unchecked@2x.png rename to resources/icons/ui/dark/checkbox_unchecked@2x.png diff --git a/resources/icons/ui/dark_checkbox_unchecked_disabled.png b/resources/icons/ui/dark/checkbox_unchecked_disabled.png similarity index 100% rename from resources/icons/ui/dark_checkbox_unchecked_disabled.png rename to resources/icons/ui/dark/checkbox_unchecked_disabled.png diff --git a/resources/icons/ui/dark_checkbox_unchecked_disabled@2x.png b/resources/icons/ui/dark/checkbox_unchecked_disabled@2x.png similarity index 100% rename from resources/icons/ui/dark_checkbox_unchecked_disabled@2x.png rename to resources/icons/ui/dark/checkbox_unchecked_disabled@2x.png diff --git a/resources/icons/ui/dark/radio_checked.png b/resources/icons/ui/dark/radio_checked.png new file mode 100644 index 00000000..acb89015 Binary files /dev/null and b/resources/icons/ui/dark/radio_checked.png differ diff --git a/resources/icons/ui/dark/radio_checked@2x.png b/resources/icons/ui/dark/radio_checked@2x.png new file mode 100644 index 00000000..e1904762 Binary files /dev/null and b/resources/icons/ui/dark/radio_checked@2x.png differ diff --git a/resources/icons/ui/dark/radio_checked_disabled.png b/resources/icons/ui/dark/radio_checked_disabled.png new file mode 100644 index 00000000..49df4392 Binary files /dev/null and b/resources/icons/ui/dark/radio_checked_disabled.png differ diff --git a/resources/icons/ui/dark/radio_checked_disabled@2x.png b/resources/icons/ui/dark/radio_checked_disabled@2x.png new file mode 100644 index 00000000..a9ffd40c Binary files /dev/null and b/resources/icons/ui/dark/radio_checked_disabled@2x.png differ diff --git a/resources/icons/ui/dark/radio_unchecked.png b/resources/icons/ui/dark/radio_unchecked.png new file mode 100644 index 00000000..9ffddc4a Binary files /dev/null and b/resources/icons/ui/dark/radio_unchecked.png differ diff --git a/resources/icons/ui/dark/radio_unchecked@2x.png b/resources/icons/ui/dark/radio_unchecked@2x.png new file mode 100644 index 00000000..2160a32e Binary files /dev/null and b/resources/icons/ui/dark/radio_unchecked@2x.png differ diff --git a/resources/icons/ui/dark/radio_unchecked_disabled.png b/resources/icons/ui/dark/radio_unchecked_disabled.png new file mode 100644 index 00000000..7ddff642 Binary files /dev/null and b/resources/icons/ui/dark/radio_unchecked_disabled.png differ diff --git a/resources/icons/ui/dark/radio_unchecked_disabled@2x.png b/resources/icons/ui/dark/radio_unchecked_disabled@2x.png new file mode 100644 index 00000000..4de5d0d2 Binary files /dev/null and b/resources/icons/ui/dark/radio_unchecked_disabled@2x.png differ diff --git a/resources/icons/ui/midnight_branch_closed.png b/resources/icons/ui/midnight/branch_closed.png similarity index 100% rename from resources/icons/ui/midnight_branch_closed.png rename to resources/icons/ui/midnight/branch_closed.png diff --git a/resources/icons/ui/midnight_branch_end.png b/resources/icons/ui/midnight/branch_end.png similarity index 100% rename from resources/icons/ui/midnight_branch_end.png rename to resources/icons/ui/midnight/branch_end.png diff --git a/resources/icons/ui/midnight_branch_more.png b/resources/icons/ui/midnight/branch_more.png similarity index 100% rename from resources/icons/ui/midnight_branch_more.png rename to resources/icons/ui/midnight/branch_more.png diff --git a/resources/icons/ui/midnight_branch_open.png b/resources/icons/ui/midnight/branch_open.png similarity index 100% rename from resources/icons/ui/midnight_branch_open.png rename to resources/icons/ui/midnight/branch_open.png diff --git a/resources/icons/ui/midnight_checkbox_checked.png b/resources/icons/ui/midnight/checkbox_checked.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_checked.png rename to resources/icons/ui/midnight/checkbox_checked.png diff --git a/resources/icons/ui/midnight_checkbox_checked@2x.png b/resources/icons/ui/midnight/checkbox_checked@2x.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_checked@2x.png rename to resources/icons/ui/midnight/checkbox_checked@2x.png diff --git a/resources/icons/ui/midnight_checkbox_checked_disabled.png b/resources/icons/ui/midnight/checkbox_checked_disabled.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_checked_disabled.png rename to resources/icons/ui/midnight/checkbox_checked_disabled.png diff --git a/resources/icons/ui/midnight_checkbox_checked_disabled@2x.png b/resources/icons/ui/midnight/checkbox_checked_disabled@2x.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_checked_disabled@2x.png rename to resources/icons/ui/midnight/checkbox_checked_disabled@2x.png diff --git a/resources/icons/ui/midnight_checkbox_unchecked.png b/resources/icons/ui/midnight/checkbox_unchecked.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_unchecked.png rename to resources/icons/ui/midnight/checkbox_unchecked.png diff --git a/resources/icons/ui/midnight_checkbox_unchecked@2x.png b/resources/icons/ui/midnight/checkbox_unchecked@2x.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_unchecked@2x.png rename to resources/icons/ui/midnight/checkbox_unchecked@2x.png diff --git a/resources/icons/ui/midnight_checkbox_unchecked_disabled.png b/resources/icons/ui/midnight/checkbox_unchecked_disabled.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_unchecked_disabled.png rename to resources/icons/ui/midnight/checkbox_unchecked_disabled.png diff --git a/resources/icons/ui/midnight_checkbox_unchecked_disabled@2x.png b/resources/icons/ui/midnight/checkbox_unchecked_disabled@2x.png similarity index 100% rename from resources/icons/ui/midnight_checkbox_unchecked_disabled@2x.png rename to resources/icons/ui/midnight/checkbox_unchecked_disabled@2x.png diff --git a/resources/icons/ui/midnight/radio_checked.png b/resources/icons/ui/midnight/radio_checked.png new file mode 100644 index 00000000..acb89015 Binary files /dev/null and b/resources/icons/ui/midnight/radio_checked.png differ diff --git a/resources/icons/ui/midnight/radio_checked@2x.png b/resources/icons/ui/midnight/radio_checked@2x.png new file mode 100644 index 00000000..e1904762 Binary files /dev/null and b/resources/icons/ui/midnight/radio_checked@2x.png differ diff --git a/resources/icons/ui/midnight/radio_checked_disabled.png b/resources/icons/ui/midnight/radio_checked_disabled.png new file mode 100644 index 00000000..49df4392 Binary files /dev/null and b/resources/icons/ui/midnight/radio_checked_disabled.png differ diff --git a/resources/icons/ui/midnight/radio_checked_disabled@2x.png b/resources/icons/ui/midnight/radio_checked_disabled@2x.png new file mode 100644 index 00000000..a9ffd40c Binary files /dev/null and b/resources/icons/ui/midnight/radio_checked_disabled@2x.png differ diff --git a/resources/icons/ui/midnight/radio_unchecked.png b/resources/icons/ui/midnight/radio_unchecked.png new file mode 100644 index 00000000..9ffddc4a Binary files /dev/null and b/resources/icons/ui/midnight/radio_unchecked.png differ diff --git a/resources/icons/ui/midnight/radio_unchecked@2x.png b/resources/icons/ui/midnight/radio_unchecked@2x.png new file mode 100644 index 00000000..2160a32e Binary files /dev/null and b/resources/icons/ui/midnight/radio_unchecked@2x.png differ diff --git a/resources/icons/ui/midnight/radio_unchecked_disabled.png b/resources/icons/ui/midnight/radio_unchecked_disabled.png new file mode 100644 index 00000000..7ddff642 Binary files /dev/null and b/resources/icons/ui/midnight/radio_unchecked_disabled.png differ diff --git a/resources/icons/ui/midnight/radio_unchecked_disabled@2x.png b/resources/icons/ui/midnight/radio_unchecked_disabled@2x.png new file mode 100644 index 00000000..4de5d0d2 Binary files /dev/null and b/resources/icons/ui/midnight/radio_unchecked_disabled@2x.png differ diff --git a/resources/icons/ui/midnight_vline.png b/resources/icons/ui/midnight_vline.png deleted file mode 100644 index 8f0c336f..00000000 Binary files a/resources/icons/ui/midnight_vline.png and /dev/null differ diff --git a/resources/icons/unlock_edit.ico b/resources/icons/unlock_edit.ico new file mode 100644 index 00000000..85a99e98 Binary files /dev/null and b/resources/icons/unlock_edit.ico differ diff --git a/resources/images.qrc b/resources/images.qrc index 26f51f42..8d6af6fd 100644 --- a/resources/images.qrc +++ b/resources/images.qrc @@ -1,6 +1,7 @@ icons/add.ico + icons/chart_bar.ico icons/collapse_all.ico icons/cursor.ico icons/delete.ico @@ -10,18 +11,25 @@ icons/file_put.ico icons/fill_color_cursor.ico icons/fill_color.ico + icons/folder_add.ico icons/folder_closed_map.ico icons/folder_closed.ico + icons/folder_eye_closed.ico + icons/folder_eye_open.ico icons/folder_map_edited.ico icons/folder_map_opened.ico icons/folder_map.ico icons/folder.ico + icons/lock_edit.ico + icons/unlock_edit.ico icons/help.ico icons/link_broken.ico icons/link.ico + icons/magnifier.ico icons/map_edited.ico icons/map_opened.ico icons/map.ico + icons/map_grayed.ico icons/move.ico icons/pencil_cursor.ico icons/pencil.ico @@ -37,36 +45,61 @@ icons/sort_map.ico icons/sort_number.ico icons/tall_grass.ico - icons/viewsprites.ico - icons/ui/dark_checkbox_checked_disabled.png - icons/ui/dark_checkbox_checked_disabled@2x.png - icons/ui/dark_checkbox_checked.png - icons/ui/dark_checkbox_checked@2x.png - icons/ui/dark_checkbox_unchecked_disabled.png - icons/ui/dark_checkbox_unchecked_disabled@2x.png - icons/ui/dark_checkbox_unchecked.png - icons/ui/dark_checkbox_unchecked@2x.png - icons/ui/dark_branch_closed.png - icons/ui/dark_branch_open.png - icons/ui/dark_branch_end.png - icons/ui/dark_branch_more.png - icons/ui/midnight_checkbox_checked_disabled.png - icons/ui/midnight_checkbox_checked_disabled@2x.png - icons/ui/midnight_checkbox_checked.png - icons/ui/midnight_checkbox_checked@2x.png - icons/ui/midnight_checkbox_unchecked_disabled.png - icons/ui/midnight_checkbox_unchecked_disabled@2x.png - icons/ui/midnight_checkbox_unchecked.png - icons/ui/midnight_checkbox_unchecked@2x.png - icons/ui/midnight_branch_closed.png - icons/ui/midnight_branch_open.png - icons/ui/midnight_branch_end.png - icons/ui/midnight_branch_more.png + icons/minimap.ico + icons/application_form_edit.ico + icons/connections.ico + icons/player/brendan_em.ico + icons/player/brendan_rs.ico + icons/player/green.ico + icons/player/may_em.ico + icons/player/may_rs.ico + icons/player/red.ico + icons/ui/dark/checkbox_checked_disabled.png + icons/ui/dark/checkbox_checked_disabled@2x.png + icons/ui/dark/checkbox_checked.png + icons/ui/dark/checkbox_checked@2x.png + icons/ui/dark/checkbox_unchecked_disabled.png + icons/ui/dark/checkbox_unchecked_disabled@2x.png + icons/ui/dark/checkbox_unchecked.png + icons/ui/dark/checkbox_unchecked@2x.png + icons/ui/dark/branch_closed.png + icons/ui/dark/branch_open.png + icons/ui/dark/branch_end.png + icons/ui/dark/branch_more.png + icons/ui/dark/radio_checked_disabled.png + icons/ui/dark/radio_checked_disabled@2x.png + icons/ui/dark/radio_checked.png + icons/ui/dark/radio_checked@2x.png + icons/ui/dark/radio_unchecked_disabled.png + icons/ui/dark/radio_unchecked_disabled@2x.png + icons/ui/dark/radio_unchecked.png + icons/ui/dark/radio_unchecked@2x.png + icons/ui/midnight/checkbox_checked_disabled.png + icons/ui/midnight/checkbox_checked_disabled@2x.png + icons/ui/midnight/checkbox_checked.png + icons/ui/midnight/checkbox_checked@2x.png + icons/ui/midnight/checkbox_unchecked_disabled.png + icons/ui/midnight/checkbox_unchecked_disabled@2x.png + icons/ui/midnight/checkbox_unchecked.png + icons/ui/midnight/checkbox_unchecked@2x.png + icons/ui/midnight/branch_closed.png + icons/ui/midnight/branch_open.png + icons/ui/midnight/branch_end.png + icons/ui/midnight/branch_more.png + icons/ui/midnight/radio_checked_disabled.png + icons/ui/midnight/radio_checked_disabled@2x.png + icons/ui/midnight/radio_checked.png + icons/ui/midnight/radio_checked@2x.png + icons/ui/midnight/radio_unchecked_disabled.png + icons/ui/midnight/radio_unchecked_disabled@2x.png + icons/ui/midnight/radio_unchecked.png + icons/ui/midnight/radio_unchecked@2x.png images/blank_tileset.png images/collisions.png images/collisions_unknown.png images/Entities_16x16.png images/pokemon_icon_placeholder.png + images/porysplash.gif icons/clipboard.ico icons/map_go.ico diff --git a/resources/images/porysplash.gif b/resources/images/porysplash.gif new file mode 100644 index 00000000..900874d4 Binary files /dev/null and b/resources/images/porysplash.gif differ diff --git a/resources/text/script_template.txt b/resources/text/script_template.txt index fdd1949f..bee6e56e 100644 --- a/resources/text/script_template.txt +++ b/resources/text/script_template.txt @@ -13,6 +13,11 @@ export function onMapOpened(mapName) { } +// Called when a layout is opened, either by selecting a new map/layout in the map list or swapping the layout for the current map. +export function onLayoutOpened(layoutName) { + +} + // Called when a block is changed on the map. For example, this is called when a user paints a new tile or changes the collision property of a block. export function onBlockChanged(x, y, prevBlock, newBlock) { @@ -34,7 +39,7 @@ export function onBlockHoverCleared() { } // Called when the dimensions of the map are changed. -export function onMapResized(oldWidth, oldHeight, newWidth, newHeight) { +export function onMapResized(oldWidth, oldHeight, delta) { } diff --git a/resources/themes/dark.qss b/resources/themes/dark.qss index e02d2a45..69dc7cd5 100644 --- a/resources/themes/dark.qss +++ b/resources/themes/dark.qss @@ -100,39 +100,39 @@ QMenu::indicator { } QMenu::indicator { - image: url(":/icons/ui/dark_checkbox_unchecked.png"); + image: url(":/icons/ui/dark/checkbox_unchecked.png"); } QMenu::indicator:non-exclusive:unchecked { - image: url(":/icons/ui/dark_checkbox_unchecked.png"); + image: url(":/icons/ui/dark/checkbox_unchecked.png"); } QMenu::indicator:non-exclusive:unchecked:selected { - image: url(":/icons/ui/dark_checkbox_unchecked.png"); + image: url(":/icons/ui/dark/checkbox_unchecked.png"); } QMenu::indicator:non-exclusive:checked { - image: url(":/icons/ui/dark_checkbox_checked.png"); + image: url(":/icons/ui/dark/checkbox_checked.png"); } QMenu::indicator:non-exclusive:checked:selected { - image: url(":/icons/ui/dark_checkbox_checked.png"); + image: url(":/icons/ui/dark/checkbox_checked.png"); } QMenu::indicator:exclusive:unchecked { - image: url(":/icons/ui/dark_checkbox_unchecked.png"); + image: url(":/icons/ui/dark/checkbox_unchecked.png"); } QMenu::indicator:exclusive:unchecked:selected { - image: url(":/icons/ui/dark_checkbox_unchecked.png"); + image: url(":/icons/ui/dark/checkbox_unchecked.png"); } QMenu::indicator:exclusive:checked { - image: url(":/icons/ui/dark_checkbox_checked.png"); + image: url(":/icons/ui/dark/checkbox_checked.png"); } QMenu::indicator:exclusive:checked:selected { - image: url(":/icons/ui/dark_checkbox_checked.png"); + image: url(":/icons/ui/dark/checkbox_checked.png"); } @@ -198,35 +198,85 @@ QCheckBox QWidget:disabled { } QCheckBox::indicator { - margin-left: 4px; height: 16px; width: 16px; } -QRadioButton::indicator::unchecked, QCheckBox::indicator::unchecked { - image: url(:/icons/ui/dark_checkbox_unchecked.png) +QCheckBox::indicator::unchecked { + image: url(:/icons/ui/dark/checkbox_unchecked.png) } -QRadioButton::indicator:unchecked:hover, QCheckBox::indicator:unchecked:hover { +QCheckBox::indicator::checked { + image: url(":/icons/ui/dark/checkbox_checked.png"); +} + +QCheckBox::indicator:unchecked:hover { + +} + +QCheckBox::indicator:checked:hover { } QCheckBox::indicator:unchecked:disabled { - image: url(":/icons/ui/dark_checkbox_unchecked_disabled.png"); -} - -QRadioButton::indicator::checked, QCheckBox::indicator::checked { - image: url(":/icons/ui/dark_checkbox_checked.png"); -} - -QRadioButton::indicator:checked:hover, QCheckBox::indicator:checked:hover { - + image: url(":/icons/ui/dark/checkbox_unchecked_disabled.png"); } QCheckBox::indicator:checked:disabled { - image: url(":/icons/ui/dark_checkbox_checked_disabled.png"); + image: url(":/icons/ui/dark/checkbox_checked_disabled.png"); } + +/* Radio Buttons */ + +QRadioButton { + background-color: #19232D; + color: #F0F0F0; + spacing: 4px; + outline: none; + padding-top: 4px; + padding-bottom: 4px; +} + +QRadioButton:focus { + border: none; +} + +QRadioButton QWidget:disabled { + background-color: #19232D; + color: #787878; +} + +QRadioButton::indicator { + height: 16px; + width: 16px; +} + +QRadioButton::indicator::unchecked { + image: url(:/icons/ui/dark/radio_unchecked.png) +} + +QRadioButton::indicator::checked { + image: url(":/icons/ui/dark/radio_checked.png"); +} + +QRadioButton::indicator:unchecked:hover { + +} + +QRadioButton::indicator:checked:hover { + +} + +QRadioButton::indicator:unchecked:disabled { + image: url(":/icons/ui/dark/radio_unchecked_disabled.png"); +} + +QRadioButton::indicator:checked:disabled { + image: url(":/icons/ui/dark/radio_checked_disabled.png"); +} + + /* Map List View */ QTreeView { @@ -247,27 +297,27 @@ QTreeView::branch { } QTreeView::branch:has-siblings:!adjoins-item { - border-image: url(:/icons/ui/dark_vline.png) 0; + border-image: url(:/icons/ui/dark/vline.png) 0; } QTreeView::branch:has-siblings:adjoins-item { - border-image: url(:/icons/ui/dark_branch_more.png) 0; + border-image: url(:/icons/ui/dark/branch_more.png) 0; } QTreeView::branch:!has-children:!has-siblings:adjoins-item { - border-image: url(:/icons/ui/dark_branch_end.png) 0; + border-image: url(:/icons/ui/dark/branch_end.png) 0; } QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { border-image: none; - image: url(:/icons/ui/dark_branch_closed.png); + image: url(:/icons/ui/dark/branch_closed.png); } QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings { border-image: none; - image: url(:/icons/ui/dark_branch_open.png); + image: url(:/icons/ui/dark/branch_open.png); } /* Scroll Bar */ diff --git a/resources/themes/midnight.qss b/resources/themes/midnight.qss index a8587e59..734dffa1 100644 --- a/resources/themes/midnight.qss +++ b/resources/themes/midnight.qss @@ -102,39 +102,39 @@ QMenu::indicator { } QMenu::indicator { - image: url(":/icons/ui/midnight_checkbox_unchecked.png"); + image: url(":/icons/ui/midnight/checkbox_unchecked.png"); } QMenu::indicator:non-exclusive:unchecked { - image: url(":/icons/ui/midnight_checkbox_unchecked.png"); + image: url(":/icons/ui/midnight/checkbox_unchecked.png"); } QMenu::indicator:non-exclusive:unchecked:selected { - image: url(":/icons/ui/midnight_checkbox_unchecked.png"); + image: url(":/icons/ui/midnight/checkbox_unchecked.png"); } QMenu::indicator:non-exclusive:checked { - image: url(":/icons/ui/midnight_checkbox_checked.png"); + image: url(":/icons/ui/midnight/checkbox_checked.png"); } QMenu::indicator:non-exclusive:checked:selected { - image: url(":/icons/ui/midnight_checkbox_checked.png"); + image: url(":/icons/ui/midnight/checkbox_checked.png"); } QMenu::indicator:exclusive:unchecked { - image: url(":/icons/ui/midnight_checkbox_unchecked.png"); + image: url(":/icons/ui/midnight/checkbox_unchecked.png"); } QMenu::indicator:exclusive:unchecked:selected { - image: url(":/icons/ui/midnight_checkbox_unchecked.png"); + image: url(":/icons/ui/midnight/checkbox_unchecked.png"); } QMenu::indicator:exclusive:checked { - image: url(":/icons/ui/midnight_checkbox_checked.png"); + image: url(":/icons/ui/midnight/checkbox_checked.png"); } QMenu::indicator:exclusive:checked:selected { - image: url(":/icons/ui/midnight_checkbox_checked.png"); + image: url(":/icons/ui/midnight/checkbox_checked.png"); } /* Combo Boxes */ @@ -178,6 +178,8 @@ QLineEdit { color: #F8F8F2; } +/* Checkboxes */ + QCheckBox { background-color: #31332b; color: #F8F8F2; @@ -197,35 +199,85 @@ QCheckBox QWidget:disabled { } QCheckBox::indicator { - margin-left: 4px; height: 16px; width: 16px; } -QRadioButton::indicator::unchecked, QCheckBox::indicator::unchecked { - image: url(:/icons/ui/midnight_checkbox_unchecked.png) +QCheckBox::indicator::unchecked { + image: url(:/icons/ui/midnight/checkbox_unchecked.png) } -QRadioButton::indicator:unchecked:hover, QCheckBox::indicator:unchecked:hover { +QCheckBox::indicator::checked { + image: url(":/icons/ui/midnight/checkbox_checked.png"); +} + +QCheckBox::indicator:unchecked:hover { + +} + +QCheckBox::indicator:checked:hover { } QCheckBox::indicator:unchecked:disabled { - image: url(":/icons/ui/midnight_checkbox_unchecked_disabled.png"); -} - -QRadioButton::indicator::checked, QCheckBox::indicator::checked { - image: url(":/icons/ui/midnight_checkbox_checked.png"); -} - -QRadioButton::indicator:checked:hover, QCheckBox::indicator:checked:hover { - + image: url(":/icons/ui/midnight/checkbox_unchecked_disabled.png"); } QCheckBox::indicator:checked:disabled { - image: url(":/icons/ui/midnight_checkbox_checked_disabled.png"); + image: url(":/icons/ui/midnight/checkbox_checked_disabled.png"); } + +/* Radio Buttons */ + +QRadioButton { + background-color: #31332b; + color: #F8F8F2; + spacing: 4px; + outline: none; + padding-top: 4px; + padding-bottom: 4px; +} + +QRadioButton:focus { + border: none; +} + +QRadioButton QWidget:disabled { + background-color: #49483E; + color: #F8F8F2; +} + +QRadioButton::indicator { + height: 16px; + width: 16px; +} + +QRadioButton::indicator::unchecked { + image: url(:/icons/ui/midnight/radio_unchecked.png) +} + +QRadioButton::indicator::checked { + image: url(":/icons/ui/midnight/radio_checked.png"); +} + +QRadioButton::indicator:unchecked:hover { + +} + +QRadioButton::indicator:checked:hover { + +} + +QRadioButton::indicator:unchecked:disabled { + image: url(":/icons/ui/midnight/radio_unchecked_disabled.png"); +} + +QRadioButton::indicator:checked:disabled { + image: url(":/icons/ui/midnight/radio_checked_disabled.png"); +} + + /* Map List View */ QTreeView { @@ -246,27 +298,27 @@ QTreeView::branch { } QTreeView::branch:has-siblings:!adjoins-item { - border-image: url(:/icons/ui/midnight_vline.png) 0; + border-image: url(:/icons/ui/midnight/vline.png) 0; } QTreeView::branch:has-siblings:adjoins-item { - border-image: url(:/icons/ui/midnight_branch_more.png) 0; + border-image: url(:/icons/ui/midnight/branch_more.png) 0; } QTreeView::branch:!has-children:!has-siblings:adjoins-item { - border-image: url(:/icons/ui/midnight_branch_end.png) 0; + border-image: url(:/icons/ui/midnight/branch_end.png) 0; } QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { border-image: none; - image: url(:/icons/ui/midnight_branch_closed.png); + image: url(:/icons/ui/midnight/branch_closed.png); } QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings { border-image: none; - image: url(:/icons/ui/midnight_branch_open.png); + image: url(:/icons/ui/midnight/branch_open.png); } /* Scroll Bar */ diff --git a/src/config.cpp b/src/config.cpp index 8c4213d8..7914cdce 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -2,6 +2,8 @@ #include "log.h" #include "shortcut.h" #include "map.h" +#include "validator.h" +#include "utility.h" #include #include #include @@ -16,7 +18,13 @@ #include #include -const QSet defaultWarpBehaviors_RSE = { +const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_VERSION); + +// In both versions the default new map border is a generic tree +const QList defaultBorder_RSE = {0x1D4, 0x1D5, 0x1DC, 0x1DD}; +const QList defaultBorder_FRLG = {0x14, 0x15, 0x1C, 0x1D}; + +const QList defaultWarpBehaviors_RSE = { 0x0E, // MB_MOSSDEEP_GYM_WARP 0x0F, // MB_MT_PYRE_HOLE 0x1B, // MB_STAIRS_OUTSIDE_ABANDONED_SHIP @@ -47,7 +55,7 @@ const QSet defaultWarpBehaviors_RSE = { 0x9D, // MB_SECRET_BASE_SPOT_TREE_RIGHT_OPEN }; -const QSet defaultWarpBehaviors_FRLG = { +const QList defaultWarpBehaviors_FRLG = { 0x60, // MB_CAVE_DOOR 0x61, // MB_LADDER 0x62, // MB_EAST_ARROW_WARP @@ -67,20 +75,14 @@ const QSet defaultWarpBehaviors_FRLG = { 0x71, // MB_UNION_ROOM_WARP }; -// TODO: symbol_wild_encounters should ultimately be removed from the table below. We can determine this name when we read the project. const QMap> ProjectConfig::defaultIdentifiers = { // Symbols {ProjectIdentifier::symbol_facing_directions, {"symbol_facing_directions", "gInitialMovementTypeFacingDirections"}}, {ProjectIdentifier::symbol_obj_event_gfx_pointers, {"symbol_obj_event_gfx_pointers", "gObjectEventGraphicsInfoPointers"}}, {ProjectIdentifier::symbol_pokemon_icon_table, {"symbol_pokemon_icon_table", "gMonIconTable"}}, - {ProjectIdentifier::symbol_wild_encounters, {"symbol_wild_encounters", "gWildMonHeaders"}}, - {ProjectIdentifier::symbol_heal_locations_type, {"symbol_heal_locations_type", "struct HealLocation"}}, - {ProjectIdentifier::symbol_heal_locations, {"symbol_heal_locations", "sHealLocations"}}, - {ProjectIdentifier::symbol_spawn_points, {"symbol_spawn_points", "sSpawnPoints"}}, - {ProjectIdentifier::symbol_spawn_maps, {"symbol_spawn_maps", "u16 sWhiteoutRespawnHealCenterMapIdxs"}}, - {ProjectIdentifier::symbol_spawn_npcs, {"symbol_spawn_npcs", "u8 sWhiteoutRespawnHealerNpcIds"}}, {ProjectIdentifier::symbol_attribute_table, {"symbol_attribute_table", "sMetatileAttrMasks"}}, {ProjectIdentifier::symbol_tilesets_prefix, {"symbol_tilesets_prefix", "gTileset_"}}, + {ProjectIdentifier::symbol_dynamic_map_name, {"symbol_dynamic_map_name", "Dynamic"}}, // Defines {ProjectIdentifier::define_obj_event_count, {"define_obj_event_count", "OBJECT_EVENT_TEMPLATES_COUNT"}}, {ProjectIdentifier::define_min_level, {"define_min_level", "MIN_LEVEL"}}, @@ -93,6 +95,8 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::define_pals_total, {"define_pals_total", "NUM_PALS_TOTAL"}}, {ProjectIdentifier::define_tiles_per_metatile, {"define_tiles_per_metatile", "NUM_TILES_PER_METATILE"}}, {ProjectIdentifier::define_map_size, {"define_map_size", "MAX_MAP_DATA_SIZE"}}, + {ProjectIdentifier::define_map_offset_width, {"define_map_offset_width", "MAP_OFFSET_W"}}, + {ProjectIdentifier::define_map_offset_height, {"define_map_offset_height", "MAP_OFFSET_H"}}, {ProjectIdentifier::define_mask_metatile, {"define_mask_metatile", "MAPGRID_METATILE_ID_MASK"}}, {ProjectIdentifier::define_mask_collision, {"define_mask_collision", "MAPGRID_COLLISION_MASK"}}, {ProjectIdentifier::define_mask_elevation, {"define_mask_elevation", "MAPGRID_ELEVATION_MASK"}}, @@ -104,14 +108,14 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::define_attribute_encounter, {"define_attribute_encounter", "METATILE_ATTRIBUTE_ENCOUNTER_TYPE"}}, {ProjectIdentifier::define_metatile_label_prefix, {"define_metatile_label_prefix", "METATILE_"}}, {ProjectIdentifier::define_heal_locations_prefix, {"define_heal_locations_prefix", "HEAL_LOCATION_"}}, - {ProjectIdentifier::define_spawn_prefix, {"define_spawn_prefix", "SPAWN_"}}, + {ProjectIdentifier::define_layout_prefix, {"define_layout_prefix", "LAYOUT_"}}, {ProjectIdentifier::define_map_prefix, {"define_map_prefix", "MAP_"}}, {ProjectIdentifier::define_map_dynamic, {"define_map_dynamic", "DYNAMIC"}}, {ProjectIdentifier::define_map_empty, {"define_map_empty", "UNDEFINED"}}, {ProjectIdentifier::define_map_section_prefix, {"define_map_section_prefix", "MAPSEC_"}}, {ProjectIdentifier::define_map_section_empty, {"define_map_section_empty", "NONE"}}, - {ProjectIdentifier::define_map_section_count, {"define_map_section_count", "COUNT"}}, {ProjectIdentifier::define_species_prefix, {"define_species_prefix", "SPECIES_"}}, + {ProjectIdentifier::define_species_empty, {"define_species_empty", "NONE"}}, // Regex {ProjectIdentifier::regex_behaviors, {"regex_behaviors", "\\bMB_"}}, {ProjectIdentifier::regex_obj_event_gfx, {"regex_obj_event_gfx", "\\bOBJ_EVENT_GFX_"}}, @@ -127,6 +131,13 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::regex_sign_facing_directions, {"regex_sign_facing_directions", "\\bBG_EVENT_PLAYER_FACING_"}}, {ProjectIdentifier::regex_trainer_types, {"regex_trainer_types", "\\bTRAINER_TYPE_"}}, {ProjectIdentifier::regex_music, {"regex_music", "\\b(SE|MUS)_"}}, + {ProjectIdentifier::regex_encounter_types, {"regex_encounter_types", "\\bTILE_ENCOUNTER_"}}, + {ProjectIdentifier::regex_terrain_types, {"regex_terrain_types", "\\bTILE_TERRAIN_"}}, + {ProjectIdentifier::regex_gbapal, {"regex_gbapal", "\\.gbapal(\\.[\\w]+)?$"}}, + {ProjectIdentifier::regex_bpp, {"regex_bpp", "\\.[\\d]+bpp(\\.[\\w]+)?$"}}, + // Other + {ProjectIdentifier::pals_output_extension, {"pals_output_extension", ".gbapal"}}, + {ProjectIdentifier::tiles_output_extension, {"tiles_output_extension", ".4bpp.lz"}}, }; const QMap> ProjectConfig::defaultPaths = { @@ -138,6 +149,7 @@ const QMap> ProjectConfig::defaultPaths {ProjectFilePath::json_map_groups, { "json_map_groups", "data/maps/map_groups.json"}}, {ProjectFilePath::json_layouts, { "json_layouts", "data/layouts/layouts.json"}}, {ProjectFilePath::json_wild_encounters, { "json_wild_encounters", "src/data/wild_encounters.json"}}, + {ProjectFilePath::json_heal_locations, { "json_heal_locations", "src/data/heal_locations.json"}}, {ProjectFilePath::json_region_map_entries, { "json_region_map_entries", "src/data/region_map/region_map_sections.json"}}, {ProjectFilePath::json_region_porymap_cfg, { "json_region_porymap_cfg", "src/data/region_map/porymap_config.json"}}, {ProjectFilePath::tilesets_headers, { "tilesets_headers", "src/data/tilesets/headers.h"}}, @@ -151,15 +163,12 @@ const QMap> ProjectConfig::defaultPaths {ProjectFilePath::data_obj_event_pic_tables, { "data_obj_event_pic_tables", "src/data/object_events/object_event_pic_tables.h"}}, {ProjectFilePath::data_obj_event_gfx, { "data_obj_event_gfx", "src/data/object_events/object_event_graphics.h"}}, {ProjectFilePath::data_pokemon_gfx, { "data_pokemon_gfx", "src/data/graphics/pokemon.h"}}, - {ProjectFilePath::data_heal_locations, { "data_heal_locations", "src/data/heal_locations.h"}}, {ProjectFilePath::constants_global, { "constants_global", "include/constants/global.h"}}, - {ProjectFilePath::constants_map_groups, { "constants_map_groups", "include/constants/map_groups.h"}}, {ProjectFilePath::constants_items, { "constants_items", "include/constants/items.h"}}, {ProjectFilePath::constants_flags, { "constants_flags", "include/constants/flags.h"}}, {ProjectFilePath::constants_vars, { "constants_vars", "include/constants/vars.h"}}, {ProjectFilePath::constants_weather, { "constants_weather", "include/constants/weather.h"}}, {ProjectFilePath::constants_songs, { "constants_songs", "include/constants/songs.h"}}, - {ProjectFilePath::constants_heal_locations, { "constants_heal_locations", "include/constants/heal_locations.h"}}, {ProjectFilePath::constants_pokemon, { "constants_pokemon", "include/constants/pokemon.h"}}, {ProjectFilePath::constants_map_types, { "constants_map_types", "include/constants/map_types.h"}}, {ProjectFilePath::constants_trainer_types, { "constants_trainer_types", "include/constants/trainer_types.h"}}, @@ -167,7 +176,6 @@ const QMap> ProjectConfig::defaultPaths {ProjectFilePath::constants_obj_event_movement, { "constants_obj_event_movement", "include/constants/event_object_movement.h"}}, {ProjectFilePath::constants_obj_events, { "constants_obj_events", "include/constants/event_objects.h"}}, {ProjectFilePath::constants_event_bg, { "constants_event_bg", "include/constants/event_bg.h"}}, - {ProjectFilePath::constants_region_map_sections, { "constants_region_map_sections", "include/constants/region_map_sections.h"}}, {ProjectFilePath::constants_metatile_labels, { "constants_metatile_labels", "include/constants/metatile_labels.h"}}, {ProjectFilePath::constants_metatile_behaviors, { "constants_metatile_behaviors", "include/constants/metatile_behaviors.h"}}, {ProjectFilePath::constants_species, { "constants_species", "include/constants/species.h"}}, @@ -226,14 +234,14 @@ void KeyValueConfigBase::load() { continue; } - this->parseConfigKeyValue(match.captured("key").trimmed().toLower(), match.captured("value").trimmed()); + this->parseConfigKeyValue(match.captured("key").trimmed(), match.captured("value").trimmed()); } this->setUnreadKeys(); file.close(); } -void KeyValueConfigBase::save() { +bool KeyValueConfigBase::save() { QString text = ""; QMap map = this->getKeyValueMap(); for (QMap::iterator it = map.begin(); it != map.end(); it++) { @@ -241,15 +249,17 @@ void KeyValueConfigBase::save() { } QFile file(this->getConfigFilepath()); - if (file.open(QIODevice::WriteOnly)) { - file.write(text.toUtf8()); - file.close(); - } else { + if (!file.open(QIODevice::WriteOnly)) { logError(QString("Could not open config file '%1' for writing: ").arg(this->getConfigFilepath()) + file.errorString()); + return false; } + + file.write(text.toUtf8()); + file.close(); + return true; } -bool KeyValueConfigBase::getConfigBool(QString key, QString value) { +bool KeyValueConfigBase::getConfigBool(const QString &key, const QString &value) { bool ok; int result = value.toInt(&ok, 0); if (!ok || (result != 0 && result != 1)) { @@ -258,37 +268,34 @@ bool KeyValueConfigBase::getConfigBool(QString key, QString value) { return (result != 0); } -int KeyValueConfigBase::getConfigInteger(QString key, QString value, int min, int max, int defaultValue) { +int KeyValueConfigBase::getConfigInteger(const QString &key, const QString &value, int min, int max, int defaultValue) { bool ok; int result = value.toInt(&ok, 0); if (!ok) { - logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value)); - return defaultValue; + logWarn(QString("Invalid config value for %1: '%2'. Must be an integer. Using default value '%3'.").arg(key).arg(value).arg(defaultValue)); + result = defaultValue; } return qMin(max, qMax(min, result)); } -uint32_t KeyValueConfigBase::getConfigUint32(QString key, QString value, uint32_t min, uint32_t max, uint32_t defaultValue) { +uint32_t KeyValueConfigBase::getConfigUint32(const QString &key, const QString &value, uint32_t min, uint32_t max, uint32_t defaultValue) { bool ok; uint32_t result = value.toUInt(&ok, 0); if (!ok) { - logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value)); - return defaultValue; + logWarn(QString("Invalid config value for %1: '%2'. Must be an integer. Using default value '%3'.").arg(key).arg(value).arg(defaultValue)); + result = defaultValue; } return qMin(max, qMax(min, result)); } -const QMap mapSortOrderMap = { - {MapSortOrder::Group, "group"}, - {MapSortOrder::Layout, "layout"}, - {MapSortOrder::Area, "area"}, -}; - -const QMap mapSortOrderReverseMap = { - {"group", MapSortOrder::Group}, - {"layout", MapSortOrder::Layout}, - {"area", MapSortOrder::Area}, -}; +QColor KeyValueConfigBase::getConfigColor(const QString &key, const QString &value, const QColor &defaultValue) { + QColor color = QColor("#" + value); + if (!color.isValid()) { + logWarn(QString("Invalid config value for %1: '%2'. Must be a color in the format 'RRGGBB'. Using default value '%3'.").arg(key).arg(value).arg(defaultValue.name())); + color = defaultValue; + } + return color; +} PorymapConfig porymapConfig; @@ -314,14 +321,18 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->reopenOnLaunch = getConfigBool(key, value); } else if (key == "pretty_cursors") { this->prettyCursors = getConfigBool(key, value); - } else if (key == "map_sort_order") { - QString sortOrder = value.toLower(); - if (mapSortOrderReverseMap.contains(sortOrder)) { - this->mapSortOrder = mapSortOrderReverseMap.value(sortOrder); - } else { - this->mapSortOrder = MapSortOrder::Group; - logWarn(QString("Invalid config value for map_sort_order: '%1'. Must be 'group', 'area', or 'layout'.").arg(value)); + } else if (key == "map_list_tab") { + this->mapListTab = getConfigInteger(key, value, 0, 2, 0); + } else if (key == "map_list_edit_groups_enabled") { + this->mapListEditGroupsEnabled = getConfigBool(key, value); + } else if (key.startsWith("map_list_hide_empty_enabled/")) { + bool ok; + int tab = key.mid(QStringLiteral("map_list_hide_empty_enabled/").length()).toInt(&ok, 0); + if (!ok) { + logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); + return; } + this->mapListHideEmptyEnabled.insert(tab, getConfigBool(key, value)); } else if (key == "main_window_geometry") { this->mainWindowGeometry = bytesFromString(value); } else if (key == "main_window_state") { @@ -368,6 +379,10 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->customScriptsEditorState = bytesFromString(value); } else if (key == "wild_mon_chart_geometry") { this->wildMonChartGeometry = bytesFromString(value); + } else if (key == "new_map_dialog_geometry") { + this->newMapDialogGeometry = bytesFromString(value); + } else if (key == "new_layout_dialog_geometry") { + this->newLayoutDialogGeometry = bytesFromString(value); } else if (key == "metatiles_zoom") { this->metatilesZoom = getConfigInteger(key, value, 10, 100, 30); } else if (key == "collision_zoom") { @@ -388,10 +403,16 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->showTilesetEditorMetatileGrid = getConfigBool(key, value); } else if (key == "show_tileset_editor_layer_grid") { this->showTilesetEditorLayerGrid = getConfigBool(key, value); + } else if (key == "show_tileset_editor_divider") { + this->showTilesetEditorDivider = getConfigBool(key, value); + } else if (key == "show_tileset_editor_raw_attributes") { + this->showTilesetEditorRawAttributes = getConfigBool(key, value); } else if (key == "monitor_files") { this->monitorFiles = getConfigBool(key, value); } else if (key == "tileset_checkerboard_fill") { this->tilesetCheckerboardFill = getConfigBool(key, value); + } else if (key == "new_map_header_section_expanded") { + this->newMapHeaderSectionExpanded = getConfigBool(key, value); } else if (key == "theme") { this->theme = value; } else if (key == "wild_mon_chart_theme") { @@ -407,8 +428,14 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { } } else if (key == "project_settings_tab") { this->projectSettingsTab = getConfigInteger(key, value, 0); + } else if (key == "load_all_event_scripts") { + this->loadAllEventScripts = getConfigBool(key, value); } else if (key == "warp_behavior_warning_disabled") { this->warpBehaviorWarningDisabled = getConfigBool(key, value); + } else if (key == "event_delete_warning_disabled") { + this->eventDeleteWarningDisabled = getConfigBool(key, value); + } else if (key == "event_overlay_enabled") { + this->eventOverlayEnabled = getConfigBool(key, value); } else if (key == "check_for_updates") { this->checkForUpdates = getConfigBool(key, value); } else if (key == "last_update_check_time") { @@ -427,6 +454,28 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { if (match.hasMatch()) { this->rateLimitTimes.insert(match.captured("url"), QDateTime::fromString(value).toLocalTime()); } + } else if (key == "event_selection_shape_mode") { + if (value == "mask") { + this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape; + } else if (value == "bounding_rect") { + this->eventSelectionShapeMode = QGraphicsPixmapItem::BoundingRectShape; + } else { + logWarn(QString("Invalid config value for %1: '%2'. Must be 'mask' or 'bounding_rect'.").arg(key).arg(value)); + } + } else if (key == "shown_in_game_reload_message") { + this->shownInGameReloadMessage = getConfigBool(key, value); + } else if (key == "grid_width") { + this->gridSettings.width = getConfigUint32(key, value); + } else if (key == "grid_height") { + this->gridSettings.height = getConfigUint32(key, value); + } else if (key == "grid_x") { + this->gridSettings.offsetX = getConfigInteger(key, value, 0, 999); + } else if (key == "grid_y") { + this->gridSettings.offsetY = getConfigInteger(key, value, 0, 999); + } else if (key == "grid_style") { + this->gridSettings.style = GridSettings::getStyleFromName(value); + } else if (key == "grid_color") { + this->gridSettings.color = getConfigColor(key, value); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -438,7 +487,11 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("project_manually_closed", this->projectManuallyClosed ? "1" : "0"); map.insert("reopen_on_launch", this->reopenOnLaunch ? "1" : "0"); map.insert("pretty_cursors", this->prettyCursors ? "1" : "0"); - map.insert("map_sort_order", mapSortOrderMap.value(this->mapSortOrder)); + map.insert("map_list_tab", QString::number(this->mapListTab)); + map.insert("map_list_edit_groups_enabled", this->mapListEditGroupsEnabled ? "1" : "0"); + for (auto i = this->mapListHideEmptyEnabled.constBegin(); i != this->mapListHideEmptyEnabled.constEnd(); i++) { + map.insert(QStringLiteral("map_list_hide_empty_enabled/") + QString::number(i.key()), i.value() ? "1" : "0"); + } map.insert("main_window_geometry", stringFromByteArray(this->mainWindowGeometry)); map.insert("main_window_state", stringFromByteArray(this->mainWindowState)); map.insert("map_splitter_state", stringFromByteArray(this->mapSplitterState)); @@ -456,6 +509,8 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("custom_scripts_editor_geometry", stringFromByteArray(this->customScriptsEditorGeometry)); map.insert("custom_scripts_editor_state", stringFromByteArray(this->customScriptsEditorState)); map.insert("wild_mon_chart_geometry", stringFromByteArray(this->wildMonChartGeometry)); + map.insert("new_map_dialog_geometry", stringFromByteArray(this->newMapDialogGeometry)); + map.insert("new_layout_dialog_geometry", stringFromByteArray(this->newLayoutDialogGeometry)); map.insert("mirror_connecting_maps", this->mirrorConnectingMaps ? "1" : "0"); map.insert("show_dive_emerge_maps", this->showDiveEmergeMaps ? "1" : "0"); map.insert("dive_emerge_map_opacity", QString::number(this->diveEmergeMapOpacity)); @@ -472,15 +527,21 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("show_grid", this->showGrid ? "1" : "0"); map.insert("show_tileset_editor_metatile_grid", this->showTilesetEditorMetatileGrid ? "1" : "0"); map.insert("show_tileset_editor_layer_grid", this->showTilesetEditorLayerGrid ? "1" : "0"); + map.insert("show_tileset_editor_divider", this->showTilesetEditorDivider ? "1" : "0"); + map.insert("show_tileset_editor_raw_attributes", this->showTilesetEditorRawAttributes ? "1" : "0"); map.insert("monitor_files", this->monitorFiles ? "1" : "0"); map.insert("tileset_checkerboard_fill", this->tilesetCheckerboardFill ? "1" : "0"); + map.insert("new_map_header_section_expanded", this->newMapHeaderSectionExpanded ? "1" : "0"); map.insert("theme", this->theme); map.insert("wild_mon_chart_theme", this->wildMonChartTheme); map.insert("text_editor_open_directory", this->textEditorOpenFolder); map.insert("text_editor_goto_line", this->textEditorGotoLine); map.insert("palette_editor_bit_depth", QString::number(this->paletteEditorBitDepth)); map.insert("project_settings_tab", QString::number(this->projectSettingsTab)); + map.insert("load_all_event_scripts", QString::number(this->loadAllEventScripts)); map.insert("warp_behavior_warning_disabled", QString::number(this->warpBehaviorWarningDisabled)); + map.insert("event_delete_warning_disabled", QString::number(this->eventDeleteWarningDisabled)); + map.insert("event_overlay_enabled", QString::number(this->eventOverlayEnabled)); map.insert("check_for_updates", QString::number(this->checkForUpdates)); map.insert("last_update_check_time", this->lastUpdateCheckTime.toUTC().toString()); map.insert("last_update_check_version", this->lastUpdateCheckVersion.toString()); @@ -490,22 +551,30 @@ QMap PorymapConfig::getKeyValueMap() { if (!time.isNull() && time > QDateTime::currentDateTime()) map.insert("rate_limit_time/" + i.key().toString(), time.toUTC().toString()); } + map.insert("event_selection_shape_mode", (this->eventSelectionShapeMode == QGraphicsPixmapItem::MaskShape) ? "mask" : "bounding_rect"); + map.insert("shown_in_game_reload_message", this->shownInGameReloadMessage ? "1" : "0"); + map.insert("grid_width", QString::number(this->gridSettings.width)); + map.insert("grid_height", QString::number(this->gridSettings.height)); + map.insert("grid_x", QString::number(this->gridSettings.offsetX)); + map.insert("grid_y", QString::number(this->gridSettings.offsetY)); + map.insert("grid_style", GridSettings::getStyleName(this->gridSettings.style)); + map.insert("grid_color", this->gridSettings.color.name().remove("#")); // Our text config treats '#' as the start of a comment. return map; } -QString PorymapConfig::stringFromByteArray(QByteArray bytearray) { +QString PorymapConfig::stringFromByteArray(const QByteArray &bytearray) { QString ret; - for (auto ch : bytearray) { + for (const auto &ch : bytearray) { ret += QString::number(static_cast(ch)) + ":"; } return ret; } -QByteArray PorymapConfig::bytesFromString(QString in) { +QByteArray PorymapConfig::bytesFromString(const QString &in) { QByteArray ba; - QStringList split = in.split(":"); - for (auto ch : split) { + QStringList split = in.split(":", Qt::SkipEmptyParts); + for (const auto &ch : split) { ba.append(static_cast(ch.toInt())); } return ba; @@ -662,6 +731,32 @@ BaseGameVersion ProjectConfig::stringToBaseGameVersion(const QString &string) { return version; } +QString ProjectConfig::getPlayerIconPath(BaseGameVersion baseGameVersion, int character) { + switch (baseGameVersion) { + case BaseGameVersion::pokeemerald: { + static const QStringList paths = { QStringLiteral(":/icons/player/brendan_em.ico"), + QStringLiteral(":/icons/player/may_em.ico"), }; + return paths.value(character); + } + case BaseGameVersion::pokefirered: { + static const QStringList paths = { QStringLiteral(":/icons/player/red.ico"), + QStringLiteral(":/icons/player/green.ico"), }; + return paths.value(character); + } + case BaseGameVersion::pokeruby: { + static const QStringList paths = { QStringLiteral(":/icons/player/brendan_rs.ico"), + QStringLiteral(":/icons/player/may_rs.ico"), }; + return paths.value(character); + } + default: break; + } + return QString(); +} + +QIcon ProjectConfig::getPlayerIcon(BaseGameVersion baseGameVersion, int character) { + return QIcon(getPlayerIconPath(baseGameVersion, character)); +} + ProjectConfig projectConfig; QString ProjectConfig::getConfigFilepath() { @@ -705,6 +800,10 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->defaultElevation = getConfigUint32(key, value, 0, Block::maxValue); } else if (key == "default_collision") { this->defaultCollision = getConfigUint32(key, value, 0, Block::maxValue); + } else if (key == "default_map_width") { + this->defaultMapSize.setWidth(getConfigInteger(key, value, 1)); + } else if (key == "default_map_height") { + this->defaultMapSize.setHeight(getConfigInteger(key, value, 1)); } else if (key == "new_map_border_metatiles") { this->newMapBorderMetatileIds.clear(); QList metatileIds = value.split(","); @@ -737,30 +836,40 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->blockCollisionMask = getConfigUint32(key, value, 0, Block::maxValue); } else if (key == "block_elevation_mask") { this->blockElevationMask = getConfigUint32(key, value, 0, Block::maxValue); + } else if (key == "unused_tile_normal") { + this->unusedTileNormal = getConfigUint32(key, value, 0, Tile::maxValue); + } else if (key == "unused_tile_covered") { + this->unusedTileCovered = getConfigUint32(key, value, 0, Tile::maxValue); + } else if (key == "unused_tile_split") { + this->unusedTileSplit = getConfigUint32(key, value, 0, Tile::maxValue); } else if (key == "enable_map_allow_flags") { this->mapAllowFlagsEnabled = getConfigBool(key, value); #ifdef CONFIG_BACKWARDS_COMPATABILITY - } else if (key == "recent_map") { - userConfig.recentMap = value; + } else if (key == "recent_map_or_layout") { + userConfig.recentMapOrLayout = value; } else if (key == "use_encounter_json") { userConfig.useEncounterJson = getConfigBool(key, value); } else if (key == "custom_scripts") { userConfig.parseCustomScripts(value); #endif } else if (key.startsWith("path/")) { - auto k = reverseDefaultPaths(key.mid(5)); + auto k = reverseDefaultPaths(key.mid(QStringLiteral("path/").length())); if (k != static_cast(-1)) { this->setFilePath(k, value); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } } else if (key.startsWith("ident/")) { - auto identifierId = reverseDefaultIdentifier(key.mid(6)); + auto identifierId = reverseDefaultIdentifier(key.mid(QStringLiteral("ident/").length())); if (identifierId != static_cast(-1)) { this->setIdentifier(identifierId, value); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } + } else if (key.startsWith("global_constant/")) { + this->globalConstants.insert(key.mid(QStringLiteral("global_constant/").length()), value); + } else if (key == "global_constants_filepaths") { + this->globalConstantsFilepaths = value.split(",", Qt::SkipEmptyParts); } else if (key == "prefabs_filepath") { this->prefabFilepath = value; } else if (key == "prefabs_import_prompted") { @@ -769,6 +878,10 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->tilesetsHaveCallback = getConfigBool(key, value); } else if (key == "tilesets_have_is_compressed") { this->tilesetsHaveIsCompressed = getConfigBool(key, value); + } else if (key == "set_transparent_pixels_black") { + this->setTransparentPixelsBlack = getConfigBool(key, value); + } else if (key == "preserve_matching_only_data") { + this->preserveMatchingOnlyData = getConfigBool(key, value); } else if (key == "event_icon_path_object") { this->eventIconPaths[Event::Group::Object] = value; } else if (key == "event_icon_path_warp") { @@ -780,19 +893,33 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "event_icon_path_heal") { this->eventIconPaths[Event::Group::Heal] = value; } else if (key.startsWith("pokemon_icon_path/")) { - this->pokemonIconPaths.insert(key.mid(18).toUpper(), value); + this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()), value); + } else if (key == "events_tab_icon_path") { + this->eventsTabIconPath = value; } else if (key == "collision_sheet_path") { this->collisionSheetPath = value; } else if (key == "collision_sheet_width") { - this->collisionSheetWidth = getConfigUint32(key, value, 1, Block::maxValue); + this->collisionSheetSize.setWidth(getConfigInteger(key, value, 1, Block::maxValue)); } else if (key == "collision_sheet_height") { - this->collisionSheetHeight = getConfigUint32(key, value, 1, Block::maxValue); + this->collisionSheetSize.setHeight(getConfigInteger(key, value, 1, Block::maxValue)); + } else if (key == "player_view_north") { + this->playerViewDistance.setTop(getConfigInteger(key, value, 0, INT_MAX, GBA_V_DIST_TO_CENTER)); + } else if (key == "player_view_south") { + this->playerViewDistance.setBottom(getConfigInteger(key, value, 0, INT_MAX, GBA_V_DIST_TO_CENTER)); + } else if (key == "player_view_west") { + this->playerViewDistance.setLeft(getConfigInteger(key, value, 0, INT_MAX, GBA_H_DIST_TO_CENTER)); + } else if (key == "player_view_east") { + this->playerViewDistance.setRight(getConfigInteger(key, value, 0, INT_MAX, GBA_H_DIST_TO_CENTER)); } else if (key == "warp_behaviors") { this->warpBehaviors.clear(); value.remove(" "); - QStringList behaviorList = value.split(",", Qt::SkipEmptyParts); + const QStringList behaviorList = value.split(",", Qt::SkipEmptyParts); for (auto s : behaviorList) - this->warpBehaviors.insert(getConfigUint32(key, s)); + this->warpBehaviors.append(getConfigUint32(key, s)); + } else if (key == "max_events_per_group") { + this->maxEventsPerGroup = getConfigInteger(key, value, 1, INT_MAX, 255); + } else if (key == "forced_major_version") { + this->forcedMajorVersion = getConfigInteger(key, value); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -818,7 +945,7 @@ void ProjectConfig::setUnreadKeys() { if (!readKeys.contains("enable_event_clone_object")) this->eventCloneObjectEnabled = isPokefirered; if (!readKeys.contains("enable_floor_number")) this->floorNumberEnabled = isPokefirered; if (!readKeys.contains("create_map_text_file")) this->createMapTextFileEnabled = (this->baseGameVersion != BaseGameVersion::pokeemerald); - if (!readKeys.contains("new_map_border_metatiles")) this->newMapBorderMetatileIds = isPokefirered ? DEFAULT_BORDER_FRLG : DEFAULT_BORDER_RSE; + if (!readKeys.contains("new_map_border_metatiles")) this->newMapBorderMetatileIds = isPokefirered ? defaultBorder_FRLG : defaultBorder_RSE; if (!readKeys.contains("default_secondary_tileset")) this->defaultSecondaryTileset = isPokefirered ? "gTileset_PalletTown" : "gTileset_Petalburg"; if (!readKeys.contains("metatile_attributes_size")) this->metatileAttributesSize = Metatile::getDefaultAttributesSize(this->baseGameVersion); if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = Metatile::getDefaultAttributesMask(this->baseGameVersion, Metatile::Attr::Behavior); @@ -846,6 +973,8 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("default_metatile", Metatile::getMetatileIdString(this->defaultMetatileId)); map.insert("default_elevation", QString::number(this->defaultElevation)); map.insert("default_collision", QString::number(this->defaultCollision)); + map.insert("default_map_width", QString::number(this->defaultMapSize.width())); + map.insert("default_map_height", QString::number(this->defaultMapSize.height())); map.insert("new_map_border_metatiles", Metatile::getMetatileIdStrings(this->newMapBorderMetatileIds)); map.insert("default_primary_tileset", this->defaultPrimaryTileset); map.insert("default_secondary_tileset", this->defaultSecondaryTileset); @@ -856,34 +985,50 @@ QMap ProjectConfig::getKeyValueMap() { } map.insert("tilesets_have_callback", QString::number(this->tilesetsHaveCallback)); map.insert("tilesets_have_is_compressed", QString::number(this->tilesetsHaveIsCompressed)); + map.insert("set_transparent_pixels_black", QString::number(this->setTransparentPixelsBlack)); + map.insert("preserve_matching_only_data", QString::number(this->preserveMatchingOnlyData)); map.insert("metatile_attributes_size", QString::number(this->metatileAttributesSize)); - map.insert("metatile_behavior_mask", "0x" + QString::number(this->metatileBehaviorMask, 16).toUpper()); - map.insert("metatile_terrain_type_mask", "0x" + QString::number(this->metatileTerrainTypeMask, 16).toUpper()); - map.insert("metatile_encounter_type_mask", "0x" + QString::number(this->metatileEncounterTypeMask, 16).toUpper()); - map.insert("metatile_layer_type_mask", "0x" + QString::number(this->metatileLayerTypeMask, 16).toUpper()); - map.insert("block_metatile_id_mask", "0x" + QString::number(this->blockMetatileIdMask, 16).toUpper()); - map.insert("block_collision_mask", "0x" + QString::number(this->blockCollisionMask, 16).toUpper()); - map.insert("block_elevation_mask", "0x" + QString::number(this->blockElevationMask, 16).toUpper()); + map.insert("metatile_behavior_mask", Util::toHexString(this->metatileBehaviorMask)); + map.insert("metatile_terrain_type_mask", Util::toHexString(this->metatileTerrainTypeMask)); + map.insert("metatile_encounter_type_mask", Util::toHexString(this->metatileEncounterTypeMask)); + map.insert("metatile_layer_type_mask", Util::toHexString(this->metatileLayerTypeMask)); + map.insert("block_metatile_id_mask", Util::toHexString(this->blockMetatileIdMask)); + map.insert("block_collision_mask", Util::toHexString(this->blockCollisionMask)); + map.insert("block_elevation_mask", Util::toHexString(this->blockElevationMask)); + map.insert("unused_tile_normal", Util::toHexString(this->unusedTileNormal)); + map.insert("unused_tile_covered", Util::toHexString(this->unusedTileCovered)); + map.insert("unused_tile_split", Util::toHexString(this->unusedTileSplit)); map.insert("enable_map_allow_flags", QString::number(this->mapAllowFlagsEnabled)); map.insert("event_icon_path_object", this->eventIconPaths[Event::Group::Object]); map.insert("event_icon_path_warp", this->eventIconPaths[Event::Group::Warp]); map.insert("event_icon_path_coord", this->eventIconPaths[Event::Group::Coord]); map.insert("event_icon_path_bg", this->eventIconPaths[Event::Group::Bg]); map.insert("event_icon_path_heal", this->eventIconPaths[Event::Group::Heal]); - for (auto i = this->pokemonIconPaths.cbegin(), end = this->pokemonIconPaths.cend(); i != end; i++){ - const QString path = i.value(); - if (!path.isEmpty()) map.insert("pokemon_icon_path/" + i.key(), path); + for (auto it = this->pokemonIconPaths.constBegin(); it != this->pokemonIconPaths.constEnd(); it++) { + const QString path = it.value(); + if (!path.isEmpty()) map.insert("pokemon_icon_path/" + it.key(), path); } - for (auto i = this->identifiers.cbegin(), end = this->identifiers.cend(); i != end; i++) { - map.insert("ident/"+defaultIdentifiers.value(i.key()).first, i.value()); + for (auto it = this->globalConstants.constBegin(); it != this->globalConstants.constEnd(); it++) { + map.insert("global_constant/" + it.key(), it.value()); } + map.insert("global_constants_filepaths", this->globalConstantsFilepaths.join(",")); + for (auto it = this->identifiers.constBegin(); it != this->identifiers.constEnd(); it++) { + map.insert("ident/"+defaultIdentifiers.value(it.key()).first, it.value()); + } + map.insert("events_tab_icon_path", this->eventsTabIconPath); map.insert("collision_sheet_path", this->collisionSheetPath); - map.insert("collision_sheet_width", QString::number(this->collisionSheetWidth)); - map.insert("collision_sheet_height", QString::number(this->collisionSheetHeight)); + map.insert("collision_sheet_width", QString::number(this->collisionSheetSize.width())); + map.insert("collision_sheet_height", QString::number(this->collisionSheetSize.height())); + map.insert("player_view_north", QString::number(this->playerViewDistance.top())); + map.insert("player_view_south", QString::number(this->playerViewDistance.bottom())); + map.insert("player_view_west", QString::number(this->playerViewDistance.left())); + map.insert("player_view_east", QString::number(this->playerViewDistance.right())); QStringList warpBehaviorStrs; - for (auto value : this->warpBehaviors) + for (const auto &value : this->warpBehaviors) warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper()); map.insert("warp_behaviors", warpBehaviorStrs.join(",")); + map.insert("max_events_per_group", QString::number(this->maxEventsPerGroup)); + map.insert("forced_major_version", QString::number(this->forcedMajorVersion)); return map; } @@ -937,7 +1082,7 @@ void ProjectConfig::setFilePath(const QString &pathId, const QString &path) { } QString ProjectConfig::getCustomFilePath(ProjectFilePath pathId) { - return this->filePaths.value(pathId); + return QDir::cleanPath(this->filePaths.value(pathId)); } QString ProjectConfig::getCustomFilePath(const QString &pathId) { @@ -945,27 +1090,40 @@ QString ProjectConfig::getCustomFilePath(const QString &pathId) { } QString ProjectConfig::getFilePath(ProjectFilePath pathId) { - const QString customPath = this->getCustomFilePath(pathId); + QString customPath = this->getCustomFilePath(pathId); if (!customPath.isEmpty()) { // A custom filepath has been specified. If the file/folder exists, use that. - const QString absCustomPath = this->projectDir + QDir::separator() + customPath; - if (QFileInfo::exists(absCustomPath)) { + const QString baseDir = this->projectDir + "/"; + if (customPath.startsWith(baseDir)) { + customPath.remove(0, baseDir.length()); + } + if (QFileInfo::exists(QDir::cleanPath(baseDir + customPath))) { return customPath; } else { - logError(QString("Custom project filepath '%1' not found. Using default.").arg(absCustomPath)); + logError(QString("Custom project filepath '%1' not found. Using default.").arg(customPath)); } } return defaultPaths.contains(pathId) ? defaultPaths[pathId].second : QString(); } -void ProjectConfig::setIdentifier(ProjectIdentifier id, const QString &text) { - if (!defaultIdentifiers.contains(id)) return; - QString copy(text); - if (copy.isEmpty()) { +void ProjectConfig::setIdentifier(ProjectIdentifier id, QString text) { + if (!defaultIdentifiers.contains(id)) + return; + + if (text.isEmpty()) { this->identifiers.remove(id); } else { - this->identifiers[id] = copy; + const QString idName = defaultIdentifiers.value(id).first; + if (idName.startsWith("define_") || idName.startsWith("symbol_")) { + // Validate the input for the identifier, depending on the type. + IdentifierValidator validator; + if (!validator.isValid(text)) { + logError(QString("The name '%1' for project identifier '%2' is invalid. It must only contain word characters, and cannot start with a digit.").arg(text).arg(idName)); + return; + } + } + this->identifiers[id] = text; } } @@ -1023,7 +1181,7 @@ QString ProjectConfig::getPokemonIconPath(const QString &species) { return this->pokemonIconPaths.value(species); } -QHash ProjectConfig::getPokemonIconPaths() { +QMap ProjectConfig::getPokemonIconPaths() { return this->pokemonIconPaths; } @@ -1035,8 +1193,8 @@ QString UserConfig::getConfigFilepath() { } void UserConfig::parseConfigKeyValue(QString key, QString value) { - if (key == "recent_map") { - this->recentMap = value; + if (key == "recent_map_or_layout") { + this->recentMapOrLayout = value; } else if (key == "use_encounter_json") { this->useEncounterJson = getConfigBool(key, value); } else if (key == "custom_scripts") { @@ -1052,7 +1210,7 @@ void UserConfig::setUnreadKeys() { QMap UserConfig::getKeyValueMap() { QMap map; - map.insert("recent_map", this->recentMap); + map.insert("recent_map_or_layout", this->recentMapOrLayout); map.insert("use_encounter_json", QString::number(this->useEncounterJson)); map.insert("custom_scripts", this->outputCustomScripts()); return map; diff --git a/src/core/advancemapparser.cpp b/src/core/advancemapparser.cpp new file mode 100644 index 00000000..6a8428ec --- /dev/null +++ b/src/core/advancemapparser.cpp @@ -0,0 +1,217 @@ +#include "advancemapparser.h" +#include "log.h" +#include "project.h" +#include "maplayout.h" + +Layout *AdvanceMapParser::parseLayout(const QString &filepath, bool *error, const Project *project) +{ + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly)) { + *error = true; + logError(QString("Could not open Advance Map 1.92 Map .map file '%1': ").arg(filepath) + file.errorString()); + return nullptr; + } + + QByteArray in = file.readAll(); + file.close(); + + if (in.length() < 20 || in.length() % 2 != 0) { + *error = true; + logError(QString("Advance Map 1.92 Map .map file '%1' is an unexpected size.").arg(filepath)); + return nullptr; + } + + int borderWidth = static_cast(in.at(16)); // 0 in RSE .map files + int borderHeight = static_cast(in.at(17)); // 0 in RSE .map files + int numBorderTiles = borderWidth * borderHeight; // 0 if RSE + + int mapDataOffset = 20 + (numBorderTiles * 2); // FRLG .map files store border metatile data after the header + int mapWidth = static_cast(in.at(0)) | + (static_cast(in.at(1)) << 8) | + (static_cast(in.at(2)) << 16) | + (static_cast(in.at(3)) << 24); + int mapHeight = static_cast(in.at(4)) | + (static_cast(in.at(5)) << 8) | + (static_cast(in.at(6)) << 16) | + (static_cast(in.at(7)) << 24); + int mapPrimaryTilesetNum = static_cast(in.at(8)) | + (static_cast(in.at(9)) << 8) | + (static_cast(in.at(10)) << 16) | + (static_cast(in.at(11)) << 24); + int mapSecondaryTilesetNum = static_cast(in.at(12)) | + (static_cast(in.at(13)) << 8) | + (static_cast(in.at(14)) << 16) | + (static_cast(in.at(15)) << 24); + + int numMetatiles = mapWidth * mapHeight; + int expectedFileSize = 20 + (numBorderTiles * 2) + (numMetatiles * 2); + if (in.length() != expectedFileSize) { + *error = true; + logError(QString(".map file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length())); + return nullptr; + } + + Blockdata blockdata; + for (int i = mapDataOffset; (i + 1) < in.length(); i += 2) { + uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); + blockdata.append(word); + } + + Blockdata border; + if (numBorderTiles != 0) { + for (int i = 20; (i + 1) < mapDataOffset; i += 2) { + uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); + border.append(word); + } + } + + Layout *mapLayout = new Layout(); + mapLayout->width = mapWidth; + mapLayout->height = mapHeight; + mapLayout->border_width = (borderWidth == 0) ? DEFAULT_BORDER_WIDTH : borderWidth; + mapLayout->border_height = (borderHeight == 0) ? DEFAULT_BORDER_HEIGHT : borderHeight; + + const QList tilesets = project->tilesetLabelsOrdered; + + if (mapPrimaryTilesetNum > tilesets.size()) + mapLayout->tileset_primary_label = project->getDefaultPrimaryTilesetLabel(); + else + mapLayout->tileset_primary_label = tilesets.at(mapPrimaryTilesetNum); + + if (mapSecondaryTilesetNum > tilesets.size()) + mapLayout->tileset_secondary_label = project->getDefaultSecondaryTilesetLabel(); + else + mapLayout->tileset_secondary_label = tilesets.at(mapSecondaryTilesetNum); + + mapLayout->blockdata = blockdata; + + if (!border.isEmpty()) { + mapLayout->border = border; + } + + return mapLayout; +} + +QList AdvanceMapParser::parseMetatiles(const QString &filepath, bool *error, bool primaryTileset) +{ + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly)) { + *error = true; + logError(QString("Could not open Advance Map 1.92 Metatile .bvd file '%1': ").arg(filepath) + file.errorString()); + return { }; + } + + QByteArray in = file.readAll(); + file.close(); + + if (in.length() < 9 || in.length() % 2 != 0) { + *error = true; + logError(QString("Advance Map 1.92 Metatile .bvd file '%1' is an unexpected size.").arg(filepath)); + return { }; + } + + int projIdOffset = in.length() - 4; + int metatileSize = 16; + BaseGameVersion version; + if (in.at(projIdOffset + 0) == 'R' + && in.at(projIdOffset + 1) == 'S' + && in.at(projIdOffset + 2) == 'E' + && in.at(projIdOffset + 3) == ' ') { + // ruby and emerald are handled equally here. + version = BaseGameVersion::pokeemerald; + } else if (in.at(projIdOffset + 0) == 'F' + && in.at(projIdOffset + 1) == 'R' + && in.at(projIdOffset + 2) == 'L' + && in.at(projIdOffset + 3) == 'G') { + version = BaseGameVersion::pokefirered; + } else { + *error = true; + logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE ' or 'FRLG'.")); + return { }; + } + + int attrSize = Metatile::getDefaultAttributesSize(version); + int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary(); + int numMetatiles = static_cast(in.at(0)) | + (static_cast(in.at(1)) << 8) | + (static_cast(in.at(2)) << 16) | + (static_cast(in.at(3)) << 24); + if (numMetatiles > maxMetatiles) { + *error = true; + logError(QString(".bvd file contains data for %1 metatiles, but the maximum number of metatiles is %2.").arg(numMetatiles).arg(maxMetatiles)); + return { }; + } + if (numMetatiles < 1) { + *error = true; + logError(QString(".bvd file contains no data for metatiles.")); + return { }; + } + + int expectedFileSize = 4 + (metatileSize * numMetatiles) + (attrSize * numMetatiles) + 4; + if (in.length() != expectedFileSize) { + *error = true; + logError(QString(".bvd file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length())); + return { }; + } + + QList metatiles; + for (int i = 0; i < numMetatiles; i++) { + Metatile *metatile = new Metatile(); + QList tiles; + for (int j = 0; j < 8; j++) { + int metatileOffset = 4 + i * metatileSize + j * 2; + Tile tile(static_cast( + static_cast(in.at(metatileOffset)) | + (static_cast(in.at(metatileOffset + 1)) << 8))); + tiles.append(tile); + } + + // AdvanceMap .bvd files only contain 8 tiles of data per metatile. + // If the user has triple-layer metatiles enabled we need to fill the remaining 4 tiles ourselves. + if (projectConfig.tripleLayerMetatilesEnabled) { + Tile tile = Tile(); + for (int j = 0; j < 4; j++) + tiles.append(tile); + } + + int attrOffset = 4 + (numMetatiles * metatileSize) + (i * attrSize); + uint32_t attributes = 0; + for (int j = 0; j < attrSize; j++) + attributes |= static_cast(in.at(attrOffset + j)) << (8 * j); + metatile->setAttributes(attributes, version); + metatile->tiles = tiles; + metatiles.append(metatile); + } + + return metatiles; +} + +QList AdvanceMapParser::parsePalette(const QString &filepath, bool *error) { + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly)) { + *error = true; + logError(QString("Could not open Advance Map 1.92 palette file '%1': ").arg(filepath) + file.errorString()); + return QList(); + } + + QByteArray in = file.readAll(); + file.close(); + + if (in.length() % 4 != 0) { + *error = true; + logError(QString("Advance Map 1.92 palette file '%1' had an unexpected format. File's length must be a multiple of 4, but the length is %2.").arg(filepath).arg(in.length())); + return QList(); + } + + QList palette; + int i = 0; + while (i < in.length()) { + unsigned char red = static_cast(in.at(i + 0)); + unsigned char green = static_cast(in.at(i + 1)); + unsigned char blue = static_cast(in.at(i + 2)); + palette.append(qRgb(red, green, blue)); + i += 4; + } + + return palette; +} diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index f557dddd..684d98c7 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -1,12 +1,11 @@ #include "editcommands.h" -#include "mappixmapitem.h" -#include "draggablepixmapitem.h" +#include "eventpixmapitem.h" #include "bordermetatilespixmapitem.h" #include "editor.h" #include -int getEventTypeMask(QList events) { +int getEventTypeMask(const QList &events) { int eventTypeMask = 0; for (auto event : events) { Event::Group groupType = event->getEventGroup(); @@ -25,17 +24,37 @@ int getEventTypeMask(QList events) { return eventTypeMask; } -void renderMapBlocks(Map *map, bool ignoreCache = false) { - map->mapItem->draw(ignoreCache); - map->collisionItem->draw(ignoreCache); +int getConnectionDirectionMask(const QList &directions) { + int mask = 0; + for (auto direction : directions) { + if (direction == "up") { + mask |= IDMask_ConnectionDirection_Up; + } else if (direction == "down") { + mask |= IDMask_ConnectionDirection_Down; + } else if (direction == "left") { + mask |= IDMask_ConnectionDirection_Left; + } else if (direction == "right") { + mask |= IDMask_ConnectionDirection_Right; + } else if (direction == "dive") { + mask |= IDMask_ConnectionDirection_Dive; + } else if (direction == "emerge") { + mask |= IDMask_ConnectionDirection_Emerge; + } + } + return mask; } -PaintMetatile::PaintMetatile(Map *map, +void renderBlocks(Layout *layout, bool ignoreCache = false) { + layout->layoutItem->draw(ignoreCache); + layout->collisionItem->draw(ignoreCache); +} + +PaintMetatile::PaintMetatile(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent) : QUndoCommand(parent) { setText("Paint Metatiles"); - this->map = map; + this->layout = layout; this->oldMetatiles = oldMetatiles; this->newMetatiles = newMetatiles; @@ -45,23 +64,23 @@ PaintMetatile::PaintMetatile(Map *map, void PaintMetatile::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - map->setBlockdata(newMetatiles, true); + layout->setBlockdata(newMetatiles, true); - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; + layout->lastCommitBlocks.blocks = layout->blockdata; - renderMapBlocks(map); + renderBlocks(layout); } void PaintMetatile::undo() { - if (!map) return; + if (!layout) return; - map->setBlockdata(oldMetatiles, true); + layout->setBlockdata(oldMetatiles, true); - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; + layout->lastCommitBlocks.blocks = layout->blockdata; - renderMapBlocks(map); + renderBlocks(layout); QUndoCommand::undo(); } @@ -69,7 +88,7 @@ void PaintMetatile::undo() { bool PaintMetatile::mergeWith(const QUndoCommand *command) { const PaintMetatile *other = static_cast(command); - if (map != other->map) + if (layout != other->layout) return false; if (actionId != other->actionId) @@ -84,12 +103,12 @@ bool PaintMetatile::mergeWith(const QUndoCommand *command) { ************************************************************************ ******************************************************************************/ -PaintBorder::PaintBorder(Map *map, +PaintBorder::PaintBorder(Layout *layout, const Blockdata &oldBorder, const Blockdata &newBorder, unsigned actionId, QUndoCommand *parent) : QUndoCommand(parent) { setText("Paint Border"); - this->map = map; + this->layout = layout; this->oldBorder = oldBorder; this->newBorder = newBorder; @@ -99,23 +118,23 @@ PaintBorder::PaintBorder(Map *map, void PaintBorder::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - map->setBorderBlockData(newBorder, true); + layout->setBorderBlockData(newBorder, true); - map->layout->lastCommitBlocks.border = map->layout->border; + layout->lastCommitBlocks.border = layout->border; - map->borderItem->draw(); + layout->borderItem->draw(); } void PaintBorder::undo() { - if (!map) return; + if (!layout) return; - map->setBorderBlockData(oldBorder, true); + layout->setBorderBlockData(oldBorder, true); - map->layout->lastCommitBlocks.border = map->layout->border; + layout->lastCommitBlocks.border = layout->border; - map->borderItem->draw(); + layout->borderItem->draw(); QUndoCommand::undo(); } @@ -124,12 +143,12 @@ void PaintBorder::undo() { ************************************************************************ ******************************************************************************/ -ShiftMetatiles::ShiftMetatiles(Map *map, +ShiftMetatiles::ShiftMetatiles(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent) : QUndoCommand(parent) { setText("Shift Metatiles"); - this->map = map; + this->layout = layout; this->oldMetatiles = oldMetatiles; this->newMetatiles = newMetatiles; @@ -139,23 +158,23 @@ ShiftMetatiles::ShiftMetatiles(Map *map, void ShiftMetatiles::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - map->setBlockdata(newMetatiles, true); + layout->setBlockdata(newMetatiles, true); - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; + layout->lastCommitBlocks.blocks = layout->blockdata; - renderMapBlocks(map, true); + renderBlocks(layout, true); } void ShiftMetatiles::undo() { - if (!map) return; + if (!layout) return; - map->setBlockdata(oldMetatiles, true); + layout->setBlockdata(oldMetatiles, true); - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; + layout->lastCommitBlocks.blocks = layout->blockdata; - renderMapBlocks(map, true); + renderBlocks(layout, true); QUndoCommand::undo(); } @@ -163,7 +182,7 @@ void ShiftMetatiles::undo() { bool ShiftMetatiles::mergeWith(const QUndoCommand *command) { const ShiftMetatiles *other = static_cast(command); - if (this->map != other->map) + if (this->layout != other->layout) return false; if (actionId != other->actionId) @@ -178,20 +197,19 @@ bool ShiftMetatiles::mergeWith(const QUndoCommand *command) { ************************************************************************ ******************************************************************************/ -ResizeMap::ResizeMap(Map *map, QSize oldMapDimensions, QSize newMapDimensions, +ResizeLayout::ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QMargins newLayoutMargins, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, QUndoCommand *parent) : QUndoCommand(parent) { setText("Resize Map"); - this->map = map; + this->layout = layout; - this->oldMapWidth = oldMapDimensions.width(); - this->oldMapHeight = oldMapDimensions.height(); + this->oldLayoutWidth = oldLayoutDimensions.width(); + this->oldLayoutHeight = oldLayoutDimensions.height(); - this->newMapWidth = newMapDimensions.width(); - this->newMapHeight = newMapDimensions.height(); + this->newLayoutMargins = newLayoutMargins; this->oldMetatiles = oldMetatiles; this->newMetatiles = newMetatiles; @@ -206,36 +224,40 @@ ResizeMap::ResizeMap(Map *map, QSize oldMapDimensions, QSize newMapDimensions, this->newBorder = newBorder; } -void ResizeMap::redo() { +void ResizeLayout::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - map->layout->blockdata = newMetatiles; - map->setDimensions(newMapWidth, newMapHeight, false, true); + layout->border = newBorder; + layout->setBorderDimensions(newBorderWidth, newBorderHeight, false, true); - map->layout->border = newBorder; - map->setBorderDimensions(newBorderWidth, newBorderHeight, false, true); + layout->width = oldLayoutWidth; + layout->height = oldLayoutHeight; + layout->adjustDimensions(this->newLayoutMargins); + layout->blockdata = newMetatiles; - map->layout->lastCommitBlocks.mapDimensions = QSize(map->getWidth(), map->getHeight()); - map->layout->lastCommitBlocks.borderDimensions = QSize(map->getBorderWidth(), map->getBorderHeight()); + layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); + layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); - map->mapNeedsRedrawing(); + emit layout->needsRedrawing(); } -void ResizeMap::undo() { - if (!map) return; +void ResizeLayout::undo() { + if (!layout) return; - map->layout->blockdata = oldMetatiles; - map->setDimensions(oldMapWidth, oldMapHeight, false, true); + layout->border = oldBorder; + layout->setBorderDimensions(oldBorderWidth, oldBorderHeight, false, true); - map->layout->border = oldBorder; - map->setBorderDimensions(oldBorderWidth, oldBorderHeight, false, true); + layout->width = oldLayoutWidth + newLayoutMargins.left() + newLayoutMargins.right(); + layout->height = oldLayoutHeight + newLayoutMargins.top() + newLayoutMargins.bottom(); + layout->adjustDimensions(-this->newLayoutMargins); + layout->blockdata = oldMetatiles; - map->layout->lastCommitBlocks.mapDimensions = QSize(map->getWidth(), map->getHeight()); - map->layout->lastCommitBlocks.borderDimensions = QSize(map->getBorderWidth(), map->getBorderHeight()); + layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); + layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); - map->mapNeedsRedrawing(); + emit layout->needsRedrawing(); QUndoCommand::undo(); } @@ -325,23 +347,13 @@ void EventCreate::redo() { QUndoCommand::redo(); map->addEvent(event); - - editor->project->setEventPixmap(event); - editor->addMapEvent(event); - - // select this event - editor->selected_events->clear(); - editor->selectMapEvent(event->getPixmapItem()); + editor->addEventPixmapItem(event); + editor->selectMapEvent(event); } void EventCreate::undo() { map->removeEvent(event); - - if (editor->scene->items().contains(event->getPixmapItem())) { - editor->scene->removeItem(event->getPixmapItem()); - } - editor->selected_events->removeOne(event->getPixmapItem()); - + editor->removeEventPixmapItem(event); editor->shouldReselectEvents(); QUndoCommand::undo(); @@ -376,31 +388,22 @@ void EventDelete::redo() { for (Event *event : selectedEvents) { map->removeEvent(event); - - if (editor->scene->items().contains(event->getPixmapItem())) { - editor->scene->removeItem(event->getPixmapItem()); - } - editor->selected_events->removeOne(event->getPixmapItem()); + editor->removeEventPixmapItem(event); } - editor->selected_events->clear(); + editor->selectedEvents.clear(); if (nextSelectedEvent) - editor->selected_events->append(nextSelectedEvent->getPixmapItem()); + editor->selectedEvents.append(nextSelectedEvent); editor->shouldReselectEvents(); } void EventDelete::undo() { for (Event *event : selectedEvents) { map->addEvent(event); - editor->project->setEventPixmap(event); - editor->addMapEvent(event); + editor->addEventPixmapItem(event); } - // select these events - editor->selected_events->clear(); - for (Event *event : selectedEvents) { - editor->selected_events->append(event->getPixmapItem()); - } + editor->selectedEvents = selectedEvents; editor->shouldReselectEvents(); QUndoCommand::undo(); @@ -434,26 +437,17 @@ void EventDuplicate::redo() { for (Event *event : selectedEvents) { map->addEvent(event); - editor->project->setEventPixmap(event); - editor->addMapEvent(event); + editor->addEventPixmapItem(event); } - // select these events - editor->selected_events->clear(); - for (Event *event : selectedEvents) { - editor->selected_events->append(event->getPixmapItem()); - } + editor->selectedEvents = selectedEvents; editor->shouldReselectEvents(); } void EventDuplicate::undo() { for (Event *event : selectedEvents) { map->removeEvent(event); - - if (editor->scene->items().contains(event->getPixmapItem())) { - editor->scene->removeItem(event->getPixmapItem()); - } - editor->selected_events->removeOne(event->getPixmapItem()); + editor->removeEventPixmapItem(event); } editor->shouldReselectEvents(); @@ -487,23 +481,23 @@ int EventPaste::id() const { ************************************************************************ ******************************************************************************/ -ScriptEditMap::ScriptEditMap(Map *map, - QSize oldMapDimensions, QSize newMapDimensions, +ScriptEditLayout::ScriptEditLayout(Layout *layout, + QSize oldLayoutDimensions, QSize newLayoutDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, QUndoCommand *parent) : QUndoCommand(parent) { - setText("Script Edit Map"); + setText("Script Edit Layout"); - this->map = map; + this->layout = layout; this->newMetatiles = newMetatiles; this->oldMetatiles = oldMetatiles; - this->oldMapWidth = oldMapDimensions.width(); - this->oldMapHeight = oldMapDimensions.height(); - this->newMapWidth = newMapDimensions.width(); - this->newMapHeight = newMapDimensions.height(); + this->oldLayoutWidth = oldLayoutDimensions.width(); + this->oldLayoutHeight = oldLayoutDimensions.height(); + this->newLayoutWidth = newLayoutDimensions.width(); + this->newLayoutHeight = newLayoutDimensions.height(); this->oldBorder = oldBorder; this->newBorder = newBorder; @@ -514,58 +508,58 @@ ScriptEditMap::ScriptEditMap(Map *map, this->newBorderHeight = newBorderDimensions.height(); } -void ScriptEditMap::redo() { +void ScriptEditLayout::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - if (newMapWidth != map->getWidth() || newMapHeight != map->getHeight()) { - map->layout->blockdata = newMetatiles; - map->setDimensions(newMapWidth, newMapHeight, false); + if (newLayoutWidth != layout->getWidth() || newLayoutHeight != layout->getHeight()) { + layout->blockdata = newMetatiles; + layout->setDimensions(newLayoutWidth, newLayoutHeight, false); } else { - map->setBlockdata(newMetatiles); + layout->setBlockdata(newMetatiles); } - if (newBorderWidth != map->getBorderWidth() || newBorderHeight != map->getBorderHeight()) { - map->layout->border = newBorder; - map->setBorderDimensions(newBorderWidth, newBorderHeight, false); + if (newBorderWidth != layout->getBorderWidth() || newBorderHeight != layout->getBorderHeight()) { + layout->border = newBorder; + layout->setBorderDimensions(newBorderWidth, newBorderHeight, false); } else { - map->setBorderBlockData(newBorder); + layout->setBorderBlockData(newBorder); } - map->layout->lastCommitBlocks.blocks = newMetatiles; - map->layout->lastCommitBlocks.mapDimensions = QSize(newMapWidth, newMapHeight); - map->layout->lastCommitBlocks.border = newBorder; - map->layout->lastCommitBlocks.borderDimensions = QSize(newBorderWidth, newBorderHeight); + layout->lastCommitBlocks.blocks = newMetatiles; + layout->lastCommitBlocks.layoutDimensions = QSize(newLayoutWidth, newLayoutHeight); + layout->lastCommitBlocks.border = newBorder; + layout->lastCommitBlocks.borderDimensions = QSize(newBorderWidth, newBorderHeight); - renderMapBlocks(map); - map->borderItem->draw(); + renderBlocks(layout, true); + layout->borderItem->draw(); } -void ScriptEditMap::undo() { - if (!map) return; +void ScriptEditLayout::undo() { + if (!layout) return; - if (oldMapWidth != map->getWidth() || oldMapHeight != map->getHeight()) { - map->layout->blockdata = oldMetatiles; - map->setDimensions(oldMapWidth, oldMapHeight, false); + if (oldLayoutWidth != layout->getWidth() || oldLayoutHeight != layout->getHeight()) { + layout->blockdata = oldMetatiles; + layout->setDimensions(oldLayoutWidth, oldLayoutHeight, false); } else { - map->setBlockdata(oldMetatiles); + layout->setBlockdata(oldMetatiles); } - if (oldBorderWidth != map->getBorderWidth() || oldBorderHeight != map->getBorderHeight()) { - map->layout->border = oldBorder; - map->setBorderDimensions(oldBorderWidth, oldBorderHeight, false); + if (oldBorderWidth != layout->getBorderWidth() || oldBorderHeight != layout->getBorderHeight()) { + layout->border = oldBorder; + layout->setBorderDimensions(oldBorderWidth, oldBorderHeight, false); } else { - map->setBorderBlockData(oldBorder); + layout->setBorderBlockData(oldBorder); } - map->layout->lastCommitBlocks.blocks = oldMetatiles; - map->layout->lastCommitBlocks.mapDimensions = QSize(oldMapWidth, oldMapHeight); - map->layout->lastCommitBlocks.border = oldBorder; - map->layout->lastCommitBlocks.borderDimensions = QSize(oldBorderWidth, oldBorderHeight); + layout->lastCommitBlocks.blocks = oldMetatiles; + layout->lastCommitBlocks.layoutDimensions = QSize(oldLayoutWidth, oldLayoutHeight); + layout->lastCommitBlocks.border = oldBorder; + layout->lastCommitBlocks.borderDimensions = QSize(oldBorderWidth, oldBorderHeight); - renderMapBlocks(map); - map->borderItem->draw(); + renderBlocks(layout, true); + layout->borderItem->draw(); QUndoCommand::undo(); } @@ -613,6 +607,10 @@ bool MapConnectionMove::mergeWith(const QUndoCommand *command) { return true; } +int MapConnectionMove::id() const { + return CommandId::ID_MapConnectionMove | getConnectionDirectionMask({this->connection->direction()}); +} + /****************************************************************************** ************************************************************************ ******************************************************************************/ @@ -655,6 +653,10 @@ void MapConnectionChangeDirection::undo() { QUndoCommand::undo(); } +int MapConnectionChangeDirection::id() const { + return CommandId::ID_MapConnectionChangeDirection | getConnectionDirectionMask({this->oldDirection, this->newDirection}); +} + /****************************************************************************** ************************************************************************ ******************************************************************************/ @@ -690,6 +692,10 @@ void MapConnectionChangeMap::undo() { QUndoCommand::undo(); } +int MapConnectionChangeMap::id() const { + return CommandId::ID_MapConnectionChangeMap | getConnectionDirectionMask({this->connection->direction()}); +} + /****************************************************************************** ************************************************************************ ******************************************************************************/ @@ -734,6 +740,10 @@ void MapConnectionAdd::undo() { QUndoCommand::undo(); } +int MapConnectionAdd::id() const { + return CommandId::ID_MapConnectionAdd | getConnectionDirectionMask({this->connection->direction()}); +} + /****************************************************************************** ************************************************************************ ******************************************************************************/ @@ -771,3 +781,7 @@ void MapConnectionRemove::undo() { QUndoCommand::undo(); } + +int MapConnectionRemove::id() const { + return CommandId::ID_MapConnectionRemove | getConnectionDirectionMask({this->connection->direction()}); +} diff --git a/src/core/events.cpp b/src/core/events.cpp index 89416eae..ce376c44 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -4,8 +4,6 @@ #include "project.h" #include "config.h" -QMap Event::icons; - Event* Event::create(Event::Type type) { switch (type) { case Event::Type::Object: return new ObjectEvent(); @@ -22,8 +20,7 @@ Event* Event::create(Event::Type type) { } Event::~Event() { - if (this->eventFrame) - this->eventFrame->deleteLater(); + delete this->eventFrame; } EventFrame *Event::getEventFrame() { @@ -36,7 +33,7 @@ void Event::destroyEventFrame() { this->eventFrame = nullptr; } -void Event::setPixmapItem(DraggablePixmapItem *item) { +void Event::setPixmapItem(EventPixmapItem *item) { this->pixmapItem = item; if (this->eventFrame) { this->eventFrame->invalidateConnections(); @@ -44,7 +41,7 @@ void Event::setPixmapItem(DraggablePixmapItem *item) { } int Event::getEventIndex() { - return this->map->events.value(this->getEventGroup()).indexOf(this); + return this->map->getIndexOfEvent(this); } void Event::setDefaultValues(Project *) { @@ -53,142 +50,84 @@ void Event::setDefaultValues(Project *) { this->setElevation(projectConfig.defaultElevation); } -void Event::readCustomValues(QJsonObject values) { - this->customValues.clear(); - QSet expectedFields = this->getExpectedFields(); - for (QString key : values.keys()) { - if (!expectedFields.contains(key)) { - this->customValues[key] = values[key]; - } - } -} - -void Event::addCustomValuesTo(OrderedJson::object *obj) { - for (QString key : this->customValues.keys()) { - if (!obj->contains(key)) { - (*obj)[key] = OrderedJson::fromQJsonValue(this->customValues[key]); - } - } -} - void Event::modify() { this->map->modify(); } -QString Event::eventGroupToString(Event::Group group) { - switch (group) { - case Event::Group::Object: - return "Object"; - case Event::Group::Warp: - return "Warp"; - case Event::Group::Coord: - return "Trigger"; - case Event::Group::Bg: - return "BG"; - case Event::Group::Heal: - return "Healspot"; - default: - return ""; - } +const QMap groupToStringMap = { + {Event::Group::Object, "Object"}, + {Event::Group::Warp, "Warp"}, + {Event::Group::Coord, "Trigger"}, + {Event::Group::Bg, "BG"}, + {Event::Group::Heal, "Heal Location"}, +}; + +QString Event::groupToString(Event::Group group) { + return groupToStringMap.value(group); } -QString Event::eventTypeToString(Event::Type type) { - switch (type) { - case Event::Type::Object: - return "event_object"; - case Event::Type::CloneObject: - return "event_clone_object"; - case Event::Type::Warp: - return "event_warp"; - case Event::Type::Trigger: - return "event_trigger"; - case Event::Type::WeatherTrigger: - return "event_weather_trigger"; - case Event::Type::Sign: - return "event_sign"; - case Event::Type::HiddenItem: - return "event_hidden_item"; - case Event::Type::SecretBase: - return "event_secret_base"; - case Event::Type::HealLocation: - return "event_healspot"; - default: - return ""; - } +QList Event::groups() { + static QList groupList = groupToStringMap.keys(); + return groupList; } -Event::Type Event::eventTypeFromString(QString type) { - if (type == "event_object") { - return Event::Type::Object; - } else if (type == "event_clone_object") { - return Event::Type::CloneObject; - } else if (type == "event_warp") { - return Event::Type::Warp; - } else if (type == "event_trigger") { - return Event::Type::Trigger; - } else if (type == "event_weather_trigger") { - return Event::Type::WeatherTrigger; - } else if (type == "event_sign") { - return Event::Type::Sign; - } else if (type == "event_hidden_item") { - return Event::Type::HiddenItem; - } else if (type == "event_secret_base") { - return Event::Type::SecretBase; - } else if (type == "event_healspot") { - return Event::Type::HealLocation; - } else { - return Event::Type::None; - } +// These are the expected key names used in the map.json files. +// We re-use them for key names in the copy/paste JSON data, +const QMap typeToJsonKeyMap = { + {Event::Type::Object, "object"}, + {Event::Type::CloneObject, "clone"}, + {Event::Type::Warp, "warp"}, + {Event::Type::Trigger, "trigger"}, + {Event::Type::WeatherTrigger, "weather"}, + {Event::Type::Sign, "sign"}, + {Event::Type::HiddenItem, "hidden_item"}, + {Event::Type::SecretBase, "secret_base"}, + {Event::Type::HealLocation, "heal_location"}, +}; + +QString Event::typeToJsonKey(Event::Type type) { + return typeToJsonKeyMap.value(type); } -void Event::loadPixmap(Project *) { - const QPixmap * pixmap = Event::icons.value(this->getEventGroup()); - this->pixmap = pixmap ? *pixmap : QPixmap(); +Event::Type Event::typeFromJsonKey(QString type) { + return typeToJsonKeyMap.key(type, Event::Type::None); } -void Event::clearIcons() { - qDeleteAll(icons); - icons.clear(); +QList Event::types() { + static QList typeList = typeToJsonKeyMap.keys(); + return typeList; } -void Event::setIcons() { - clearIcons(); - const int w = 16; - const int h = 16; - static const QPixmap defaultIcons = QPixmap(":/images/Entities_16x16.png"); +QString Event::typeToString(Event::Type type) { + static const QMap typeToStringMap = { + {Event::Type::Object, "Object"}, + {Event::Type::CloneObject, "Clone Object"}, + {Event::Type::Warp, "Warp"}, + {Event::Type::Trigger, "Trigger"}, + {Event::Type::WeatherTrigger, "Weather Trigger"}, + {Event::Type::Sign, "Sign"}, + {Event::Type::HiddenItem, "Hidden Item"}, + {Event::Type::SecretBase, "Secret Base"}, + {Event::Type::HealLocation, "Heal Location"}, + }; + return typeToStringMap.value(type); +} - // Custom event icons may be provided by the user. - const int numIcons = qMin(defaultIcons.width() / w, static_cast(Event::Group::None)); - for (int i = 0; i < numIcons; i++) { - Event::Group group = static_cast(i); - QString customIconPath = projectConfig.getEventIconPath(group); - if (customIconPath.isEmpty()) { - // No custom icon specified, use the default icon. - icons[group] = new QPixmap(defaultIcons.copy(i * w, 0, w, h)); - continue; - } - - // Try to load custom icon - QString validPath = Project::getExistingFilepath(customIconPath); - if (!validPath.isEmpty()) customIconPath = validPath; // Otherwise allow it to fail with the original path - const QPixmap customIcon = QPixmap(customIconPath); - if (customIcon.isNull()) { - // Custom icon failed to load, use the default icon. - icons[group] = new QPixmap(defaultIcons.copy(i * w, 0, w, h)); - logWarn(QString("Failed to load custom event icon '%1', using default icon.").arg(customIconPath)); - } else { - icons[group] = new QPixmap(customIcon.scaled(w, h)); - } - } +QPixmap Event::loadPixmap(Project *project) { + this->pixmap = project ? project->getEventPixmap(this->getEventGroup()) : QPixmap(); + this->usesDefaultPixmap = true; + return this->pixmap; } -Event *ObjectEvent::duplicate() { + +Event *ObjectEvent::duplicate() const { ObjectEvent *copy = new ObjectEvent(); copy->setX(this->getX()); copy->setY(this->getY()); copy->setElevation(this->getElevation()); + copy->setIdName(this->getIdName()); copy->setGfx(this->getGfx()); copy->setMovement(this->getMovement()); copy->setRadiusX(this->getRadiusX()); @@ -197,7 +136,7 @@ Event *ObjectEvent::duplicate() { copy->setSightRadiusBerryTreeID(this->getSightRadiusBerryTreeID()); copy->setScript(this->getScript()); copy->setFlag(this->getFlag()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -213,8 +152,12 @@ EventFrame *ObjectEvent::createEventFrame() { OrderedJson::object ObjectEvent::buildEventJson(Project *) { OrderedJson::object objectJson; + QString idName = this->getIdName(); + if (!idName.isEmpty()) + objectJson["local_id"] = idName; + if (projectConfig.eventCloneObjectEnabled) { - objectJson["type"] = "object"; + objectJson["type"] = Event::typeToJsonKey(Event::Type::Object); } objectJson["graphics_id"] = this->getGfx(); objectJson["x"] = this->getX(); @@ -227,31 +170,31 @@ OrderedJson::object ObjectEvent::buildEventJson(Project *) { objectJson["trainer_sight_or_berry_tree_id"] = this->getSightRadiusBerryTreeID(); objectJson["script"] = this->getScript(); objectJson["flag"] = this->getFlag(); - this->addCustomValuesTo(&objectJson); + OrderedJson::append(&objectJson, this->getCustomAttributes()); return objectJson; } bool ObjectEvent::loadFromJson(QJsonObject json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); - this->setMovement(ParseUtil::jsonToQString(json["movement_type"])); - this->setRadiusX(ParseUtil::jsonToInt(json["movement_range_x"])); - this->setRadiusY(ParseUtil::jsonToInt(json["movement_range_y"])); - this->setTrainerType(ParseUtil::jsonToQString(json["trainer_type"])); - this->setSightRadiusBerryTreeID(ParseUtil::jsonToQString(json["trainer_sight_or_berry_tree_id"])); - this->setScript(ParseUtil::jsonToQString(json["script"])); - this->setFlag(ParseUtil::jsonToQString(json["flag"])); + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setIdName(readString(&json, "local_id")); + this->setGfx(readString(&json, "graphics_id")); + this->setMovement(readString(&json, "movement_type")); + this->setRadiusX(readInt(&json, "movement_range_x")); + this->setRadiusY(readInt(&json, "movement_range_y")); + this->setTrainerType(readString(&json, "trainer_type")); + this->setSightRadiusBerryTreeID(readString(&json, "trainer_sight_or_berry_tree_id")); + this->setScript(readString(&json, "script")); + this->setFlag(readString(&json, "flag")); - this->readCustomValues(json); - + this->setCustomAttributes(json); return true; } void ObjectEvent::setDefaultValues(Project *project) { - this->setGfx(project->gfxDefines.keys().value(0, "0")); + this->setGfx(project->gfxDefines.key(0, "0")); this->setMovement(project->movementTypes.value(0, "0")); this->setScript("NULL"); this->setTrainerType(project->trainerTypes.value(0, "0")); @@ -259,119 +202,51 @@ void ObjectEvent::setDefaultValues(Project *project) { this->setRadiusX(0); this->setRadiusY(0); this->setSightRadiusBerryTreeID("0"); - this->setFrameFromMovement(project->facingDirections.value(this->getMovement())); } -const QSet expectedObjectFields = { - "graphics_id", - "elevation", - "movement_type", - "movement_range_x", - "movement_range_y", - "trainer_type", - "trainer_sight_or_berry_tree_id", - "script", - "flag", -}; - QSet ObjectEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedObjectFields; + QSet expectedFields = { + "x", + "y", + "local_id", + "graphics_id", + "elevation", + "movement_type", + "movement_range_x", + "movement_range_y", + "trainer_type", + "trainer_sight_or_berry_tree_id", + "script", + "flag", + }; if (projectConfig.eventCloneObjectEnabled) { expectedFields.insert("type"); } - expectedFields << "x" << "y"; return expectedFields; } -void ObjectEvent::loadPixmap(Project *project) { - EventGraphics *eventGfx = project->eventGraphicsMap.value(this->gfx, nullptr); - if (!eventGfx) { - // Invalid gfx constant. - // If this is a number, try to use that instead. - bool ok; - int altGfx = ParseUtil::gameStringToInt(this->gfx, &ok); - if (ok && (altGfx < project->gfxDefines.count())) { - eventGfx = project->eventGraphicsMap.value(project->gfxDefines.key(altGfx, "NULL"), nullptr); - } - } - if (!eventGfx || eventGfx->spritesheet.isNull()) { - // No sprite associated with this gfx constant. - // Use default sprite instead. - Event::loadPixmap(project); - this->spriteWidth = 16; - this->spriteHeight = 16; - this->usingSprite = false; - } else { - this->setFrameFromMovement(project->facingDirections.value(this->movement)); - this->setPixmapFromSpritesheet(eventGfx); - } -} - -void ObjectEvent::setPixmapFromSpritesheet(EventGraphics * gfx) -{ - QImage img; - if (gfx->inanimate) { - img = gfx->spritesheet.copy(0, 0, gfx->spriteWidth, gfx->spriteHeight); - } else { - int x = 0; - int y = 0; - - // Get frame's position in spritesheet. - // Assume horizontal layout. If position would exceed sheet width, try vertical layout. - if ((this->frame + 1) * gfx->spriteWidth <= gfx->spritesheet.width()) { - x = this->frame * gfx->spriteWidth; - } else if ((this->frame + 1) * gfx->spriteHeight <= gfx->spritesheet.height()) { - y = this->frame * gfx->spriteHeight; - } - - img = gfx->spritesheet.copy(x, y, gfx->spriteWidth, gfx->spriteHeight); - - // Right-facing sprite is just the left-facing sprite mirrored - if (this->hFlip) { - img = img.transformed(QTransform().scale(-1, 1)); - } - } - // Set first palette color fully transparent. - img.setColor(0, qRgba(0, 0, 0, 0)); - pixmap = QPixmap::fromImage(img); - this->spriteWidth = gfx->spriteWidth; - this->spriteHeight = gfx->spriteHeight; - this->usingSprite = true; -} - -void ObjectEvent::setFrameFromMovement(QString facingDir) { - // defaults - // TODO: read this from a file somewhere? - this->frame = 0; - this->hFlip = false; - if (facingDir == "DIR_NORTH") { - this->frame = 1; - this->hFlip = false; - } else if (facingDir == "DIR_SOUTH") { - this->frame = 0; - this->hFlip = false; - } else if (facingDir == "DIR_WEST") { - this->frame = 2; - this->hFlip = false; - } else if (facingDir == "DIR_EAST") { - this->frame = 2; - this->hFlip = true; +QPixmap ObjectEvent::loadPixmap(Project *project) { + this->pixmap = project ? project->getEventPixmap(this->gfx, this->movement) : QPixmap(); + if (!this->pixmap.isNull()) { + this->usesDefaultPixmap = false; + return this->pixmap; } + return Event::loadPixmap(project); } -Event *CloneObjectEvent::duplicate() { +Event *CloneObjectEvent::duplicate() const { CloneObjectEvent *copy = new CloneObjectEvent(); copy->setX(this->getX()); copy->setY(this->getY()); copy->setElevation(this->getElevation()); + copy->setIdName(this->getIdName()); copy->setGfx(this->getGfx()); copy->setTargetID(this->getTargetID()); copy->setTargetMap(this->getTargetMap()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -387,94 +262,82 @@ EventFrame *CloneObjectEvent::createEventFrame() { OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { OrderedJson::object cloneJson; - cloneJson["type"] = "clone"; + QString idName = this->getIdName(); + if (!idName.isEmpty()) + cloneJson["local_id"] = idName; + + cloneJson["type"] = Event::typeToJsonKey(Event::Type::CloneObject); cloneJson["graphics_id"] = this->getGfx(); cloneJson["x"] = this->getX(); cloneJson["y"] = this->getY(); cloneJson["target_local_id"] = this->getTargetID(); - cloneJson["target_map"] = project->mapNamesToMapConstants.value(this->getTargetMap()); - this->addCustomValuesTo(&cloneJson); + const QString mapName = this->getTargetMap(); + cloneJson["target_map"] = project->getMapConstant(mapName, mapName); + OrderedJson::append(&cloneJson, this->getCustomAttributes()); return cloneJson; } bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); - this->setTargetID(ParseUtil::jsonToInt(json["target_local_id"])); + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setIdName(readString(&json, "local_id")); + this->setGfx(readString(&json, "graphics_id")); + this->setTargetID(readString(&json, "target_local_id")); - // Ensure the target map constant is valid before adding it to the events. - const QString dynamicMapConstant = project->getDynamicMapDefineName(); - QString mapConstant = ParseUtil::jsonToQString(json["target_map"]); - if (project->mapConstantsToMapNames.contains(mapConstant)) { - this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant)); - } else if (mapConstant == dynamicMapConstant) { - this->setTargetMap(DYNAMIC_MAP_NAME); - } else { - logWarn(QString("Target Map constant '%1' is invalid. Using default '%2'.").arg(mapConstant).arg(dynamicMapConstant)); - this->setTargetMap(DYNAMIC_MAP_NAME); - } - - this->readCustomValues(json); + // Log a warning if "target_map" isn't a known map ID, but don't overwrite user data. + const QString mapConstant = readString(&json, "target_map"); + if (!project->mapConstantsToMapNames.contains(mapConstant)) + logWarn(QString("Unknown Target Map constant '%1'.").arg(mapConstant)); + this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); + this->setCustomAttributes(json); return true; } void CloneObjectEvent::setDefaultValues(Project *project) { - this->setGfx(project->gfxDefines.keys().value(0, "0")); - this->setTargetID(1); - if (this->getMap()) this->setTargetMap(this->getMap()->name); + this->setGfx(project->gfxDefines.key(0, "0")); + this->setTargetID(QString::number(Event::getIndexOffset(Event::Group::Object))); + if (this->getMap()) this->setTargetMap(this->getMap()->name()); } -const QSet expectedCloneObjectFields = { - "type", - "graphics_id", - "target_local_id", - "target_map", -}; - QSet CloneObjectEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedCloneObjectFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "local_id", + "graphics_id", + "target_local_id", + "target_map", + }; return expectedFields; } -void CloneObjectEvent::loadPixmap(Project *project) { +QPixmap CloneObjectEvent::loadPixmap(Project *project) { // Try to get the targeted object to clone - int eventIndex = this->targetID - 1; - Map *clonedMap = project->getMap(this->targetMap); - Event *clonedEvent = clonedMap ? clonedMap->events[Event::Group::Object].value(eventIndex, nullptr) : nullptr; + Map *clonedMap = project ? project->loadMap(this->targetMap) : nullptr; + Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, this->targetID) : nullptr; if (clonedEvent && clonedEvent->getEventType() == Event::Type::Object) { // Get graphics data from cloned object ObjectEvent *clonedObject = dynamic_cast(clonedEvent); this->gfx = clonedObject->getGfx(); this->movement = clonedObject->getMovement(); - } else { + } else if (project) { // Invalid object specified, use default graphics data (as would be shown in-game) this->gfx = project->gfxDefines.key(0, "0"); this->movement = project->movementTypes.value(0, "0"); - } - - EventGraphics *eventGfx = project->eventGraphicsMap.value(gfx, nullptr); - if (!eventGfx || eventGfx->spritesheet.isNull()) { - // No sprite associated with this gfx constant. - // Use default sprite instead. - Event::loadPixmap(project); - this->spriteWidth = 16; - this->spriteHeight = 16; - this->usingSprite = false; } else { - this->setFrameFromMovement(project->facingDirections.value(this->movement)); - this->setPixmapFromSpritesheet(eventGfx); + this->gfx = "0"; + this->movement = "0"; } + return ObjectEvent::loadPixmap(project); } -Event *WarpEvent::duplicate() { +Event *WarpEvent::duplicate() const { WarpEvent *copy = new WarpEvent(); copy->setX(this->getX()); @@ -483,7 +346,7 @@ Event *WarpEvent::duplicate() { copy->setDestinationMap(this->getDestinationMap()); copy->setDestinationWarpID(this->getDestinationWarpID()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -502,65 +365,60 @@ OrderedJson::object WarpEvent::buildEventJson(Project *project) { warpJson["x"] = this->getX(); warpJson["y"] = this->getY(); warpJson["elevation"] = this->getElevation(); - warpJson["dest_map"] = project->mapNamesToMapConstants.value(this->getDestinationMap()); + const QString mapName = this->getDestinationMap(); + warpJson["dest_map"] = project->getMapConstant(mapName, mapName); warpJson["dest_warp_id"] = this->getDestinationWarpID(); - this->addCustomValuesTo(&warpJson); - + OrderedJson::append(&warpJson, this->getCustomAttributes()); return warpJson; } bool WarpEvent::loadFromJson(QJsonObject json, Project *project) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setDestinationWarpID(ParseUtil::jsonToQString(json["dest_warp_id"])); + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setDestinationWarpID(readString(&json, "dest_warp_id")); - // Ensure the warp destination map constant is valid before adding it to the warps. - const QString dynamicMapConstant = project->getDynamicMapDefineName(); - QString mapConstant = ParseUtil::jsonToQString(json["dest_map"]); - if (project->mapConstantsToMapNames.contains(mapConstant)) { - this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant)); - } else if (mapConstant == dynamicMapConstant) { - this->setDestinationMap(DYNAMIC_MAP_NAME); - } else { - logWarn(QString("Destination Map constant '%1' is invalid. Using default '%2'.").arg(mapConstant).arg(dynamicMapConstant)); - this->setDestinationMap(DYNAMIC_MAP_NAME); - } - - this->readCustomValues(json); + // Log a warning if "dest_map" isn't a known map ID, but don't overwrite user data. + const QString mapConstant = readString(&json, "dest_map"); + if (!project->mapConstantsToMapNames.contains(mapConstant)) + logWarn(QString("Unknown Destination Map constant '%1'.").arg(mapConstant)); + this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); + this->setCustomAttributes(json); return true; } void WarpEvent::setDefaultValues(Project *) { - if (this->getMap()) this->setDestinationMap(this->getMap()->name); + if (this->getMap()) this->setDestinationMap(this->getMap()->name()); this->setDestinationWarpID("0"); this->setElevation(0); } -const QSet expectedWarpFields = { - "elevation", - "dest_map", - "dest_warp_id", -}; - QSet WarpEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedWarpFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "elevation", + "dest_map", + "dest_warp_id", + }; return expectedFields; } void WarpEvent::setWarningEnabled(bool enabled) { - WarpFrame * frame = static_cast(this->getEventFrame()); + this->warningEnabled = enabled; + + // Don't call getEventFrame here, because it may create the event frame. + // If the frame hasn't been created yet then we have nothing else to do. + auto frame = static_cast(this->eventFrame.data()); if (frame && frame->warning) frame->warning->setVisible(enabled); } -Event *TriggerEvent::duplicate() { +Event *TriggerEvent::duplicate() const { TriggerEvent *copy = new TriggerEvent(); copy->setX(this->getX()); @@ -570,7 +428,7 @@ Event *TriggerEvent::duplicate() { copy->setScriptVarValue(this->getScriptVarValue()); copy->setScriptLabel(this->getScriptLabel()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -586,7 +444,7 @@ EventFrame *TriggerEvent::createEventFrame() { OrderedJson::object TriggerEvent::buildEventJson(Project *) { OrderedJson::object triggerJson; - triggerJson["type"] = "trigger"; + triggerJson["type"] = Event::typeToJsonKey(Event::Type::Trigger); triggerJson["x"] = this->getX(); triggerJson["y"] = this->getY(); triggerJson["elevation"] = this->getElevation(); @@ -594,21 +452,19 @@ OrderedJson::object TriggerEvent::buildEventJson(Project *) { triggerJson["var_value"] = this->getScriptVarValue(); triggerJson["script"] = this->getScriptLabel(); - this->addCustomValuesTo(&triggerJson); - + OrderedJson::append(&triggerJson, this->getCustomAttributes()); return triggerJson; } bool TriggerEvent::loadFromJson(QJsonObject json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setScriptVar(ParseUtil::jsonToQString(json["var"])); - this->setScriptVarValue(ParseUtil::jsonToQString(json["var_value"])); - this->setScriptLabel(ParseUtil::jsonToQString(json["script"])); - - this->readCustomValues(json); + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setScriptVar(readString(&json, "var")); + this->setScriptVarValue(readString(&json, "var_value")); + this->setScriptLabel(readString(&json, "script")); + this->setCustomAttributes(json); return true; } @@ -619,24 +475,22 @@ void TriggerEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedTriggerFields = { - "type", - "elevation", - "var", - "var_value", - "script", -}; - QSet TriggerEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedTriggerFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "var", + "var_value", + "script", + }; return expectedFields; } -Event *WeatherTriggerEvent::duplicate() { +Event *WeatherTriggerEvent::duplicate() const { WeatherTriggerEvent *copy = new WeatherTriggerEvent(); copy->setX(this->getX()); @@ -644,7 +498,7 @@ Event *WeatherTriggerEvent::duplicate() { copy->setElevation(this->getElevation()); copy->setWeather(this->getWeather()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -660,25 +514,23 @@ EventFrame *WeatherTriggerEvent::createEventFrame() { OrderedJson::object WeatherTriggerEvent::buildEventJson(Project *) { OrderedJson::object weatherJson; - weatherJson["type"] = "weather"; + weatherJson["type"] = Event::typeToJsonKey(Event::Type::WeatherTrigger); weatherJson["x"] = this->getX(); weatherJson["y"] = this->getY(); weatherJson["elevation"] = this->getElevation(); weatherJson["weather"] = this->getWeather(); - this->addCustomValuesTo(&weatherJson); - + OrderedJson::append(&weatherJson, this->getCustomAttributes()); return weatherJson; } bool WeatherTriggerEvent::loadFromJson(QJsonObject json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setWeather(ParseUtil::jsonToQString(json["weather"])); - - this->readCustomValues(json); + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setWeather(readString(&json, "weather")); + this->setCustomAttributes(json); return true; } @@ -687,22 +539,20 @@ void WeatherTriggerEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedWeatherTriggerFields = { - "type", - "elevation", - "weather", -}; - QSet WeatherTriggerEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedWeatherTriggerFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "weather", + }; return expectedFields; } -Event *SignEvent::duplicate() { +Event *SignEvent::duplicate() const { SignEvent *copy = new SignEvent(); copy->setX(this->getX()); @@ -711,7 +561,7 @@ Event *SignEvent::duplicate() { copy->setFacingDirection(this->getFacingDirection()); copy->setScriptLabel(this->getScriptLabel()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -727,27 +577,25 @@ EventFrame *SignEvent::createEventFrame() { OrderedJson::object SignEvent::buildEventJson(Project *) { OrderedJson::object signJson; - signJson["type"] = "sign"; + signJson["type"] = Event::typeToJsonKey(Event::Type::Sign); signJson["x"] = this->getX(); signJson["y"] = this->getY(); signJson["elevation"] = this->getElevation(); signJson["player_facing_dir"] = this->getFacingDirection(); signJson["script"] = this->getScriptLabel(); - this->addCustomValuesTo(&signJson); - + OrderedJson::append(&signJson, this->getCustomAttributes()); return signJson; } bool SignEvent::loadFromJson(QJsonObject json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setFacingDirection(ParseUtil::jsonToQString(json["player_facing_dir"])); - this->setScriptLabel(ParseUtil::jsonToQString(json["script"])); - - this->readCustomValues(json); + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setFacingDirection(readString(&json, "player_facing_dir")); + this->setScriptLabel(readString(&json, "script")); + this->setCustomAttributes(json); return true; } @@ -757,23 +605,21 @@ void SignEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedSignFields = { - "type", - "elevation", - "player_facing_dir", - "script", -}; - QSet SignEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedSignFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "player_facing_dir", + "script", + }; return expectedFields; } -Event *HiddenItemEvent::duplicate() { +Event *HiddenItemEvent::duplicate() const { HiddenItemEvent *copy = new HiddenItemEvent(); copy->setX(this->getX()); @@ -782,9 +628,9 @@ Event *HiddenItemEvent::duplicate() { copy->setItem(this->getItem()); copy->setFlag(this->getFlag()); copy->setQuantity(this->getQuantity()); - copy->setQuantity(this->getQuantity()); + copy->setUnderfoot(this->getUnderfoot()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -800,7 +646,7 @@ EventFrame *HiddenItemEvent::createEventFrame() { OrderedJson::object HiddenItemEvent::buildEventJson(Project *) { OrderedJson::object hiddenItemJson; - hiddenItemJson["type"] = "hidden_item"; + hiddenItemJson["type"] = Event::typeToJsonKey(Event::Type::HiddenItem); hiddenItemJson["x"] = this->getX(); hiddenItemJson["y"] = this->getY(); hiddenItemJson["elevation"] = this->getElevation(); @@ -813,26 +659,24 @@ OrderedJson::object HiddenItemEvent::buildEventJson(Project *) { hiddenItemJson["underfoot"] = this->getUnderfoot(); } - this->addCustomValuesTo(&hiddenItemJson); - + OrderedJson::append(&hiddenItemJson, this->getCustomAttributes()); return hiddenItemJson; } bool HiddenItemEvent::loadFromJson(QJsonObject json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setItem(ParseUtil::jsonToQString(json["item"])); - this->setFlag(ParseUtil::jsonToQString(json["flag"])); + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setItem(readString(&json, "item")); + this->setFlag(readString(&json, "flag")); if (projectConfig.hiddenItemQuantityEnabled) { - this->setQuantity(ParseUtil::jsonToInt(json["quantity"])); + this->setQuantity(readInt(&json, "quantity")); } if (projectConfig.hiddenItemRequiresItemfinderEnabled) { - this->setUnderfoot(ParseUtil::jsonToBool(json["underfoot"])); + this->setUnderfoot(readBool(&json, "underfoot")); } - this->readCustomValues(json); - + this->setCustomAttributes(json); return true; } @@ -847,29 +691,27 @@ void HiddenItemEvent::setDefaultValues(Project *project) { } } -const QSet expectedHiddenItemFields = { - "type", - "elevation", - "item", - "flag", -}; - QSet HiddenItemEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedHiddenItemFields; + QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "item", + "flag", + }; if (projectConfig.hiddenItemQuantityEnabled) { expectedFields << "quantity"; } if (projectConfig.hiddenItemRequiresItemfinderEnabled) { expectedFields << "underfoot"; } - expectedFields << "x" << "y"; return expectedFields; } -Event *SecretBaseEvent::duplicate() { +Event *SecretBaseEvent::duplicate() const { SecretBaseEvent *copy = new SecretBaseEvent(); copy->setX(this->getX()); @@ -877,7 +719,7 @@ Event *SecretBaseEvent::duplicate() { copy->setElevation(this->getElevation()); copy->setBaseID(this->getBaseID()); - copy->setCustomValues(this->getCustomValues()); + copy->setCustomAttributes(this->getCustomAttributes()); return copy; } @@ -893,25 +735,23 @@ EventFrame *SecretBaseEvent::createEventFrame() { OrderedJson::object SecretBaseEvent::buildEventJson(Project *) { OrderedJson::object secretBaseJson; - secretBaseJson["type"] = "secret_base"; + secretBaseJson["type"] = Event::typeToJsonKey(Event::Type::SecretBase); secretBaseJson["x"] = this->getX(); secretBaseJson["y"] = this->getY(); secretBaseJson["elevation"] = this->getElevation(); secretBaseJson["secret_base_id"] = this->getBaseID(); - this->addCustomValuesTo(&secretBaseJson); - + OrderedJson::append(&secretBaseJson, this->getCustomAttributes()); return secretBaseJson; } bool SecretBaseEvent::loadFromJson(QJsonObject json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setBaseID(ParseUtil::jsonToQString(json["secret_base_id"])); - - this->readCustomValues(json); + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setBaseID(readString(&json, "secret_base_id")); + this->setCustomAttributes(json); return true; } @@ -920,21 +760,34 @@ void SecretBaseEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedSecretBaseFields = { - "type", - "elevation", - "secret_base_id", -}; - QSet SecretBaseEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedSecretBaseFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "secret_base_id", + }; return expectedFields; } +Event *HealLocationEvent::duplicate() const { + HealLocationEvent *copy = new HealLocationEvent(); + + copy->setX(this->getX()); + copy->setY(this->getY()); + copy->setIdName(this->getIdName()); + copy->setHostMapName(this->getHostMapName()); + copy->setRespawnMapName(this->getRespawnMapName()); + copy->setRespawnNPC(this->getRespawnNPC()); + + copy->setCustomAttributes(this->getCustomAttributes()); + + return copy; +} + EventFrame *HealLocationEvent::createEventFrame() { if (!this->eventFrame) { this->eventFrame = new HealLocationFrame(this); @@ -943,22 +796,68 @@ EventFrame *HealLocationEvent::createEventFrame() { return this->eventFrame; } -OrderedJson::object HealLocationEvent::buildEventJson(Project *) { - return OrderedJson::object(); +QString HealLocationEvent::getHostMapName() const { + return this->getMap() ? this->getMap()->constantName() : this->hostMapName; } -void HealLocationEvent::setDefaultValues(Project *) { - this->setElevation(projectConfig.defaultElevation); - if (!this->getMap()) - return; - bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; - const QString mapConstant = Map::mapConstantFromName(this->getMap()->name, false); - const QString prefix = projectConfig.getIdentifier(respawnEnabled ? ProjectIdentifier::define_spawn_prefix - : ProjectIdentifier::define_heal_locations_prefix); - this->setLocationName(mapConstant); - this->setIdName(prefix + mapConstant); - if (respawnEnabled) { - this->setRespawnMap(this->getMap()->name); - this->setRespawnNPC(1); +OrderedJson::object HealLocationEvent::buildEventJson(Project *project) { + OrderedJson::object healLocationJson; + + healLocationJson["id"] = this->getIdName(); + healLocationJson["map"] = this->getHostMapName(); + healLocationJson["x"] = this->getX(); + healLocationJson["y"] = this->getY(); + if (projectConfig.healLocationRespawnDataEnabled) { + const QString mapName = this->getRespawnMapName(); + healLocationJson["respawn_map"] = project->getMapConstant(mapName, mapName); + healLocationJson["respawn_npc"] = this->getRespawnNPC(); } + + OrderedJson::append(&healLocationJson, this->getCustomAttributes()); + return healLocationJson; +} + +bool HealLocationEvent::loadFromJson(QJsonObject json, Project *project) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setIdName(readString(&json, "id")); + this->setHostMapName(readString(&json, "map")); + + if (projectConfig.healLocationRespawnDataEnabled) { + // Log a warning if "respawn_map" isn't a known map ID, but don't overwrite user data. + const QString mapConstant = readString(&json, "respawn_map"); + if (!project->mapConstantsToMapNames.contains(mapConstant)) + logWarn(QString("Unknown Respawn Map constant '%1'.").arg(mapConstant)); + this->setRespawnMapName(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); + this->setRespawnNPC(readString(&json, "respawn_npc")); + } + + this->setCustomAttributes(json); + return true; +} + +void HealLocationEvent::setDefaultValues(Project *project) { + if (this->map) { + this->setIdName(project->getNewHealLocationName(this->map)); + this->setRespawnMapName(this->map->name()); + } + this->setRespawnNPC(QString::number(0 + this->getIndexOffset(Event::Group::Object))); +} + +const QSet expectedHealLocationFields = { + +}; + +QSet HealLocationEvent::getExpectedFields() { + QSet expectedFields = { + "x", + "y", + "id", + "map", + }; + if (projectConfig.healLocationRespawnDataEnabled) { + expectedFields.insert("respawn_map"); + expectedFields.insert("respawn_npc"); + } + return expectedFields; } diff --git a/src/core/heallocation.cpp b/src/core/heallocation.cpp deleted file mode 100644 index 7fb424da..00000000 --- a/src/core/heallocation.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "heallocation.h" -#include "config.h" -#include "events.h" -#include "map.h" - -HealLocation::HealLocation(QString id, QString map, - int i, int16_t x, int16_t y, - QString respawnMap, uint8_t respawnNPC) { - this->idName = id; - this->mapName = map; - this->index = i; - this->x = x; - this->y = y; - this->respawnMap = respawnMap; - this->respawnNPC = respawnNPC; -} - -HealLocation HealLocation::fromEvent(Event *fromEvent) { - HealLocationEvent *event = dynamic_cast(fromEvent); - - HealLocation healLocation; - healLocation.idName = event->getIdName(); - healLocation.mapName = event->getLocationName(); - healLocation.index = event->getIndex(); - healLocation.x = event->getX(); - healLocation.y = event->getY(); - if (projectConfig.healLocationRespawnDataEnabled) { - healLocation.respawnNPC = event->getRespawnNPC(); - healLocation.respawnMap = Map::mapConstantFromName(event->getRespawnMap(), false); - } - return healLocation; -} - -QDebug operator<<(QDebug debug, const HealLocation &healLocation) { - debug << "HealLocation_" + healLocation.mapName << "(" << healLocation.x << ',' << healLocation.y << ")"; - return debug; -} diff --git a/src/core/map.cpp b/src/core/map.cpp index 86df7947..c1905f0a 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -2,7 +2,7 @@ #include "map.h" #include "imageproviders.h" #include "scripting.h" - +#include "utility.h" #include "editcommands.h" #include @@ -13,225 +13,91 @@ Map::Map(QObject *parent) : QObject(parent) { - editHistory.setClean(); + m_editHistory = new QUndoStack(this); + + m_scriptFileWatcher = new QFileSystemWatcher(this); + connect(m_scriptFileWatcher, &QFileSystemWatcher::fileChanged, this, &Map::invalidateScripts); + + resetEvents(); + + m_header = new MapHeader(this); + connect(m_header, &MapHeader::modified, this, &Map::modified); +} + +Map::Map(const Map &other, QObject *parent) : Map(parent) { + m_name = other.m_name; + m_constantName = other.m_constantName; + m_sharedEventsMap = other.m_sharedEventsMap; + m_sharedScriptsMap = other.m_sharedScriptsMap; + m_customAttributes = other.m_customAttributes; + *m_header = *other.m_header; + m_layout = other.m_layout; + m_isPersistedToFile = false; + m_metatileLayerOrder = other.m_metatileLayerOrder; + m_metatileLayerOpacity = other.m_metatileLayerOpacity; + + // Copy events + for (auto i = other.m_events.constBegin(); i != other.m_events.constEnd(); i++) { + for (const auto &event : i.value()) + addEvent(event->duplicate()); + } + + // Duplicating the map connections is probably not desirable, so we skip them. } Map::~Map() { - qDeleteAll(ownedEvents); - ownedEvents.clear(); + qDeleteAll(m_ownedEvents); + m_ownedEvents.clear(); deleteConnections(); } -void Map::setName(QString mapName) { - name = mapName; - constantName = mapConstantFromName(mapName); - scriptsLoaded = false; +// Note: Map does not take ownership of layout +void Map::setLayout(Layout *layout) { + if (layout == m_layout) + return; + m_layout = layout; + emit layoutChanged(); } -QString Map::mapConstantFromName(QString mapName, bool includePrefix) { - // Transform map names of the form 'GraniteCave_B1F` into map constants like 'MAP_GRANITE_CAVE_B1F'. - static const QRegularExpression caseChange("([a-z])([A-Z])"); - QString nameWithUnderscores = mapName.replace(caseChange, "\\1_\\2"); - const QString prefix = includePrefix ? projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) : ""; - QString withMapAndUppercase = prefix + nameWithUnderscores.toUpper(); - static const QRegularExpression underscores("_+"); - QString constantName = withMapAndUppercase.replace(underscores, "_"); - - // Handle special cases. - // SSTidal needs to be SS_TIDAL, rather than SSTIDAL - constantName = constantName.replace("SSTIDAL", "SS_TIDAL"); - - return constantName; +// We don't enforce this for existing maps, but for creating new maps we need to formulaically generate a new MAP_NAME ID. +QString Map::mapConstantFromName(const QString &name) { + return projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) + Util::toDefineCase(name); } -int Map::getWidth() { - return layout->getWidth(); +int Map::getWidth() const { + return m_layout->getWidth(); } -int Map::getHeight() { - return layout->getHeight(); +int Map::getHeight() const { + return m_layout->getHeight(); } -int Map::getBorderWidth() { - return layout->getBorderWidth(); +int Map::getBorderWidth() const { + return m_layout->getBorderWidth(); } -int Map::getBorderHeight() { - return layout->getBorderHeight(); -} - -bool Map::mapBlockChanged(int i, const Blockdata &cache) { - if (cache.length() <= i) - return true; - if (layout->blockdata.length() <= i) - return true; - - return layout->blockdata.at(i) != cache.at(i); -} - -bool Map::borderBlockChanged(int i, const Blockdata &cache) { - if (cache.length() <= i) - return true; - if (layout->border.length() <= i) - return true; - - return layout->border.at(i) != cache.at(i); -} - -void Map::clearBorderCache() { - layout->cached_border.clear(); -} - -void Map::cacheBorder() { - layout->cached_border.clear(); - for (const auto &block : layout->border) - layout->cached_border.append(block); -} - -void Map::cacheBlockdata() { - layout->cached_blockdata.clear(); - for (const auto &block : layout->blockdata) - layout->cached_blockdata.append(block); -} - -void Map::cacheCollision() { - layout->cached_collision.clear(); - for (const auto &block : layout->blockdata) - layout->cached_collision.append(block); -} - -QPixmap Map::renderCollision(bool ignoreCache) { - bool changed_any = false; - int width_ = getWidth(); - int height_ = getHeight(); - if (collision_image.isNull() || collision_image.width() != width_ * 16 || collision_image.height() != height_ * 16) { - collision_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); - changed_any = true; - } - if (layout->blockdata.isEmpty() || !width_ || !height_) { - collision_pixmap = collision_pixmap.fromImage(collision_image); - return collision_pixmap; - } - QPainter painter(&collision_image); - for (int i = 0; i < layout->blockdata.length(); i++) { - if (!ignoreCache && !mapBlockChanged(i, layout->cached_collision)) { - continue; - } - changed_any = true; - Block block = layout->blockdata.at(i); - QImage collision_metatile_image = getCollisionMetatileImage(block); - int map_y = width_ ? i / width_ : 0; - int map_x = width_ ? i % width_ : 0; - QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); - painter.drawImage(metatile_origin, collision_metatile_image); - } - painter.end(); - cacheCollision(); - if (changed_any) { - collision_pixmap = collision_pixmap.fromImage(collision_image); - } - return collision_pixmap; -} - -QPixmap Map::render(bool ignoreCache, MapLayout * fromLayout, QRect bounds) { - bool changed_any = false; - int width_ = getWidth(); - int height_ = getHeight(); - if (image.isNull() || image.width() != width_ * 16 || image.height() != height_ * 16) { - image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); - changed_any = true; - } - if (layout->blockdata.isEmpty() || !width_ || !height_) { - pixmap = pixmap.fromImage(image); - return pixmap; - } - - QPainter painter(&image); - for (int i = 0; i < layout->blockdata.length(); i++) { - if (!ignoreCache && !mapBlockChanged(i, layout->cached_blockdata)) { - continue; - } - changed_any = true; - int map_y = width_ ? i / width_ : 0; - int map_x = width_ ? i % width_ : 0; - if (bounds.isValid() && !bounds.contains(map_x, map_y)) { - continue; - } - QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); - Block block = layout->blockdata.at(i); - QImage metatile_image = getMetatileImage( - block.metatileId(), - fromLayout ? fromLayout->tileset_primary : layout->tileset_primary, - fromLayout ? fromLayout->tileset_secondary : layout->tileset_secondary, - metatileLayerOrder, - metatileLayerOpacity - ); - painter.drawImage(metatile_origin, metatile_image); - } - painter.end(); - if (changed_any) { - cacheBlockdata(); - pixmap = pixmap.fromImage(image); - } - - return pixmap; -} - -QPixmap Map::renderBorder(bool ignoreCache) { - bool changed_any = false, border_resized = false; - int width_ = getBorderWidth(); - int height_ = getBorderHeight(); - if (layout->border_image.isNull()) { - layout->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); - changed_any = true; - } - if (layout->border_image.width() != width_ * 16 || layout->border_image.height() != height_ * 16) { - layout->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); - border_resized = true; - } - if (layout->border.isEmpty()) { - layout->border_pixmap = layout->border_pixmap.fromImage(layout->border_image); - return layout->border_pixmap; - } - QPainter painter(&layout->border_image); - for (int i = 0; i < layout->border.length(); i++) { - if (!ignoreCache && (!border_resized && !borderBlockChanged(i, layout->cached_border))) { - continue; - } - - changed_any = true; - Block block = layout->border.at(i); - uint16_t metatileId = block.metatileId(); - QImage metatile_image = getMetatileImage(metatileId, layout->tileset_primary, layout->tileset_secondary, metatileLayerOrder, metatileLayerOpacity); - int map_y = width_ ? i / width_ : 0; - int map_x = width_ ? i % width_ : 0; - painter.drawImage(QPoint(map_x * 16, map_y * 16), metatile_image); - } - painter.end(); - if (changed_any) { - cacheBorder(); - layout->border_pixmap = layout->border_pixmap.fromImage(layout->border_image); - } - return layout->border_pixmap; +int Map::getBorderHeight() const { + return m_layout->getBorderHeight(); } // Get the portion of the map that can be rendered when rendered as a map connection. // Cardinal connections render the nearest segment of their map and within the bounds of the border draw distance, // Dive/Emerge connections are rendered normally within the bounds of their parent map. -QRect Map::getConnectionRect(const QString &direction, MapLayout * fromLayout) { +QRect Map::getConnectionRect(const QString &direction, Layout * fromLayout) const { int x = 0, y = 0; int w = getWidth(), h = getHeight(); + QMargins viewDistance = Project::getMetatileViewDistance(); if (direction == "up") { - h = qMin(h, BORDER_DISTANCE); + h = qMin(h, viewDistance.top()); y = getHeight() - h; } else if (direction == "down") { - h = qMin(h, BORDER_DISTANCE); + h = qMin(h, viewDistance.bottom()); } else if (direction == "left") { - w = qMin(w, BORDER_DISTANCE); + w = qMin(w, viewDistance.left()); x = getWidth() - w; } else if (direction == "right") { - w = qMin(w, BORDER_DISTANCE); + w = qMin(w, viewDistance.right()); } else if (MapConnection::isDiving(direction)) { if (fromLayout) { w = qMin(w, fromLayout->getWidth()); @@ -244,7 +110,7 @@ QRect Map::getConnectionRect(const QString &direction, MapLayout * fromLayout) { return QRect(x, y, w, h); } -QPixmap Map::renderConnection(const QString &direction, MapLayout * fromLayout) { +QPixmap Map::renderConnection(const QString &direction, Layout * fromLayout) { QRect bounds = getConnectionRect(direction, fromLayout); if (!bounds.isValid()) return QPixmap(); @@ -254,252 +120,48 @@ QPixmap Map::renderConnection(const QString &direction, MapLayout * fromLayout) if (MapConnection::isDiving(direction)) fromLayout = nullptr; - render(true, fromLayout, bounds); - QImage connection_image = image.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16); - return QPixmap::fromImage(connection_image); + QPixmap connectionPixmap = m_layout->render(true, fromLayout, bounds); + return connectionPixmap.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16); } -void Map::setNewDimensionsBlockdata(int newWidth, int newHeight) { - int oldWidth = getWidth(); - int oldHeight = getHeight(); - - Blockdata newBlockdata; - - for (int y = 0; y < newHeight; y++) - for (int x = 0; x < newWidth; x++) { - if (x < oldWidth && y < oldHeight) { - int index = y * oldWidth + x; - newBlockdata.append(layout->blockdata.value(index)); - } else { - newBlockdata.append(0); - } - } - - layout->blockdata = newBlockdata; -} - -void Map::setNewBorderDimensionsBlockdata(int newWidth, int newHeight) { - int oldWidth = getBorderWidth(); - int oldHeight = getBorderHeight(); - - Blockdata newBlockdata; - - for (int y = 0; y < newHeight; y++) - for (int x = 0; x < newWidth; x++) { - if (x < oldWidth && y < oldHeight) { - int index = y * oldWidth + x; - newBlockdata.append(layout->border.value(index)); - } else { - newBlockdata.append(0); - } - } - - layout->border = newBlockdata; -} - -void Map::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { - if (setNewBlockdata) { - setNewDimensionsBlockdata(newWidth, newHeight); - } - - int oldWidth = layout->width; - int oldHeight = layout->height; - layout->width = newWidth; - layout->height = newHeight; - - if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { - Scripting::cb_MapResized(oldWidth, oldHeight, newWidth, newHeight); - } - - emit mapDimensionsChanged(QSize(getWidth(), getHeight())); - modify(); -} - -void Map::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { - if (setNewBlockdata) { - setNewBorderDimensionsBlockdata(newWidth, newHeight); - } - - int oldWidth = layout->border_width; - int oldHeight = layout->border_height; - layout->border_width = newWidth; - layout->border_height = newHeight; - - if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { - Scripting::cb_BorderResized(oldWidth, oldHeight, newWidth, newHeight); - } - - modify(); -} - -void Map::openScript(QString label) { +void Map::openScript(const QString &label) { emit openScriptRequested(label); } -bool Map::getBlock(int x, int y, Block *out) { - if (isWithinBounds(x, y)) { - int i = y * getWidth() + x; - *out = layout->blockdata.value(i); - return true; - } - return false; +void Map::setSharedScriptsMap(const QString &sharedScriptsMap) { + if (m_sharedScriptsMap == sharedScriptsMap) + return; + m_sharedScriptsMap = sharedScriptsMap; + invalidateScripts(); } -void Map::setBlock(int x, int y, Block block, bool enableScriptCallback) { - if (!isWithinBounds(x, y)) return; - int i = y * getWidth() + x; - if (i < layout->blockdata.size()) { - Block prevBlock = layout->blockdata.at(i); - layout->blockdata.replace(i, block); - if (enableScriptCallback) { - Scripting::cb_MetatileChanged(x, y, prevBlock, block); - } - } -} - -void Map::setBlockdata(Blockdata blockdata, bool enableScriptCallback) { - int width = getWidth(); - int size = qMin(blockdata.size(), layout->blockdata.size()); - for (int i = 0; i < size; i++) { - Block prevBlock = layout->blockdata.at(i); - Block newBlock = blockdata.at(i); - if (prevBlock != newBlock) { - layout->blockdata.replace(i, newBlock); - if (enableScriptCallback) - Scripting::cb_MetatileChanged(i % width, i / width, prevBlock, newBlock); - } - } -} - -uint16_t Map::getBorderMetatileId(int x, int y) { - int i = y * getBorderWidth() + x; - return layout->border[i].metatileId(); -} - -void Map::setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback) { - int i = y * getBorderWidth() + x; - if (i < layout->border.size()) { - uint16_t prevMetatileId = layout->border[i].metatileId(); - layout->border[i].setMetatileId(metatileId); - if (prevMetatileId != metatileId && enableScriptCallback) { - Scripting::cb_BorderMetatileChanged(x, y, prevMetatileId, metatileId); - } - } -} - -void Map::setBorderBlockData(Blockdata blockdata, bool enableScriptCallback) { - int width = getBorderWidth(); - int size = qMin(blockdata.size(), layout->border.size()); - for (int i = 0; i < size; i++) { - Block prevBlock = layout->border.at(i); - Block newBlock = blockdata.at(i); - if (prevBlock != newBlock) { - layout->border.replace(i, newBlock); - if (enableScriptCallback) - Scripting::cb_BorderMetatileChanged(i % width, i / width, prevBlock.metatileId(), newBlock.metatileId()); - } - } -} - -void Map::_floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) { - QList todo; - todo.append(QPoint(x, y)); - while (todo.length()) { - QPoint point = todo.takeAt(0); - x = point.x(); - y = point.y(); - Block block; - if (!getBlock(x, y, &block)) { - continue; - } - - uint old_coll = block.collision(); - uint old_elev = block.elevation(); - if (old_coll == collision && old_elev == elevation) { - continue; - } - - block.setCollision(collision); - block.setElevation(elevation); - setBlock(x, y, block, true); - if (getBlock(x + 1, y, &block) && block.collision() == old_coll && block.elevation() == old_elev) { - todo.append(QPoint(x + 1, y)); - } - if (getBlock(x - 1, y, &block) && block.collision() == old_coll && block.elevation() == old_elev) { - todo.append(QPoint(x - 1, y)); - } - if (getBlock(x, y + 1, &block) && block.collision() == old_coll && block.elevation() == old_elev) { - todo.append(QPoint(x, y + 1)); - } - if (getBlock(x, y - 1, &block) && block.collision() == old_coll && block.elevation() == old_elev) { - todo.append(QPoint(x, y - 1)); - } - } -} - -void Map::floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) { - Block block; - if (getBlock(x, y, &block) && (block.collision() != collision || block.elevation() != elevation)) { - _floodFillCollisionElevation(x, y, collision, elevation); - } -} - -void Map::magicFillCollisionElevation(int initialX, int initialY, uint16_t collision, uint16_t elevation) { - Block block; - if (getBlock(initialX, initialY, &block) && (block.collision() != collision || block.elevation() != elevation)) { - uint old_coll = block.collision(); - uint old_elev = block.elevation(); - - for (int y = 0; y < getHeight(); y++) { - for (int x = 0; x < getWidth(); x++) { - if (getBlock(x, y, &block) && block.collision() == old_coll && block.elevation() == old_elev) { - block.setCollision(collision); - block.setElevation(elevation); - setBlock(x, y, block, true); - } - } - } - } -} - -QList Map::getAllEvents() const { - QList all_events; - for (const auto &event_list : events) { - all_events << event_list; - } - return all_events; +void Map::invalidateScripts() { + m_scriptsLoaded = false; + emit scriptsModified(); } QStringList Map::getScriptLabels(Event::Group group) { - if (!this->scriptsLoaded) { - this->scriptsFileLabels = ParseUtil::getGlobalScriptLabels(this->getScriptsFilePath()); - this->scriptsLoaded = true; + if (!m_scriptsLoaded) { + const QString scriptsFilePath = getScriptsFilePath(); + m_scriptLabels = ParseUtil::getGlobalScriptLabels(scriptsFilePath); + m_scriptsLoaded = true; + + // Track the scripts file for changes. Path may have changed, so stop tracking old files. + m_scriptFileWatcher->removePaths(m_scriptFileWatcher->files()); + m_scriptFileWatcher->addPath(scriptsFilePath); } - QStringList scriptLabels; + QStringList scriptLabels = m_scriptLabels; - // Get script labels currently in-use by the map's events - if (group == Event::Group::None) { - ScriptTracker scriptTracker; - for (Event *event : this->getAllEvents()) { - event->accept(&scriptTracker); - } - scriptLabels = scriptTracker.getScripts(); - } else { - ScriptTracker scriptTracker; - for (Event *event : events.value(group)) { - event->accept(&scriptTracker); - } - scriptLabels = scriptTracker.getScripts(); + // Add script labels currently in-use by the map's events + for (const auto &event : getEvents(group)) { + scriptLabels.append(event->getScripts()); } - // Add scripts from map's scripts file, and empty names. - scriptLabels.append(this->scriptsFileLabels); scriptLabels.sort(Qt::CaseInsensitive); - scriptLabels.prepend("0x0"); - scriptLabels.prepend("NULL"); - scriptLabels.removeAll(""); + scriptLabels.removeAll("0"); + scriptLabels.removeAll("0x0"); scriptLabels.removeDuplicates(); return scriptLabels; @@ -510,7 +172,7 @@ QString Map::getScriptsFilePath() const { auto path = QDir::cleanPath(QString("%1/%2/%3/scripts") .arg(projectConfig.projectDir) .arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)) - .arg(this->name)); + .arg(!m_sharedScriptsMap.isEmpty() ? m_sharedScriptsMap : m_name)); auto extension = Project::getScriptFileExtension(usePoryscript); if (usePoryscript && !QFile::exists(path + extension)) extension = Project::getScriptFileExtension(false); @@ -518,37 +180,120 @@ QString Map::getScriptsFilePath() const { return path; } +void Map::resetEvents() { + m_events[Event::Group::Object].clear(); + m_events[Event::Group::Warp].clear(); + m_events[Event::Group::Coord].clear(); + m_events[Event::Group::Bg].clear(); + m_events[Event::Group::Heal].clear(); +} + +QList Map::getEvents(Event::Group group) const { + if (group == Event::Group::None) { + // Get all events + QList all_events; + for (const auto &event_list : m_events) { + all_events << event_list; + } + return all_events; + } + return m_events[group]; +} + +Event* Map::getEvent(Event::Group group, int index) const { + return m_events[group].value(index, nullptr); +} + +Event* Map::getEvent(Event::Group group, const QString &idName) const { + if (idName.isEmpty()) + return nullptr; + + bool idIsNumber; + int id = idName.toInt(&idIsNumber, 0); + if (idIsNumber) + return getEvent(group, id - Event::getIndexOffset(group)); + + auto events = getEvents(group); + for (const auto &event : events) { + if (event->getIdName() == idName) { + return event; + } + } + return nullptr; +} + +// Returns a list of ID names for the given event group (or all events, if no group is given). +// For events with no explicit ID name, their index string is given instead. +QStringList Map::getEventIdNames(Event::Group group) const { + QList groups; + if (group == Event::Group::None) { + groups = Event::groups(); + } else { + groups.append(group); + } + + QStringList idNames; + for (const auto &group : groups) { + const auto events = m_events[group]; + int indexOffset = Event::getIndexOffset(group); + for (int i = 0; i < events.length(); i++) { + QString idName = events.at(i)->getIdName(); + if (idName.isEmpty()) { + idName = QString::number(i + indexOffset); + } + idNames.append(idName); + } + } + return idNames; +} + +int Map::getNumEvents(Event::Group group) const { + if (group == Event::Group::None) { + // Total number of events + int numEvents = 0; + for (auto i = m_events.constBegin(); i != m_events.constEnd(); i++) { + numEvents += i.value().length(); + } + return numEvents; + } + return m_events[group].length(); +} + void Map::removeEvent(Event *event) { - for (Event::Group key : events.keys()) { - events[key].removeAll(event); + for (auto i = m_events.begin(); i != m_events.end(); i++) { + i.value().removeAll(event); } } void Map::addEvent(Event *event) { event->setMap(this); - events[event->getEventGroup()].append(event); - if (!ownedEvents.contains(event)) ownedEvents.append(event); + m_events[event->getEventGroup()].append(event); + if (!m_ownedEvents.contains(event)) m_ownedEvents.insert(event); +} + +int Map::getIndexOfEvent(Event *event) const { + return m_events.value(event->getEventGroup()).indexOf(event); +} + +bool Map::hasEvent(Event *event) const { + return getIndexOfEvent(event) >= 0; } void Map::deleteConnections() { - qDeleteAll(this->ownedConnections); - this->ownedConnections.clear(); - this->connections.clear(); -} - -QList Map::getConnections() const { - return this->connections; + qDeleteAll(m_ownedConnections); + m_ownedConnections.clear(); + m_connections.clear(); } void Map::addConnection(MapConnection *connection) { - if (!connection || this->connections.contains(connection)) + if (!connection || m_connections.contains(connection)) return; // Maps should only have one Dive/Emerge connection at a time. // (Users can technically have more by editing their data manually, but we will only display one at a time) // Any additional connections being added (this can happen via mirroring) are tracked for deleting but otherwise ignored. - if (MapConnection::isDiving(connection->direction())) { - for (auto i : this->connections) { + if (connection->isDiving()) { + for (const auto &i : m_connections) { if (i->direction() == connection->direction()) { trackConnection(connection); return; @@ -566,8 +311,8 @@ void Map::loadConnection(MapConnection *connection) { if (!connection) return; - if (!this->connections.contains(connection)) - this->connections.append(connection); + if (!m_connections.contains(connection)) + m_connections.append(connection); trackConnection(connection); } @@ -575,12 +320,12 @@ void Map::loadConnection(MapConnection *connection) { void Map::trackConnection(MapConnection *connection) { connection->setParentMap(this, false); - if (!this->ownedConnections.contains(connection)) { - this->ownedConnections.insert(connection); + if (!m_ownedConnections.contains(connection)) { + m_ownedConnections.insert(connection); connect(connection, &MapConnection::parentMapChanged, [=](Map *, Map *after) { if (after != this && after != nullptr) { // MapConnection's parent has been reassigned, it's no longer our responsibility - this->ownedConnections.remove(connection); + m_ownedConnections.remove(connection); QObject::disconnect(connection, &MapConnection::parentMapChanged, this, nullptr); } }); @@ -589,23 +334,38 @@ void Map::trackConnection(MapConnection *connection) { // We retain ownership of this MapConnection until it's assigned to a new parent map. void Map::removeConnection(MapConnection *connection) { - if (!this->connections.removeOne(connection)) + if (!m_connections.removeOne(connection)) return; connection->setParentMap(nullptr, false); modify(); emit connectionRemoved(connection); } +// Return the first map connection that has the given direction. +MapConnection* Map::getConnection(const QString &direction) const { + for (const auto &connection : m_connections) { + if (connection->direction() == direction) + return connection; + } + return nullptr; +} + +void Map::commit(QUndoCommand *cmd) { + m_editHistory->push(cmd); +} + void Map::modify() { emit modified(); } -void Map::clean() { - this->hasUnsavedDataChanges = false; +void Map::setClean() { + m_editHistory->setClean(); + m_hasUnsavedDataChanges = false; + m_isPersistedToFile = true; } -bool Map::hasUnsavedChanges() { - return !editHistory.isClean() || hasUnsavedDataChanges || !isPersistedToFile; +bool Map::hasUnsavedChanges() const { + return !m_editHistory->isClean() || m_layout->hasUnsavedChanges() || m_hasUnsavedDataChanges || !m_isPersistedToFile; } void Map::pruneEditHistory() { @@ -619,20 +379,12 @@ void Map::pruneEditHistory() { ID_MapConnectionAdd, ID_MapConnectionRemove }; - for (int i = 0; i < this->editHistory.count(); i++) { + for (int i = 0; i < m_editHistory->count(); i++) { // Qt really doesn't expect editing commands in the stack to be valid (fair). // A better future design might be to have separate edit histories per map tab, // and dumping the entire Connections tab history with QUndoStack::clear. - auto command = const_cast(this->editHistory.command(i)); + auto command = const_cast(m_editHistory->command(i)); if (mapConnectionIds.contains(command->id())) command->setObsolete(true); } } - -bool Map::isWithinBounds(int x, int y) { - return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight()); -} - -bool Map::isWithinBorderBounds(int x, int y) { - return (x >= 0 && x < this->getBorderWidth() && y >= 0 && y < this->getBorderHeight()); -} diff --git a/src/core/mapconnection.cpp b/src/core/mapconnection.cpp index b80b8d09..d37a8c74 100644 --- a/src/core/mapconnection.cpp +++ b/src/core/mapconnection.cpp @@ -53,19 +53,41 @@ void MapConnection::markMapEdited() { } Map* MapConnection::getMap(const QString& mapName) const { - return project ? project->getMap(mapName) : nullptr; + return project ? project->loadMap(mapName) : nullptr; } Map* MapConnection::targetMap() const { return getMap(m_targetMapName); } -QPixmap MapConnection::getPixmap() { +QPixmap MapConnection::render() const { auto map = targetMap(); if (!map) return QPixmap(); - return map->renderConnection(m_direction, m_parentMap ? m_parentMap->layout : nullptr); + return map->renderConnection(m_direction, m_parentMap ? m_parentMap->layout() : nullptr); +} + +// Get the position of the target map relative to its parent map. +// For right/down connections this is offset by the dimensions of the parent map. +// For left/up connections this is offset by the dimensions of the target map. +// If 'clipped' is true, only the rendered dimensions of the target map will be used, rather than its full dimensions. +QPoint MapConnection::relativePos(bool clipped) const { + int x = 0, y = 0; + if (m_direction == "right") { + if (m_parentMap) x = m_parentMap->getWidth(); + y = m_offset; + } else if (m_direction == "down") { + x = m_offset; + if (m_parentMap) y = m_parentMap->getHeight(); + } else if (m_direction == "left") { + if (targetMap()) x = !clipped ? -targetMap()->getWidth() : -targetMap()->getConnectionRect(m_direction).width(); + y = m_offset; + } else if (m_direction == "up") { + x = m_offset; + if (targetMap()) y = !clipped ? -targetMap()->getHeight() : -targetMap()->getConnectionRect(m_direction).height(); + } + return QPoint(x, y); } void MapConnection::setParentMap(Map* map, bool mirror) { @@ -75,7 +97,7 @@ void MapConnection::setParentMap(Map* map, bool mirror) { if (mirror) { auto connection = findMirror(); if (connection) - connection->setTargetMapName(map ? map->name : QString(), false); + connection->setTargetMapName(map ? map->name() : QString(), false); } if (m_parentMap) @@ -91,7 +113,7 @@ void MapConnection::setParentMap(Map* map, bool mirror) { } QString MapConnection::parentMapName() const { - return m_parentMap ? m_parentMap->name : QString(); + return m_parentMap ? m_parentMap->name() : QString(); } void MapConnection::setTargetMapName(const QString &targetMapName, bool mirror) { diff --git a/src/core/mapheader.cpp b/src/core/mapheader.cpp new file mode 100644 index 00000000..689a52dc --- /dev/null +++ b/src/core/mapheader.cpp @@ -0,0 +1,121 @@ +#include "mapheader.h" + +MapHeader::MapHeader(const MapHeader& other) : MapHeader() { + m_song = other.m_song; + m_location = other.m_location; + m_requiresFlash = other.m_requiresFlash; + m_weather = other.m_weather; + m_type = other.m_type; + m_showsLocationName = other.m_showsLocationName; + m_allowsRunning = other.m_allowsRunning; + m_allowsBiking = other.m_allowsBiking; + m_allowsEscaping = other.m_allowsEscaping; + m_floorNumber = other.m_floorNumber; + m_battleScene = other.m_battleScene; +} + +MapHeader &MapHeader::operator=(const MapHeader &other) { + // We want to call each set function here to ensure any fieldChanged signals + // are sent as necessary. This does also mean the modified signal can be sent + // repeatedly (but for now at least that's not a big issue). + setSong(other.m_song); + setLocation(other.m_location); + setRequiresFlash(other.m_requiresFlash); + setWeather(other.m_weather); + setType(other.m_type); + setShowsLocationName(other.m_showsLocationName); + setAllowsRunning(other.m_allowsRunning); + setAllowsBiking(other.m_allowsBiking); + setAllowsEscaping(other.m_allowsEscaping); + setFloorNumber(other.m_floorNumber); + setBattleScene(other.m_battleScene); + return *this; +} + +void MapHeader::setSong(const QString &song) { + if (m_song == song) + return; + m_song = song; + emit songChanged(m_song); + emit modified(); +} + +void MapHeader::setLocation(const QString &location) { + if (m_location == location) + return; + m_location = location; + emit locationChanged(m_location); + emit modified(); +} + +void MapHeader::setRequiresFlash(bool requiresFlash) { + if (m_requiresFlash == requiresFlash) + return; + m_requiresFlash = requiresFlash; + emit requiresFlashChanged(m_requiresFlash); + emit modified(); +} + +void MapHeader::setWeather(const QString &weather) { + if (m_weather == weather) + return; + m_weather = weather; + emit weatherChanged(m_weather); + emit modified(); +} + +void MapHeader::setType(const QString &type) { + if (m_type == type) + return; + m_type = type; + emit typeChanged(m_type); + emit modified(); +} + +void MapHeader::setShowsLocationName(bool showsLocationName) { + if (m_showsLocationName == showsLocationName) + return; + m_showsLocationName = showsLocationName; + emit showsLocationNameChanged(m_showsLocationName); + emit modified(); +} + +void MapHeader::setAllowsRunning(bool allowsRunning) { + if (m_allowsRunning == allowsRunning) + return; + m_allowsRunning = allowsRunning; + emit allowsRunningChanged(m_allowsRunning); + emit modified(); +} + +void MapHeader::setAllowsBiking(bool allowsBiking) { + if (m_allowsBiking == allowsBiking) + return; + m_allowsBiking = allowsBiking; + emit allowsBikingChanged(m_allowsBiking); + emit modified(); +} + +void MapHeader::setAllowsEscaping(bool allowsEscaping) { + if (m_allowsEscaping == allowsEscaping) + return; + m_allowsEscaping = allowsEscaping; + emit allowsEscapingChanged(m_allowsEscaping); + emit modified(); +} + +void MapHeader::setFloorNumber(int floorNumber) { + if (m_floorNumber == floorNumber) + return; + m_floorNumber = floorNumber; + emit floorNumberChanged(m_floorNumber); + emit modified(); +} + +void MapHeader::setBattleScene(const QString &battleScene) { + if (m_battleScene == battleScene) + return; + m_battleScene = battleScene; + emit battleSceneChanged(m_battleScene); + emit modified(); +} diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 0f5deecd..c3e91ba3 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -2,33 +2,518 @@ #include -QString MapLayout::layoutConstantFromName(QString mapName) { - // Transform map names of the form 'GraniteCave_B1F` into layout constants like 'LAYOUT_GRANITE_CAVE_B1F'. - static const QRegularExpression caseChange("([a-z])([A-Z])"); - QString nameWithUnderscores = mapName.replace(caseChange, "\\1_\\2"); - QString withMapAndUppercase = "LAYOUT_" + nameWithUnderscores.toUpper(); - static const QRegularExpression underscores("_+"); - QString constantName = withMapAndUppercase.replace(underscores, "_"); +#include "scripting.h" +#include "imageproviders.h" +#include "utility.h" - // Handle special cases. - // SSTidal should be SS_TIDAL, rather than SSTIDAL - constantName = constantName.replace("SSTIDAL", "SS_TIDAL"); - - return constantName; +Layout::Layout(const Layout &other) : Layout() { + copyFrom(&other); } -int MapLayout::getWidth() { - return width; +Layout *Layout::copy() const { + Layout *layout = new Layout; + layout->copyFrom(this); + return layout; } -int MapLayout::getHeight() { - return height; +void Layout::copyFrom(const Layout *other) { + this->id = other->id; + this->name = other->name; + this->width = other->width; + this->height = other->height; + this->border_width = other->border_width; + this->border_height = other->border_height; + this->border_path = other->border_path; + this->blockdata_path = other->blockdata_path; + this->tileset_primary_label = other->tileset_primary_label; + this->tileset_secondary_label = other->tileset_secondary_label; + this->tileset_primary = other->tileset_primary; + this->tileset_secondary = other->tileset_secondary; + this->blockdata = other->blockdata; + this->border = other->border; + this->customData = other->customData; } -int MapLayout::getBorderWidth() { - return border_width; +QString Layout::layoutConstantFromName(const QString &name) { + return projectConfig.getIdentifier(ProjectIdentifier::define_layout_prefix) + Util::toDefineCase(name); } -int MapLayout::getBorderHeight() { - return border_height; +Layout::Settings Layout::settings() const { + Layout::Settings settings; + settings.id = this->id; + settings.name = this->name; + settings.width = this->width; + settings.height = this->height; + settings.borderWidth = this->border_width; + settings.borderHeight = this->border_height; + settings.primaryTilesetLabel = this->tileset_primary_label; + settings.secondaryTilesetLabel = this->tileset_secondary_label; + return settings; +} + +bool Layout::isWithinBounds(int x, int y) const { + return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight()); +} + +bool Layout::isWithinBounds(const QRect &rect) const { + return rect.left() >= 0 && rect.right() < this->getWidth() && rect.top() >= 0 && rect.bottom() < this->getHeight(); +} + +bool Layout::isWithinBorderBounds(int x, int y) const { + return (x >= 0 && x < this->getBorderWidth() && y >= 0 && y < this->getBorderHeight()); +} + +// Calculate the distance away from the layout's edge that we need to start drawing border blocks. +// We need to fulfill two requirements here: +// - We should draw enough to fill the player's in-game view +// - The value should be some multiple of the border's dimension +// (otherwise the border won't be positioned the same as it would in-game). +int Layout::getBorderDrawDistance(int dimension, qreal minimum) { + if (dimension >= minimum) + return dimension; + + // Get first multiple of dimension >= the minimum + return dimension * qCeil(minimum / qMax(dimension, 1)); +} +QMargins Layout::getBorderMargins() const { + QMargins minimum = Project::getMetatileViewDistance(); + QMargins distance; + distance.setTop(getBorderDrawDistance(this->border_height, minimum.top())); + distance.setBottom(getBorderDrawDistance(this->border_height, minimum.bottom())); + distance.setLeft(getBorderDrawDistance(this->border_width, minimum.left())); + distance.setRight(getBorderDrawDistance(this->border_width, minimum.right())); + return distance; +} + +// Get a rectangle that represents (in pixels) the layout's map area and the visible area of its border. +// At maximum, this is equal to the map size plus the border margins. +// If the border is large (and so beyond player the view) it may be smaller than that. +QRect Layout::getVisibleRect() const { + QRect area = QRect(0, 0, this->width * 16, this->height * 16); + return area += (Project::getMetatileViewDistance() * 16); +} + +bool Layout::getBlock(int x, int y, Block *out) { + if (isWithinBounds(x, y)) { + int i = y * getWidth() + x; + *out = this->blockdata.value(i); + return true; + } + return false; +} + +void Layout::setBlock(int x, int y, Block block, bool enableScriptCallback) { + if (!isWithinBounds(x, y)) return; + int i = y * getWidth() + x; + if (i < this->blockdata.size()) { + Block prevBlock = this->blockdata.at(i); + this->blockdata.replace(i, block); + if (enableScriptCallback) { + Scripting::cb_MetatileChanged(x, y, prevBlock, block); + } + } +} + +void Layout::setBlockdata(Blockdata newBlockdata, bool enableScriptCallback) { + int width = getWidth(); + int size = qMin(newBlockdata.size(), this->blockdata.size()); + for (int i = 0; i < size; i++) { + Block prevBlock = this->blockdata.at(i); + Block newBlock = newBlockdata.at(i); + if (prevBlock != newBlock) { + this->blockdata.replace(i, newBlock); + if (enableScriptCallback) + Scripting::cb_MetatileChanged(i % width, i / width, prevBlock, newBlock); + } + } +} + +void Layout::clearBorderCache() { + this->cached_border.clear(); +} + +void Layout::cacheBorder() { + this->cached_border.clear(); + for (const auto &block : this->border) + this->cached_border.append(block); +} + +void Layout::cacheBlockdata() { + this->cached_blockdata.clear(); + for (const auto &block : this->blockdata) + this->cached_blockdata.append(block); +} + +void Layout::cacheCollision() { + this->cached_collision.clear(); + for (const auto &block : this->blockdata) + this->cached_collision.append(block); +} + +bool Layout::layoutBlockChanged(int i, const Blockdata &cache) { + if (cache.length() <= i) + return true; + if (this->blockdata.length() <= i) + return true; + + return this->blockdata.at(i) != cache.at(i); +} + +uint16_t Layout::getBorderMetatileId(int x, int y) { + int i = y * getBorderWidth() + x; + return this->border[i].metatileId(); +} + +void Layout::setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback) { + int i = y * getBorderWidth() + x; + if (i < this->border.size()) { + uint16_t prevMetatileId = this->border[i].metatileId(); + this->border[i].setMetatileId(metatileId); + if (prevMetatileId != metatileId && enableScriptCallback) { + Scripting::cb_BorderMetatileChanged(x, y, prevMetatileId, metatileId); + } + } +} + +void Layout::setBorderBlockData(Blockdata newBlockdata, bool enableScriptCallback) { + int width = getBorderWidth(); + int size = qMin(newBlockdata.size(), this->border.size()); + for (int i = 0; i < size; i++) { + Block prevBlock = this->border.at(i); + Block newBlock = newBlockdata.at(i); + if (prevBlock != newBlock) { + this->border.replace(i, newBlock); + if (enableScriptCallback) + Scripting::cb_BorderMetatileChanged(i % width, i / width, prevBlock.metatileId(), newBlock.metatileId()); + } + } +} + +void Layout::setDimensions(int newWidth, int newHeight, bool setNewBlockdata) { + if (setNewBlockdata) { + setNewDimensionsBlockdata(newWidth, newHeight); + } + this->width = newWidth; + this->height = newHeight; + emit dimensionsChanged(QSize(this->width, this->height)); +} + +void Layout::adjustDimensions(const QMargins &margins, bool setNewBlockdata) { + int oldWidth = this->width; + int oldHeight = this->height; + this->width = oldWidth + margins.left() + margins.right(); + this->height = oldHeight + margins.top() + margins.bottom(); + + if (setNewBlockdata) { + // Fill new blockdata + Blockdata newBlockdata; + for (int y = 0; y < this->height; y++) + for (int x = 0; x < this->width; x++) { + if ((x < margins.left()) || (x >= this->width - margins.right()) || (y < margins.top()) || (y >= this->height - margins.bottom())) { + newBlockdata.append(0); + } else { + int index = (y - margins.top()) * oldWidth + (x - margins.left()); + newBlockdata.append(this->blockdata.value(index)); + } + } + this->blockdata = newBlockdata; + } + + Scripting::cb_MapResized(oldWidth, oldHeight, margins); + emit dimensionsChanged(QSize(this->width, this->height)); +} + +void Layout::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { + if (setNewBlockdata) { + setNewBorderDimensionsBlockdata(newWidth, newHeight); + } + + int oldWidth = this->border_width; + int oldHeight = this->border_height; + this->border_width = newWidth; + this->border_height = newHeight; + + if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { + Scripting::cb_BorderResized(oldWidth, oldHeight, newWidth, newHeight); + } +} + +void Layout::setNewDimensionsBlockdata(int newWidth, int newHeight) { + int oldWidth = getWidth(); + int oldHeight = getHeight(); + + Blockdata newBlockdata; + + for (int y = 0; y < newHeight; y++) + for (int x = 0; x < newWidth; x++) { + if (x < oldWidth && y < oldHeight) { + int index = y * oldWidth + x; + newBlockdata.append(this->blockdata.value(index)); + } else { + newBlockdata.append(0); + } + } + + this->blockdata = newBlockdata; +} + +void Layout::setNewBorderDimensionsBlockdata(int newWidth, int newHeight) { + int oldWidth = getBorderWidth(); + int oldHeight = getBorderHeight(); + + Blockdata newBlockdata; + + for (int y = 0; y < newHeight; y++) + for (int x = 0; x < newWidth; x++) { + if (x < oldWidth && y < oldHeight) { + int index = y * oldWidth + x; + newBlockdata.append(this->border.value(index)); + } else { + newBlockdata.append(0); + } + } + + this->border = newBlockdata; +} + +void Layout::_floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) { + QList todo; + todo.append(QPoint(x, y)); + while (todo.length()) { + QPoint point = todo.takeAt(0); + x = point.x(); + y = point.y(); + Block block; + if (!getBlock(x, y, &block)) { + continue; + } + + uint old_coll = block.collision(); + uint old_elev = block.elevation(); + if (old_coll == collision && old_elev == elevation) { + continue; + } + + block.setCollision(collision); + block.setElevation(elevation); + setBlock(x, y, block, true); + if (getBlock(x + 1, y, &block) && block.collision() == old_coll && block.elevation() == old_elev) { + todo.append(QPoint(x + 1, y)); + } + if (getBlock(x - 1, y, &block) && block.collision() == old_coll && block.elevation()== old_elev) { + todo.append(QPoint(x - 1, y)); + } + if (getBlock(x, y + 1, &block) && block.collision() == old_coll && block.elevation() == old_elev) { + todo.append(QPoint(x, y + 1)); + } + if (getBlock(x, y - 1, &block) && block.collision() == old_coll && block.elevation() == old_elev) { + todo.append(QPoint(x, y - 1)); + } + } +} + +void Layout::floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) { + Block block; + if (getBlock(x, y, &block) && (block.collision() != collision || block.elevation() != elevation)) { + _floodFillCollisionElevation(x, y, collision, elevation); + } +} + +void Layout::magicFillCollisionElevation(int initialX, int initialY, uint16_t collision, uint16_t elevation) { + Block block; + if (getBlock(initialX, initialY, &block) && (block.collision() != collision || block.elevation() != elevation)) { + uint old_coll = block.collision(); + uint old_elev = block.elevation(); + + for (int y = 0; y < getHeight(); y++) { + for (int x = 0; x < getWidth(); x++) { + if (getBlock(x, y, &block) && block.collision() == old_coll && block.elevation() == old_elev) { + block.setCollision(collision); + block.setElevation(elevation); + setBlock(x, y, block, true); + } + } + } + } +} + +QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, QRect bounds) { + bool changed_any = false; + int width_ = getWidth(); + int height_ = getHeight(); + if (this->image.isNull() || this->image.width() != width_ * 16 || this->image.height() != height_ * 16) { + this->image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + changed_any = true; + } + if (this->blockdata.isEmpty() || !width_ || !height_) { + this->pixmap = this->pixmap.fromImage(this->image); + return this->pixmap; + } + + // There are a lot of external changes that can invalidate a general metatile image cache. + // However, during a single pass at rendering the layout there shouldn't be any changes to + // the tiles, tileset palettes, or metatile layer order/opacity, and layouts often have + // many repeated metatile IDs, so we create a cache for each request to render the layout. + QHash imageCache; + + QPainter painter(&this->image); + for (int i = 0; i < this->blockdata.length(); i++) { + if (!ignoreCache && !layoutBlockChanged(i, this->cached_blockdata)) { + continue; + } + int map_y = width_ ? i / width_ : 0; + int map_x = width_ ? i % width_ : 0; + if (bounds.isValid() && !bounds.contains(map_x, map_y)) { + continue; + } + + uint16_t metatileId = this->blockdata.at(i).metatileId(); + QImage metatileImage; + if (imageCache.contains(metatileId)) { + metatileImage = imageCache.value(metatileId); + } else { + metatileImage = getMetatileImage( + metatileId, + fromLayout ? fromLayout->tileset_primary : this->tileset_primary, + fromLayout ? fromLayout->tileset_secondary : this->tileset_secondary, + metatileLayerOrder, + metatileLayerOpacity + ); + imageCache.insert(metatileId, metatileImage); + } + + QPoint metatileOrigin = QPoint(map_x * 16, map_y * 16); + painter.drawImage(metatileOrigin, metatileImage); + changed_any = true; + } + painter.end(); + if (changed_any) { + cacheBlockdata(); + this->pixmap = this->pixmap.fromImage(this->image); + } + + return this->pixmap; +} + +QPixmap Layout::renderCollision(bool ignoreCache) { + bool changed_any = false; + int width_ = getWidth(); + int height_ = getHeight(); + if (collision_image.isNull() || collision_image.width() != width_ * 16 || collision_image.height() != height_ * 16) { + collision_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + changed_any = true; + } + if (this->blockdata.isEmpty() || !width_ || !height_) { + collision_pixmap = collision_pixmap.fromImage(collision_image); + return collision_pixmap; + } + QPainter painter(&collision_image); + for (int i = 0; i < this->blockdata.length(); i++) { + if (!ignoreCache && !layoutBlockChanged(i, this->cached_collision)) { + continue; + } + changed_any = true; + Block block = this->blockdata.at(i); + QImage collision_metatile_image = getCollisionMetatileImage(block); + int map_y = width_ ? i / width_ : 0; + int map_x = width_ ? i % width_ : 0; + QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); + painter.drawImage(metatile_origin, collision_metatile_image); + } + painter.end(); + cacheCollision(); + if (changed_any) { + collision_pixmap = collision_pixmap.fromImage(collision_image); + } + return collision_pixmap; +} + +QPixmap Layout::renderBorder(bool ignoreCache) { + bool changed_any = false, border_resized = false; + int width_ = getBorderWidth(); + int height_ = getBorderHeight(); + if (this->border_image.isNull()) { + this->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + changed_any = true; + } + if (this->border_image.width() != width_ * 16 || this->border_image.height() != height_ * 16) { + this->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + border_resized = true; + } + if (this->border.isEmpty()) { + this->border_pixmap = this->border_pixmap.fromImage(this->border_image); + return this->border_pixmap; + } + QPainter painter(&this->border_image); + for (int i = 0; i < this->border.length(); i++) { + if (!ignoreCache && (!border_resized && !layoutBlockChanged(i, this->cached_border))) { + continue; + } + + changed_any = true; + Block block = this->border.at(i); + uint16_t metatileId = block.metatileId(); + QImage metatile_image = getMetatileImage(metatileId, this->tileset_primary, this->tileset_secondary, metatileLayerOrder, metatileLayerOpacity); + int map_y = width_ ? i / width_ : 0; + int map_x = width_ ? i % width_ : 0; + painter.drawImage(QPoint(map_x * 16, map_y * 16), metatile_image); + } + painter.end(); + if (changed_any) { + cacheBorder(); + this->border_pixmap = this->border_pixmap.fromImage(this->border_image); + } + return this->border_pixmap; +} + +QPixmap Layout::getLayoutItemPixmap() { + return this->layoutItem ? this->layoutItem->pixmap() : QPixmap(); +} + +bool Layout::hasUnsavedChanges() const { + return !this->editHistory.isClean() || this->hasUnsavedDataChanges || !this->newFolderPath.isEmpty(); +} + +bool Layout::save(const QString &root) { + if (!this->newFolderPath.isEmpty()) { + // Layout directory doesn't exist yet, create it now. + const QString fullPath = QString("%1/%2").arg(root).arg(this->newFolderPath); + if (!QDir::root().mkpath(fullPath)) { + logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath)); + return false; + } + this->newFolderPath = QString(); + } + + bool success = true; + if (!saveBorder(root)) success = false; + if (!saveBlockdata(root)) success = false; + if (!success) + return false; + + this->editHistory.setClean(); + this->hasUnsavedDataChanges = false; + return true; +} + +bool Layout::saveBorder(const QString &root) { + QString path = QString("%1/%2").arg(root).arg(this->border_path); + return writeBlockdata(path, this->border); +} + +bool Layout::saveBlockdata(const QString &root) { + QString path = QString("%1/%2").arg(root).arg(this->blockdata_path); + return writeBlockdata(path, this->blockdata); +} + +bool Layout::writeBlockdata(const QString &path, const Blockdata &blockdata) const { + QFile file(path); + if (!file.open(QIODevice::WriteOnly)) { + logError(QString("Could not open '%1' for writing: %2").arg(path).arg(file.errorString())); + return false; + } + + QByteArray data = blockdata.serialize(); + file.write(data); + return true; } diff --git a/src/core/mapparser.cpp b/src/core/mapparser.cpp deleted file mode 100644 index 986c2c24..00000000 --- a/src/core/mapparser.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "mapparser.h" -#include "config.h" -#include "log.h" -#include "project.h" - -MapParser::MapParser() -{ -} - -MapLayout *MapParser::parse(QString filepath, bool *error, Project *project) -{ - QFile file(filepath); - if (!file.open(QIODevice::ReadOnly)) { - *error = true; - logError(QString("Could not open Advance Map 1.92 Map .map file '%1': ").arg(filepath) + file.errorString()); - return nullptr; - } - - QByteArray in = file.readAll(); - file.close(); - - if (in.length() < 20 || in.length() % 2 != 0) { - *error = true; - logError(QString("Advance Map 1.92 Map .map file '%1' is an unexpected size.").arg(filepath)); - return nullptr; - } - - int borderWidth = static_cast(in.at(16)); // 0 in RSE .map files - int borderHeight = static_cast(in.at(17)); // 0 in RSE .map files - int numBorderTiles = borderWidth * borderHeight; // 0 if RSE - - int mapDataOffset = 20 + (numBorderTiles * 2); // FRLG .map files store border metatile data after the header - int mapWidth = static_cast(in.at(0)) | - (static_cast(in.at(1)) << 8) | - (static_cast(in.at(2)) << 16) | - (static_cast(in.at(3)) << 24); - int mapHeight = static_cast(in.at(4)) | - (static_cast(in.at(5)) << 8) | - (static_cast(in.at(6)) << 16) | - (static_cast(in.at(7)) << 24); - int mapPrimaryTilesetNum = static_cast(in.at(8)) | - (static_cast(in.at(9)) << 8) | - (static_cast(in.at(10)) << 16) | - (static_cast(in.at(11)) << 24); - int mapSecondaryTilesetNum = static_cast(in.at(12)) | - (static_cast(in.at(13)) << 8) | - (static_cast(in.at(14)) << 16) | - (static_cast(in.at(15)) << 24); - - int numMetatiles = mapWidth * mapHeight; - int expectedFileSize = 20 + (numBorderTiles * 2) + (numMetatiles * 2); - if (in.length() != expectedFileSize) { - *error = true; - logError(QString(".map file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length())); - return nullptr; - } - - Blockdata blockdata; - for (int i = mapDataOffset; (i + 1) < in.length(); i += 2) { - uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); - blockdata.append(word); - } - - Blockdata border; - if (numBorderTiles != 0) { - for (int i = 20; (i + 1) < mapDataOffset; i += 2) { - uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); - border.append(word); - } - } - - MapLayout *mapLayout = new MapLayout(); - mapLayout->width = mapWidth; - mapLayout->height = mapHeight; - mapLayout->border_width = (borderWidth == 0) ? DEFAULT_BORDER_WIDTH : borderWidth; - mapLayout->border_height = (borderHeight == 0) ? DEFAULT_BORDER_HEIGHT : borderHeight; - - QList tilesets = project->tilesetLabelsOrdered; - - if (mapPrimaryTilesetNum > tilesets.size()) - mapLayout->tileset_primary_label = tilesets.at(0); - else - mapLayout->tileset_primary_label = tilesets.at(mapPrimaryTilesetNum); - - if (mapSecondaryTilesetNum > tilesets.size()) - mapLayout->tileset_secondary_label = tilesets.at(1); - else - mapLayout->tileset_secondary_label = tilesets.at(mapSecondaryTilesetNum); - - mapLayout->blockdata = blockdata; - - if (!border.isEmpty()) { - mapLayout->border = border; - } - - return mapLayout; -} diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index 5f79129c..073fd867 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -1,6 +1,7 @@ #include "metatile.h" #include "tileset.h" #include "project.h" +#include "utility.h" // Stores how each attribute should be laid out for all metatiles, according to the vanilla games. // Used to set default config values and import maps with AdvanceMap. @@ -9,11 +10,11 @@ static const QMap attributePackersFRLG = { {Metatile::Attr::TerrainType, BitPacker(0x00003E00) }, {Metatile::Attr::EncounterType, BitPacker(0x07000000) }, {Metatile::Attr::LayerType, BitPacker(0x60000000) }, - //{Metatile::Attr::Unused, BitPacker(0x98FFC000) }, + {Metatile::Attr::Unused, BitPacker(0x98FFC000) }, }; static const QMap attributePackersRSE = { {Metatile::Attr::Behavior, BitPacker(0x00FF) }, - //{Metatile::Attr::Unused, BitPacker(0x0F00) }, + {Metatile::Attr::Unused, BitPacker(0x0F00) }, {Metatile::Attr::LayerType, BitPacker(0xF000) }, }; @@ -42,7 +43,7 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) { static int numMetatileIdChars = 4; QString Metatile::getMetatileIdString(uint16_t metatileId) { - return "0x" + QString("%1").arg(metatileId, numMetatileIdChars, 16, QChar('0')).toUpper(); + return Util::toHexString(metatileId, numMetatileIdChars); }; QString Metatile::getMetatileIdStrings(const QList metatileIds) { @@ -127,37 +128,42 @@ void Metatile::setLayout(Project * project) { if (behaviorMask && !project->metatileBehaviorMapInverse.isEmpty()) { uint32_t maxBehavior = project->metatileBehaviorMapInverse.lastKey(); if (packer.clamp(maxBehavior) != maxBehavior) - logWarn(QString("Metatile Behavior mask '0x%1' is insufficient to contain all available options.") - .arg(QString::number(behaviorMask, 16).toUpper())); + logWarn(QString("Metatile Behavior mask '%1' is insufficient to contain largest value '%2'.") + .arg(Util::toHexString(behaviorMask)) + .arg(Util::toHexString(maxBehavior))); } attributePackers.insert(Metatile::Attr::Behavior, packer); // Validate terrain type mask packer.setMask(terrainTypeMask); - const uint32_t maxTerrainType = NUM_METATILE_TERRAIN_TYPES - 1; - if (terrainTypeMask && packer.clamp(maxTerrainType) != maxTerrainType) { - logWarn(QString("Metatile Terrain Type mask '0x%1' is insufficient to contain all %2 available options.") - .arg(QString::number(terrainTypeMask, 16).toUpper()) - .arg(maxTerrainType + 1)); + if (terrainTypeMask && !project->terrainTypeToName.isEmpty()) { + uint32_t maxTerrainType = project->terrainTypeToName.lastKey(); + if (packer.clamp(maxTerrainType) != maxTerrainType) { + logWarn(QString("Metatile Terrain Type mask '%1' is insufficient to contain largest value '%2'.") + .arg(Util::toHexString(terrainTypeMask)) + .arg(Util::toHexString(maxTerrainType))); + } } attributePackers.insert(Metatile::Attr::TerrainType, packer); // Validate encounter type mask packer.setMask(encounterTypeMask); - const uint32_t maxEncounterType = NUM_METATILE_ENCOUNTER_TYPES - 1; - if (encounterTypeMask && packer.clamp(maxEncounterType) != maxEncounterType) { - logWarn(QString("Metatile Encounter Type mask '0x%1' is insufficient to contain all %2 available options.") - .arg(QString::number(encounterTypeMask, 16).toUpper()) - .arg(maxEncounterType + 1)); + if (encounterTypeMask && !project->encounterTypeToName.isEmpty()) { + uint32_t maxEncounterType = project->encounterTypeToName.lastKey(); + if (packer.clamp(maxEncounterType) != maxEncounterType) { + logWarn(QString("Metatile Encounter Type mask '%1' is insufficient to contain largest value '%2'.") + .arg(Util::toHexString(encounterTypeMask)) + .arg(Util::toHexString(maxEncounterType))); + } } attributePackers.insert(Metatile::Attr::EncounterType, packer); - // Validate terrain type mask + // Validate layer type mask packer.setMask(layerTypeMask); - const uint32_t maxLayerType = NUM_METATILE_LAYER_TYPES - 1; + const uint32_t maxLayerType = Metatile::LayerType::Count - 1; if (layerTypeMask && packer.clamp(maxLayerType) != maxLayerType) { - logWarn(QString("Metatile Layer Type mask '0x%1' is insufficient to contain all %2 available options.") - .arg(QString::number(layerTypeMask, 16).toUpper()) + logWarn(QString("Metatile Layer Type mask '%1' is insufficient to contain all %2 available options.") + .arg(Util::toHexString(layerTypeMask)) .arg(maxLayerType + 1)); } attributePackers.insert(Metatile::Attr::LayerType, packer); diff --git a/src/core/metatileparser.cpp b/src/core/metatileparser.cpp deleted file mode 100644 index 104b757a..00000000 --- a/src/core/metatileparser.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "metatileparser.h" -#include "config.h" -#include "log.h" -#include "project.h" -#include - -QList MetatileParser::parse(QString filepath, bool *error, bool primaryTileset) -{ - QFile file(filepath); - if (!file.open(QIODevice::ReadOnly)) { - *error = true; - logError(QString("Could not open Advance Map 1.92 Metatile .bvd file '%1': ").arg(filepath) + file.errorString()); - return { }; - } - - QByteArray in = file.readAll(); - file.close(); - - if (in.length() < 9 || in.length() % 2 != 0) { - *error = true; - logError(QString("Advance Map 1.92 Metatile .bvd file '%1' is an unexpected size.").arg(filepath)); - return { }; - } - - int projIdOffset = in.length() - 4; - int metatileSize = 16; - BaseGameVersion version; - if (in.at(projIdOffset + 0) == 'R' - && in.at(projIdOffset + 1) == 'S' - && in.at(projIdOffset + 2) == 'E' - && in.at(projIdOffset + 3) == ' ') { - // ruby and emerald are handled equally here. - version = BaseGameVersion::pokeemerald; - } else if (in.at(projIdOffset + 0) == 'F' - && in.at(projIdOffset + 1) == 'R' - && in.at(projIdOffset + 2) == 'L' - && in.at(projIdOffset + 3) == 'G') { - version = BaseGameVersion::pokefirered; - } else { - *error = true; - logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE ' or 'FRLG'.")); - return { }; - } - - int attrSize = Metatile::getDefaultAttributesSize(version); - int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary(); - int numMetatiles = static_cast(in.at(0)) | - (static_cast(in.at(1)) << 8) | - (static_cast(in.at(2)) << 16) | - (static_cast(in.at(3)) << 24); - if (numMetatiles > maxMetatiles) { - *error = true; - logError(QString(".bvd file contains data for %1 metatiles, but the maximum number of metatiles is %2.").arg(numMetatiles).arg(maxMetatiles)); - return { }; - } - if (numMetatiles < 1) { - *error = true; - logError(QString(".bvd file contains no data for metatiles.")); - return { }; - } - - int expectedFileSize = 4 + (metatileSize * numMetatiles) + (attrSize * numMetatiles) + 4; - if (in.length() != expectedFileSize) { - *error = true; - logError(QString(".bvd file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length())); - return { }; - } - - QList metatiles; - for (int i = 0; i < numMetatiles; i++) { - Metatile *metatile = new Metatile(); - QList tiles; - for (int j = 0; j < 8; j++) { - int metatileOffset = 4 + i * metatileSize + j * 2; - Tile tile(static_cast( - static_cast(in.at(metatileOffset)) | - (static_cast(in.at(metatileOffset + 1)) << 8))); - tiles.append(tile); - } - - // AdvanceMap .bvd files only contain 8 tiles of data per metatile. - // If the user has triple-layer metatiles enabled we need to fill the remaining 4 tiles ourselves. - if (projectConfig.tripleLayerMetatilesEnabled) { - Tile tile = Tile(); - for (int j = 0; j < 4; j++) - tiles.append(tile); - } - - int attrOffset = 4 + (numMetatiles * metatileSize) + (i * attrSize); - uint32_t attributes = 0; - for (int j = 0; j < attrSize; j++) - attributes |= static_cast(in.at(attrOffset + j)) << (8 * j); - metatile->setAttributes(attributes, version); - metatile->tiles = tiles; - metatiles.append(metatile); - } - - return metatiles; -} diff --git a/src/core/paletteutil.cpp b/src/core/paletteutil.cpp index da281ce6..76a3d0c4 100644 --- a/src/core/paletteutil.cpp +++ b/src/core/paletteutil.cpp @@ -1,4 +1,5 @@ #include "paletteutil.h" +#include "advancemapparser.h" #include "log.h" #include #include @@ -6,7 +7,6 @@ QList parsePal(QString filepath, bool *error); QList parseJASC(QString filepath, bool *error); -QList parseAdvanceMapPal(QString filepath, bool *error); QList parseAdobeColorTable(QString filepath, bool *error); QList parseTileLayerPro(QString filepath, bool *error); QList parseAdvancePaletteEditor(QString filepath, bool *error); @@ -38,14 +38,14 @@ QList PaletteUtil::parse(QString filepath, bool *error) { return QList(); } -void PaletteUtil::writeJASC(QString filepath, QVector palette, int offset, int nColors) { +bool PaletteUtil::writeJASC(const QString &filepath, const QVector &palette, int offset, int nColors) { if (!nColors) { - logWarn(QString("Cannot save a palette with no colors.")); - return; + logError(QString("Cannot save a palette with no colors.")); + return false; } if (offset > palette.size() || offset + nColors > palette.size()) { - logWarn("Palette offset out of range for color table."); - return; + logError("Palette offset out of range for color table."); + return false; } QString text = "JASC-PAL\r\n0100\r\n"; @@ -59,11 +59,13 @@ void PaletteUtil::writeJASC(QString filepath, QVector palette, int offset, } QFile file(filepath); - if (file.open(QIODevice::WriteOnly)) { - file.write(text.toUtf8()); - } else { - logWarn(QString("Could not write to file '%1': ").arg(filepath) + file.errorString()); + if (!file.open(QIODevice::WriteOnly)) { + logError(QString("Could not write to file '%1': ").arg(filepath) + file.errorString()); + return false; } + + file.write(text.toUtf8()); + return true; } QList parsePal(QString filepath, bool *error) { @@ -81,7 +83,7 @@ QList parsePal(QString filepath, bool *error) { return parseJASC(filepath, error); } else { file.close(); - return parseAdvanceMapPal(filepath, error); + return AdvanceMapParser::parsePalette(filepath, error); } } @@ -152,38 +154,6 @@ QList parseJASC(QString filepath, bool *error) { return palette; } -QList parseAdvanceMapPal(QString filepath, bool *error) { - QFile file(filepath); - if (!file.open(QIODevice::ReadOnly)) { - *error = true; - logError(QString("Could not open Advance Map 1.92 palette file '%1': ").arg(filepath) + file.errorString()); - return QList(); - } - - QByteArray in = file.readAll(); - file.close(); - - if (in.length() % 4 != 0) { - *error = true; - logError(QString("Advance Map 1.92 palette file '%1' had an unexpected format. File's length must be a multiple of 4, but the length is %2.").arg(filepath).arg(in.length())); - return QList(); - } - - QList palette; - int i = 0; - while (i < in.length()) { - unsigned char red = static_cast(in.at(i)); - unsigned char green = static_cast(in.at(i + 1)); - unsigned char blue = static_cast(in.at(i + 2)); - palette.append(qRgb(clampColorValue(red), - clampColorValue(green), - clampColorValue(blue))); - i += 4; - } - - return palette; -} - QList parseAdobeColorTable(QString filepath, bool *error) { QFile file(filepath); if (!file.open(QIODevice::ReadOnly)) { diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 9664fdc7..80252c13 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -1,5 +1,6 @@ #include "log.h" #include "parseutil.h" +#include "loadingscreen.h" #include #include @@ -15,28 +16,14 @@ const QRegularExpression ParseUtil::re_poryScriptLabel("\\b(script)(\\((global|l const QRegularExpression ParseUtil::re_globalPoryScriptLabel("\\b(script)(\\((global)\\))?\\s*\\b(?