Merge pull request #689 from GriffinRichards/event-speed-improvements

Event speed improvements
This commit is contained in:
GriffinR
2025-02-26 11:56:07 -05:00
committed by GitHub
16 changed files with 552 additions and 466 deletions

View File

@@ -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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this command is set a button will appear next to the &lt;span style=&quot; font-weight:600; font-style:italic;&quot;&gt;Script&lt;/span&gt; combo-box in the &lt;span style=&quot; font-weight:600; font-style:italic;&quot;&gt;Events&lt;/span&gt; tab which executes this command.&lt;span style=&quot; font-weight:600;&quot;&gt; %F&lt;/span&gt; will be substituted with the file path of the script and &lt;span style=&quot; font-weight:600;&quot;&gt;%L&lt;/span&gt; will be substituted with the line number of the script in that file. &lt;span style=&quot; font-weight:600;&quot;&gt;%F &lt;/span&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;must&lt;/span&gt; be given if &lt;span style=&quot; font-weight:600;&quot;&gt;%L&lt;/span&gt; is given. If &lt;span style=&quot; font-weight:600;&quot;&gt;%F&lt;/span&gt; is &lt;span style=&quot; font-style:italic;&quot;&gt;not&lt;/span&gt; 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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This is the command that is executed when clicking &lt;span style=&quot; font-weight:600; font-style:italic;&quot;&gt;Open Project in Text Editor&lt;/span&gt; in the &lt;span style=&quot; font-weight:600; font-style:italic;&quot;&gt;Tools&lt;/span&gt; menu. &lt;span style=&quot; font-weight:600;&quot;&gt;%D&lt;/span&gt; will be substituted with the project's root directory. If &lt;span style=&quot; font-weight:600;&quot;&gt;%D&lt;/span&gt; is &lt;span style=&quot; font-style:italic;&quot;&gt;not&lt;/span&gt; specified then the project directory will be added to the end of the command.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this command is set a button will appear next to the &lt;span style=&quot; font-weight:600; font-style:italic;&quot;&gt;Script&lt;/span&gt; combo-box in the &lt;span style=&quot; font-weight:600; font-style:italic;&quot;&gt;Events&lt;/span&gt; tab which executes this command.&lt;span style=&quot; font-weight:600;&quot;&gt; %F&lt;/span&gt; will be substituted with the file path of the script and &lt;span style=&quot; font-weight:600;&quot;&gt;%L&lt;/span&gt; will be substituted with the line number of the script in that file. &lt;span style=&quot; font-weight:600;&quot;&gt;%F &lt;/span&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;must&lt;/span&gt; be given if &lt;span style=&quot; font-weight:600;&quot;&gt;%L&lt;/span&gt; is given. If &lt;span style=&quot; font-weight:600;&quot;&gt;%F&lt;/span&gt; is &lt;span style=&quot; font-style:italic;&quot;&gt;not&lt;/span&gt; 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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This is the command that is executed when clicking &lt;span style=&quot; font-weight:600; font-style:italic;&quot;&gt;Open Project in Text Editor&lt;/span&gt; in the &lt;span style=&quot; font-weight:600; font-style:italic;&quot;&gt;Tools&lt;/span&gt; menu. &lt;span style=&quot; font-weight:600;&quot;&gt;%D&lt;/span&gt; will be substituted with the project's root directory. If &lt;span style=&quot; font-weight:600;&quot;&gt;%D&lt;/span&gt; is &lt;span style=&quot; font-style:italic;&quot;&gt;not&lt;/span&gt; specified then the project directory will be added to the end of the command.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>

View File

@@ -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;
@@ -136,6 +137,7 @@ public:
QString textEditorGotoLine;
int paletteEditorBitDepth;
int projectSettingsTab;
bool loadAllEventScripts;
bool warpBehaviorWarningDisabled;
bool eventDeleteWarningDisabled;
bool eventOverlayEnabled;

View File

@@ -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,14 +158,8 @@ 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();
@@ -189,8 +169,6 @@ public:
static QString groupToString(Event::Group group);
static QString typeToString(Event::Type type);
static Event::Type typeFromString(QString type);
static void clearIcons();
static void setIcons();
// 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;

View File

@@ -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;
@@ -210,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);
@@ -218,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;
@@ -254,6 +256,18 @@ public:
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 *);
@@ -284,6 +298,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

View File

@@ -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;
};

View File

@@ -23,6 +23,7 @@ public:
signals:
void preferencesSaved();
void themeChanged(const QString &theme);
void scriptSettingsChanged(bool on);
private:
Ui::PreferenceEditor *ui;

View File

@@ -390,6 +390,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") {
@@ -481,6 +483,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));

View File

@@ -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();
@@ -107,46 +105,11 @@ 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 {
@@ -230,7 +193,6 @@ 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 = {
@@ -257,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;
}
}
@@ -430,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);
}

View File

@@ -152,13 +152,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;

View File

@@ -13,6 +13,7 @@
#include "customattributesframe.h"
#include "validator.h"
#include "message.h"
#include "eventframes.h"
#include <QCheckBox>
#include <QPainter>
#include <QMouseEvent>
@@ -1675,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);
@@ -1960,13 +1961,13 @@ 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);

View File

@@ -1104,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());
@@ -1162,8 +1161,6 @@ void MainWindow::clearProjectUI() {
delete this->layoutTreeModel;
delete this->layoutListProxyModel;
resetMapListFilters();
Event::clearIcons();
}
void MainWindow::scrollMapList(MapTree *list, const QString &itemName) {
@@ -2739,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);
@@ -2759,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) {

View File

@@ -44,6 +44,7 @@ Project::~Project()
clearMapLayouts();
clearEventGraphics();
clearHealLocations();
QPixmapCache::clear();
}
void Project::set_root(QString dir) {
@@ -2637,16 +2638,27 @@ bool Project::readMiscellaneousConstants() {
}
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");
@@ -2702,92 +2714,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();

View File

@@ -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());

View File

@@ -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);
}

View File

@@ -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());
}
}

View File

@@ -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();