mirror of
https://github.com/huderlem/porymap.git
synced 2026-04-24 14:57:08 -05:00
Merge branch 'dev' of https://github.com/huderlem/porymap into utility
This commit is contained in:
commit
75b8b2c16c
|
|
@ -8,8 +8,9 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
|
|||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Redesigned the map list, adding new features including opening/editing layouts with no associated map, duplicating maps or layouts (accessible via right-click), editing the names of map groups, rearranging maps and map groups, and hiding empty folders.
|
||||
- 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 a search button to the `Wild Pokémon` tab that shows the encounter data for a species across all maps.
|
||||
|
|
@ -19,12 +20,13 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
|
|||
- Add an option to display a dividing line between tilesets in the Tileset Editor.
|
||||
- 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 buttons to hide and show empty folders in each map tree view.
|
||||
- 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.
|
||||
|
||||
### 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.
|
||||
- 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.
|
||||
|
|
@ -42,6 +44,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
|
|||
- 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.
|
||||
|
||||
### Fixed
|
||||
- Fix `Add Region Map...` not updating the region map settings file.
|
||||
|
|
@ -54,6 +57,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
|
|||
- 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 unsaved.
|
||||
- Fix editing an event's `Custom Attributes` not marking the map as unsaved.
|
||||
|
|
@ -86,6 +90,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
|
|||
- 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
|
||||
|
|
|
|||
|
|
@ -2906,6 +2906,7 @@
|
|||
<addaction name="action_NewMap"/>
|
||||
<addaction name="action_NewLayout"/>
|
||||
<addaction name="actionNew_Tileset"/>
|
||||
<addaction name="actionDuplicate_Current_Map_Layout"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionTileset_Editor"/>
|
||||
<addaction name="actionRegion_Map_Editor"/>
|
||||
|
|
@ -3278,6 +3279,11 @@
|
|||
<string>New Layout...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDuplicate_Current_Map_Layout">
|
||||
<property name="text">
|
||||
<string>Duplicate Current Map/Layout...</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<customwidgets>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>522</width>
|
||||
<height>493</height>
|
||||
<width>530</width>
|
||||
<height>432</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
|
@ -19,244 +19,302 @@
|
|||
<number>9</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Miscellaneous</string>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_MonitorProjectFiles">
|
||||
<property name="toolTip">
|
||||
<string>If checked, a prompt to reload your project will appear if relevant project files are edited</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Monitor project files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_OpenRecentProject">
|
||||
<property name="toolTip">
|
||||
<string>If checked, Porymap will automatically open your most recently opened project on startup</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Open recent project on launch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_CheckForUpdates">
|
||||
<property name="toolTip">
|
||||
<string>If checked, Porymap will automatically alert you on startup if a new release is available</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Automatically check for updates</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_DisableEventWarning">
|
||||
<property name="toolTip">
|
||||
<string>If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Disable warning when deleting events with IDs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_EventSelectionMode">
|
||||
<property name="title">
|
||||
<string>Event Selection Mode</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_EventSelectionMode">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButton_OnSprite">
|
||||
<property name="toolTip">
|
||||
<string>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.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select by clicking on sprite</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButton_WithinRect">
|
||||
<property name="toolTip">
|
||||
<string>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.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select by clicking within bounding rectangle</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_Themes">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Application Theme</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_TextEditor">
|
||||
<property name="title">
|
||||
<string>Preferred Text Editor</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="2" column="0">
|
||||
<widget class="QScrollArea" name="scrollArea_TextEditor">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents_TextEditor">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>492</width>
|
||||
<height>327</height>
|
||||
</rect>
|
||||
<widget class="QWidget" name="tab_General">
|
||||
<attribute name="title">
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="leftMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_MonitorProjectFiles">
|
||||
<property name="toolTip">
|
||||
<string>If checked, a prompt to reload your project will appear if relevant project files are edited</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
</property>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_TextEditorGotoLineHelp">
|
||||
<property name="text">
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_TextEditorGotoLine">
|
||||
<property name="text">
|
||||
<string>Goto Line Command</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_TextEditorOpenFolder">
|
||||
<property name="text">
|
||||
<string>Monitor project files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_OpenRecentProject">
|
||||
<property name="toolTip">
|
||||
<string>If checked, Porymap will automatically open your most recently opened project on startup</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Open recent project on launch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_CheckForUpdates">
|
||||
<property name="toolTip">
|
||||
<string>If checked, Porymap will automatically alert you on startup if a new release is available</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Automatically check for updates</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_Themes">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Application Theme</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_Events">
|
||||
<attribute name="title">
|
||||
<string>Events</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="leftMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_DisableEventWarning">
|
||||
<property name="toolTip">
|
||||
<string>If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Disable warning when deleting events with IDs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_AutocompleteAllScripts">
|
||||
<property name="toolTip">
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Autocomplete Script labels using all possible scripts</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_EventSelectionMode">
|
||||
<property name="title">
|
||||
<string>Selection Mode</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_EventSelectionMode">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButton_OnSprite">
|
||||
<property name="toolTip">
|
||||
<string>The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH).</string>
|
||||
<string>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.</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>e.g. code %D</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_TextEditorOpenFolderHelp">
|
||||
<property name="text">
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
<string>Select by clicking on sprite</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_TextEditorOpenFolder">
|
||||
<property name="text">
|
||||
<string>Open Directory Command</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_TextEditorGotoLine">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButton_WithinRect">
|
||||
<property name="toolTip">
|
||||
<string>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).</string>
|
||||
<string>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.</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>e.g. code --goto %F:%L</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
<property name="text">
|
||||
<string>Select by clicking within bounding rectangle</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>15</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Text Editor</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea_TextEditor">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents_TextEditor">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>476</width>
|
||||
<height>343</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SizeConstraint::SetMinimumSize</enum>
|
||||
</property>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_TextEditorGotoLineHelp">
|
||||
<property name="text">
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_TextEditorGotoLine">
|
||||
<property name="text">
|
||||
<string>Goto Line Command</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_TextEditorOpenFolder">
|
||||
<property name="toolTip">
|
||||
<string>The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH).</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>e.g. code %D</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_TextEditorOpenFolderHelp">
|
||||
<property name="text">
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_TextEditorOpenFolder">
|
||||
<property name="text">
|
||||
<string>Open Directory Command</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_TextEditorGotoLine">
|
||||
<property name="toolTip">
|
||||
<string>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).</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>e.g. code --goto %F:%L</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Policy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>15</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
<set>QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
|||
|
|
@ -1341,6 +1341,29 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_MaxEvents" native="true">
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_MaxEvents">
|
||||
<property name="text">
|
||||
<string>Maximum Events per Event group</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="NoScrollSpinBox" name="spinBox_MaxEvents">
|
||||
<property name="toolTip">
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_Events">
|
||||
<property name="title">
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ public:
|
|||
this->textEditorGotoLine = "";
|
||||
this->paletteEditorBitDepth = 24;
|
||||
this->projectSettingsTab = 0;
|
||||
this->loadAllEventScripts = false;
|
||||
this->warpBehaviorWarningDisabled = false;
|
||||
this->eventDeleteWarningDisabled = false;
|
||||
this->eventOverlayEnabled = false;
|
||||
|
|
@ -87,6 +88,7 @@ public:
|
|||
this->lastUpdateCheckVersion = porymapVersion;
|
||||
this->rateLimitTimes.clear();
|
||||
this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape;
|
||||
this->shownInGameReloadMessage = false;
|
||||
}
|
||||
void addRecentProject(QString project);
|
||||
void setRecentProjects(QStringList projects);
|
||||
|
|
@ -135,6 +137,7 @@ public:
|
|||
QString textEditorGotoLine;
|
||||
int paletteEditorBitDepth;
|
||||
int projectSettingsTab;
|
||||
bool loadAllEventScripts;
|
||||
bool warpBehaviorWarningDisabled;
|
||||
bool eventDeleteWarningDisabled;
|
||||
bool eventOverlayEnabled;
|
||||
|
|
@ -146,6 +149,7 @@ public:
|
|||
QByteArray wildMonChartGeometry;
|
||||
QByteArray newMapDialogGeometry;
|
||||
QByteArray newLayoutDialogGeometry;
|
||||
bool shownInGameReloadMessage;
|
||||
|
||||
protected:
|
||||
virtual QString getConfigFilepath() override;
|
||||
|
|
@ -320,6 +324,7 @@ public:
|
|||
this->unusedTileNormal = 0x3014;
|
||||
this->unusedTileCovered = 0x0000;
|
||||
this->unusedTileSplit = 0x0000;
|
||||
this->maxEventsPerGroup = 255;
|
||||
this->identifiers.clear();
|
||||
this->readKeys.clear();
|
||||
}
|
||||
|
|
@ -389,6 +394,7 @@ public:
|
|||
int collisionSheetWidth;
|
||||
int collisionSheetHeight;
|
||||
QList<uint32_t> warpBehaviors;
|
||||
int maxEventsPerGroup;
|
||||
|
||||
protected:
|
||||
virtual QString getConfigFilepath() override;
|
||||
|
|
|
|||
|
|
@ -41,14 +41,6 @@ public:
|
|||
virtual void visitSign(SignEvent *) = 0;
|
||||
};
|
||||
|
||||
struct EventGraphics
|
||||
{
|
||||
QImage spritesheet;
|
||||
int spriteWidth;
|
||||
int spriteHeight;
|
||||
bool inanimate;
|
||||
};
|
||||
|
||||
|
||||
///
|
||||
/// Event base class -- purely virtual
|
||||
|
|
@ -64,11 +56,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:
|
||||
|
|
@ -119,8 +107,6 @@ public:
|
|||
|
||||
static Event* create(Event::Type type);
|
||||
|
||||
static QMap<Event::Group, const QPixmap*> icons;
|
||||
|
||||
// standard public methods
|
||||
public:
|
||||
|
||||
|
|
@ -143,8 +129,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;
|
||||
|
|
@ -172,25 +158,17 @@ public:
|
|||
void setPixmapItem(DraggablePixmapItem *item);
|
||||
DraggablePixmapItem *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();
|
||||
|
||||
void setIdName(QString newIdName) { this->idName = newIdName; }
|
||||
QString getIdName() const { return this->idName; }
|
||||
|
||||
static QString eventGroupToString(Event::Group group);
|
||||
static QString eventTypeToString(Event::Type type);
|
||||
static Event::Type eventTypeFromString(QString type);
|
||||
static void clearIcons();
|
||||
static void setIcons();
|
||||
static QString groupToString(Event::Group group);
|
||||
static QString typeToString(Event::Type type);
|
||||
static Event::Type typeFromString(QString type);
|
||||
|
||||
// protected attributes
|
||||
protected:
|
||||
|
|
@ -204,9 +182,7 @@ protected:
|
|||
int y = 0;
|
||||
int elevation = 0;
|
||||
|
||||
int spriteWidth = 16;
|
||||
int spriteHeight = 16;
|
||||
bool usingSprite = false;
|
||||
bool usesDefaultPixmap = true;
|
||||
|
||||
// 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.
|
||||
|
|
@ -273,10 +249,6 @@ public:
|
|||
void setFlag(QString newFlag) { this->flag = newFlag; }
|
||||
QString getFlag() const { return this->flag; }
|
||||
|
||||
public:
|
||||
void setFrameFromMovement(QString movement);
|
||||
void setPixmapFromSpritesheet(EventGraphics * gfx);
|
||||
|
||||
|
||||
protected:
|
||||
QString gfx;
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ public:
|
|||
void removeEvent(Event *);
|
||||
void addEvent(Event *);
|
||||
int getIndexOfEvent(Event *) const;
|
||||
bool hasEvent(Event *) const;
|
||||
|
||||
void deleteConnections();
|
||||
QList<MapConnection*> getConnections() const;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ public:
|
|||
|
||||
QString id;
|
||||
QString name;
|
||||
QString newFolderPath;
|
||||
|
||||
int width;
|
||||
int height;
|
||||
|
|
|
|||
|
|
@ -44,25 +44,24 @@ class ParseUtil
|
|||
public:
|
||||
ParseUtil();
|
||||
void set_root(const QString &dir);
|
||||
static QString readTextFile(const QString &path);
|
||||
static QString readTextFile(const QString &path, QString *error = nullptr);
|
||||
void invalidateTextFile(const QString &path);
|
||||
static int textFileLineCount(const QString &path);
|
||||
QList<QStringList> parseAsm(const QString &filename);
|
||||
QStringList readCArray(const QString &filename, const QString &label);
|
||||
QMap<QString, QStringList> readCArrayMulti(const QString &filename);
|
||||
QMap<QString, QString> readNamedIndexCArray(const QString &text, const QString &label);
|
||||
QMap<QString, QString> readNamedIndexCArray(const QString &text, const QString &label, QString *error = nullptr);
|
||||
QString readCIncbin(const QString &text, const QString &label);
|
||||
QMap<QString, QString> readCIncbinMulti(const QString &filepath);
|
||||
QStringList readCIncbinArray(const QString &filename, const QString &label);
|
||||
QMap<QString, int> readCDefinesByRegex(const QString &filename, const QStringList ®exList);
|
||||
QMap<QString, int> readCDefinesByName(const QString &filename, const QStringList &names);
|
||||
QStringList readCDefineNames(const QString &filename, const QStringList ®exList);
|
||||
QMap<QString, int> readCDefinesByRegex(const QString &filename, const QStringList ®exList, QString *error = nullptr);
|
||||
QMap<QString, int> readCDefinesByName(const QString &filename, const QStringList &names, QString *error = nullptr);
|
||||
QStringList readCDefineNames(const QString &filename, const QStringList ®exList, QString *error = nullptr);
|
||||
tsl::ordered_map<QString, QHash<QString, QString>> readCStructs(const QString &, const QString & = "", const QHash<int, QString>& = {});
|
||||
QList<QStringList> getLabelMacros(const QList<QStringList>&, const QString&);
|
||||
QStringList getLabelValues(const QList<QStringList>&, 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<QString> &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.
|
||||
|
|
@ -102,8 +101,8 @@ private:
|
|||
QMap<QString,QString> 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<QString, int> evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex);
|
||||
ParsedDefines readCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error);
|
||||
QMap<QString, int> evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error);
|
||||
bool defineNameMatchesFilter(const QString &name, const QStringList &filterList) const;
|
||||
bool defineNameMatchesFilter(const QString &name, const QList<QRegularExpression> &filterList) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -57,9 +57,8 @@ public:
|
|||
GridSettings gridSettings;
|
||||
|
||||
void setProject(Project * project);
|
||||
void save();
|
||||
void saveProject();
|
||||
void saveUiFields();
|
||||
void saveAll();
|
||||
void saveCurrent();
|
||||
void saveEncounterTabData();
|
||||
|
||||
void closeProject();
|
||||
|
|
@ -110,15 +109,14 @@ public:
|
|||
|
||||
DraggablePixmapItem *addEventPixmapItem(Event *event);
|
||||
void removeEventPixmapItem(Event *event);
|
||||
bool eventLimitReached(Map *, Event::Type);
|
||||
void selectMapEvent(DraggablePixmapItem *item, bool toggle = false);
|
||||
DraggablePixmapItem *addNewEvent(Event::Type type);
|
||||
void updateSelectedEvents();
|
||||
bool canAddEvents(const QList<Event*> &events);
|
||||
void selectMapEvent(Event *event, bool toggle = false);
|
||||
Event *addNewEvent(Event::Type type);
|
||||
void updateEvents();
|
||||
void duplicateSelectedEvents();
|
||||
void redrawAllEvents();
|
||||
void redrawEvents(const QList<Event*> &events);
|
||||
void redrawEventPixmapItem(DraggablePixmapItem *item);
|
||||
QList<DraggablePixmapItem *> getEventPixmapItems();
|
||||
qreal getEventOpacity(const Event *event) const;
|
||||
|
||||
void updateCursorRectPos(int x, int y);
|
||||
|
|
@ -153,7 +151,7 @@ public:
|
|||
CurrentSelectedMetatilesPixmapItem *current_metatile_selection_item = nullptr;
|
||||
QPointer<MovementPermissionsSelector> movement_permissions_selector_item = nullptr;
|
||||
|
||||
QList<DraggablePixmapItem *> *selected_events = nullptr;
|
||||
QList<Event*> selectedEvents;
|
||||
QPointer<ConnectionPixmapItem> selected_connection_item = nullptr;
|
||||
QPointer<MapConnection> connection_to_select = nullptr;
|
||||
|
||||
|
|
@ -185,7 +183,6 @@ public:
|
|||
void shouldReselectEvents();
|
||||
void scaleMapView(int);
|
||||
static void openInTextEditor(const QString &path, int lineNum = 0);
|
||||
bool eventLimitReached(Event::Type type);
|
||||
void setCollisionGraphics();
|
||||
|
||||
public slots:
|
||||
|
|
@ -204,6 +201,7 @@ private:
|
|||
|
||||
EditMode editMode = EditMode::None;
|
||||
|
||||
void save(bool currentOnly);
|
||||
void clearMap();
|
||||
void clearMetatileSelector();
|
||||
void clearMovementPermissionSelector();
|
||||
|
|
|
|||
|
|
@ -182,15 +182,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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ private slots:
|
|||
void on_action_Reload_Project_triggered();
|
||||
void on_action_Close_Project_triggered();
|
||||
void on_action_Save_Project_triggered();
|
||||
void save(bool currentOnly = false);
|
||||
|
||||
void openWarpMap(QString map_name, int event_id, Event::Group event_group);
|
||||
|
||||
|
|
@ -218,7 +219,6 @@ private slots:
|
|||
void on_actionMove_triggered();
|
||||
void on_actionMap_Shift_triggered();
|
||||
|
||||
void addNewEvent(Event::Type type);
|
||||
void tryAddEventTab(QWidget * tab);
|
||||
void displayEventTabs();
|
||||
void updateSelectedEvents();
|
||||
|
|
@ -333,7 +333,7 @@ private:
|
|||
|
||||
MapHeaderForm *mapHeaderForm = nullptr;
|
||||
|
||||
QMap<Event::Group, DraggablePixmapItem*> lastSelectedEvent;
|
||||
QMap<Event::Group, Event*> lastSelectedEvent;
|
||||
|
||||
bool isProgrammaticEventTabChange;
|
||||
|
||||
|
|
@ -361,6 +361,7 @@ private:
|
|||
NewLayoutDialog* createNewLayoutDialog(const Layout *layoutToCopy = nullptr);
|
||||
void openNewLayoutDialog();
|
||||
void openDuplicateLayoutDialog(const QString &layoutId);
|
||||
void openDuplicateMapOrLayoutDialog();
|
||||
void openNewMapGroupDialog();
|
||||
void openNewLocationDialog();
|
||||
void openSubWindow(QWidget * window);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ public:
|
|||
QStringList layoutIdsMaster;
|
||||
QMap<QString, Layout*> mapLayouts;
|
||||
QMap<QString, Layout*> mapLayoutsMaster;
|
||||
QMap<QString, EventGraphics*> eventGraphicsMap;
|
||||
QMap<QString, int> gfxDefines;
|
||||
QString defaultSong;
|
||||
QStringList songNames;
|
||||
|
|
@ -68,7 +67,6 @@ public:
|
|||
QMap<QString, uint16_t> unusedMetatileLabels;
|
||||
QMap<QString, uint32_t> metatileBehaviorMap;
|
||||
QMap<uint32_t, QString> metatileBehaviorMapInverse;
|
||||
QMap<QString, QString> facingDirections;
|
||||
ParseUtil parser;
|
||||
QFileSystemWatcher fileWatcher;
|
||||
QSet<QString> modifiedFiles;
|
||||
|
|
@ -157,6 +155,7 @@ public:
|
|||
|
||||
void initTopLevelMapFields();
|
||||
bool readMapJson(const QString &mapName, QJsonDocument * out);
|
||||
bool loadMapEvent(Map *map, const QJsonObject &json, Event::Type defaultType = Event::Type::None);
|
||||
bool loadMapData(Map*);
|
||||
bool readMapLayouts();
|
||||
Layout *loadLayout(QString layoutId);
|
||||
|
|
@ -167,13 +166,13 @@ public:
|
|||
void loadTilesetMetatileLabels(Tileset*);
|
||||
void readTilesetPaths(Tileset* tileset);
|
||||
|
||||
void saveAll();
|
||||
void saveGlobalData();
|
||||
void saveLayout(Layout *);
|
||||
void saveLayoutBlockdata(Layout *);
|
||||
void saveLayoutBorder(Layout *);
|
||||
void writeBlockdata(QString, const Blockdata &);
|
||||
void saveAllMaps();
|
||||
void saveMap(Map *);
|
||||
void saveAllDataStructures();
|
||||
void saveMap(Map *map, bool skipLayout = false);
|
||||
void saveConfig();
|
||||
void saveMapLayouts();
|
||||
void saveMapGroups();
|
||||
|
|
@ -209,7 +208,10 @@ public:
|
|||
bool readFieldmapMasks();
|
||||
QMap<QString, QMap<QString, QString>> 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);
|
||||
|
|
@ -217,6 +219,7 @@ public:
|
|||
static QString getScriptFileExtension(bool usePoryScript);
|
||||
QString getScriptDefaultString(bool usePoryScript, QString mapName) const;
|
||||
QStringList getEventScriptsFilePaths() const;
|
||||
void insertGlobalScriptLabels(QStringList &scriptLabels) const;
|
||||
|
||||
QString getDefaultPrimaryTilesetLabel() const;
|
||||
QString getDefaultSecondaryTilesetLabel() const;
|
||||
|
|
@ -244,13 +247,25 @@ public:
|
|||
static int getMapDataSize(int width, int height);
|
||||
static bool mapDimensionsValid(int width, int height);
|
||||
bool calculateDefaultMapSize();
|
||||
static int getMaxObjectEvents();
|
||||
int getMaxEvents(Event::Group group);
|
||||
static QString getEmptyMapsecName();
|
||||
static QString getMapGroupPrefix();
|
||||
|
||||
private:
|
||||
QMap<QString, QString> mapSectionDisplayNames;
|
||||
QMap<QString, qint64> modifiedFileTimestamps;
|
||||
QMap<QString, QString> facingDirections;
|
||||
|
||||
struct EventGraphics
|
||||
{
|
||||
QString filepath;
|
||||
bool loaded = false;
|
||||
QImage spritesheet;
|
||||
int spriteWidth = -1;
|
||||
int spriteHeight = -1;
|
||||
bool inanimate = false;
|
||||
};
|
||||
QMap<QString, EventGraphics*> eventGraphicsMap;
|
||||
|
||||
void updateLayout(Layout *);
|
||||
|
||||
|
|
@ -260,6 +275,8 @@ private:
|
|||
void ignoreWatchedFileTemporarily(QString filepath);
|
||||
void recordFileChange(const QString &filepath);
|
||||
|
||||
int maxEventsPerGroup;
|
||||
int maxObjectEvents;
|
||||
static int num_tiles_primary;
|
||||
static int num_tiles_total;
|
||||
static int num_metatiles_primary;
|
||||
|
|
@ -267,7 +284,6 @@ private:
|
|||
static int num_pals_total;
|
||||
static int max_map_data_size;
|
||||
static int default_map_dimension;
|
||||
static int max_object_events;
|
||||
|
||||
signals:
|
||||
void fileChanged(const QString &filepath);
|
||||
|
|
@ -280,6 +296,7 @@ signals:
|
|||
void mapSectionDisplayNameChanged(const QString &idName, const QString &displayName);
|
||||
void mapSectionIdNamesChanged(const QStringList &idNames);
|
||||
void mapsExcluded(const QStringList &excludedMapNames);
|
||||
void eventScriptLabelsRead();
|
||||
};
|
||||
|
||||
#endif // PROJECT_H
|
||||
|
|
|
|||
|
|
@ -31,8 +31,6 @@ public:
|
|||
void invalidateUi();
|
||||
void invalidateValues();
|
||||
|
||||
void populateScriptDropdown(NoScrollComboBox * combo, Project * project);
|
||||
|
||||
virtual void setActive(bool active);
|
||||
|
||||
public:
|
||||
|
|
@ -59,6 +57,8 @@ protected:
|
|||
bool initialized = false;
|
||||
bool connected = false;
|
||||
|
||||
void populateScriptDropdown(NoScrollComboBox * combo, Project * project);
|
||||
|
||||
private:
|
||||
Event *event;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ public:
|
|||
signals:
|
||||
void preferencesSaved();
|
||||
void themeChanged(const QString &theme);
|
||||
void scriptSettingsChanged(bool on);
|
||||
|
||||
private:
|
||||
Ui::PreferenceEditor *ui;
|
||||
|
|
|
|||
|
|
@ -391,6 +391,8 @@ 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") {
|
||||
|
|
@ -423,6 +425,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
} 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 {
|
||||
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key));
|
||||
}
|
||||
|
|
@ -480,6 +484,7 @@ QMap<QString, QString> PorymapConfig::getKeyValueMap() {
|
|||
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));
|
||||
|
|
@ -493,6 +498,7 @@ QMap<QString, QString> PorymapConfig::getKeyValueMap() {
|
|||
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");
|
||||
|
||||
return map;
|
||||
}
|
||||
|
|
@ -804,6 +810,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
const QStringList behaviorList = value.split(",", Qt::SkipEmptyParts);
|
||||
for (auto s : behaviorList)
|
||||
this->warpBehaviors.append(getConfigUint32(key, s));
|
||||
} else if (key == "max_events_per_group") {
|
||||
this->maxEventsPerGroup = getConfigInteger(key, value, 1, INT_MAX, 255);
|
||||
} else {
|
||||
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key));
|
||||
}
|
||||
|
|
@ -899,6 +907,7 @@ QMap<QString, QString> ProjectConfig::getKeyValueMap() {
|
|||
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));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -328,10 +328,7 @@ void EventCreate::redo() {
|
|||
|
||||
map->addEvent(event);
|
||||
editor->addEventPixmapItem(event);
|
||||
|
||||
// select this event
|
||||
editor->selected_events->clear();
|
||||
editor->selectMapEvent(event->getPixmapItem());
|
||||
editor->selectMapEvent(event);
|
||||
}
|
||||
|
||||
void EventCreate::undo() {
|
||||
|
|
@ -374,9 +371,9 @@ void EventDelete::redo() {
|
|||
editor->removeEventPixmapItem(event);
|
||||
}
|
||||
|
||||
editor->selected_events->clear();
|
||||
editor->selectedEvents.clear();
|
||||
if (nextSelectedEvent)
|
||||
editor->selected_events->append(nextSelectedEvent->getPixmapItem());
|
||||
editor->selectedEvents.append(nextSelectedEvent);
|
||||
editor->shouldReselectEvents();
|
||||
}
|
||||
|
||||
|
|
@ -386,11 +383,7 @@ void EventDelete::undo() {
|
|||
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();
|
||||
|
|
@ -427,11 +420,7 @@ void EventDuplicate::redo() {
|
|||
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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
#include "project.h"
|
||||
#include "config.h"
|
||||
|
||||
QMap<Event::Group, const QPixmap*> Event::icons;
|
||||
|
||||
Event* Event::create(Event::Type type) {
|
||||
switch (type) {
|
||||
case Event::Type::Object: return new ObjectEvent();
|
||||
|
|
@ -75,112 +73,43 @@ 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 "Heal Location";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
const QMap<Event::Group, QString> 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_heal_location";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
const QMap<Event::Type, QString> typeToStringMap = {
|
||||
{Event::Type::Object, "object"},
|
||||
{Event::Type::CloneObject, "clone_object"},
|
||||
{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::typeToString(Event::Type type) {
|
||||
return typeToStringMap.value(type);
|
||||
}
|
||||
|
||||
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_heal_location") {
|
||||
return Event::Type::HealLocation;
|
||||
} else {
|
||||
return Event::Type::None;
|
||||
}
|
||||
Event::Type Event::typeFromString(QString type) {
|
||||
return typeToStringMap.key(type, Event::Type::None);
|
||||
}
|
||||
|
||||
void Event::loadPixmap(Project *) {
|
||||
const QPixmap * pixmap = Event::icons.value(this->getEventGroup());
|
||||
this->pixmap = pixmap ? *pixmap : QPixmap();
|
||||
void Event::loadPixmap(Project *project) {
|
||||
this->pixmap = project->getEventPixmap(this->getEventGroup());
|
||||
this->usesDefaultPixmap = true;
|
||||
}
|
||||
|
||||
void Event::clearIcons() {
|
||||
qDeleteAll(icons);
|
||||
icons.clear();
|
||||
}
|
||||
|
||||
void Event::setIcons() {
|
||||
clearIcons();
|
||||
const int w = 16;
|
||||
const int h = 16;
|
||||
static const QPixmap defaultIcons = QPixmap(":/images/Entities_16x16.png");
|
||||
|
||||
// Custom event icons may be provided by the user.
|
||||
const int numIcons = qMin(defaultIcons.width() / w, static_cast<int>(Event::Group::None));
|
||||
for (int i = 0; i < numIcons; i++) {
|
||||
Event::Group group = static_cast<Event::Group>(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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Event *ObjectEvent::duplicate() const {
|
||||
|
|
@ -189,6 +118,7 @@ Event *ObjectEvent::duplicate() const {
|
|||
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());
|
||||
|
|
@ -216,6 +146,9 @@ OrderedJson::object ObjectEvent::buildEventJson(Project *) {
|
|||
if (projectConfig.eventCloneObjectEnabled) {
|
||||
objectJson["type"] = "object";
|
||||
}
|
||||
QString idName = this->getIdName();
|
||||
if (!idName.isEmpty())
|
||||
objectJson["local_id"] = idName;
|
||||
objectJson["graphics_id"] = this->getGfx();
|
||||
objectJson["x"] = this->getX();
|
||||
objectJson["y"] = this->getY();
|
||||
|
|
@ -236,6 +169,7 @@ bool ObjectEvent::loadFromJson(const QJsonObject &json, Project *) {
|
|||
this->setX(ParseUtil::jsonToInt(json["x"]));
|
||||
this->setY(ParseUtil::jsonToInt(json["y"]));
|
||||
this->setElevation(ParseUtil::jsonToInt(json["elevation"]));
|
||||
this->setIdName(ParseUtil::jsonToQString(json["local_id"]));
|
||||
this->setGfx(ParseUtil::jsonToQString(json["graphics_id"]));
|
||||
this->setMovement(ParseUtil::jsonToQString(json["movement_type"]));
|
||||
this->setRadiusX(ParseUtil::jsonToInt(json["movement_range_x"]));
|
||||
|
|
@ -259,10 +193,10 @@ void ObjectEvent::setDefaultValues(Project *project) {
|
|||
this->setRadiusX(0);
|
||||
this->setRadiusY(0);
|
||||
this->setSightRadiusBerryTreeID("0");
|
||||
this->setFrameFromMovement(project->facingDirections.value(this->getMovement()));
|
||||
}
|
||||
|
||||
const QSet<QString> expectedObjectFields = {
|
||||
"local_id",
|
||||
"graphics_id",
|
||||
"elevation",
|
||||
"movement_type",
|
||||
|
|
@ -285,78 +219,11 @@ QSet<QString> ObjectEvent::getExpectedFields() {
|
|||
}
|
||||
|
||||
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.
|
||||
this->pixmap = project->getEventPixmap(this->gfx, this->movement);
|
||||
if (!this->pixmap.isNull()) {
|
||||
this->usesDefaultPixmap = false;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -368,6 +235,7 @@ Event *CloneObjectEvent::duplicate() const {
|
|||
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());
|
||||
|
|
@ -388,6 +256,9 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) {
|
|||
OrderedJson::object cloneJson;
|
||||
|
||||
cloneJson["type"] = "clone";
|
||||
QString idName = this->getIdName();
|
||||
if (!idName.isEmpty())
|
||||
cloneJson["local_id"] = idName;
|
||||
cloneJson["graphics_id"] = this->getGfx();
|
||||
cloneJson["x"] = this->getX();
|
||||
cloneJson["y"] = this->getY();
|
||||
|
|
@ -402,6 +273,7 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) {
|
|||
bool CloneObjectEvent::loadFromJson(const QJsonObject &json, Project *project) {
|
||||
this->setX(ParseUtil::jsonToInt(json["x"]));
|
||||
this->setY(ParseUtil::jsonToInt(json["y"]));
|
||||
this->setIdName(ParseUtil::jsonToQString(json["local_id"]));
|
||||
this->setGfx(ParseUtil::jsonToQString(json["graphics_id"]));
|
||||
this->setTargetID(ParseUtil::jsonToInt(json["target_local_id"]));
|
||||
|
||||
|
|
@ -424,6 +296,7 @@ void CloneObjectEvent::setDefaultValues(Project *project) {
|
|||
|
||||
const QSet<QString> expectedCloneObjectFields = {
|
||||
"type",
|
||||
"local_id",
|
||||
"graphics_id",
|
||||
"target_local_id",
|
||||
"target_map",
|
||||
|
|
@ -452,19 +325,7 @@ void CloneObjectEvent::loadPixmap(Project *project) {
|
|||
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);
|
||||
}
|
||||
ObjectEvent::loadPixmap(project);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -147,13 +147,12 @@ QStringList Map::getScriptLabels(Event::Group group) {
|
|||
scriptLabels = scriptTracker.getScripts();
|
||||
}
|
||||
|
||||
// Add scripts from map's scripts file, and empty names.
|
||||
// Add labels from the map's scripts file
|
||||
scriptLabels.append(m_scriptsFileLabels);
|
||||
scriptLabels.sort(Qt::CaseInsensitive);
|
||||
scriptLabels.prepend("0x0");
|
||||
scriptLabels.prepend("NULL");
|
||||
|
||||
scriptLabels.removeAll("");
|
||||
scriptLabels.removeAll("0");
|
||||
scriptLabels.removeAll("0x0");
|
||||
scriptLabels.removeDuplicates();
|
||||
|
||||
return scriptLabels;
|
||||
|
|
@ -164,7 +163,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(m_name));
|
||||
.arg(!m_sharedScriptsMap.isEmpty() ? m_sharedScriptsMap : m_name));
|
||||
auto extension = Project::getScriptFileExtension(usePoryscript);
|
||||
if (usePoryscript && !QFile::exists(path + extension))
|
||||
extension = Project::getScriptFileExtension(false);
|
||||
|
|
@ -224,6 +223,10 @@ 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(m_ownedConnections);
|
||||
m_ownedConnections.clear();
|
||||
|
|
|
|||
|
|
@ -434,5 +434,5 @@ QPixmap Layout::getLayoutItemPixmap() {
|
|||
}
|
||||
|
||||
bool Layout::hasUnsavedChanges() const {
|
||||
return !this->editHistory.isClean();
|
||||
return !this->editHistory.isClean() || !this->newFolderPath.isEmpty();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,10 +67,10 @@ QString ParseUtil::createErrorMessage(const QString &message, const QString &exp
|
|||
return QString("%1:%2:%3: %4").arg(this->file).arg(lineNum).arg(colNum).arg(message);
|
||||
}
|
||||
|
||||
QString ParseUtil::readTextFile(const QString &path) {
|
||||
QString ParseUtil::readTextFile(const QString &path, QString *error) {
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
logError(QString("Could not open '%1': ").arg(path) + file.errorString());
|
||||
if (error) *error = file.errorString();
|
||||
return QString();
|
||||
}
|
||||
QTextStream in(&file);
|
||||
|
|
@ -380,7 +380,7 @@ bool ParseUtil::defineNameMatchesFilter(const QString &name, const QList<QRegula
|
|||
return false;
|
||||
}
|
||||
|
||||
ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const QStringList &filterList, bool useRegex) {
|
||||
ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error) {
|
||||
ParsedDefines result;
|
||||
this->file = filename;
|
||||
|
||||
|
|
@ -389,12 +389,9 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const
|
|||
}
|
||||
|
||||
QString filepath = this->root + "/" + this->file;
|
||||
this->text = readTextFile(filepath);
|
||||
|
||||
if (this->text.isNull()) {
|
||||
logError(QString("Failed to read C defines file: '%1'").arg(filepath));
|
||||
this->text = readTextFile(filepath, error);
|
||||
if (this->text.isNull())
|
||||
return result;
|
||||
}
|
||||
|
||||
static const QRegularExpression re_extraChars("(//.*)|(\\/+\\*+[^*]*\\*+\\/+)");
|
||||
this->text.replace(re_extraChars, "");
|
||||
|
|
@ -466,8 +463,8 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const
|
|||
}
|
||||
|
||||
// Read all the define names and their expressions in the specified file, then evaluate the ones matching the search text (and any they depend on).
|
||||
QMap<QString, int> ParseUtil::evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex) {
|
||||
ParsedDefines defines = readCDefines(filename, filterList, useRegex);
|
||||
QMap<QString, int> ParseUtil::evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error) {
|
||||
ParsedDefines defines = readCDefines(filename, filterList, useRegex, error);
|
||||
|
||||
// Evaluate defines
|
||||
QMap<QString, int> filteredValues;
|
||||
|
|
@ -486,20 +483,20 @@ QMap<QString, int> ParseUtil::evaluateCDefines(const QString &filename, const QS
|
|||
}
|
||||
|
||||
// Find and evaluate a specific set of defines with known names.
|
||||
QMap<QString, int> ParseUtil::readCDefinesByName(const QString &filename, const QStringList &names) {
|
||||
return evaluateCDefines(filename, names, false);
|
||||
QMap<QString, int> ParseUtil::readCDefinesByName(const QString &filename, const QStringList &names, QString *error) {
|
||||
return evaluateCDefines(filename, names, false, error);
|
||||
}
|
||||
|
||||
// Find and evaluate an unknown list of defines with a known name pattern.
|
||||
QMap<QString, int> ParseUtil::readCDefinesByRegex(const QString &filename, const QStringList ®exList) {
|
||||
return evaluateCDefines(filename, regexList, true);
|
||||
QMap<QString, int> ParseUtil::readCDefinesByRegex(const QString &filename, const QStringList ®exList, QString *error) {
|
||||
return evaluateCDefines(filename, regexList, true, error);
|
||||
}
|
||||
|
||||
// Find an unknown list of defines with a known name pattern.
|
||||
// Similar to readCDefinesByRegex, but for cases where we only need to show a list of define names.
|
||||
// We can skip evaluating any expressions (and by extension skip reporting any errors from this process).
|
||||
QStringList ParseUtil::readCDefineNames(const QString &filename, const QStringList ®exList) {
|
||||
return readCDefines(filename, regexList, true).filteredNames;
|
||||
QStringList ParseUtil::readCDefineNames(const QString &filename, const QStringList ®exList, QString *error) {
|
||||
return readCDefines(filename, regexList, true, error).filteredNames;
|
||||
}
|
||||
|
||||
QStringList ParseUtil::readCArray(const QString &filename, const QString &label) {
|
||||
|
|
@ -558,8 +555,8 @@ QMap<QString, QStringList> ParseUtil::readCArrayMulti(const QString &filename) {
|
|||
return map;
|
||||
}
|
||||
|
||||
QMap<QString, QString> ParseUtil::readNamedIndexCArray(const QString &filename, const QString &label) {
|
||||
this->text = readTextFile(this->root + "/" + filename);
|
||||
QMap<QString, QString> ParseUtil::readNamedIndexCArray(const QString &filename, const QString &label, QString *error) {
|
||||
this->text = readTextFile(this->root + "/" + filename, error);
|
||||
QMap<QString, QString> map;
|
||||
|
||||
QRegularExpression re_text(QString(R"(\b%1\b\s*(\[?[^\]]*\])?\s*=\s*\{([^\}]*)\})").arg(label));
|
||||
|
|
@ -659,10 +656,10 @@ QStringList ParseUtil::getLabelValues(const QList<QStringList> &list, const QStr
|
|||
return values;
|
||||
}
|
||||
|
||||
bool ParseUtil::tryParseJsonFile(QJsonDocument *out, const QString &filepath) {
|
||||
bool ParseUtil::tryParseJsonFile(QJsonDocument *out, const QString &filepath, QString *error) {
|
||||
QFile file(filepath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
logError(QString("Error: Could not open %1 for reading").arg(filepath));
|
||||
if (error) *error = file.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -671,7 +668,7 @@ bool ParseUtil::tryParseJsonFile(QJsonDocument *out, const QString &filepath) {
|
|||
const QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &parseError);
|
||||
file.close();
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
logError(QString("Error: Failed to parse json file %1: %2").arg(filepath).arg(parseError.errorString()));
|
||||
if (error) *error = parseError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -679,23 +676,15 @@ bool ParseUtil::tryParseJsonFile(QJsonDocument *out, const QString &filepath) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ParseUtil::tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath) {
|
||||
bool ParseUtil::tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath, QString *error) {
|
||||
QString err;
|
||||
QString jsonTxt = readTextFile(filepath);
|
||||
*out = OrderedJson::parse(jsonTxt, err).object_items();
|
||||
if (!err.isEmpty()) {
|
||||
logError(QString("Error: Failed to parse json file %1: %2").arg(filepath).arg(err));
|
||||
QString jsonTxt = readTextFile(filepath, error);
|
||||
if (error && !error->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseUtil::ensureFieldsExist(const QJsonObject &obj, const QList<QString> &fields) {
|
||||
for (QString field : fields) {
|
||||
if (!obj.contains(field)) {
|
||||
logError(QString("JSON object is missing field '%1'.").arg(field));
|
||||
return false;
|
||||
}
|
||||
*out = OrderedJson::parse(jsonTxt, error).object_items();
|
||||
if (error && !error->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
210
src/editor.cpp
210
src/editor.cpp
|
|
@ -13,6 +13,7 @@
|
|||
#include "customattributesframe.h"
|
||||
#include "validator.h"
|
||||
#include "message.h"
|
||||
#include "eventframes.h"
|
||||
#include <QCheckBox>
|
||||
#include <QPainter>
|
||||
#include <QMouseEvent>
|
||||
|
|
@ -28,7 +29,6 @@ QList<QList<const QImage*>> Editor::collisionIcons;
|
|||
Editor::Editor(Ui::MainWindow* ui)
|
||||
{
|
||||
this->ui = ui;
|
||||
this->selected_events = new QList<DraggablePixmapItem*>;
|
||||
this->settings = new Settings();
|
||||
this->playerViewRect = new MovableRect(&this->settings->playerViewRectEnabled, 30 * 8, 20 * 8, qRgb(255, 255, 255));
|
||||
this->cursorMapTileRect = new CursorTileRect(&this->settings->cursorTileRectEnabled, qRgb(255, 255, 255));
|
||||
|
|
@ -40,7 +40,7 @@ Editor::Editor(Ui::MainWindow* ui)
|
|||
/// the index is changed.
|
||||
connect(&editGroup, &QUndoGroup::indexChanged, [this](int) {
|
||||
if (selectNewEvents) {
|
||||
updateSelectedEvents();
|
||||
updateEvents();
|
||||
selectNewEvents = false;
|
||||
}
|
||||
});
|
||||
|
|
@ -58,7 +58,6 @@ Editor::Editor(Ui::MainWindow* ui)
|
|||
|
||||
Editor::~Editor()
|
||||
{
|
||||
delete this->selected_events;
|
||||
delete this->settings;
|
||||
delete this->playerViewRect;
|
||||
delete this->cursorMapTileRect;
|
||||
|
|
@ -69,28 +68,30 @@ Editor::~Editor()
|
|||
closeProject();
|
||||
}
|
||||
|
||||
void Editor::saveProject() {
|
||||
if (project) {
|
||||
saveUiFields();
|
||||
project->saveAllMaps();
|
||||
project->saveAllDataStructures();
|
||||
}
|
||||
void Editor::saveCurrent() {
|
||||
save(true);
|
||||
}
|
||||
|
||||
void Editor::save() {
|
||||
if (this->project && this->map) {
|
||||
saveUiFields();
|
||||
this->project->saveMap(this->map);
|
||||
this->project->saveAllDataStructures();
|
||||
}
|
||||
else if (this->project && this->layout) {
|
||||
this->project->saveLayout(this->layout);
|
||||
this->project->saveAllDataStructures();
|
||||
}
|
||||
void Editor::saveAll() {
|
||||
save(false);
|
||||
}
|
||||
|
||||
void Editor::saveUiFields() {
|
||||
void Editor::save(bool currentOnly) {
|
||||
if (!this->project)
|
||||
return;
|
||||
|
||||
saveEncounterTabData();
|
||||
|
||||
if (currentOnly) {
|
||||
if (this->map) {
|
||||
this->project->saveMap(this->map);
|
||||
} else if (this->layout) {
|
||||
this->project->saveLayout(this->layout);
|
||||
}
|
||||
this->project->saveGlobalData();
|
||||
} else {
|
||||
this->project->saveAll();
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::setProject(Project * project) {
|
||||
|
|
@ -651,6 +652,9 @@ void Editor::configureEncounterJSON(QWidget *window) {
|
|||
}
|
||||
|
||||
void Editor::saveEncounterTabData() {
|
||||
if (!this->map || !this->project)
|
||||
return;
|
||||
|
||||
// This function does not save to disk so it is safe to use before user clicks Save.
|
||||
QStackedWidget *stack = ui->stackedWidget_WildMons;
|
||||
QComboBox *labelCombo = ui->comboBox_EncounterGroupLabel;
|
||||
|
|
@ -1167,7 +1171,7 @@ bool Editor::setMap(QString map_name) {
|
|||
editGroup.addStack(map->editHistory());
|
||||
editGroup.setActiveStack(map->editHistory());
|
||||
|
||||
selected_events->clear();
|
||||
this->selectedEvents.clear();
|
||||
if (!displayMap()) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1176,7 +1180,7 @@ bool Editor::setMap(QString map_name) {
|
|||
connect(map, &Map::openScriptRequested, this, &Editor::openScript);
|
||||
connect(map, &Map::connectionAdded, this, &Editor::displayConnection);
|
||||
connect(map, &Map::connectionRemoved, this, &Editor::removeConnectionPixmap);
|
||||
updateSelectedEvents();
|
||||
updateEvents();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1330,17 +1334,13 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i
|
|||
} else {
|
||||
// Left-clicking while in paint mode will add a new event of the
|
||||
// type of the first currently selected events.
|
||||
// Disallow adding heal locations, deleting them is not possible yet
|
||||
Event::Type eventType = Event::Type::Object;
|
||||
if (this->selected_events->size() > 0)
|
||||
eventType = this->selected_events->first()->event->getEventType();
|
||||
if (!this->selectedEvents.isEmpty())
|
||||
eventType = this->selectedEvents.first()->getEventType();
|
||||
|
||||
DraggablePixmapItem *newEvent = addNewEvent(eventType);
|
||||
if (newEvent) {
|
||||
newEvent->move(pos.x(), pos.y());
|
||||
emit eventsChanged();
|
||||
selectMapEvent(newEvent);
|
||||
}
|
||||
Event* event = addNewEvent(eventType);
|
||||
if (event && event->getPixmapItem())
|
||||
event->getPixmapItem()->moveTo(pos);
|
||||
}
|
||||
} else if (eventEditAction == EditAction::Select) {
|
||||
// do nothing here, at least for now
|
||||
|
|
@ -1356,15 +1356,9 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i
|
|||
if (pos.x() != selection_origin.x() || pos.y() != selection_origin.y()) {
|
||||
int xDelta = pos.x() - selection_origin.x();
|
||||
int yDelta = pos.y() - selection_origin.y();
|
||||
|
||||
QList<Event *> selectedEvents;
|
||||
|
||||
for (DraggablePixmapItem *pixmapItem : getEventPixmapItems()) {
|
||||
selectedEvents.append(pixmapItem->event);
|
||||
}
|
||||
selection_origin = QPoint(pos.x(), pos.y());
|
||||
|
||||
map->commit(new EventShift(selectedEvents, xDelta, yDelta, this->eventShiftActionId));
|
||||
this->map->commit(new EventShift(this->map->getEvents(), xDelta, yDelta, this->eventShiftActionId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1665,7 +1659,7 @@ void Editor::clearMapEvents() {
|
|||
delete events_group;
|
||||
events_group = nullptr;
|
||||
}
|
||||
selected_events->clear();
|
||||
this->selectedEvents.clear();
|
||||
}
|
||||
|
||||
void Editor::displayMapEvents() {
|
||||
|
|
@ -1682,7 +1676,7 @@ void Editor::displayMapEvents() {
|
|||
}
|
||||
|
||||
DraggablePixmapItem *Editor::addEventPixmapItem(Event *event) {
|
||||
this->project->setEventPixmap(event);
|
||||
this->project->loadEventPixmap(event);
|
||||
auto item = new DraggablePixmapItem(event, this);
|
||||
redrawEventPixmapItem(item);
|
||||
this->events_group->addToGroup(item);
|
||||
|
|
@ -1694,7 +1688,7 @@ void Editor::removeEventPixmapItem(Event *event) {
|
|||
if (!item) return;
|
||||
|
||||
this->events_group->removeFromGroup(item);
|
||||
this->selected_events->removeOne(item);
|
||||
this->selectedEvents.removeOne(event);
|
||||
|
||||
event->setPixmapItem(nullptr);
|
||||
delete item;
|
||||
|
|
@ -1959,14 +1953,6 @@ void Editor::redrawEvents(const QList<Event*> &events) {
|
|||
}
|
||||
}
|
||||
|
||||
QList<DraggablePixmapItem *> Editor::getEventPixmapItems() {
|
||||
QList<DraggablePixmapItem *> list;
|
||||
for (QGraphicsItem *child : events_group->childItems()) {
|
||||
list.append(static_cast<DraggablePixmapItem *>(child));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
qreal Editor::getEventOpacity(const Event *event) const {
|
||||
// There are 4 possible opacities for an event's sprite:
|
||||
// - Off the Events tab, and the event overlay is off (0.0)
|
||||
|
|
@ -1975,18 +1961,18 @@ qreal Editor::getEventOpacity(const Event *event) const {
|
|||
// - On the Events tab, and the event has a custom sprite (1.0)
|
||||
if (this->editMode != EditMode::Events)
|
||||
return porymapConfig.eventOverlayEnabled ? 0.5 : 0.0;
|
||||
return event->getUsingSprite() ? 1.0 : 0.7;
|
||||
return event->getUsesDefaultPixmap() ? 0.7 : 1.0;
|
||||
}
|
||||
|
||||
void Editor::redrawEventPixmapItem(DraggablePixmapItem *item) {
|
||||
if (item && item->event && !item->event->getPixmap().isNull()) {
|
||||
item->setOpacity(getEventOpacity(item->event));
|
||||
project->setEventPixmap(item->event, true);
|
||||
project->loadEventPixmap(item->event, true);
|
||||
item->setPixmap(item->event->getPixmap());
|
||||
item->setShapeMode(porymapConfig.eventSelectionShapeMode);
|
||||
|
||||
if (this->editMode == EditMode::Events) {
|
||||
if (selected_events && selected_events->contains(item)) {
|
||||
if (this->selectedEvents.contains(item->event)) {
|
||||
// Draw the selection rectangle
|
||||
QImage image = item->pixmap().toImage();
|
||||
QPainter painter(&image);
|
||||
|
|
@ -2031,44 +2017,40 @@ void Editor::updateWarpEventWarning(Event *event) {
|
|||
void Editor::updateWarpEventWarnings() {
|
||||
if (porymapConfig.warpBehaviorWarningDisabled)
|
||||
return;
|
||||
if (selected_events) {
|
||||
for (auto selection : *selected_events)
|
||||
updateWarpEventWarning(selection->event);
|
||||
}
|
||||
for (const auto &event : this->selectedEvents)
|
||||
updateWarpEventWarning(event);
|
||||
}
|
||||
|
||||
void Editor::shouldReselectEvents() {
|
||||
selectNewEvents = true;
|
||||
}
|
||||
|
||||
void Editor::updateSelectedEvents() {
|
||||
for (DraggablePixmapItem *item : getEventPixmapItems()) {
|
||||
redrawEventPixmapItem(item);
|
||||
}
|
||||
|
||||
// TODO: This is frequently used to do more work than necessary.
|
||||
void Editor::updateEvents() {
|
||||
redrawAllEvents();
|
||||
emit eventsChanged();
|
||||
}
|
||||
|
||||
void Editor::selectMapEvent(DraggablePixmapItem *item, bool toggle) {
|
||||
if (!selected_events || !item)
|
||||
void Editor::selectMapEvent(Event *event, bool toggle) {
|
||||
if (!event)
|
||||
return;
|
||||
|
||||
if (!toggle) {
|
||||
// Selecting just this event
|
||||
selected_events->clear();
|
||||
selected_events->append(item);
|
||||
} else if (!selected_events->contains(item)) {
|
||||
this->selectedEvents.clear();
|
||||
this->selectedEvents.append(event);
|
||||
} else if (!this->selectedEvents.contains(event)) {
|
||||
// Adding event to group selection
|
||||
selected_events->append(item);
|
||||
} else if (selected_events->length() > 1) {
|
||||
this->selectedEvents.append(event);
|
||||
} else if (this->selectedEvents.length() > 1) {
|
||||
// Removing event from group selection
|
||||
selected_events->removeOne(item);
|
||||
this->selectedEvents.removeOne(event);
|
||||
} else {
|
||||
// Attempting to toggle the only currently-selected event.
|
||||
// Unselecting an event this way would be unexpected, so we ignore it.
|
||||
return;
|
||||
}
|
||||
updateSelectedEvents();
|
||||
updateEvents();
|
||||
}
|
||||
|
||||
void Editor::selectedEventIndexChanged(int index, Event::Group eventGroup) {
|
||||
|
|
@ -2076,64 +2058,82 @@ void Editor::selectedEventIndexChanged(int index, Event::Group eventGroup) {
|
|||
index = index - event_offs;
|
||||
Event *event = this->map->getEvent(eventGroup, index);
|
||||
|
||||
if (event && event->getPixmapItem()) {
|
||||
this->selectMapEvent(event->getPixmapItem());
|
||||
if (event) {
|
||||
selectMapEvent(event);
|
||||
} else {
|
||||
updateSelectedEvents();
|
||||
updateEvents();
|
||||
}
|
||||
}
|
||||
|
||||
bool Editor::canAddEvents(const QList<Event*> &events) {
|
||||
if (!this->project || !this->map)
|
||||
return false;
|
||||
|
||||
QMap<Event::Group, int> newEventCounts;
|
||||
for (const auto &event : events) {
|
||||
Event::Group group = event->getEventGroup();
|
||||
int maxEvents = this->project->getMaxEvents(group);
|
||||
if (this->map->getNumEvents(group) + newEventCounts[group]++ >= maxEvents) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Editor::duplicateSelectedEvents() {
|
||||
if (!selected_events || !selected_events->length() || !map || !current_view || this->getEditingLayout())
|
||||
if (this->selectedEvents.isEmpty() || !project || !map || !current_view || this->getEditingLayout())
|
||||
return;
|
||||
|
||||
QList<Event *> selectedEvents;
|
||||
for (int i = 0; i < selected_events->length(); i++) {
|
||||
Event *original = selected_events->at(i)->event;
|
||||
Event::Type eventType = original->getEventType();
|
||||
if (eventLimitReached(eventType)) {
|
||||
logWarn(QString("Skipping duplication, the map limit for events of type '%1' has been reached.").arg(Event::eventTypeToString(eventType)));
|
||||
continue;
|
||||
}
|
||||
Event *duplicate = original->duplicate();
|
||||
duplicate->setX(duplicate->getX() + 1);
|
||||
duplicate->setY(duplicate->getY() + 1);
|
||||
selectedEvents.append(duplicate);
|
||||
QList<Event *> duplicatedEvents;
|
||||
for (const auto &event : this->selectedEvents) {
|
||||
duplicatedEvents.append(event->duplicate());
|
||||
}
|
||||
map->commit(new EventDuplicate(this, map, selectedEvents));
|
||||
if (!canAddEvents(duplicatedEvents)) {
|
||||
WarningMessage::show(QStringLiteral("Unable to duplicate, the maximum number of events would be exceeded."), ui->graphicsView_Map);
|
||||
qDeleteAll(duplicatedEvents);
|
||||
return;
|
||||
}
|
||||
this->map->commit(new EventDuplicate(this, this->map, duplicatedEvents));
|
||||
}
|
||||
|
||||
DraggablePixmapItem *Editor::addNewEvent(Event::Type type) {
|
||||
if (!project || !map || eventLimitReached(type))
|
||||
Event *Editor::addNewEvent(Event::Type type) {
|
||||
if (!this->project || !this->map)
|
||||
return nullptr;
|
||||
|
||||
Event::Group group = Event::typeToGroup(type);
|
||||
int maxEvents = this->project->getMaxEvents(group);
|
||||
if (this->map->getNumEvents(group) >= maxEvents) {
|
||||
WarningMessage::show(QString("The maximum number of %1 events (%2) has been reached.").arg(Event::groupToString(group)).arg(maxEvents), ui->graphicsView_Map);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Event *event = Event::create(type);
|
||||
if (!event)
|
||||
return nullptr;
|
||||
|
||||
event->setMap(this->map);
|
||||
event->setDefaultValues(this->project);
|
||||
map->commit(new EventCreate(this, map, event));
|
||||
return event->getPixmapItem();
|
||||
}
|
||||
|
||||
// Currently only object events have an explicit limit
|
||||
bool Editor::eventLimitReached(Event::Type event_type) {
|
||||
if (project && map) {
|
||||
if (Event::typeToGroup(event_type) == Event::Group::Object)
|
||||
return map->getNumEvents(Event::Group::Object) >= project->getMaxObjectEvents();
|
||||
// This will add the event to the map, create the event pixmap item, and select the event.
|
||||
this->map->commit(new EventCreate(this, this->map, event));
|
||||
|
||||
auto pixmapItem = event->getPixmapItem();
|
||||
if (pixmapItem) {
|
||||
auto halfSize = ui->graphicsView_Map->size() / 2;
|
||||
auto centerPos = ui->graphicsView_Map->mapToScene(halfSize.width(), halfSize.height());
|
||||
pixmapItem->moveTo(Metatile::coordFromPixmapCoord(centerPos));
|
||||
}
|
||||
return false;
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
void Editor::deleteSelectedEvents() {
|
||||
if (!this->selected_events || this->selected_events->length() == 0 || !this->map || this->editMode != EditMode::Events)
|
||||
if (this->selectedEvents.isEmpty() || !this->map || this->editMode != EditMode::Events)
|
||||
return;
|
||||
|
||||
QList<Event*> eventsToDelete;
|
||||
bool skipWarning = porymapConfig.eventDeleteWarningDisabled;
|
||||
for (DraggablePixmapItem *item : *this->selected_events) {
|
||||
Event* event = item->event;
|
||||
for (auto event : this->selectedEvents) {
|
||||
const QString idName = event->getIdName();
|
||||
if (skipWarning || idName.isEmpty()) {
|
||||
eventsToDelete.append(event);
|
||||
|
|
@ -2151,7 +2151,7 @@ void Editor::deleteSelectedEvents() {
|
|||
msgBox.setCheckBox(new QCheckBox(QStringLiteral("Don't warn me again")));
|
||||
|
||||
QAbstractButton* deleteAllButton = nullptr;
|
||||
if (this->selected_events->length() > 1) {
|
||||
if (this->selectedEvents.length() > 1) {
|
||||
deleteAllButton = msgBox.addButton(QStringLiteral("Delete All"), QMessageBox::DestructiveRole);
|
||||
msgBox.addButton(QStringLiteral("Skip"), QMessageBox::NoRole);
|
||||
}
|
||||
|
|
@ -2176,7 +2176,7 @@ void Editor::deleteSelectedEvents() {
|
|||
}
|
||||
}
|
||||
// TODO: Are we just calling this to invalidate connections?
|
||||
event->setPixmapItem(item);
|
||||
event->setPixmapItem(event->getPixmapItem());
|
||||
}
|
||||
if (eventsToDelete.isEmpty())
|
||||
return;
|
||||
|
|
@ -2293,9 +2293,9 @@ void Editor::eventsView_onMousePress(QMouseEvent *event) {
|
|||
}
|
||||
|
||||
bool multiSelect = event->modifiers() & Qt::ControlModifier;
|
||||
if (!selectingEvent && !multiSelect && selected_events->length() > 1) {
|
||||
if (!selectingEvent && !multiSelect && this->selectedEvents.length() > 1) {
|
||||
// User is clearing group selection by clicking on the background
|
||||
this->selectMapEvent(selected_events->first());
|
||||
this->selectMapEvent(this->selectedEvents.first());
|
||||
}
|
||||
selectingEvent = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -393,7 +393,7 @@ struct JsonParser final {
|
|||
*/
|
||||
const QString &str;
|
||||
int i;
|
||||
QString &err;
|
||||
QString *err;
|
||||
bool failed;
|
||||
const JsonParse strategy;
|
||||
|
||||
|
|
@ -407,8 +407,8 @@ struct JsonParser final {
|
|||
|
||||
template <typename T>
|
||||
T fail(QString &&msg, const T err_ret) {
|
||||
if (!failed)
|
||||
err = std::move(msg);
|
||||
if (!failed && err)
|
||||
*err = std::move(msg);
|
||||
failed = true;
|
||||
return err_ret;
|
||||
}
|
||||
|
|
@ -775,7 +775,7 @@ struct JsonParser final {
|
|||
};
|
||||
}//namespace {
|
||||
|
||||
Json Json::parse(const QString &in, QString &err, JsonParse strategy) {
|
||||
Json Json::parse(const QString &in, QString *err, JsonParse strategy) {
|
||||
JsonParser parser { in, 0, err, false, strategy };
|
||||
Json result = parser.parse_json(0);
|
||||
|
||||
|
|
|
|||
|
|
@ -264,8 +264,6 @@ void MainWindow::initCustomUI() {
|
|||
}
|
||||
|
||||
void MainWindow::initExtraSignals() {
|
||||
// other signals
|
||||
connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this, &MainWindow::addNewEvent);
|
||||
connect(ui->tabWidget_EventType, &QTabWidget::currentChanged, this, &MainWindow::eventTabChanged);
|
||||
|
||||
// Change pages on wild encounter groups
|
||||
|
|
@ -299,6 +297,7 @@ void MainWindow::initExtraSignals() {
|
|||
|
||||
connect(ui->action_NewMap, &QAction::triggered, this, &MainWindow::openNewMapDialog);
|
||||
connect(ui->action_NewLayout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog);
|
||||
connect(ui->actionDuplicate_Current_Map_Layout, &QAction::triggered, this, &MainWindow::openDuplicateMapOrLayoutDialog);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionCheck_for_Updates_triggered() {
|
||||
|
|
@ -343,6 +342,7 @@ void MainWindow::initEditor() {
|
|||
connect(this->editor, &Editor::wildMonTableEdited, [this] { this->markMapEdited(); });
|
||||
connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged);
|
||||
connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated);
|
||||
connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this->editor, &Editor::addNewEvent);
|
||||
connect(ui->toolButton_deleteEvent, &QAbstractButton::clicked, this->editor, &Editor::deleteSelectedEvents);
|
||||
|
||||
this->loadUserSettings();
|
||||
|
|
@ -935,7 +935,7 @@ void MainWindow::setLayoutOnlyMode(bool layoutOnly) {
|
|||
this->ui->mainTabBar->setTabEnabled(MainTab::Events, mapEditingEnabled);
|
||||
this->ui->mainTabBar->setTabEnabled(MainTab::Header, mapEditingEnabled);
|
||||
this->ui->mainTabBar->setTabEnabled(MainTab::Connections, mapEditingEnabled);
|
||||
this->ui->mainTabBar->setTabEnabled(MainTab::WildPokemon, mapEditingEnabled);
|
||||
this->ui->mainTabBar->setTabEnabled(MainTab::WildPokemon, mapEditingEnabled && editor->project->wildEncountersLoaded);
|
||||
|
||||
this->ui->comboBox_LayoutSelector->setEnabled(mapEditingEnabled);
|
||||
}
|
||||
|
|
@ -1028,18 +1028,13 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_
|
|||
|
||||
// Select the target event.
|
||||
int index = event_id - Event::getIndexOffset(event_group);
|
||||
Event* event = editor->map->getEvent(event_group, index);
|
||||
Event* event = this->editor->map->getEvent(event_group, index);
|
||||
if (event) {
|
||||
auto item = event->getPixmapItem();
|
||||
if (item) {
|
||||
editor->selected_events->clear();
|
||||
editor->selected_events->append(item);
|
||||
editor->updateSelectedEvents();
|
||||
return;
|
||||
}
|
||||
this->editor->selectMapEvent(event);
|
||||
} else {
|
||||
// Can still warp to this map, but can't select the specified event
|
||||
logWarn(QString("%1 %2 doesn't exist on map '%3'").arg(Event::groupToString(event_group)).arg(event_id).arg(map_name));
|
||||
}
|
||||
// Can still warp to this map, but can't select the specified event
|
||||
logWarn(QString("%1 %2 doesn't exist on map '%3'").arg(Event::eventGroupToString(event_group)).arg(event_id).arg(map_name));
|
||||
}
|
||||
|
||||
void MainWindow::displayMapProperties() {
|
||||
|
|
@ -1109,7 +1104,6 @@ bool MainWindow::setProjectUI() {
|
|||
ui->newEventToolButton->newSecretBaseAction->setVisible(projectConfig.eventSecretBaseEnabled);
|
||||
ui->newEventToolButton->newCloneObjectAction->setVisible(projectConfig.eventCloneObjectEnabled);
|
||||
|
||||
Event::setIcons();
|
||||
editor->setCollisionGraphics();
|
||||
ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation());
|
||||
ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision());
|
||||
|
|
@ -1167,8 +1161,6 @@ void MainWindow::clearProjectUI() {
|
|||
delete this->layoutTreeModel;
|
||||
delete this->layoutListProxyModel;
|
||||
resetMapListFilters();
|
||||
|
||||
Event::clearIcons();
|
||||
}
|
||||
|
||||
void MainWindow::scrollMapList(MapTree *list, const QString &itemName) {
|
||||
|
|
@ -1302,15 +1294,9 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) {
|
|||
logInfo(QString("Created a new map named %1.").arg(newMap->name()));
|
||||
|
||||
if (newMap->needsHealLocation()) {
|
||||
addNewEvent(Event::Type::HealLocation);
|
||||
this->editor->addNewEvent(Event::Type::HealLocation);
|
||||
}
|
||||
|
||||
// TODO: Creating a new map shouldn't be automatically saved.
|
||||
// For one, it takes away the option to discard the new map.
|
||||
// For two, if the new map uses an existing layout, any unsaved changes to that layout will also be saved.
|
||||
editor->project->saveMap(newMap);
|
||||
editor->project->saveAllDataStructures();
|
||||
|
||||
// Add new map to the map lists
|
||||
this->mapGroupModel->insertMapItem(newMap->name(), groupName);
|
||||
this->mapLocationModel->insertMapItem(newMap->name(), newMap->header()->location());
|
||||
|
|
@ -1419,6 +1405,14 @@ void MainWindow::openDuplicateLayoutDialog(const QString &layoutId) {
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::openDuplicateMapOrLayoutDialog() {
|
||||
if (this->editor->map) {
|
||||
openDuplicateMapDialog(this->editor->map->name());
|
||||
} else if (this->editor->layout) {
|
||||
openDuplicateLayoutDialog(this->editor->layout->id);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_actionNew_Tileset_triggered() {
|
||||
auto dialog = new NewTilesetDialog(editor->project, this);
|
||||
connect(dialog, &NewTilesetDialog::applied, [this](Tileset *tileset) {
|
||||
|
|
@ -1549,16 +1543,30 @@ void MainWindow::updateMapList() {
|
|||
}
|
||||
|
||||
void MainWindow::on_action_Save_Project_triggered() {
|
||||
editor->saveProject();
|
||||
updateWindowTitle();
|
||||
updateMapList();
|
||||
saveGlobalConfigs();
|
||||
save(false);
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Save_triggered() {
|
||||
editor->save();
|
||||
save(true);
|
||||
}
|
||||
|
||||
void MainWindow::save(bool currentOnly) {
|
||||
if (currentOnly) {
|
||||
this->editor->saveCurrent();
|
||||
} else {
|
||||
this->editor->saveAll();
|
||||
}
|
||||
updateWindowTitle();
|
||||
updateMapList();
|
||||
|
||||
if (!porymapConfig.shownInGameReloadMessage) {
|
||||
// Show a one-time warning that the user may need to reload their map to see their new changes.
|
||||
static const QString message = QStringLiteral("Reload your map in-game!\n\nIf your game is currently saved on a map you have edited, "
|
||||
"the changes may not appear until you leave the map and return.");
|
||||
InfoMessage::show(message, this);
|
||||
porymapConfig.shownInGameReloadMessage = true;
|
||||
}
|
||||
|
||||
saveGlobalConfigs();
|
||||
}
|
||||
|
||||
|
|
@ -1627,17 +1635,10 @@ void MainWindow::copy() {
|
|||
OrderedJson::object copyObject;
|
||||
copyObject["object"] = "events";
|
||||
|
||||
QList<DraggablePixmapItem *> events;
|
||||
if (editor->selected_events && editor->selected_events->length()) {
|
||||
events = *editor->selected_events;
|
||||
}
|
||||
|
||||
OrderedJson::array eventsArray;
|
||||
|
||||
for (auto item : events) {
|
||||
Event *event = item->event;
|
||||
for (const auto &event : this->editor->selectedEvents) {
|
||||
OrderedJson::object eventContainer;
|
||||
eventContainer["event_type"] = Event::eventTypeToString(event->getEventType());
|
||||
eventContainer["event_type"] = Event::typeToString(event->getEventType());
|
||||
OrderedJson::object eventJson = event->buildEventJson(editor->project);
|
||||
eventContainer["event"] = eventJson;
|
||||
eventsArray.append(eventContainer);
|
||||
|
|
@ -1748,14 +1749,7 @@ void MainWindow::paste() {
|
|||
QJsonArray events = pasteObject["events"].toArray();
|
||||
for (QJsonValue event : events) {
|
||||
// paste the event to the map
|
||||
const QString typeString = event["event_type"].toString();
|
||||
Event::Type type = Event::eventTypeFromString(typeString);
|
||||
|
||||
if (this->editor->eventLimitReached(type)) {
|
||||
logWarn(QString("Cannot paste event, the limit for type '%1' has been reached.").arg(typeString));
|
||||
continue;
|
||||
}
|
||||
|
||||
Event::Type type = Event::typeFromString(event["event_type"].toString());
|
||||
Event *pasteEvent = Event::create(type);
|
||||
if (!pasteEvent)
|
||||
continue;
|
||||
|
|
@ -1764,12 +1758,16 @@ void MainWindow::paste() {
|
|||
pasteEvent->setMap(this->editor->map);
|
||||
newEvents.append(pasteEvent);
|
||||
}
|
||||
if (newEvents.empty())
|
||||
return;
|
||||
|
||||
if (!newEvents.empty()) {
|
||||
editor->map->commit(new EventPaste(this->editor, editor->map, newEvents));
|
||||
updateEvents();
|
||||
if (!this->editor->canAddEvents(newEvents)) {
|
||||
WarningMessage::show(QStringLiteral("Unable to paste, the maximum number of events would be exceeded."), this);
|
||||
qDeleteAll(newEvents);
|
||||
return;
|
||||
}
|
||||
|
||||
this->editor->map->commit(new EventPaste(this->editor, this->editor->map, newEvents));
|
||||
updateEvents();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1981,33 +1979,10 @@ void MainWindow::resetMapViewScale() {
|
|||
editor->scaleMapView(0);
|
||||
}
|
||||
|
||||
void MainWindow::addNewEvent(Event::Type type) {
|
||||
if (editor && editor->project) {
|
||||
DraggablePixmapItem *item = editor->addNewEvent(type);
|
||||
if (item) {
|
||||
auto halfSize = ui->graphicsView_Map->size() / 2;
|
||||
auto centerPos = ui->graphicsView_Map->mapToScene(halfSize.width(), halfSize.height());
|
||||
item->moveTo(Metatile::coordFromPixmapCoord(centerPos));
|
||||
updateEvents();
|
||||
editor->selectMapEvent(item);
|
||||
} else {
|
||||
WarningMessage msgBox(QStringLiteral("Failed to add new event."), this);
|
||||
if (Event::typeToGroup(type) == Event::Group::Object) {
|
||||
msgBox.setInformativeText(QString("The limit for object events (%1) has been reached.\n\n"
|
||||
"This limit can be adjusted with %2 in '%3'.")
|
||||
.arg(editor->project->getMaxObjectEvents())
|
||||
.arg(projectConfig.getIdentifier(ProjectIdentifier::define_obj_event_count))
|
||||
.arg(projectConfig.getFilePath(ProjectFilePath::constants_global)));
|
||||
}
|
||||
msgBox.exec();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::tryAddEventTab(QWidget * tab) {
|
||||
auto group = getEventGroupFromTabWidget(tab);
|
||||
if (editor->map->getNumEvents(group))
|
||||
ui->tabWidget_EventType->addTab(tab, QString("%1s").arg(Event::eventGroupToString(group)));
|
||||
ui->tabWidget_EventType->addTab(tab, QString("%1s").arg(Event::groupToString(group)));
|
||||
}
|
||||
|
||||
void MainWindow::displayEventTabs() {
|
||||
|
|
@ -2022,31 +1997,32 @@ void MainWindow::displayEventTabs() {
|
|||
}
|
||||
|
||||
void MainWindow::updateEvents() {
|
||||
QList<DraggablePixmapItem *> items = editor->getEventPixmapItems();
|
||||
for (auto i = this->lastSelectedEvent.cbegin(), end = this->lastSelectedEvent.cend(); i != end; i++) {
|
||||
if (i.value() && !items.contains(i.value()))
|
||||
this->lastSelectedEvent.insert(i.key(), nullptr);
|
||||
if (this->editor->map) {
|
||||
for (auto i = this->lastSelectedEvent.begin(); i != this->lastSelectedEvent.end(); i++) {
|
||||
if (i.value() && !this->editor->map->hasEvent(i.value()))
|
||||
this->lastSelectedEvent.insert(i.key(), nullptr);
|
||||
}
|
||||
}
|
||||
displayEventTabs();
|
||||
updateSelectedEvents();
|
||||
}
|
||||
|
||||
void MainWindow::updateSelectedEvents() {
|
||||
QList<DraggablePixmapItem *> events;
|
||||
QList<Event*> events;
|
||||
|
||||
if (editor->selected_events && editor->selected_events->length()) {
|
||||
events = *editor->selected_events;
|
||||
if (!this->editor->selectedEvents.isEmpty()) {
|
||||
events = this->editor->selectedEvents;
|
||||
}
|
||||
else {
|
||||
QList<Event *> all_events;
|
||||
if (editor->map) {
|
||||
all_events = editor->map->getEvents();
|
||||
QList<Event *> allEvents;
|
||||
if (this->editor->map) {
|
||||
allEvents = this->editor->map->getEvents();
|
||||
}
|
||||
if (all_events.length()) {
|
||||
DraggablePixmapItem *selectedEvent = all_events.first()->getPixmapItem();
|
||||
if (!allEvents.isEmpty()) {
|
||||
Event *selectedEvent = allEvents.first();
|
||||
if (selectedEvent) {
|
||||
editor->selected_events->append(selectedEvent);
|
||||
editor->redrawEventPixmapItem(selectedEvent);
|
||||
this->editor->selectedEvents.append(selectedEvent);
|
||||
this->editor->redrawEventPixmapItem(selectedEvent->getPixmapItem());
|
||||
events.append(selectedEvent);
|
||||
}
|
||||
}
|
||||
|
|
@ -2059,12 +2035,13 @@ void MainWindow::updateSelectedEvents() {
|
|||
|
||||
if (events.length() == 1) {
|
||||
// single selected event case
|
||||
Event *current = events[0]->event;
|
||||
Event *current = events.constFirst();
|
||||
Event::Group eventGroup = current->getEventGroup();
|
||||
int event_offs = Event::getIndexOffset(eventGroup);
|
||||
|
||||
if (eventGroup != Event::Group::None)
|
||||
this->lastSelectedEvent.insert(eventGroup, current->getPixmapItem());
|
||||
if (eventGroup != Event::Group::None) {
|
||||
this->lastSelectedEvent.insert(eventGroup, current);
|
||||
}
|
||||
|
||||
switch (eventGroup) {
|
||||
case Event::Group::Object: {
|
||||
|
|
@ -2135,8 +2112,7 @@ void MainWindow::updateSelectedEvents() {
|
|||
this->isProgrammaticEventTabChange = false;
|
||||
|
||||
QList<QFrame *> frames;
|
||||
for (DraggablePixmapItem *item : events) {
|
||||
Event *event = item->event;
|
||||
for (auto event : events) {
|
||||
EventFrame *eventFrame = event->createEventFrame();
|
||||
eventFrame->populate(this->editor->project);
|
||||
eventFrame->initialize();
|
||||
|
|
@ -2191,7 +2167,7 @@ Event::Group MainWindow::getEventGroupFromTabWidget(QWidget *tab) {
|
|||
void MainWindow::eventTabChanged(int index) {
|
||||
if (editor->map) {
|
||||
Event::Group group = getEventGroupFromTabWidget(ui->tabWidget_EventType->widget(index));
|
||||
DraggablePixmapItem *selectedItem = this->lastSelectedEvent.value(group, nullptr);
|
||||
Event *selectedEvent = this->lastSelectedEvent.value(group, nullptr);
|
||||
|
||||
switch (group) {
|
||||
case Event::Group::Object:
|
||||
|
|
@ -2214,11 +2190,8 @@ void MainWindow::eventTabChanged(int index) {
|
|||
}
|
||||
|
||||
if (!isProgrammaticEventTabChange) {
|
||||
if (!selectedItem) {
|
||||
Event *event = editor->map->getEvent(group, 0);
|
||||
if (event) selectedItem = event->getPixmapItem();
|
||||
}
|
||||
if (selectedItem) editor->selectMapEvent(selectedItem);
|
||||
if (!selectedEvent) selectedEvent = this->editor->map->getEvent(group, 0);
|
||||
this->editor->selectMapEvent(selectedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2763,12 +2736,10 @@ void MainWindow::on_actionOpen_Config_Folder_triggered() {
|
|||
void MainWindow::on_actionPreferences_triggered() {
|
||||
if (!preferenceEditor) {
|
||||
preferenceEditor = new PreferenceEditor(this);
|
||||
connect(preferenceEditor, &PreferenceEditor::themeChanged,
|
||||
this, &MainWindow::setTheme);
|
||||
connect(preferenceEditor, &PreferenceEditor::themeChanged,
|
||||
editor, &Editor::maskNonVisibleConnectionTiles);
|
||||
connect(preferenceEditor, &PreferenceEditor::preferencesSaved,
|
||||
this, &MainWindow::togglePreferenceSpecificUi);
|
||||
connect(preferenceEditor, &PreferenceEditor::themeChanged, this, &MainWindow::setTheme);
|
||||
connect(preferenceEditor, &PreferenceEditor::themeChanged, editor, &Editor::maskNonVisibleConnectionTiles);
|
||||
connect(preferenceEditor, &PreferenceEditor::preferencesSaved, this, &MainWindow::togglePreferenceSpecificUi);
|
||||
connect(preferenceEditor, &PreferenceEditor::scriptSettingsChanged, editor->project, &Project::readEventScriptLabels);
|
||||
}
|
||||
|
||||
openSubWindow(preferenceEditor);
|
||||
|
|
@ -2783,8 +2754,9 @@ void MainWindow::togglePreferenceSpecificUi() {
|
|||
if (this->updatePromoter)
|
||||
this->updatePromoter->updatePreferences();
|
||||
|
||||
// Redraw all events to use updated porymapConfig.eventSelectionShapeMode
|
||||
this->editor->redrawAllEvents();
|
||||
// Changes to porymapConfig.loadAllEventScripts or porymapConfig.eventSelectionShapeMode
|
||||
// require us to repopulate the EventFrames and redraw event pixmaps, respectively.
|
||||
this->editor->updateEvents();
|
||||
}
|
||||
|
||||
void MainWindow::openProjectSettingsEditor(int tab) {
|
||||
|
|
@ -3006,7 +2978,7 @@ bool MainWindow::closeProject() {
|
|||
|
||||
auto reply = msgBox.exec();
|
||||
if (reply == QMessageBox::Yes) {
|
||||
editor->saveProject();
|
||||
save();
|
||||
} else if (reply == QMessageBox::No) {
|
||||
logWarn("Closing project with unsaved changes.");
|
||||
} else if (reply == QMessageBox::Cancel) {
|
||||
|
|
|
|||
620
src/project.cpp
620
src/project.cpp
|
|
@ -31,7 +31,6 @@ int Project::num_pals_primary = 6;
|
|||
int Project::num_pals_total = 13;
|
||||
int Project::max_map_data_size = 10240; // 0x2800
|
||||
int Project::default_map_dimension = 20;
|
||||
int Project::max_object_events = 64;
|
||||
|
||||
Project::Project(QObject *parent) :
|
||||
QObject(parent)
|
||||
|
|
@ -46,6 +45,7 @@ Project::~Project()
|
|||
clearMapLayouts();
|
||||
clearEventGraphics();
|
||||
clearHealLocations();
|
||||
QPixmapCache::clear();
|
||||
}
|
||||
|
||||
void Project::set_root(QString dir) {
|
||||
|
|
@ -197,13 +197,29 @@ void Project::initTopLevelMapFields() {
|
|||
|
||||
bool Project::readMapJson(const QString &mapName, QJsonDocument * out) {
|
||||
const QString mapFilepath = QString("%1%2/map.json").arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)).arg(mapName);
|
||||
if (!parser.tryParseJsonFile(out, QString("%1/%2").arg(this->root).arg(mapFilepath))) {
|
||||
logError(QString("Failed to read map data from %1").arg(mapFilepath));
|
||||
QString error;
|
||||
if (!parser.tryParseJsonFile(out, QString("%1/%2").arg(this->root).arg(mapFilepath), &error)) {
|
||||
logError(QString("Failed to read map data from '%1': %2").arg(mapFilepath).arg(error));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Project::loadMapEvent(Map *map, const QJsonObject &json, Event::Type defaultType) {
|
||||
QString typeString = ParseUtil::jsonToQString(json["type"]);
|
||||
Event::Type type = typeString.isEmpty() ? defaultType : Event::typeFromString(typeString);
|
||||
Event* event = Event::create(type);
|
||||
if (!event) {
|
||||
return false;
|
||||
}
|
||||
if (!event->loadFromJson(json, this)) {
|
||||
delete event;
|
||||
return false;
|
||||
}
|
||||
map->addEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Project::loadMapData(Map* map) {
|
||||
if (!map->isPersistedToFile()) {
|
||||
return true;
|
||||
|
|
@ -242,75 +258,22 @@ bool Project::loadMapData(Map* map) {
|
|||
|
||||
// Events
|
||||
map->resetEvents();
|
||||
QJsonArray objectEventsArr = mapObj["object_events"].toArray();
|
||||
for (int i = 0; i < objectEventsArr.size(); i++) {
|
||||
QJsonObject event = objectEventsArr[i].toObject();
|
||||
// If clone objects are not enabled then no type field is present
|
||||
QString type = projectConfig.eventCloneObjectEnabled ? ParseUtil::jsonToQString(event["type"]) : "object";
|
||||
if (type.isEmpty() || type == "object") {
|
||||
ObjectEvent *object = new ObjectEvent();
|
||||
object->loadFromJson(event, this);
|
||||
map->addEvent(object);
|
||||
} else if (type == "clone") {
|
||||
CloneObjectEvent *clone = new CloneObjectEvent();
|
||||
if (clone->loadFromJson(event, this)) {
|
||||
map->addEvent(clone);
|
||||
static const QMap<QString, Event::Type> defaultEventTypes = {
|
||||
// Map of the expected keys for each event group, and the default type of that group.
|
||||
// If the default type is Type::None then each event must specify its type, or its an error.
|
||||
{"object_events", Event::Type::Object},
|
||||
{"warp_events", Event::Type::Warp},
|
||||
{"coord_events", Event::Type::None},
|
||||
{"bg_events", Event::Type::None},
|
||||
};
|
||||
for (auto i = defaultEventTypes.constBegin(); i != defaultEventTypes.constEnd(); i++) {
|
||||
QString eventGroupKey = i.key();
|
||||
Event::Type defaultType = i.value();
|
||||
const QJsonArray eventsJsonArr = mapObj[eventGroupKey].toArray();
|
||||
for (int i = 0; i < eventsJsonArr.size(); i++) {
|
||||
if (!loadMapEvent(map, eventsJsonArr.at(i).toObject(), defaultType)) {
|
||||
logError(QString("Failed to load event for %1, in %2 at index %3.").arg(map->name()).arg(eventGroupKey).arg(i));
|
||||
}
|
||||
else {
|
||||
delete clone;
|
||||
}
|
||||
} else {
|
||||
logError(QString("Map %1 object_event %2 has invalid type '%3'. Must be 'object' or 'clone'.").arg(map->name()).arg(i).arg(type));
|
||||
}
|
||||
}
|
||||
|
||||
QJsonArray warpEventsArr = mapObj["warp_events"].toArray();
|
||||
for (int i = 0; i < warpEventsArr.size(); i++) {
|
||||
QJsonObject event = warpEventsArr[i].toObject();
|
||||
WarpEvent *warp = new WarpEvent();
|
||||
if (warp->loadFromJson(event, this)) {
|
||||
map->addEvent(warp);
|
||||
}
|
||||
else {
|
||||
delete warp;
|
||||
}
|
||||
}
|
||||
|
||||
QJsonArray coordEventsArr = mapObj["coord_events"].toArray();
|
||||
for (int i = 0; i < coordEventsArr.size(); i++) {
|
||||
QJsonObject event = coordEventsArr[i].toObject();
|
||||
QString type = ParseUtil::jsonToQString(event["type"]);
|
||||
if (type == "trigger") {
|
||||
TriggerEvent *coord = new TriggerEvent();
|
||||
coord->loadFromJson(event, this);
|
||||
map->addEvent(coord);
|
||||
} else if (type == "weather") {
|
||||
WeatherTriggerEvent *coord = new WeatherTriggerEvent();
|
||||
coord->loadFromJson(event, this);
|
||||
map->addEvent(coord);
|
||||
} else {
|
||||
logError(QString("Map %1 coord_event %2 has invalid type '%3'. Must be 'trigger' or 'weather'.").arg(map->name()).arg(i).arg(type));
|
||||
}
|
||||
}
|
||||
|
||||
QJsonArray bgEventsArr = mapObj["bg_events"].toArray();
|
||||
for (int i = 0; i < bgEventsArr.size(); i++) {
|
||||
QJsonObject event = bgEventsArr[i].toObject();
|
||||
QString type = ParseUtil::jsonToQString(event["type"]);
|
||||
if (type == "sign") {
|
||||
SignEvent *bg = new SignEvent();
|
||||
bg->loadFromJson(event, this);
|
||||
map->addEvent(bg);
|
||||
} else if (type == "hidden_item") {
|
||||
HiddenItemEvent *bg = new HiddenItemEvent();
|
||||
bg->loadFromJson(event, this);
|
||||
map->addEvent(bg);
|
||||
} else if (type == "secret_base") {
|
||||
SecretBaseEvent *bg = new SecretBaseEvent();
|
||||
bg->loadFromJson(event, this);
|
||||
map->addEvent(bg);
|
||||
} else {
|
||||
logError(QString("Map %1 bg_event %2 has invalid type '%3'. Must be 'sign', 'hidden_item', or 'secret_base'.").arg(map->name()).arg(i).arg(type));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -352,6 +315,26 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t
|
|||
// Generate a unique MAP constant.
|
||||
map->setConstantName(toUniqueIdentifier(map->expectedConstantName()));
|
||||
|
||||
// Make sure we keep the order of the map names the same as in the map group order.
|
||||
int mapNamePos;
|
||||
if (this->groupNames.contains(settings.group)) {
|
||||
mapNamePos = 0;
|
||||
for (const auto &name : this->groupNames) {
|
||||
mapNamePos += this->groupNameToMapNames[name].length();
|
||||
if (name == settings.group)
|
||||
break;
|
||||
}
|
||||
} else if (isValidNewIdentifier(settings.group)) {
|
||||
// Adding map to a map group that doesn't exist yet.
|
||||
// Create the group, and we already know the map will be last in the list.
|
||||
addNewMapGroup(settings.group);
|
||||
mapNamePos = this->mapNames.length();
|
||||
} else {
|
||||
logError(QString("Cannot create new map with invalid map group name '%1'.").arg(settings.group));
|
||||
delete map;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Layout *layout = this->mapLayouts.value(settings.layout.id);
|
||||
if (!layout) {
|
||||
// Layout doesn't already exist, create it.
|
||||
|
|
@ -367,24 +350,6 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t
|
|||
}
|
||||
map->setLayout(layout);
|
||||
|
||||
// Make sure we keep the order of the map names the same as in the map group order.
|
||||
int mapNamePos;
|
||||
if (this->groupNames.contains(settings.group)) {
|
||||
mapNamePos = 0;
|
||||
for (const auto &name : this->groupNames) {
|
||||
mapNamePos += this->groupNameToMapNames[name].length();
|
||||
if (name == settings.group)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Adding map to a map group that doesn't exist yet.
|
||||
// Create the group, and we already know the map will be last in the list.
|
||||
if (isValidNewIdentifier(settings.group)) {
|
||||
addNewMapGroup(settings.group);
|
||||
}
|
||||
mapNamePos = this->mapNames.length();
|
||||
}
|
||||
|
||||
const QString location = map->header()->location();
|
||||
if (!this->mapSectionIdNames.contains(location) && isValidNewIdentifier(location)) {
|
||||
// Unrecognized MAPSEC name, we can automatically add a new MAPSEC for it.
|
||||
|
|
@ -424,20 +389,10 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout
|
|||
// Otherwise the new layout's folder name will just be the layout's name.
|
||||
const QString folderName = !settings.folderName.isEmpty() ? settings.folderName : layout->name;
|
||||
const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders) + folderName;
|
||||
layout->newFolderPath = folderPath;
|
||||
layout->border_path = folderPath + "/border.bin";
|
||||
layout->blockdata_path = folderPath + "/map.bin";
|
||||
|
||||
// Create a new directory for the layout, if it doesn't already exist.
|
||||
const QString fullPath = QString("%1/%2").arg(this->root).arg(folderPath);
|
||||
if (!QDir::root().mkpath(fullPath)) {
|
||||
logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath));
|
||||
delete layout;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
this->mapLayouts.insert(layout->id, layout);
|
||||
this->layoutIds.append(layout->id);
|
||||
|
||||
if (layout->blockdata.isEmpty()) {
|
||||
// Fill layout using default fill settings
|
||||
setNewLayoutBlockdata(layout);
|
||||
|
|
@ -447,7 +402,15 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout
|
|||
setNewLayoutBorder(layout);
|
||||
}
|
||||
|
||||
saveLayout(layout); // TODO: Ideally we shouldn't automatically save new layouts
|
||||
// No need for a full load, we already have all the blockdata.
|
||||
layout->loaded = loadLayoutTilesets(layout);
|
||||
if (!layout->loaded) {
|
||||
delete layout;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
this->mapLayouts.insert(layout->id, layout);
|
||||
this->layoutIds.append(layout->id);
|
||||
|
||||
emit layoutCreated(layout);
|
||||
|
||||
|
|
@ -515,8 +478,9 @@ bool Project::readMapLayouts() {
|
|||
const QString fullFilepath = QString("%1/%2").arg(this->root).arg(layoutsFilepath);
|
||||
fileWatcher.addPath(fullFilepath);
|
||||
QJsonDocument layoutsDoc;
|
||||
if (!parser.tryParseJsonFile(&layoutsDoc, fullFilepath)) {
|
||||
logError(QString("Failed to read map layouts from %1").arg(fullFilepath));
|
||||
QString error;
|
||||
if (!parser.tryParseJsonFile(&layoutsDoc, fullFilepath, &error)) {
|
||||
logError(QString("Failed to read map layouts from '%1': %2").arg(fullFilepath).arg(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -701,6 +665,10 @@ void Project::saveMapGroups() {
|
|||
for (const auto &groupName : this->groupNames) {
|
||||
OrderedJson::array groupArr;
|
||||
for (const auto &mapName : this->groupNameToMapNames.value(groupName)) {
|
||||
if (this->mapCache.value(mapName) && !this->mapCache.value(mapName)->isPersistedToFile()) {
|
||||
// This is a new map that hasn't been saved yet, don't add it to the global map groups list yet.
|
||||
continue;
|
||||
}
|
||||
groupArr.push_back(mapName);
|
||||
}
|
||||
mapGroupsObj[groupName] = groupArr;
|
||||
|
|
@ -986,25 +954,25 @@ bool Project::loadLayoutTilesets(Layout *layout) {
|
|||
layout->tileset_primary = getTileset(layout->tileset_primary_label);
|
||||
if (!layout->tileset_primary) {
|
||||
QString defaultTileset = this->getDefaultPrimaryTilesetLabel();
|
||||
logWarn(QString("%1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_primary_label).arg(defaultTileset));
|
||||
layout->tileset_primary_label = defaultTileset;
|
||||
layout->tileset_primary = getTileset(layout->tileset_primary_label);
|
||||
if (!layout->tileset_primary) {
|
||||
logError(QString("Failed to set default primary tileset."));
|
||||
logError(QString("%1 has invalid primary tileset '%2'.").arg(layout->name).arg(layout->tileset_primary_label));
|
||||
return false;
|
||||
}
|
||||
logWarn(QString("%1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_primary_label).arg(defaultTileset));
|
||||
}
|
||||
|
||||
layout->tileset_secondary = getTileset(layout->tileset_secondary_label);
|
||||
if (!layout->tileset_secondary) {
|
||||
QString defaultTileset = this->getDefaultSecondaryTilesetLabel();
|
||||
logWarn(QString("%1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_secondary_label).arg(defaultTileset));
|
||||
layout->tileset_secondary_label = defaultTileset;
|
||||
layout->tileset_secondary = getTileset(layout->tileset_secondary_label);
|
||||
if (!layout->tileset_secondary) {
|
||||
logError(QString("Failed to set default secondary tileset."));
|
||||
logError(QString("%1 has invalid secondary tileset '%2'.").arg(layout->name).arg(layout->tileset_secondary_label));
|
||||
return false;
|
||||
}
|
||||
logWarn(QString("%1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_secondary_label).arg(defaultTileset));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1156,18 +1124,24 @@ void Project::writeBlockdata(QString path, const Blockdata &blockdata) {
|
|||
}
|
||||
}
|
||||
|
||||
void Project::saveAllMaps() {
|
||||
for (auto *map : mapCache.values())
|
||||
saveMap(map);
|
||||
void Project::saveAll() {
|
||||
for (auto map : this->mapCache) {
|
||||
saveMap(map, true); // Avoid double-saving the layouts
|
||||
}
|
||||
for (auto layout : this->mapLayouts) {
|
||||
saveLayout(layout);
|
||||
}
|
||||
saveGlobalData();
|
||||
}
|
||||
|
||||
void Project::saveMap(Map *map) {
|
||||
void Project::saveMap(Map *map, bool skipLayout) {
|
||||
// Create/Modify a few collateral files for brand new maps.
|
||||
const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_map_folders) + map->name();
|
||||
const QString fullPath = QString("%1/%2").arg(this->root).arg(folderPath);
|
||||
if (!map->isPersistedToFile()) {
|
||||
if (!QDir::root().mkpath(fullPath)) {
|
||||
logError(QString("Failed to create directory for new map: '%1'").arg(fullPath));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create file data/maps/<map_name>/scripts.inc
|
||||
|
|
@ -1290,12 +1264,25 @@ void Project::saveMap(Map *map) {
|
|||
jsonDoc.dump(&mapFile);
|
||||
mapFile.close();
|
||||
|
||||
saveLayout(map->layout());
|
||||
if (!skipLayout) saveLayout(map->layout());
|
||||
|
||||
map->setClean();
|
||||
}
|
||||
|
||||
void Project::saveLayout(Layout *layout) {
|
||||
if (!layout || !layout->loaded)
|
||||
return;
|
||||
|
||||
if (!layout->newFolderPath.isEmpty()) {
|
||||
// Layout directory doesn't exist yet, create it now.
|
||||
const QString fullPath = QString("%1/%2").arg(this->root).arg(layout->newFolderPath);
|
||||
if (!QDir::root().mkpath(fullPath)) {
|
||||
logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath));
|
||||
return;
|
||||
}
|
||||
layout->newFolderPath = QString();
|
||||
}
|
||||
|
||||
saveLayoutBorder(layout);
|
||||
saveLayoutBlockdata(layout);
|
||||
|
||||
|
|
@ -1318,7 +1305,7 @@ void Project::updateLayout(Layout *layout) {
|
|||
}
|
||||
}
|
||||
|
||||
void Project::saveAllDataStructures() {
|
||||
void Project::saveGlobalData() {
|
||||
saveMapLayouts();
|
||||
saveMapGroups();
|
||||
saveRegionMapSections();
|
||||
|
|
@ -1645,13 +1632,15 @@ bool Project::readWildMonData() {
|
|||
this->pokemonMaxLevel = qMax(this->pokemonMinLevel, this->pokemonMaxLevel);
|
||||
|
||||
// Read encounter data
|
||||
QString wildMonJsonFilepath = QString("%1/%2").arg(root).arg(projectConfig.getFilePath(ProjectFilePath::json_wild_encounters));
|
||||
const QString wildMonJsonBaseFilepath = projectConfig.getFilePath(ProjectFilePath::json_wild_encounters);
|
||||
QString wildMonJsonFilepath = QString("%1/%2").arg(root).arg(wildMonJsonBaseFilepath);
|
||||
fileWatcher.addPath(wildMonJsonFilepath);
|
||||
|
||||
OrderedJson::object wildMonObj;
|
||||
if (!parser.tryParseOrderedJsonFile(&wildMonObj, wildMonJsonFilepath)) {
|
||||
QString error;
|
||||
if (!parser.tryParseOrderedJsonFile(&wildMonObj, wildMonJsonFilepath, &error)) {
|
||||
// Failing to read wild encounters data is not a critical error, the encounter editor will just be disabled
|
||||
logWarn(QString("Failed to read wild encounters from %1").arg(wildMonJsonFilepath));
|
||||
logWarn(QString("Failed to read wild encounters from '%1': %2").arg(wildMonJsonBaseFilepath).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1780,8 +1769,9 @@ bool Project::readMapGroups() {
|
|||
const QString filepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::json_map_groups);
|
||||
fileWatcher.addPath(filepath);
|
||||
QJsonDocument mapGroupsDoc;
|
||||
if (!parser.tryParseJsonFile(&mapGroupsDoc, filepath)) {
|
||||
logError(QString("Failed to read map groups from %1").arg(filepath));
|
||||
QString error;
|
||||
if (!parser.tryParseJsonFile(&mapGroupsDoc, filepath, &error)) {
|
||||
logError(QString("Failed to read map groups from '%1': %2").arg(filepath).arg(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -2287,8 +2277,9 @@ bool Project::readRegionMapSections() {
|
|||
QJsonDocument doc;
|
||||
const QString baseFilepath = projectConfig.getFilePath(ProjectFilePath::json_region_map_entries);
|
||||
const QString filepath = QString("%1/%2").arg(this->root).arg(baseFilepath);
|
||||
if (!parser.tryParseJsonFile(&doc, filepath)) {
|
||||
logError(QString("Failed to read region map sections from '%1'").arg(baseFilepath));
|
||||
QString error;
|
||||
if (!parser.tryParseJsonFile(&doc, filepath, &error)) {
|
||||
logError(QString("Failed to read region map sections from '%1': %2").arg(baseFilepath).arg(error));
|
||||
return false;
|
||||
}
|
||||
fileWatcher.addPath(filepath);
|
||||
|
|
@ -2413,8 +2404,9 @@ bool Project::readHealLocations() {
|
|||
QJsonDocument doc;
|
||||
const QString baseFilepath = projectConfig.getFilePath(ProjectFilePath::json_heal_locations);
|
||||
const QString filepath = QString("%1/%2").arg(this->root).arg(baseFilepath);
|
||||
if (!parser.tryParseJsonFile(&doc, filepath)) {
|
||||
logError(QString("Failed to read heal locations from '%1'").arg(baseFilepath));
|
||||
QString error;
|
||||
if (!parser.tryParseJsonFile(&doc, filepath, &error)) {
|
||||
logError(QString("Failed to read heal locations from '%1': %2").arg(baseFilepath).arg(error));
|
||||
return false;
|
||||
}
|
||||
fileWatcher.addPath(filepath);
|
||||
|
|
@ -2440,9 +2432,10 @@ bool Project::readItemNames() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_items)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_items);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
itemNames = parser.readCDefineNames(filename, regexList);
|
||||
if (itemNames.isEmpty())
|
||||
logWarn(QString("Failed to read item constants from %1").arg(filename));
|
||||
QString error;
|
||||
this->itemNames = parser.readCDefineNames(filename, regexList, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read item constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2450,9 +2443,10 @@ bool Project::readFlagNames() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_flags)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_flags);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
flagNames = parser.readCDefineNames(filename, regexList);
|
||||
if (flagNames.isEmpty())
|
||||
logWarn(QString("Failed to read flag constants from %1").arg(filename));
|
||||
QString error;
|
||||
this->flagNames = parser.readCDefineNames(filename, regexList, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read flag constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2460,9 +2454,10 @@ bool Project::readVarNames() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_vars)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_vars);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
varNames = parser.readCDefineNames(filename, regexList);
|
||||
if (varNames.isEmpty())
|
||||
logWarn(QString("Failed to read var constants from %1").arg(filename));
|
||||
QString error;
|
||||
this->varNames = parser.readCDefineNames(filename, regexList, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read var constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2470,18 +2465,20 @@ bool Project::readMovementTypes() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_movement_types)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_event_movement);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
movementTypes = parser.readCDefineNames(filename, regexList);
|
||||
if (movementTypes.isEmpty())
|
||||
logWarn(QString("Failed to read movement type constants from %1").arg(filename));
|
||||
QString error;
|
||||
this->movementTypes = parser.readCDefineNames(filename, regexList, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read movement type constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Project::readInitialFacingDirections() {
|
||||
QString filename = projectConfig.getFilePath(ProjectFilePath::initial_facing_table);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
facingDirections = parser.readNamedIndexCArray(filename, projectConfig.getIdentifier(ProjectIdentifier::symbol_facing_directions));
|
||||
if (facingDirections.isEmpty())
|
||||
logWarn(QString("Failed to read initial movement type facing directions from %1").arg(filename));
|
||||
QString error;
|
||||
this->facingDirections = parser.readNamedIndexCArray(filename, projectConfig.getIdentifier(ProjectIdentifier::symbol_facing_directions), &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read initial movement type facing directions from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2489,9 +2486,10 @@ bool Project::readMapTypes() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_map_types)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
mapTypes = parser.readCDefineNames(filename, regexList);
|
||||
if (mapTypes.isEmpty())
|
||||
logWarn(QString("Failed to read map type constants from %1").arg(filename));
|
||||
QString error;
|
||||
this->mapTypes = parser.readCDefineNames(filename, regexList, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read map type constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2499,9 +2497,10 @@ bool Project::readMapBattleScenes() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_battle_scenes)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
mapBattleScenes = parser.readCDefineNames(filename, regexList);
|
||||
if (mapBattleScenes.isEmpty())
|
||||
logWarn(QString("Failed to read map battle scene constants from %1").arg(filename));
|
||||
QString error;
|
||||
this->mapBattleScenes = parser.readCDefineNames(filename, regexList, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read map battle scene constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2509,9 +2508,10 @@ bool Project::readWeatherNames() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_weather)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
weatherNames = parser.readCDefineNames(filename, regexList);
|
||||
if (weatherNames.isEmpty())
|
||||
logWarn(QString("Failed to read weather constants from %1").arg(filename));
|
||||
QString error;
|
||||
this->weatherNames = parser.readCDefineNames(filename, regexList, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read weather constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2522,9 +2522,10 @@ bool Project::readCoordEventWeatherNames() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_coord_event_weather)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
coordEventWeatherNames = parser.readCDefineNames(filename, regexList);
|
||||
if (coordEventWeatherNames.isEmpty())
|
||||
logWarn(QString("Failed to read coord event weather constants from %1").arg(filename));
|
||||
QString error;
|
||||
this->coordEventWeatherNames = parser.readCDefineNames(filename, regexList, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read coord event weather constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2535,9 +2536,10 @@ bool Project::readSecretBaseIds() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_secret_bases)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_secret_bases);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
secretBaseIds = parser.readCDefineNames(filename, regexList);
|
||||
if (secretBaseIds.isEmpty())
|
||||
logWarn(QString("Failed to read secret base id constants from '%1'").arg(filename));
|
||||
QString error;
|
||||
this->secretBaseIds = parser.readCDefineNames(filename, regexList, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read secret base id constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2545,9 +2547,10 @@ bool Project::readBgEventFacingDirections() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_sign_facing_directions)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_event_bg);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
bgEventFacingDirections = parser.readCDefineNames(filename, regexList);
|
||||
if (bgEventFacingDirections.isEmpty())
|
||||
logWarn(QString("Failed to read bg event facing direction constants from %1").arg(filename));
|
||||
QString error;
|
||||
this->bgEventFacingDirections = parser.readCDefineNames(filename, regexList, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read bg event facing direction constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2555,9 +2558,10 @@ bool Project::readTrainerTypes() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_trainer_types)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_trainer_types);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
trainerTypes = parser.readCDefineNames(filename, regexList);
|
||||
if (trainerTypes.isEmpty())
|
||||
logWarn(QString("Failed to read trainer type constants from %1").arg(filename));
|
||||
QString error;
|
||||
this->trainerTypes = parser.readCDefineNames(filename, regexList, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read trainer type constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2568,13 +2572,14 @@ bool Project::readMetatileBehaviors() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)};
|
||||
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_behaviors);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QMap<QString, int> defines = parser.readCDefinesByRegex(filename, regexList);
|
||||
if (defines.isEmpty()) {
|
||||
// Not having any metatile behavior names is ok (their values will be displayed instead).
|
||||
// If the user's metatiles can have nonzero values then warn them, as they likely want names.
|
||||
if (projectConfig.metatileBehaviorMask)
|
||||
logWarn(QString("Failed to read metatile behaviors from %1.").arg(filename));
|
||||
return true;
|
||||
QString error;
|
||||
QMap<QString, int> defines = parser.readCDefinesByRegex(filename, regexList, &error);
|
||||
if (defines.isEmpty() && projectConfig.metatileBehaviorMask) {
|
||||
// Not having any metatile behavior names is ok (their values will be displayed instead)
|
||||
// but if the user's metatiles can have nonzero values then warn them, as they likely want names.
|
||||
QString warning = QString("Failed to read metatile behaviors from '%1'").arg(filename);
|
||||
if (!error.isEmpty()) warning += QString(": %1").arg(error);
|
||||
logWarn(warning);
|
||||
}
|
||||
|
||||
for (auto i = defines.cbegin(), end = defines.cend(); i != end; i++) {
|
||||
|
|
@ -2590,9 +2595,10 @@ bool Project::readSongNames() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_music)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_songs);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
this->songNames = parser.readCDefineNames(filename, regexList);
|
||||
if (this->songNames.isEmpty())
|
||||
logWarn(QString("Failed to read song names from %1.").arg(filename));
|
||||
QString error;
|
||||
this->songNames = parser.readCDefineNames(filename, regexList, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read song names from '%1': %2").arg(filename).arg(error));
|
||||
|
||||
// Song names don't have a very useful order (esp. if we include SE_* values), so sort them alphabetically.
|
||||
// The default song should be the first in the list, not the first alphabetically, so save that before sorting.
|
||||
|
|
@ -2605,9 +2611,10 @@ bool Project::readObjEventGfxConstants() {
|
|||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)};
|
||||
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_events);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
this->gfxDefines = parser.readCDefinesByRegex(filename, regexList);
|
||||
if (this->gfxDefines.isEmpty())
|
||||
logWarn(QString("Failed to read object event graphics constants from %1.").arg(filename));
|
||||
QString error;
|
||||
this->gfxDefines = parser.readCDefinesByRegex(filename, regexList, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read object event graphics constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2617,37 +2624,49 @@ bool Project::readMiscellaneousConstants() {
|
|||
fileWatcher.addPath(root + "/" + filename);
|
||||
QMap<QString, int> defines = parser.readCDefinesByName(filename, {maxObjectEventsName});
|
||||
|
||||
this->maxObjectEvents = 64; // Default value
|
||||
auto it = defines.find(maxObjectEventsName);
|
||||
if (it != defines.end()) {
|
||||
if (it.value() > 0) {
|
||||
Project::max_object_events = it.value();
|
||||
this->maxObjectEvents = it.value();
|
||||
} else {
|
||||
logWarn(QString("Value for '%1' is %2, must be greater than 0. Using default (%3) instead.")
|
||||
.arg(maxObjectEventsName)
|
||||
.arg(it.value())
|
||||
.arg(Project::max_object_events));
|
||||
.arg(this->maxObjectEvents));
|
||||
}
|
||||
}
|
||||
else {
|
||||
logWarn(QString("Value for '%1' not found. Using default (%2) instead.")
|
||||
.arg(maxObjectEventsName)
|
||||
.arg(Project::max_object_events));
|
||||
.arg(this->maxObjectEvents));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Project::readEventScriptLabels() {
|
||||
globalScriptLabels.clear();
|
||||
for (const auto &filePath : getEventScriptsFilePaths())
|
||||
globalScriptLabels << ParseUtil::getGlobalScriptLabels(filePath);
|
||||
this->globalScriptLabels.clear();
|
||||
|
||||
globalScriptLabels.sort(Qt::CaseInsensitive);
|
||||
globalScriptLabels.removeDuplicates();
|
||||
if (porymapConfig.loadAllEventScripts) {
|
||||
for (const auto &filePath : getEventScriptsFilePaths())
|
||||
this->globalScriptLabels << ParseUtil::getGlobalScriptLabels(filePath);
|
||||
|
||||
this->globalScriptLabels.sort(Qt::CaseInsensitive);
|
||||
this->globalScriptLabels.removeDuplicates();
|
||||
}
|
||||
emit eventScriptLabelsRead();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Project::insertGlobalScriptLabels(QStringList &scriptLabels) const {
|
||||
if (this->globalScriptLabels.isEmpty())
|
||||
return;
|
||||
scriptLabels.append(this->globalScriptLabels);
|
||||
scriptLabels.sort();
|
||||
scriptLabels.removeDuplicates();
|
||||
}
|
||||
|
||||
QString Project::fixPalettePath(QString path) {
|
||||
static const QRegularExpression re_gbapal("\\.gbapal$");
|
||||
path = path.replace(re_gbapal, ".pal");
|
||||
|
|
@ -2703,92 +2722,220 @@ QStringList Project::getEventScriptsFilePaths() const {
|
|||
return filePaths;
|
||||
}
|
||||
|
||||
void Project::setEventPixmap(Event *event, bool forceLoad) {
|
||||
void Project::loadEventPixmap(Event *event, bool forceLoad) {
|
||||
if (event && (event->getPixmap().isNull() || forceLoad))
|
||||
event->loadPixmap(this);
|
||||
}
|
||||
|
||||
void Project::clearEventGraphics() {
|
||||
qDeleteAll(eventGraphicsMap);
|
||||
eventGraphicsMap.clear();
|
||||
qDeleteAll(this->eventGraphicsMap);
|
||||
this->eventGraphicsMap.clear();
|
||||
}
|
||||
|
||||
bool Project::readEventGraphics() {
|
||||
clearEventGraphics();
|
||||
|
||||
fileWatcher.addPaths(QStringList() << root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_pointers)
|
||||
<< root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_info)
|
||||
<< root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_pic_tables)
|
||||
<< root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx));
|
||||
|
||||
const QString pointersFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_pointers);
|
||||
const QString pointersName = projectConfig.getIdentifier(ProjectIdentifier::symbol_obj_event_gfx_pointers);
|
||||
QMap<QString, QString> pointerHash = parser.readNamedIndexCArray(pointersFilepath, pointersName);
|
||||
const QString gfxInfoFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_info);
|
||||
const QString picTablesFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_pic_tables);
|
||||
const QString gfxFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx);
|
||||
fileWatcher.addPaths({pointersFilepath, gfxInfoFilepath, picTablesFilepath, gfxFilepath});
|
||||
|
||||
QStringList gfxNames = gfxDefines.keys();
|
||||
// Read the table mapping OBJ_EVENT_GFX constants to the names of pointers to data about their graphics.
|
||||
const QString pointersName = projectConfig.getIdentifier(ProjectIdentifier::symbol_obj_event_gfx_pointers);
|
||||
const QMap<QString, QString> pointerMap = parser.readNamedIndexCArray(pointersFilepath, pointersName);
|
||||
|
||||
// The positions of each of the required members for the gfx info struct.
|
||||
// For backwards compatibility if the struct doesn't use initializers.
|
||||
static const auto gfxInfoMemberMap = QHash<int, QString>{
|
||||
static const QHash<int, QString> gfxInfoMemberMap = {
|
||||
{4, "width"},
|
||||
{5, "height"},
|
||||
{8, "inanimate"},
|
||||
{11, "oam"},
|
||||
{12, "subspriteTables"},
|
||||
{14, "images"},
|
||||
};
|
||||
// Read the structs containing data about each of the event sprites.
|
||||
auto gfxInfos = parser.readCStructs(gfxInfoFilepath, "", gfxInfoMemberMap);
|
||||
|
||||
QString filepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_info);
|
||||
auto gfxInfos = parser.readCStructs(filepath, "", gfxInfoMemberMap);
|
||||
// We need data in both of these files to translate data from the structs above into the path for a .png file.
|
||||
const QMap<QString, QStringList> picTables = parser.readCArrayMulti(picTablesFilepath);
|
||||
const QMap<QString, QString> graphicIncbins = parser.readCIncbinMulti(gfxFilepath);
|
||||
|
||||
QMap<QString, QStringList> picTables = parser.readCArrayMulti(projectConfig.getFilePath(ProjectFilePath::data_obj_event_pic_tables));
|
||||
QMap<QString, QString> graphicIncbins = parser.readCIncbinMulti(projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx));
|
||||
for (auto i = this->gfxDefines.constBegin(); i != this->gfxDefines.constEnd(); i++) {
|
||||
const QString gfxName = i.key();
|
||||
|
||||
for (QString gfxName : gfxNames) {
|
||||
QString info_label = pointerHash[gfxName].replace("&", "");
|
||||
// Strip the address-of operator to get the pointer's name. We'll use this name to get data about the event's sprite.
|
||||
// If we don't recognize the name, ignore it. The event will use a default sprite.
|
||||
QString info_label = pointerMap.value(gfxName);
|
||||
info_label.replace("&", "");
|
||||
if (!gfxInfos.contains(info_label))
|
||||
continue;
|
||||
const QHash<QString, QString> gfxInfoAttributes = gfxInfos[info_label];
|
||||
|
||||
auto gfxInfoAttributes = gfxInfos[info_label];
|
||||
auto gfx = new EventGraphics;
|
||||
|
||||
auto eventGraphics = new EventGraphics;
|
||||
eventGraphics->inanimate = ParseUtil::gameStringToBool(gfxInfoAttributes.value("inanimate"));
|
||||
QString pic_label = gfxInfoAttributes.value("images");
|
||||
QString dimensions_label = gfxInfoAttributes.value("oam");
|
||||
QString subsprites_label = gfxInfoAttributes.value("subspriteTables");
|
||||
|
||||
QString gfx_label = picTables[pic_label].value(0);
|
||||
// We need the .png filepath for the event's sprite. This is buried behind a few levels of indirection.
|
||||
// The 'images' field gives us the name of the table containing the sprite's image data.
|
||||
// The entries in this table are expected to be in the format (PngSymbolName, ...).
|
||||
// We extract the symbol name of the .png's INCBIN'd data by looking at the first entry in this table.
|
||||
// Once we have the .png's symbol name we can get the actual filepath from its INCBIN.
|
||||
QString gfx_label = picTables[gfxInfoAttributes.value("images")].value(0);
|
||||
static const QRegularExpression re_parens("[\\(\\)]");
|
||||
gfx_label = gfx_label.section(re_parens, 1, 1);
|
||||
QString path = graphicIncbins[gfx_label];
|
||||
gfx->filepath = fixGraphicPath(graphicIncbins[gfx_label]);
|
||||
|
||||
if (!path.isNull()) {
|
||||
path = fixGraphicPath(path);
|
||||
eventGraphics->spritesheet = QImage(root + "/" + path);
|
||||
if (!eventGraphics->spritesheet.isNull()) {
|
||||
// Infer the sprite dimensions from the OAM labels.
|
||||
static const QRegularExpression re("\\S+_(\\d+)x(\\d+)");
|
||||
QRegularExpressionMatch dimensionMatch = re.match(dimensions_label);
|
||||
QRegularExpressionMatch oamTablesMatch = re.match(subsprites_label);
|
||||
if (oamTablesMatch.hasMatch()) {
|
||||
eventGraphics->spriteWidth = oamTablesMatch.captured(1).toInt(nullptr, 0);
|
||||
eventGraphics->spriteHeight = oamTablesMatch.captured(2).toInt(nullptr, 0);
|
||||
} else if (dimensionMatch.hasMatch()) {
|
||||
eventGraphics->spriteWidth = dimensionMatch.captured(1).toInt(nullptr, 0);
|
||||
eventGraphics->spriteHeight = dimensionMatch.captured(2).toInt(nullptr, 0);
|
||||
} else {
|
||||
eventGraphics->spriteWidth = eventGraphics->spritesheet.width();
|
||||
eventGraphics->spriteHeight = eventGraphics->spritesheet.height();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eventGraphics->spritesheet = QImage();
|
||||
eventGraphics->spriteWidth = 16;
|
||||
eventGraphics->spriteHeight = 16;
|
||||
// Note: gfx has a 'spritesheet' field that will contain a QImage for the event's sprite.
|
||||
// We don't create this QImage yet. Reading the image now is unnecessary overhead for startup.
|
||||
// We'll read the image file when the event's sprite is first requested to be drawn.
|
||||
|
||||
// The .png file is expected to be a spritesheet that can have multiple frames.
|
||||
// We only want to show one frame at a time, so we need to know the dimensions of each frame.
|
||||
// The true dimensions are buried in the subsprite data, so we try to infer the dimensions from the name of the 'subspriteTables' symbol.
|
||||
// If we are unable to do this, we can read the dimensions from the width and height fields.
|
||||
// This is much more straightforward, but the numbers are not necessarily accurate (one vanilla event sprite,
|
||||
// the Town Map in FRLG, has width/height values that differ from its true dimensions).
|
||||
static const QRegularExpression re_dimensions("\\S+_(\\d+)x(\\d+)");
|
||||
const QRegularExpressionMatch dimensionsMatch = re_dimensions.match(gfxInfoAttributes.value("subspriteTables"));
|
||||
if (dimensionsMatch.hasMatch()) {
|
||||
gfx->spriteWidth = dimensionsMatch.captured(1).toInt(nullptr, 0);
|
||||
gfx->spriteHeight = dimensionsMatch.captured(2).toInt(nullptr, 0);
|
||||
} else if (gfxInfoAttributes.contains("width") && gfxInfoAttributes.contains("height")) {
|
||||
gfx->spriteWidth = gfxInfoAttributes.value("width").toInt(nullptr, 0);
|
||||
gfx->spriteHeight = gfxInfoAttributes.value("height").toInt(nullptr, 0);
|
||||
}
|
||||
eventGraphicsMap.insert(gfxName, eventGraphics);
|
||||
// If we fail to get sprite dimensions then they should remain -1, and the sprite will use the full spritesheet as its image.
|
||||
if (gfx->spriteWidth <= 0 || gfx->spriteHeight <= 0) {
|
||||
gfx->spriteWidth = -1;
|
||||
gfx->spriteHeight = -1;
|
||||
}
|
||||
|
||||
// Inanimate events will only ever use the first frame of their spritesheet.
|
||||
gfx->inanimate = ParseUtil::gameStringToBool(gfxInfoAttributes.value("inanimate"));
|
||||
|
||||
this->eventGraphicsMap.insert(gfxName, gfx);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QPixmap Project::getEventPixmap(const QString &gfxName, const QString &movementName) {
|
||||
struct FrameData {
|
||||
int index = 0;
|
||||
bool hFlip = false;
|
||||
};
|
||||
// TODO: Expose as a setting to users
|
||||
static const QMap<QString, FrameData> directionToFrameData = {
|
||||
{"DIR_SOUTH", { .index = 0, .hFlip = false }},
|
||||
{"DIR_NORTH", { .index = 1, .hFlip = false }},
|
||||
{"DIR_WEST", { .index = 2, .hFlip = false }},
|
||||
{"DIR_EAST", { .index = 2, .hFlip = true }}, // East-facing sprite is just the West-facing sprite mirrored
|
||||
};
|
||||
const QString direction = this->facingDirections.value(movementName, "DIR_SOUTH");
|
||||
auto frameData = directionToFrameData.value(direction);
|
||||
return getEventPixmap(gfxName, frameData.index, frameData.hFlip);
|
||||
}
|
||||
|
||||
QPixmap Project::getEventPixmap(const QString &gfxName, int frame, bool hFlip) {
|
||||
QPixmap pixmap;
|
||||
const QString cacheKey = QString("EVENT#%1#%2#%3").arg(gfxName).arg(frame).arg(hFlip ? "1" : "0");
|
||||
if (QPixmapCache::find(cacheKey, &pixmap)) {
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
EventGraphics* gfx = this->eventGraphicsMap.value(gfxName, nullptr);
|
||||
if (!gfx) {
|
||||
// Invalid gfx constant. If this is a number, try to use that instead.
|
||||
bool ok;
|
||||
int gfxNum = ParseUtil::gameStringToInt(gfxName, &ok);
|
||||
if (ok && gfxNum < this->gfxDefines.count()) {
|
||||
gfx = this->eventGraphicsMap.value(this->gfxDefines.key(gfxNum, "NULL"), nullptr);
|
||||
}
|
||||
}
|
||||
if (gfx && !gfx->loaded) {
|
||||
// This is the first request for this event's sprite. We'll attempt to load it now.
|
||||
if (!gfx->filepath.isEmpty()) {
|
||||
gfx->spritesheet = QImage(QString("%1/%2").arg(this->root).arg(gfx->filepath));
|
||||
if (gfx->spritesheet.isNull()) {
|
||||
logWarn(QString("Failed to open '%1' for event's sprite. Event will use a default sprite instead.").arg(gfx->filepath));
|
||||
} else {
|
||||
// If we were unable to find the dimensions of a frame within the spritesheet we'll use the full image dimensions.
|
||||
if (gfx->spriteWidth <= 0) {
|
||||
gfx->spriteWidth = gfx->spritesheet.width();
|
||||
}
|
||||
if (gfx->spriteHeight <= 0) {
|
||||
gfx->spriteHeight = gfx->spritesheet.height();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set this whether we were successful or not, we only need to try to load it once.
|
||||
gfx->loaded = true;
|
||||
}
|
||||
if (!gfx || gfx->spritesheet.isNull()) {
|
||||
// Either we didn't recognize the gfxName, or we were unable to load the sprite's image.
|
||||
return QPixmap();
|
||||
}
|
||||
|
||||
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 ((frame + 1) * gfx->spriteWidth <= gfx->spritesheet.width()) {
|
||||
x = frame * gfx->spriteWidth;
|
||||
} else if ((frame + 1) * gfx->spriteHeight <= gfx->spritesheet.height()) {
|
||||
y = frame * gfx->spriteHeight;
|
||||
}
|
||||
|
||||
img = gfx->spritesheet.copy(x, y, gfx->spriteWidth, gfx->spriteHeight);
|
||||
if (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);
|
||||
QPixmapCache::insert(cacheKey, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QPixmap Project::getEventPixmap(Event::Group group) {
|
||||
if (group == Event::Group::None)
|
||||
return QPixmap();
|
||||
|
||||
QPixmap pixmap;
|
||||
const QString cacheKey = QString("EVENT#%1").arg(Event::groupToString(group));
|
||||
if (QPixmapCache::find(cacheKey, &pixmap)) {
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
const int defaultWidth = 16;
|
||||
const int defaultHeight = 16;
|
||||
static const QPixmap defaultIcons = QPixmap(":/images/Entities_16x16.png");
|
||||
QPixmap defaultIcon = QPixmap(defaultIcons.copy(static_cast<int>(group) * defaultWidth, 0, defaultWidth, defaultHeight));
|
||||
|
||||
// Custom event icons may be provided by the user.
|
||||
QString customIconPath = projectConfig.getEventIconPath(group);
|
||||
if (customIconPath.isEmpty()) {
|
||||
// No custom icon specified, use the default icon.
|
||||
pixmap = defaultIcon;
|
||||
} else {
|
||||
// Try to load custom icon
|
||||
QString validPath = Project::getExistingFilepath(customIconPath);
|
||||
if (!validPath.isEmpty()) customIconPath = validPath; // Otherwise allow it to fail with the original path
|
||||
pixmap = QPixmap(customIconPath);
|
||||
if (pixmap.isNull()) {
|
||||
pixmap = defaultIcon;
|
||||
logWarn(QString("Failed to load custom event icon '%1', using default icon.").arg(customIconPath));
|
||||
}
|
||||
}
|
||||
QPixmapCache::insert(cacheKey, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
bool Project::readSpeciesIconPaths() {
|
||||
this->speciesToIconPath.clear();
|
||||
|
||||
|
|
@ -2983,9 +3130,14 @@ bool Project::calculateDefaultMapSize(){
|
|||
return true;
|
||||
}
|
||||
|
||||
int Project::getMaxObjectEvents()
|
||||
{
|
||||
return Project::max_object_events;
|
||||
// Object events have their own limit specified by ProjectIdentifier::define_obj_event_count.
|
||||
// The default value for this is 64. All events (object events included) are also limited by
|
||||
// the data types of the event counters in the project. This would normally be u8, so the limit is 255.
|
||||
// We let the users tell us this limit in case they change these data types.
|
||||
int Project::getMaxEvents(Event::Group group) {
|
||||
if (group == Event::Group::Object)
|
||||
return qMin(this->maxObjectEvents, projectConfig.maxEventsPerGroup);
|
||||
return projectConfig.maxEventsPerGroup;
|
||||
}
|
||||
|
||||
QString Project::getEmptyMapDefineName() {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ ConnectionPixmapItem::ConnectionPixmapItem(MapConnection* connection)
|
|||
this->setEditable(true);
|
||||
setFlag(ItemIsFocusable, true);
|
||||
this->basePixmap = pixmap();
|
||||
refresh();
|
||||
updateOrigin();
|
||||
render(false);
|
||||
|
||||
// If the connection changes externally we want to update the pixmap to reflect the change.
|
||||
connect(connection, &MapConnection::offsetChanged, this, &ConnectionPixmapItem::updatePos);
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ static unsigned currentActionId = 0;
|
|||
|
||||
|
||||
void DraggablePixmapItem::updatePosition() {
|
||||
int x = event->getPixelX();
|
||||
int y = event->getPixelY();
|
||||
int x = this->event->getPixelX();
|
||||
int y = this->event->getPixelY();
|
||||
setX(x);
|
||||
setY(y);
|
||||
if (editor->selected_events && editor->selected_events->contains(this)) {
|
||||
if (this->editor->selectedEvents.contains(this->event)) {
|
||||
setZValue(event->getY() + 1);
|
||||
} else {
|
||||
setZValue(event->getY());
|
||||
|
|
@ -27,7 +27,7 @@ void DraggablePixmapItem::emitPositionChanged() {
|
|||
}
|
||||
|
||||
void DraggablePixmapItem::updatePixmap() {
|
||||
editor->project->setEventPixmap(event, true);
|
||||
editor->project->loadEventPixmap(event, true);
|
||||
this->updatePosition();
|
||||
editor->redrawEventPixmapItem(this);
|
||||
emit spriteChanged(event->getPixmap());
|
||||
|
|
@ -40,10 +40,10 @@ void DraggablePixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) {
|
|||
this->lastPos = Metatile::coordFromPixmapCoord(mouse->scenePos());
|
||||
|
||||
bool selectionToggle = mouse->modifiers() & Qt::ControlModifier;
|
||||
if (selectionToggle || !editor->selected_events->contains(this)) {
|
||||
if (selectionToggle || !this->editor->selectedEvents.contains(this->event)) {
|
||||
// User is either toggling this selection on/off as part of a group selection,
|
||||
// or they're newly selecting just this item.
|
||||
this->editor->selectMapEvent(this, selectionToggle);
|
||||
this->editor->selectMapEvent(this->event, selectionToggle);
|
||||
} else {
|
||||
// This item is already selected and the user isn't toggling the selection, so there are 4 possibilities:
|
||||
// 1. This is the only selected event, and the selection is pointless.
|
||||
|
|
@ -84,10 +84,8 @@ void DraggablePixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) {
|
|||
emit this->editor->map_item->hoveredMapMetatileChanged(pos);
|
||||
|
||||
QList <Event *> selectedEvents;
|
||||
if (editor->selected_events->contains(this)) {
|
||||
for (DraggablePixmapItem *item : *editor->selected_events) {
|
||||
selectedEvents.append(item->event);
|
||||
}
|
||||
if (this->editor->selectedEvents.contains(this->event)) {
|
||||
selectedEvents = this->editor->selectedEvents;
|
||||
} else {
|
||||
selectedEvents.append(this->event);
|
||||
}
|
||||
|
|
@ -103,7 +101,7 @@ void DraggablePixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) {
|
|||
if (this->releaseSelectionQueued) {
|
||||
this->releaseSelectionQueued = false;
|
||||
if (Metatile::coordFromPixmapCoord(mouse->scenePos()) == this->lastPos)
|
||||
this->editor->selectMapEvent(this);
|
||||
this->editor->selectMapEvent(this->event);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -173,12 +173,19 @@ void EventFrame::setActive(bool active) {
|
|||
}
|
||||
|
||||
void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * project) {
|
||||
// The script dropdown is populated with scripts used by the map's events and from its scripts file.
|
||||
if (this->event->getMap())
|
||||
combo->addItems(this->event->getMap()->getScriptLabels(this->event->getEventGroup()));
|
||||
// The script dropdown and autocomplete are populated with scripts used by the map's events and from its scripts file.
|
||||
if (!this->event->getMap())
|
||||
return;
|
||||
|
||||
// The dropdown's autocomplete has all script labels across the full project.
|
||||
auto completer = new QCompleter(project->globalScriptLabels, combo);
|
||||
QStringList scripts = this->event->getMap()->getScriptLabels(this->event->getEventGroup());
|
||||
combo->addItems(scripts);
|
||||
|
||||
// Depending on the settings, the autocomplete may also contain all global scripts.
|
||||
if (porymapConfig.loadAllEventScripts) {
|
||||
project->insertGlobalScriptLabels(scripts);
|
||||
}
|
||||
|
||||
auto completer = new QCompleter(scripts, combo);
|
||||
completer->setCaseSensitivity(Qt::CaseInsensitive);
|
||||
completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
|
||||
completer->setFilterMode(Qt::MatchContains);
|
||||
|
|
@ -188,6 +195,9 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj
|
|||
if (popup) popup->setUniformItemSizes(true);
|
||||
|
||||
combo->setCompleter(completer);
|
||||
|
||||
// If the project changes the script labels, update the EventFrame.
|
||||
connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -517,7 +517,7 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) {
|
|||
|| (m_settings.showBGs && group == Event::Group::Bg)
|
||||
|| (m_settings.showTriggers && group == Event::Group::Coord)
|
||||
|| (m_settings.showHealLocations && group == Event::Group::Heal)) {
|
||||
m_editor->project->setEventPixmap(event);
|
||||
m_editor->project->loadEventPixmap(event);
|
||||
eventPainter.drawImage(QPoint(event->getPixelX() + pixelOffset, event->getPixelY() + pixelOffset), event->getPixmap().toImage());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,17 +235,23 @@ QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const {
|
|||
|
||||
QDataStream stream(&encodedData, QIODevice::WriteOnly);
|
||||
|
||||
// if dropping a selection containing a group(s) and map(s), clear all selection but first group.
|
||||
bool droppingGroups = false;
|
||||
|
||||
// if dropping a selection containing both group(s) and map(s), only copy groups
|
||||
for (const QModelIndex &index : indexes) {
|
||||
if (index.isValid() && data(index, MapListUserRoles::TypeRole).toString() == "map_group") {
|
||||
QString groupName = data(index, MapListUserRoles::NameRole).toString();
|
||||
stream << groupName;
|
||||
mimeData->setData("application/porymap.mapgroupmodel.group", encodedData);
|
||||
mimeData->setData("application/porymap.mapgroupmodel.source.row", QByteArray::number(index.row()));
|
||||
return mimeData;
|
||||
stream << index.row();
|
||||
droppingGroups = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (droppingGroups) {
|
||||
mimeData->setData("application/porymap.mapgroupmodel.group", encodedData);
|
||||
return mimeData;
|
||||
}
|
||||
|
||||
for (const QModelIndex &index : indexes) {
|
||||
if (index.isValid()) {
|
||||
QString mapName = data(index, MapListUserRoles::NameRole).toString();
|
||||
|
|
@ -277,33 +283,48 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i
|
|||
if (parentIndex.row() != -1 || parentIndex.column() != -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (row < 0) {
|
||||
row = this->rowCount(parentIndex);
|
||||
}
|
||||
|
||||
QByteArray encodedData = data->data("application/porymap.mapgroupmodel.group");
|
||||
QDataStream stream(&encodedData, QIODevice::ReadOnly);
|
||||
QString groupName;
|
||||
QStringList droppedGroups;
|
||||
QList<int> droppedGroupIndexes;
|
||||
int rowCount = 0;
|
||||
|
||||
while (!stream.atEnd()) {
|
||||
QString groupName;
|
||||
stream >> groupName;
|
||||
int groupIndex;
|
||||
stream >> groupIndex;
|
||||
droppedGroups << groupName;
|
||||
droppedGroupIndexes << groupIndex;
|
||||
rowCount++;
|
||||
}
|
||||
|
||||
this->insertRow(row, parentIndex);
|
||||
for (int r = 0; r < rowCount; r++) {
|
||||
QString groupName = droppedGroups[r];
|
||||
// copy children to new node
|
||||
int sourceRow = droppedGroupIndexes[r];
|
||||
QModelIndex originIndex = this->index(sourceRow, 0);
|
||||
QModelIndexList children;
|
||||
QStringList mapsToMove;
|
||||
for (int i = 0; i < this->rowCount(originIndex); ++i ) {
|
||||
children << this->index(i, 0, originIndex);
|
||||
mapsToMove << this->index(i, 0 , originIndex).data(MapListUserRoles::NameRole).toString();
|
||||
}
|
||||
|
||||
// copy children to new node
|
||||
int sourceRow = data->data("application/porymap.mapgroupmodel.source.row").toInt();
|
||||
QModelIndex originIndex = this->index(sourceRow, 0);
|
||||
QModelIndexList children;
|
||||
QStringList mapsToMove;
|
||||
for (int i = 0; i < this->rowCount(originIndex); ++i ) {
|
||||
children << this->index( i, 0, originIndex);
|
||||
mapsToMove << this->index( i, 0 , originIndex).data(MapListUserRoles::NameRole).toString();
|
||||
}
|
||||
this->insertRow(row + r, parentIndex);
|
||||
QModelIndex groupIndex = index(row + r, 0, parentIndex);
|
||||
QStandardItem *groupItem = this->itemFromIndex(groupIndex);
|
||||
createMapFolderItem(groupName, groupItem);
|
||||
|
||||
QModelIndex groupIndex = index(row, 0, parentIndex);
|
||||
QStandardItem *groupItem = this->itemFromIndex(groupIndex);
|
||||
createMapFolderItem(groupName, groupItem);
|
||||
|
||||
for (QString mapName : mapsToMove) {
|
||||
QStandardItem *mapItem = createMapItem(mapName);
|
||||
groupItem->appendRow(mapItem);
|
||||
for (QString mapName : mapsToMove) {
|
||||
QStandardItem *mapItem = createMapItem(mapName);
|
||||
groupItem->appendRow(mapItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (data->hasFormat("application/porymap.mapgroupmodel.map")) {
|
||||
|
|
|
|||
|
|
@ -27,8 +27,12 @@ void Prefab::loadPrefabs() {
|
|||
QJsonDocument prefabDoc;
|
||||
|
||||
QString validPath = Project::getExistingFilepath(filepath);
|
||||
if (validPath.isEmpty() || !parser.tryParseJsonFile(&prefabDoc, validPath)) {
|
||||
logError(QString("Failed to read prefab data from %1").arg(filepath));
|
||||
if (validPath.isEmpty())
|
||||
return;
|
||||
|
||||
QString error;
|
||||
if (!parser.tryParseJsonFile(&prefabDoc, validPath, &error)) {
|
||||
logError(QString("Failed to read prefab data from %1: %2").arg(filepath).arg(error));
|
||||
return;
|
||||
}
|
||||
filepath = validPath;
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ void PreferenceEditor::updateFields() {
|
|||
ui->checkBox_OpenRecentProject->setChecked(porymapConfig.reopenOnLaunch);
|
||||
ui->checkBox_CheckForUpdates->setChecked(porymapConfig.checkForUpdates);
|
||||
ui->checkBox_DisableEventWarning->setChecked(porymapConfig.eventDeleteWarningDisabled);
|
||||
ui->checkBox_AutocompleteAllScripts->setChecked(porymapConfig.loadAllEventScripts);
|
||||
}
|
||||
|
||||
void PreferenceEditor::saveFields() {
|
||||
|
|
@ -64,6 +65,11 @@ void PreferenceEditor::saveFields() {
|
|||
porymapConfig.theme = theme;
|
||||
emit themeChanged(theme);
|
||||
}
|
||||
bool loadAllEventScripts = ui->checkBox_AutocompleteAllScripts->isChecked();
|
||||
if (loadAllEventScripts != porymapConfig.loadAllEventScripts) {
|
||||
porymapConfig.loadAllEventScripts = loadAllEventScripts;
|
||||
emit scriptSettingsChanged(loadAllEventScripts);
|
||||
}
|
||||
porymapConfig.eventSelectionShapeMode = ui->radioButton_OnSprite->isChecked() ? QGraphicsPixmapItem::MaskShape : QGraphicsPixmapItem::BoundingRectShape;
|
||||
porymapConfig.textEditorOpenFolder = ui->lineEdit_TextEditorOpenFolder->text();
|
||||
porymapConfig.textEditorGotoLine = ui->lineEdit_TextEditorGotoLine->text();
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ void ProjectSettingsEditor::initUi() {
|
|||
ui->spinBox_UnusedTileNormal->setMaximum(Tile::maxValue);
|
||||
ui->spinBox_UnusedTileCovered->setMaximum(Tile::maxValue);
|
||||
ui->spinBox_UnusedTileSplit->setMaximum(Tile::maxValue);
|
||||
ui->spinBox_MaxEvents->setMaximum(INT_MAX);
|
||||
|
||||
// The values for some of the settings we provide in this window can be determined using constants in the user's projects.
|
||||
// If the user has these constants we disable these settings in the UI -- they can modify them using their constants.
|
||||
|
|
@ -465,6 +466,7 @@ void ProjectSettingsEditor::refresh() {
|
|||
ui->spinBox_UnusedTileNormal->setValue(projectConfig.unusedTileNormal);
|
||||
ui->spinBox_UnusedTileCovered->setValue(projectConfig.unusedTileCovered);
|
||||
ui->spinBox_UnusedTileSplit->setValue(projectConfig.unusedTileSplit);
|
||||
ui->spinBox_MaxEvents->setValue(projectConfig.maxEventsPerGroup);
|
||||
|
||||
// Set (and sync) border metatile IDs
|
||||
this->setBorderMetatileIds(false, projectConfig.newMapBorderMetatileIds);
|
||||
|
|
@ -539,6 +541,7 @@ void ProjectSettingsEditor::save() {
|
|||
projectConfig.unusedTileNormal = ui->spinBox_UnusedTileNormal->value();
|
||||
projectConfig.unusedTileCovered = ui->spinBox_UnusedTileCovered->value();
|
||||
projectConfig.unusedTileSplit = ui->spinBox_UnusedTileSplit->value();
|
||||
projectConfig.maxEventsPerGroup = ui->spinBox_MaxEvents->value();
|
||||
|
||||
// Save line edit settings
|
||||
projectConfig.prefabFilepath = ui->lineEdit_PrefabsPath->text();
|
||||
|
|
|
|||
|
|
@ -120,26 +120,20 @@ bool RegionMapEditor::saveRegionMapEntries() {
|
|||
void buildEmeraldDefaults(poryjson::Json &json) {
|
||||
ParseUtil parser;
|
||||
QString emeraldDefault = parser.readTextFile(":/text/region_map_default_emerald.json");
|
||||
|
||||
QString err;
|
||||
json = poryjson::Json::parse(emeraldDefault, err);
|
||||
json = poryjson::Json::parse(emeraldDefault);
|
||||
}
|
||||
|
||||
void buildRubyDefaults(poryjson::Json &json) {
|
||||
ParseUtil parser;
|
||||
QString emeraldDefault = parser.readTextFile(":/text/region_map_default_ruby.json");
|
||||
|
||||
QString err;
|
||||
json = poryjson::Json::parse(emeraldDefault, err);
|
||||
json = poryjson::Json::parse(emeraldDefault);
|
||||
}
|
||||
|
||||
void buildFireredDefaults(poryjson::Json &json) {
|
||||
|
||||
ParseUtil parser;
|
||||
QString fireredDefault = parser.readTextFile(":/text/region_map_default_firered.json");
|
||||
|
||||
QString err;
|
||||
json = poryjson::Json::parse(fireredDefault, err);
|
||||
json = poryjson::Json::parse(fireredDefault);
|
||||
}
|
||||
|
||||
poryjson::Json RegionMapEditor::buildDefaultJson() {
|
||||
|
|
@ -199,8 +193,7 @@ bool RegionMapEditor::buildConfigDialog() {
|
|||
poryjson::Json::object newJson;
|
||||
poryjson::Json::array mapArr;
|
||||
for (auto item : regionMapList->findItems("*", Qt::MatchWildcard)) {
|
||||
QString err;
|
||||
poryjson::Json itemJson = poryjson::Json::parse(item->data(Qt::UserRole).toString(), err);
|
||||
poryjson::Json itemJson = poryjson::Json::parse(item->data(Qt::UserRole).toString());
|
||||
mapArr.append(itemJson);
|
||||
}
|
||||
newJson["region_maps"] = mapArr;
|
||||
|
|
@ -213,8 +206,7 @@ bool RegionMapEditor::buildConfigDialog() {
|
|||
connect(regionMapList, &QListWidget::itemDoubleClicked, [this, &rmConfigJsonUpdate, updateMapList, regionMapList](QListWidgetItem *item) {
|
||||
int itemIndex = regionMapList->row(item);
|
||||
|
||||
QString err;
|
||||
poryjson::Json clickedJson = poryjson::Json::parse(item->data(Qt::UserRole).toString(), err);
|
||||
poryjson::Json clickedJson = poryjson::Json::parse(item->data(Qt::UserRole).toString());
|
||||
|
||||
RegionMapPropertiesDialog dialog(this);
|
||||
dialog.setProject(this->project);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user