Merge branch 'dev' of https://github.com/huderlem/porymap into status-bar-log
|
|
@ -50,6 +50,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
|
|||
- Primary/secondary metatile images are now kept on separate rows, rather than blending together if the primary size is not divisible by 8.
|
||||
- The prompt to reload the project when a file has changed will now only appear when Porymap is the active application.
|
||||
- `Script` dropdowns now autocomplete only with scripts from the current map, rather than every script in the project. The old behavior is available via a new setting.
|
||||
- `Script` dropdowns now update automatically if the current map's scripts file is edited.
|
||||
- The options for `Encounter Type` and `Terrain Type` in the Tileset Editor are not hardcoded anymore, they're now read from the project.
|
||||
- The `symbol_wild_encounters` setting was replaced; this value is now read from the project.
|
||||
- The max encounter rate is now read from the project, rather than assuming the default value from RSE.
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>570</width>
|
||||
<height>692</height>
|
||||
<height>680</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
@ -164,7 +164,7 @@
|
|||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
@ -248,7 +248,7 @@
|
|||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
@ -1197,23 +1197,62 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>570</width>
|
||||
<height>840</height>
|
||||
<height>927</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_EventsTabIcon">
|
||||
<property name="title">
|
||||
<string>Tab Icon</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_12">
|
||||
<item row="1" column="1">
|
||||
<widget class="QToolButton" name="button_EventsTabIcon">
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="lineEdit_EventsTabIcon">
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The image file path to use for the icon of the Events tab.</p></body></html></string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="NoScrollComboBox" name="comboBox_EventsTabIcon">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The icon that will be displayed for the Events tab in the editor. If 'Automatic' is chosen, the icon will be a random player character from the project's base game version. If 'Custom' is chosen an image file path may be specified.</p></body></html></string>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_DefaultIcons">
|
||||
<property name="title">
|
||||
<string>Default Icons</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_TriggersIcon">
|
||||
<property name="text">
|
||||
<string>Triggers</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_WarpsIcon">
|
||||
<property name="text">
|
||||
|
|
@ -1221,23 +1260,31 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_WarpsIcon">
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_BGsIcon">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The icon that will be used to represent Warp events</p></body></html></string>
|
||||
<string><html><head/><body><p>The icon that will be used to represent BG events</p></body></html></string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_HealLocationsIcon">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The icon that will be used to represent Heal Location events</p></body></html></string>
|
||||
<item row="4" column="2">
|
||||
<widget class="QToolButton" name="button_HealLocationsIcon">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_ObjectsIcon">
|
||||
<property name="text">
|
||||
<string>Objects</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -1248,6 +1295,24 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_TriggersIcon">
|
||||
<property name="text">
|
||||
<string>Triggers</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QToolButton" name="button_WarpsIcon">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_HealLocationsIcon">
|
||||
<property name="text">
|
||||
|
|
@ -1265,10 +1330,56 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_ObjectsIcon">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_WarpsIcon">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The icon that will be used to represent Warp events</p></body></html></string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QToolButton" name="button_BGsIcon">
|
||||
<property name="text">
|
||||
<string>Objects</string>
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_HealLocationsIcon">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The icon that will be used to represent Heal Location events</p></body></html></string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QToolButton" name="button_TriggersIcon">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QToolButton" name="button_ObjectsIcon">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -1282,71 +1393,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_BGsIcon">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The icon that will be used to represent BG events</p></body></html></string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QToolButton" name="button_ObjectsIcon">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QToolButton" name="button_WarpsIcon">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QToolButton" name="button_TriggersIcon">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QToolButton" name="button_BGsIcon">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QToolButton" name="button_HealLocationsIcon">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<normaloff>:/icons/folder.ico</normaloff>:/icons/folder.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -1365,7 +1411,7 @@
|
|||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/delete.ico</normaloff>:/icons/delete.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
@ -1418,7 +1464,7 @@
|
|||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/add.ico</normaloff>:/icons/add.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
@ -1628,7 +1674,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>544</width>
|
||||
<height>338</height>
|
||||
<height>341</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="layout_ProjectPaths">
|
||||
|
|
@ -1657,7 +1703,7 @@
|
|||
<string>Add Global Constants File...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/add.ico</normaloff>:/icons/add.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
@ -1668,7 +1714,7 @@
|
|||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/help.ico</normaloff>:/icons/help.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
@ -1708,7 +1754,7 @@
|
|||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/help.ico</normaloff>:/icons/help.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
@ -1745,7 +1791,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>544</width>
|
||||
<height>421</height>
|
||||
<height>425</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="layout_Identifiers">
|
||||
|
|
@ -1774,7 +1820,7 @@
|
|||
<string>Add Global Constant...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/add.ico</normaloff>:/icons/add.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
@ -1840,8 +1886,6 @@
|
|||
<header location="global">uintspinbox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../resources/images.qrc"/>
|
||||
</resources>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
|
|||
|
|
@ -335,6 +335,7 @@ public:
|
|||
this->filePaths.clear();
|
||||
this->eventIconPaths.clear();
|
||||
this->pokemonIconPaths.clear();
|
||||
this->eventsTabIconPath = QString();
|
||||
this->collisionSheetPath = QString();
|
||||
this->collisionSheetSize = QSize(2, 16);
|
||||
this->playerViewDistance = QMargins(GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER, GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER);
|
||||
|
|
@ -345,6 +346,7 @@ public:
|
|||
this->unusedTileCovered = 0x0000;
|
||||
this->unusedTileSplit = 0x0000;
|
||||
this->maxEventsPerGroup = 255;
|
||||
this->forcedMajorVersion = 0;
|
||||
this->globalConstantsFilepaths.clear();
|
||||
this->globalConstants.clear();
|
||||
this->identifiers.clear();
|
||||
|
|
@ -355,6 +357,9 @@ public:
|
|||
static const QStringList versionStrings;
|
||||
static BaseGameVersion stringToBaseGameVersion(const QString &string);
|
||||
|
||||
static QString getPlayerIconPath(BaseGameVersion baseGameVersion, int character);
|
||||
static QIcon getPlayerIcon(BaseGameVersion baseGameVersion, int character);
|
||||
|
||||
void reset(BaseGameVersion baseGameVersion);
|
||||
void setFilePath(ProjectFilePath pathId, const QString &path);
|
||||
void setFilePath(const QString &pathId, const QString &path);
|
||||
|
|
@ -414,11 +419,13 @@ public:
|
|||
uint16_t unusedTileCovered;
|
||||
uint16_t unusedTileSplit;
|
||||
bool mapAllowFlagsEnabled;
|
||||
QString eventsTabIconPath;
|
||||
QString collisionSheetPath;
|
||||
QSize collisionSheetSize;
|
||||
QMargins playerViewDistance;
|
||||
QList<uint32_t> warpBehaviors;
|
||||
int maxEventsPerGroup;
|
||||
int forcedMajorVersion;
|
||||
QStringList globalConstantsFilepaths;
|
||||
QMap<QString,QString> globalConstants;
|
||||
|
||||
|
|
|
|||
|
|
@ -34,15 +34,6 @@ class HiddenItemEvent;
|
|||
class SecretBaseEvent;
|
||||
class HealLocationEvent;
|
||||
|
||||
class EventVisitor {
|
||||
public:
|
||||
virtual void nothing() { }
|
||||
virtual void visitObject(ObjectEvent *) = 0;
|
||||
virtual void visitTrigger(TriggerEvent *) = 0;
|
||||
virtual void visitSign(SignEvent *) = 0;
|
||||
};
|
||||
|
||||
|
||||
///
|
||||
/// Event base class -- purely virtual
|
||||
///
|
||||
|
|
@ -121,8 +112,6 @@ public:
|
|||
|
||||
void modify();
|
||||
|
||||
virtual void accept(EventVisitor *) { }
|
||||
|
||||
void setX(int newX) { this->x = newX; }
|
||||
void setY(int newY) { this->y = newY; }
|
||||
void setZ(int newZ) { this->elevation = newZ; }
|
||||
|
|
@ -150,6 +139,8 @@ public:
|
|||
|
||||
virtual QSet<QString> getExpectedFields() = 0;
|
||||
|
||||
virtual QStringList getScripts() const { return QStringList(); }
|
||||
|
||||
QJsonObject getCustomAttributes() const { return this->customAttributes; }
|
||||
void setCustomAttributes(const QJsonObject &newCustomAttributes) { this->customAttributes = newCustomAttributes; }
|
||||
|
||||
|
|
@ -222,8 +213,6 @@ public:
|
|||
|
||||
virtual Event *duplicate() const override;
|
||||
|
||||
virtual void accept(EventVisitor *visitor) override { visitor->visitObject(this); }
|
||||
|
||||
virtual EventFrame *createEventFrame() override;
|
||||
|
||||
virtual OrderedJson::object buildEventJson(Project *project) override;
|
||||
|
|
@ -233,6 +222,8 @@ public:
|
|||
|
||||
virtual QSet<QString> getExpectedFields() override;
|
||||
|
||||
virtual QStringList getScripts() const override { return {getScript()}; }
|
||||
|
||||
virtual QPixmap loadPixmap(Project *project) override;
|
||||
|
||||
void setGfx(QString newGfx) { this->gfx = newGfx; }
|
||||
|
|
@ -392,8 +383,6 @@ public:
|
|||
|
||||
virtual Event *duplicate() const override;
|
||||
|
||||
virtual void accept(EventVisitor *visitor) override { visitor->visitTrigger(this); }
|
||||
|
||||
virtual EventFrame *createEventFrame() override;
|
||||
|
||||
virtual OrderedJson::object buildEventJson(Project *project) override;
|
||||
|
|
@ -403,6 +392,8 @@ public:
|
|||
|
||||
virtual QSet<QString> getExpectedFields() override;
|
||||
|
||||
virtual QStringList getScripts() const override { return {getScriptLabel()}; }
|
||||
|
||||
void setScriptVar(QString newScriptVar) { this->scriptVar = newScriptVar; }
|
||||
QString getScriptVar() const { return this->scriptVar; }
|
||||
|
||||
|
|
@ -490,8 +481,6 @@ public:
|
|||
|
||||
virtual Event *duplicate() const override;
|
||||
|
||||
virtual void accept(EventVisitor *visitor) override { visitor->visitSign(this); }
|
||||
|
||||
virtual EventFrame *createEventFrame() override;
|
||||
|
||||
virtual OrderedJson::object buildEventJson(Project *project) override;
|
||||
|
|
@ -501,6 +490,8 @@ public:
|
|||
|
||||
virtual QSet<QString> getExpectedFields() override;
|
||||
|
||||
virtual QStringList getScripts() const override { return {getScriptLabel()}; }
|
||||
|
||||
void setFacingDirection(QString newFacingDirection) { this->facingDirection = newFacingDirection; }
|
||||
QString getFacingDirection() const { return this->facingDirection; }
|
||||
|
||||
|
|
@ -633,21 +624,4 @@ inline uint qHash(const Event::Group &key, uint seed = 0) {
|
|||
return qHash(static_cast<int>(key), seed);
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// Keeps track of scripts
|
||||
///
|
||||
class ScriptTracker : public EventVisitor {
|
||||
public:
|
||||
virtual void visitObject(ObjectEvent *object) override { this->scripts << object->getScript(); };
|
||||
virtual void visitTrigger(TriggerEvent *trigger) override { this->scripts << trigger->getScriptLabel(); };
|
||||
virtual void visitSign(SignEvent *sign) override { this->scripts << sign->getScriptLabel(); };
|
||||
|
||||
QStringList getScripts() const { return this->scripts; }
|
||||
|
||||
private:
|
||||
QStringList scripts;
|
||||
};
|
||||
|
||||
|
||||
#endif // EVENTS_H
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <QPixmap>
|
||||
#include <QObject>
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <math.h>
|
||||
|
||||
#define DEFAULT_BORDER_WIDTH 2
|
||||
|
|
@ -56,7 +57,7 @@ public:
|
|||
MapHeader* header() const { return m_header; }
|
||||
|
||||
void setSharedEventsMap(const QString &sharedEventsMap) { m_sharedEventsMap = sharedEventsMap; }
|
||||
void setSharedScriptsMap(const QString &sharedScriptsMap) { m_sharedScriptsMap = sharedScriptsMap; }
|
||||
void setSharedScriptsMap(const QString &sharedScriptsMap);
|
||||
|
||||
QString sharedEventsMap() const { return m_sharedEventsMap; }
|
||||
QString sharedScriptsMap() const { return m_sharedScriptsMap; }
|
||||
|
|
@ -75,14 +76,15 @@ public:
|
|||
Event* getEvent(Event::Group group, const QString &idName) const;
|
||||
QStringList getEventIdNames(Event::Group group) const;
|
||||
int getNumEvents(Event::Group group = Event::Group::None) const;
|
||||
QStringList getScriptLabels(Event::Group group = Event::Group::None);
|
||||
QString getScriptsFilePath() const;
|
||||
void openScript(QString label);
|
||||
void removeEvent(Event *);
|
||||
void addEvent(Event *);
|
||||
int getIndexOfEvent(Event *) const;
|
||||
bool hasEvent(Event *) const;
|
||||
|
||||
QStringList getScriptLabels(Event::Group group = Event::Group::None);
|
||||
QString getScriptsFilePath() const;
|
||||
void openScript(const QString &label);
|
||||
|
||||
void deleteConnections();
|
||||
QList<MapConnection*> getConnections() const { return m_connections; }
|
||||
MapConnection* getConnection(const QString &direction) const;
|
||||
|
|
@ -108,7 +110,7 @@ private:
|
|||
QString m_sharedEventsMap = "";
|
||||
QString m_sharedScriptsMap = "";
|
||||
|
||||
QStringList m_scriptsFileLabels;
|
||||
QStringList m_scriptLabels;
|
||||
QJsonObject m_customAttributes;
|
||||
|
||||
MapHeader *m_header = nullptr;
|
||||
|
|
@ -131,10 +133,14 @@ private:
|
|||
QList<MapConnection*> m_connections;
|
||||
QSet<MapConnection*> m_ownedConnections;
|
||||
|
||||
QUndoStack *m_editHistory = nullptr;
|
||||
QPointer<QUndoStack> m_editHistory;
|
||||
QPointer<QFileSystemWatcher> m_scriptFileWatcher;
|
||||
|
||||
void invalidateScripts();
|
||||
|
||||
signals:
|
||||
void modified();
|
||||
void scriptsModified();
|
||||
void mapDimensionsChanged(const QSize &size);
|
||||
void openScriptRequested(QString label);
|
||||
void connectionAdded(MapConnection*);
|
||||
|
|
|
|||
|
|
@ -351,7 +351,9 @@ private:
|
|||
void refreshCollisionSelector();
|
||||
void setLayoutOnlyMode(bool layoutOnly);
|
||||
|
||||
bool checkProjectSanity();
|
||||
bool isInvalidProject(Project *project);
|
||||
bool checkProjectSanity(Project *project);
|
||||
bool checkProjectVersion(Project *project);
|
||||
bool loadProjectData();
|
||||
bool setProjectUI();
|
||||
void clearProjectUI();
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ public:
|
|||
void clearHealLocations();
|
||||
|
||||
bool sanityCheck();
|
||||
int getSupportedMajorVersion(QString *errorOut = nullptr);
|
||||
bool load();
|
||||
|
||||
Map* loadMap(const QString &mapName);
|
||||
|
|
|
|||
|
|
@ -56,9 +56,11 @@ protected:
|
|||
bool populated = false;
|
||||
bool initialized = false;
|
||||
bool connected = false;
|
||||
QPointer<Project> project;
|
||||
|
||||
void populateDropdown(NoScrollComboBox * combo, const QStringList &items);
|
||||
void populateScriptDropdown(NoScrollComboBox * combo, Project * project);
|
||||
void populateMapNameDropdown(NoScrollComboBox * combo, Project * project);
|
||||
void populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group);
|
||||
|
||||
private:
|
||||
|
|
|
|||
BIN
resources/icons/player/brendan_em.ico
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
resources/icons/player/brendan_rs.ico
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
resources/icons/player/green.ico
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
resources/icons/player/may_em.ico
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
resources/icons/player/may_rs.ico
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
|
@ -49,9 +49,14 @@
|
|||
<file>icons/tall_grass.ico</file>
|
||||
<file>icons/warning.ico</file>
|
||||
<file>icons/minimap.ico</file>
|
||||
<file>icons/viewsprites.ico</file>
|
||||
<file>icons/application_form_edit.ico</file>
|
||||
<file>icons/connections.ico</file>
|
||||
<file>icons/player/brendan_em.ico</file>
|
||||
<file>icons/player/brendan_rs.ico</file>
|
||||
<file>icons/player/green.ico</file>
|
||||
<file>icons/player/may_em.ico</file>
|
||||
<file>icons/player/may_rs.ico</file>
|
||||
<file>icons/player/red.ico</file>
|
||||
<file>icons/ui/dark/checkbox_checked_disabled.png</file>
|
||||
<file>icons/ui/dark/checkbox_checked_disabled@2x.png</file>
|
||||
<file>icons/ui/dark/checkbox_checked.png</file>
|
||||
|
|
|
|||
|
|
@ -731,6 +731,32 @@ BaseGameVersion ProjectConfig::stringToBaseGameVersion(const QString &string) {
|
|||
return version;
|
||||
}
|
||||
|
||||
QString ProjectConfig::getPlayerIconPath(BaseGameVersion baseGameVersion, int character) {
|
||||
switch (baseGameVersion) {
|
||||
case BaseGameVersion::pokeemerald: {
|
||||
static const QStringList paths = { QStringLiteral(":/icons/player/brendan_em.ico"),
|
||||
QStringLiteral(":/icons/player/may_em.ico"), };
|
||||
return paths.value(character);
|
||||
}
|
||||
case BaseGameVersion::pokefirered: {
|
||||
static const QStringList paths = { QStringLiteral(":/icons/player/red.ico"),
|
||||
QStringLiteral(":/icons/player/green.ico"), };
|
||||
return paths.value(character);
|
||||
}
|
||||
case BaseGameVersion::pokeruby: {
|
||||
static const QStringList paths = { QStringLiteral(":/icons/player/brendan_rs.ico"),
|
||||
QStringLiteral(":/icons/player/may_rs.ico"), };
|
||||
return paths.value(character);
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QIcon ProjectConfig::getPlayerIcon(BaseGameVersion baseGameVersion, int character) {
|
||||
return QIcon(getPlayerIconPath(baseGameVersion, character));
|
||||
}
|
||||
|
||||
ProjectConfig projectConfig;
|
||||
|
||||
QString ProjectConfig::getConfigFilepath() {
|
||||
|
|
@ -868,6 +894,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
this->eventIconPaths[Event::Group::Heal] = value;
|
||||
} else if (key.startsWith("pokemon_icon_path/")) {
|
||||
this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()), value);
|
||||
} else if (key == "events_tab_icon_path") {
|
||||
this->eventsTabIconPath = value;
|
||||
} else if (key == "collision_sheet_path") {
|
||||
this->collisionSheetPath = value;
|
||||
} else if (key == "collision_sheet_width") {
|
||||
|
|
@ -890,6 +918,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
this->warpBehaviors.append(getConfigUint32(key, s));
|
||||
} else if (key == "max_events_per_group") {
|
||||
this->maxEventsPerGroup = getConfigInteger(key, value, 1, INT_MAX, 255);
|
||||
} else if (key == "forced_major_version") {
|
||||
this->forcedMajorVersion = getConfigInteger(key, value);
|
||||
} else {
|
||||
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key));
|
||||
}
|
||||
|
|
@ -985,6 +1015,7 @@ QMap<QString, QString> ProjectConfig::getKeyValueMap() {
|
|||
for (auto it = this->identifiers.constBegin(); it != this->identifiers.constEnd(); it++) {
|
||||
map.insert("ident/"+defaultIdentifiers.value(it.key()).first, it.value());
|
||||
}
|
||||
map.insert("events_tab_icon_path", this->eventsTabIconPath);
|
||||
map.insert("collision_sheet_path", this->collisionSheetPath);
|
||||
map.insert("collision_sheet_width", QString::number(this->collisionSheetSize.width()));
|
||||
map.insert("collision_sheet_height", QString::number(this->collisionSheetSize.height()));
|
||||
|
|
@ -997,6 +1028,7 @@ QMap<QString, QString> ProjectConfig::getKeyValueMap() {
|
|||
warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper());
|
||||
map.insert("warp_behaviors", warpBehaviorStrs.join(","));
|
||||
map.insert("max_events_per_group", QString::number(this->maxEventsPerGroup));
|
||||
map.insert("forced_major_version", QString::number(this->forcedMajorVersion));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ QString Event::typeToString(Event::Type type) {
|
|||
}
|
||||
|
||||
QPixmap Event::loadPixmap(Project *project) {
|
||||
this->pixmap = project->getEventPixmap(this->getEventGroup());
|
||||
this->pixmap = project ? project->getEventPixmap(this->getEventGroup()) : QPixmap();
|
||||
this->usesDefaultPixmap = true;
|
||||
return this->pixmap;
|
||||
}
|
||||
|
|
@ -226,7 +226,7 @@ QSet<QString> ObjectEvent::getExpectedFields() {
|
|||
}
|
||||
|
||||
QPixmap ObjectEvent::loadPixmap(Project *project) {
|
||||
this->pixmap = project->getEventPixmap(this->gfx, this->movement);
|
||||
this->pixmap = project ? project->getEventPixmap(this->gfx, this->movement) : QPixmap();
|
||||
if (!this->pixmap.isNull()) {
|
||||
this->usesDefaultPixmap = false;
|
||||
return this->pixmap;
|
||||
|
|
@ -316,7 +316,7 @@ QSet<QString> CloneObjectEvent::getExpectedFields() {
|
|||
|
||||
QPixmap CloneObjectEvent::loadPixmap(Project *project) {
|
||||
// Try to get the targeted object to clone
|
||||
Map *clonedMap = project->loadMap(this->targetMap);
|
||||
Map *clonedMap = project ? project->loadMap(this->targetMap) : nullptr;
|
||||
Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, this->targetID) : nullptr;
|
||||
|
||||
if (clonedEvent && clonedEvent->getEventType() == Event::Type::Object) {
|
||||
|
|
@ -324,10 +324,13 @@ QPixmap CloneObjectEvent::loadPixmap(Project *project) {
|
|||
ObjectEvent *clonedObject = dynamic_cast<ObjectEvent *>(clonedEvent);
|
||||
this->gfx = clonedObject->getGfx();
|
||||
this->movement = clonedObject->getMovement();
|
||||
} else {
|
||||
} else if (project) {
|
||||
// Invalid object specified, use default graphics data (as would be shown in-game)
|
||||
this->gfx = project->gfxDefines.key(0, "0");
|
||||
this->movement = project->movementTypes.value(0, "0");
|
||||
} else {
|
||||
this->gfx = "0";
|
||||
this->movement = "0";
|
||||
}
|
||||
return ObjectEvent::loadPixmap(project);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@
|
|||
Map::Map(QObject *parent) : QObject(parent)
|
||||
{
|
||||
m_editHistory = new QUndoStack(this);
|
||||
|
||||
m_scriptFileWatcher = new QFileSystemWatcher(this);
|
||||
connect(m_scriptFileWatcher, &QFileSystemWatcher::fileChanged, this, &Map::invalidateScripts);
|
||||
|
||||
resetEvents();
|
||||
|
||||
m_header = new MapHeader(this);
|
||||
|
|
@ -120,35 +124,40 @@ QPixmap Map::renderConnection(const QString &direction, Layout * fromLayout) {
|
|||
return connectionPixmap.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16);
|
||||
}
|
||||
|
||||
void Map::openScript(QString label) {
|
||||
void Map::openScript(const QString &label) {
|
||||
emit openScriptRequested(label);
|
||||
}
|
||||
|
||||
void Map::setSharedScriptsMap(const QString &sharedScriptsMap) {
|
||||
if (m_sharedScriptsMap == sharedScriptsMap)
|
||||
return;
|
||||
m_sharedScriptsMap = sharedScriptsMap;
|
||||
invalidateScripts();
|
||||
}
|
||||
|
||||
void Map::invalidateScripts() {
|
||||
m_scriptsLoaded = false;
|
||||
emit scriptsModified();
|
||||
}
|
||||
|
||||
QStringList Map::getScriptLabels(Event::Group group) {
|
||||
if (!m_scriptsLoaded) {
|
||||
m_scriptsFileLabels = ParseUtil::getGlobalScriptLabels(getScriptsFilePath());
|
||||
const QString scriptsFilePath = getScriptsFilePath();
|
||||
m_scriptLabels = ParseUtil::getGlobalScriptLabels(scriptsFilePath);
|
||||
m_scriptsLoaded = true;
|
||||
|
||||
// Track the scripts file for changes. Path may have changed, so stop tracking old files.
|
||||
m_scriptFileWatcher->removePaths(m_scriptFileWatcher->files());
|
||||
m_scriptFileWatcher->addPath(scriptsFilePath);
|
||||
}
|
||||
|
||||
QStringList scriptLabels;
|
||||
QStringList scriptLabels = m_scriptLabels;
|
||||
|
||||
// Get script labels currently in-use by the map's events
|
||||
if (group == Event::Group::None) {
|
||||
ScriptTracker scriptTracker;
|
||||
for (const auto &event : getEvents()) {
|
||||
event->accept(&scriptTracker);
|
||||
}
|
||||
scriptLabels = scriptTracker.getScripts();
|
||||
} else {
|
||||
ScriptTracker scriptTracker;
|
||||
for (const auto &event : m_events.value(group)) {
|
||||
event->accept(&scriptTracker);
|
||||
}
|
||||
scriptLabels = scriptTracker.getScripts();
|
||||
// Add script labels currently in-use by the map's events
|
||||
for (const auto &event : getEvents(group)) {
|
||||
scriptLabels.append(event->getScripts());
|
||||
}
|
||||
|
||||
// Add labels from the map's scripts file
|
||||
scriptLabels.append(m_scriptsFileLabels);
|
||||
scriptLabels.sort(Qt::CaseInsensitive);
|
||||
scriptLabels.removeAll("");
|
||||
scriptLabels.removeAll("0");
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ void MainWindow::initCustomUI() {
|
|||
|
||||
static const QMap<int, QIcon> mainTabIcons = {
|
||||
{MainTab::Map, QIcon(QStringLiteral(":/icons/minimap.ico"))},
|
||||
{MainTab::Events, QIcon(QStringLiteral(":/icons/viewsprites.ico"))},
|
||||
{MainTab::Events, ProjectConfig::getPlayerIcon(BaseGameVersion::pokefirered, 0)}, // Arbitrary default
|
||||
{MainTab::Header, QIcon(QStringLiteral(":/icons/application_form_edit.ico"))},
|
||||
{MainTab::Connections, QIcon(QStringLiteral(":/icons/connections.ico"))},
|
||||
{MainTab::WildPokemon, QIcon(QStringLiteral(":/icons/tall_grass.ico"))},
|
||||
|
|
@ -683,7 +683,7 @@ bool MainWindow::openProject(QString dir, bool initial) {
|
|||
|
||||
// Make sure project looks reasonable before attempting to load it
|
||||
porysplash->showMessage("Verifying project");
|
||||
if (!checkProjectSanity()) {
|
||||
if (isInvalidProject(this->editor->project)) {
|
||||
delete this->editor->project;
|
||||
porysplash->stop();
|
||||
return false;
|
||||
|
|
@ -725,16 +725,20 @@ bool MainWindow::loadProjectData() {
|
|||
return success;
|
||||
}
|
||||
|
||||
bool MainWindow::checkProjectSanity() {
|
||||
if (editor->project->sanityCheck())
|
||||
bool MainWindow::isInvalidProject(Project *project) {
|
||||
return !(checkProjectSanity(project) && checkProjectVersion(project));
|
||||
}
|
||||
|
||||
bool MainWindow::checkProjectSanity(Project *project) {
|
||||
if (project->sanityCheck())
|
||||
return true;
|
||||
|
||||
logWarn(QString("The directory '%1' failed the project sanity check.").arg(editor->project->root));
|
||||
logWarn(QString("The directory '%1' failed the project sanity check.").arg(project->root));
|
||||
|
||||
ErrorMessage msgBox(QStringLiteral("The selected directory appears to be invalid."), porysplash);
|
||||
msgBox.setInformativeText(QString("The directory '%1' is missing key files.\n\n"
|
||||
"Make sure you selected the correct project directory "
|
||||
"(the one used to make your .gba file, e.g. 'pokeemerald').").arg(editor->project->root));
|
||||
"(the one used to make your .gba file, e.g. 'pokeemerald').").arg(project->root));
|
||||
auto tryAnyway = msgBox.addButton("Try Anyway", QMessageBox::ActionRole);
|
||||
msgBox.exec();
|
||||
if (msgBox.clickedButton() == tryAnyway) {
|
||||
|
|
@ -745,6 +749,43 @@ bool MainWindow::checkProjectSanity() {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool MainWindow::checkProjectVersion(Project *project) {
|
||||
QString error;
|
||||
int projectVersion = project->getSupportedMajorVersion(&error);
|
||||
if (projectVersion < 0) {
|
||||
// Failed to identify a supported major version.
|
||||
// We can't draw any conclusions from this, so we don't consider the project to be invalid.
|
||||
QString msg = QStringLiteral("Failed to check project version");
|
||||
logWarn(error.isEmpty() ? msg : QString("%1: '%2'").arg(msg).arg(error));
|
||||
} else {
|
||||
QString msg = QStringLiteral("Successfully checked project version. ");
|
||||
logInfo(msg + ((projectVersion != 0) ? QString("Supports at least Porymap v%1").arg(projectVersion)
|
||||
: QStringLiteral("Too old for any Porymap version")));
|
||||
|
||||
if (projectVersion < porymapVersion.majorVersion() && projectConfig.forcedMajorVersion < porymapVersion.majorVersion()) {
|
||||
// We were unable to find the necessary changes for Porymap's current major version.
|
||||
// Unless they have explicitly suppressed this message, warn the user that this might mean their project is missing breaking changes.
|
||||
// Note: Do not report 'projectVersion' to the user in this message. We've already logged it for troubleshooting.
|
||||
// It is very plausible that the user may have reproduced the required changes in an
|
||||
// unknown commit, rather than merging the required changes directly from the base repo.
|
||||
// In this case the 'projectVersion' may actually be too old to use for their repo.
|
||||
ErrorMessage msgBox(QStringLiteral("Your project may be incompatible!"), porysplash);
|
||||
msgBox.setInformativeText(QString("Make sure '%1' has all the required changes for Porymap version %2."
|
||||
"") // TODO: Once we have a wiki or manual page describing breaking changes, link that here.
|
||||
.arg(project->getProjectTitle())
|
||||
.arg(porymapVersion.majorVersion()));
|
||||
auto tryAnyway = msgBox.addButton("Try Anyway", QMessageBox::ActionRole);
|
||||
msgBox.exec();
|
||||
if (msgBox.clickedButton() != tryAnyway){
|
||||
return false;
|
||||
}
|
||||
// User opted to try with this version anyway. Don't warn them about this version again.
|
||||
projectConfig.forcedMajorVersion = porymapVersion.majorVersion();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::showProjectOpenFailure() {
|
||||
if (!this->isVisible()){
|
||||
// The main window is not visible during the initial project open; the splash screen is busy providing visual feedback.
|
||||
|
|
@ -1148,11 +1189,6 @@ void MainWindow::displayMapProperties() {
|
|||
ui->frame_HeaderData->setEnabled(true);
|
||||
this->mapHeaderForm->setHeader(editor->map->header());
|
||||
|
||||
const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset);
|
||||
const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset);
|
||||
ui->comboBox_PrimaryTileset->setTextItem(editor->map->layout()->tileset_primary_label);
|
||||
ui->comboBox_SecondaryTileset->setTextItem(editor->map->layout()->tileset_secondary_label);
|
||||
|
||||
ui->mapCustomAttributesFrame->table()->setAttributes(editor->map->customAttributes());
|
||||
}
|
||||
|
||||
|
|
@ -1266,6 +1302,21 @@ bool MainWindow::setProjectUI() {
|
|||
|
||||
ui->mapCustomAttributesFrame->table()->setRestrictedKeys(project->getTopLevelMapFields());
|
||||
|
||||
// Set a version dependent player icon (or user-chosen icon) for the Events tab.
|
||||
QIcon eventTabIcon;
|
||||
if (!projectConfig.eventsTabIconPath.isEmpty()) {
|
||||
eventTabIcon = QIcon(project->getExistingFilepath(projectConfig.eventsTabIconPath));
|
||||
if (eventTabIcon.isNull()) {
|
||||
logWarn(QString("Failed to load custom Events tab icon '%1'.").arg(projectConfig.eventsTabIconPath));
|
||||
}
|
||||
}
|
||||
if (eventTabIcon.isNull()) {
|
||||
// We randomly choose between the available characters for ~flavor~.
|
||||
// For now, this correctly assumes all versions have 2 icons.
|
||||
eventTabIcon = ProjectConfig::getPlayerIcon(projectConfig.baseGameVersion, QRandomGenerator::global()->bounded(0, 2));
|
||||
}
|
||||
ui->mainTabBar->setTabIcon(MainTab::Events, eventTabIcon);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
111
src/project.cpp
|
|
@ -75,6 +75,117 @@ bool Project::sanityCheck() {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Porymap projects have no standardized way for Porymap to determine whether they're compatible as of the latest breaking changes.
|
||||
// We can use the project's git history (if it has one, and we're able to get it) to make a reasonable guess.
|
||||
// We know the hashes of the commits in the base repos that contain breaking changes, so if we find one of these then the project
|
||||
// should support at least up to that Porymap major version. If this fails for any reason it returns a version of -1.
|
||||
int Project::getSupportedMajorVersion(QString *errorOut) {
|
||||
// This has relatively tight timeout windows (500ms for each process, compared to the default 30,000ms). This version check
|
||||
// is not important enough to significantly slow down project launch, we'd rather just timeout.
|
||||
const int timeoutLimit = 500;
|
||||
const int failureVersion = -1;
|
||||
QString gitName = "git";
|
||||
QString gitPath = QStandardPaths::findExecutable(gitName);
|
||||
if (gitPath.isEmpty()) {
|
||||
if (errorOut) *errorOut = QString("Unable to locate %1.").arg(gitName);
|
||||
return failureVersion;
|
||||
}
|
||||
|
||||
QProcess process;
|
||||
process.setWorkingDirectory(this->root);
|
||||
process.setProgram(gitPath);
|
||||
process.setReadChannel(QProcess::StandardOutput);
|
||||
process.setStandardInputFile(QProcess::nullDevice()); // We won't have any writing to do.
|
||||
|
||||
// First we need to know which (if any) known history this project belongs to.
|
||||
// We'll get the root commit, then compare it to the known root commits for the base project repos.
|
||||
static const QStringList args_getRootCommit = { "rev-list", "--max-parents=0", "HEAD" };
|
||||
process.setArguments(args_getRootCommit);
|
||||
process.start();
|
||||
if (!process.waitForFinished(timeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit || process.exitCode() != 0) {
|
||||
if (errorOut) {
|
||||
*errorOut = QStringLiteral("Failed to identify commit history");
|
||||
if (process.error() != QProcess::UnknownError && !process.errorString().isEmpty()) {
|
||||
errorOut->append(QString(": %1").arg(process.errorString()));
|
||||
} else {
|
||||
process.setReadChannel(QProcess::StandardError);
|
||||
QString error = QString(process.readLine()).remove('\n');
|
||||
if (!error.isEmpty()) errorOut->append(QString(": %1").arg(error));
|
||||
}
|
||||
}
|
||||
return failureVersion;
|
||||
}
|
||||
const QString rootCommit = QString(process.readLine()).remove('\n');
|
||||
|
||||
// The keys in this map are the hashes of the root commits for each of the 3 base repos.
|
||||
// The values are a list of pairs, where the first element is a major version number, and the
|
||||
// second element is the hash of the earliest commit that supports that major version.
|
||||
static const QMap<QString, QList<QPair<int, QString>>> historyMap = {
|
||||
// pokeemerald
|
||||
{"33b799c967fd63d04afe82eecc4892f3e45781b3", {
|
||||
{6, "07c897ad48c36b178093bde8ca360823127d812b"}, // TODO: Update to merge commit for pokeemerald's porymap-6 branch
|
||||
{5, "c76beed98990a57c84d3930190fd194abfedf7e8"},
|
||||
{4, "cb5b8da77b9ba6837fcc8c5163bedc5008b12c2c"},
|
||||
{3, "204c431993dad29661a9ff47326787cd0cf381e6"},
|
||||
{2, "cdae0c1444bed98e652c87dc3e3edcecacfef8be"},
|
||||
{1, ""}
|
||||
}},
|
||||
// pokefirered
|
||||
{"670fef77ac4d9116d5fdc28c0da40622919a062b", {
|
||||
{6, "7722e7a92ca5fa69925dcef82f6c89c35ec48171"}, // TODO: Update to merge commit for pokefirered's porymap-6 branch
|
||||
{5, "52591dcee42933d64f60c59276fc13c3bb89c47b"},
|
||||
{4, "200c82e01a94dbe535e6ed8768d8afad4444d4d2"},
|
||||
}},
|
||||
// pokeruby
|
||||
{"1362b60f3467f0894d55e82f3294980b6373021d", {
|
||||
{6, "bc5aeaa64ecad03aa4ab9e1000ba94916276c936"}, // TODO: Update to merge commit for pokeruby's porymap-6 branch
|
||||
{5, "d99cb43736dd1d4ee4820f838cb259d773d8bf25"},
|
||||
{4, "f302fcc134bf354c3655e3423be68fd7a99cb396"},
|
||||
{3, "b4f4d2c0f03462dcdf3492aad27890294600eb2e"},
|
||||
{2, "0e8ccfc4fd3544001f4c25fafd401f7558bdefba"},
|
||||
{1, ""}
|
||||
}},
|
||||
};
|
||||
if (!historyMap.contains(rootCommit)) {
|
||||
// Either this repo does not share history with one of the base repos, or we got some unexpected result.
|
||||
if (errorOut) *errorOut = QStringLiteral("Unrecognized commit history");
|
||||
return failureVersion;
|
||||
}
|
||||
|
||||
// We now know which base repo that the user's repo shares history with.
|
||||
// Next we check to see if it contains the changes required to support particular major versions of Porymap.
|
||||
// We'll start with the most recent major version and work backwards.
|
||||
for (const auto &pair : historyMap.value(rootCommit)) {
|
||||
int versionNum = pair.first;
|
||||
QString commitHash = pair.second;
|
||||
if (commitHash.isEmpty()) {
|
||||
// An empty commit hash means 'consider any point in the history a supported version'
|
||||
return versionNum;
|
||||
}
|
||||
process.setArguments({ "merge-base", "--is-ancestor", commitHash, "HEAD" });
|
||||
process.start();
|
||||
if (!process.waitForFinished(timeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit) {
|
||||
if (errorOut) {
|
||||
*errorOut = QStringLiteral("Failed to search commit history");
|
||||
if (process.error() != QProcess::UnknownError && !process.errorString().isEmpty()) {
|
||||
errorOut->append(QString(": %1").arg(process.errorString()));
|
||||
} else {
|
||||
process.setReadChannel(QProcess::StandardError);
|
||||
QString error = QString(process.readLine()).remove('\n');
|
||||
if (!error.isEmpty()) errorOut->append(QString(": %1").arg(error));
|
||||
}
|
||||
}
|
||||
return failureVersion;
|
||||
}
|
||||
if (process.exitCode() == 0) {
|
||||
// Identified a supported major version
|
||||
return versionNum;
|
||||
}
|
||||
}
|
||||
// We recognized the commit history, but it's too old for any version of Porymap to support.
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Project::load() {
|
||||
this->parser.setUpdatesSplashScreen(true);
|
||||
resetFileCache();
|
||||
|
|
|
|||
|
|
@ -152,8 +152,12 @@ void EventFrame::initialize() {
|
|||
this->label_icon->setPixmap(this->event->getPixmap());
|
||||
}
|
||||
|
||||
void EventFrame::populate(Project *) {
|
||||
void EventFrame::populate(Project *project) {
|
||||
this->populated = true;
|
||||
if (this->project && this->project != project) {
|
||||
this->project->disconnect(this);
|
||||
}
|
||||
this->project = project;
|
||||
}
|
||||
|
||||
void EventFrame::invalidateConnections() {
|
||||
|
|
@ -166,6 +170,10 @@ void EventFrame::invalidateUi() {
|
|||
|
||||
void EventFrame::invalidateValues() {
|
||||
this->populated = false;
|
||||
if (this->isVisible()) {
|
||||
// Repopulate immediately
|
||||
this->populate(this->project);
|
||||
}
|
||||
}
|
||||
|
||||
void EventFrame::setActive(bool active) {
|
||||
|
|
@ -186,14 +194,15 @@ void EventFrame::populateDropdown(NoScrollComboBox * combo, const QStringList &i
|
|||
|
||||
void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * project) {
|
||||
// The script dropdown and autocomplete are populated with scripts used by the map's events and from its scripts file.
|
||||
if (!this->event->getMap())
|
||||
Map *map = this->event ? this->event->getMap() : nullptr;
|
||||
if (!map)
|
||||
return;
|
||||
|
||||
QStringList scripts = this->event->getMap()->getScriptLabels(this->event->getEventGroup());
|
||||
QStringList scripts = map->getScriptLabels(this->event->getEventGroup());
|
||||
populateDropdown(combo, scripts);
|
||||
|
||||
// Depending on the settings, the autocomplete may also contain all global scripts.
|
||||
if (porymapConfig.loadAllEventScripts) {
|
||||
if (project && porymapConfig.loadAllEventScripts) {
|
||||
project->insertGlobalScriptLabels(scripts);
|
||||
}
|
||||
|
||||
|
|
@ -209,14 +218,23 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj
|
|||
|
||||
combo->setCompleter(completer);
|
||||
|
||||
// If the project changes the script labels, update the EventFrame.
|
||||
// TODO: At the moment this only happens when the user changes script settings (i.e. when 'porymapConfig.loadAllEventScripts' changes).
|
||||
// This should ultimately be connected to a file watcher so that we can also update the dropdown when the scripts file changes.
|
||||
connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
|
||||
// If the script labels change then we need to update the EventFrame.
|
||||
if (project) connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
|
||||
connect(map, &Map::scriptsModified, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
void EventFrame::populateMapNameDropdown(NoScrollComboBox * combo, Project * project) {
|
||||
if (!project)
|
||||
return;
|
||||
|
||||
populateDropdown(combo, project->mapNames);
|
||||
|
||||
// This frame type displays map names, so when a new map is created we need to repopulate it.
|
||||
connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
void EventFrame::populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group) {
|
||||
if (!project->mapNames.contains(mapName))
|
||||
if (!project || !project->mapNames.contains(mapName))
|
||||
return;
|
||||
|
||||
Map *map = project->loadMap(mapName);
|
||||
|
|
@ -325,7 +343,6 @@ void ObjectFrame::connectSignals(MainWindow *window) {
|
|||
if (this->connected) return;
|
||||
|
||||
EventFrame::connectSignals(window);
|
||||
Project *project = window->editor->project;
|
||||
|
||||
// local id
|
||||
this->line_edit_local_id->disconnect();
|
||||
|
|
@ -336,18 +353,18 @@ void ObjectFrame::connectSignals(MainWindow *window) {
|
|||
|
||||
// sprite update
|
||||
this->combo_sprite->disconnect();
|
||||
connect(this->combo_sprite, &QComboBox::currentTextChanged, [this, project](const QString &text) {
|
||||
connect(this->combo_sprite, &QComboBox::currentTextChanged, [this](const QString &text) {
|
||||
this->object->setGfx(text);
|
||||
this->object->getPixmapItem()->render(project);
|
||||
this->object->getPixmapItem()->render(this->project);
|
||||
this->object->modify();
|
||||
});
|
||||
connect(this->object->getPixmapItem(), &EventPixmapItem::rendered, this->label_icon, &QLabel::setPixmap);
|
||||
|
||||
// movement
|
||||
this->combo_movement->disconnect();
|
||||
connect(this->combo_movement, &QComboBox::currentTextChanged, [this, project](const QString &text) {
|
||||
connect(this->combo_movement, &QComboBox::currentTextChanged, [this](const QString &text) {
|
||||
this->object->setMovement(text);
|
||||
this->object->getPixmapItem()->render(project);
|
||||
this->object->getPixmapItem()->render(this->project);
|
||||
this->object->modify();
|
||||
});
|
||||
|
||||
|
|
@ -498,7 +515,6 @@ void CloneObjectFrame::connectSignals(MainWindow *window) {
|
|||
if (this->connected) return;
|
||||
|
||||
EventFrame::connectSignals(window);
|
||||
Project *project = window->editor->project;
|
||||
|
||||
// local id
|
||||
this->line_edit_local_id->disconnect();
|
||||
|
|
@ -512,26 +528,23 @@ void CloneObjectFrame::connectSignals(MainWindow *window) {
|
|||
|
||||
// target map
|
||||
this->combo_target_map->disconnect();
|
||||
connect(this->combo_target_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) {
|
||||
connect(this->combo_target_map, &QComboBox::currentTextChanged, [this](const QString &mapName) {
|
||||
this->clone->setTargetMap(mapName);
|
||||
this->clone->getPixmapItem()->render(project);
|
||||
this->clone->getPixmapItem()->render(this->project);
|
||||
this->combo_sprite->setTextItem(this->clone->getGfx());
|
||||
this->clone->modify();
|
||||
populateIdNameDropdown(this->combo_target_id, project, mapName, Event::Group::Object);
|
||||
populateIdNameDropdown(this->combo_target_id, this->project, mapName, Event::Group::Object);
|
||||
});
|
||||
connect(window, &MainWindow::mapOpened, this, &CloneObjectFrame::tryInvalidateIdDropdown, Qt::UniqueConnection);
|
||||
|
||||
// target id
|
||||
this->combo_target_id->disconnect();
|
||||
connect(this->combo_target_id, &QComboBox::currentTextChanged, [this, project](const QString &text) {
|
||||
connect(this->combo_target_id, &QComboBox::currentTextChanged, [this](const QString &text) {
|
||||
this->clone->setTargetID(text);
|
||||
this->clone->getPixmapItem()->render(project);
|
||||
this->clone->getPixmapItem()->render(this->project);
|
||||
this->combo_sprite->setTextItem(this->clone->getGfx());
|
||||
this->clone->modify();
|
||||
});
|
||||
|
||||
// This frame type displays map names, so when a new map is created we need to repopulate it.
|
||||
connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
void CloneObjectFrame::tryInvalidateIdDropdown(Map *map) {
|
||||
|
|
@ -567,7 +580,7 @@ void CloneObjectFrame::populate(Project *project) {
|
|||
const QSignalBlocker blocker(this);
|
||||
EventFrame::populate(project);
|
||||
|
||||
populateDropdown(this->combo_target_map, project->mapNames);
|
||||
populateMapNameDropdown(this->combo_target_map, project);
|
||||
populateIdNameDropdown(this->combo_target_id, project, this->clone->getTargetMap(), Event::Group::Object);
|
||||
}
|
||||
|
||||
|
|
@ -618,7 +631,6 @@ void WarpFrame::connectSignals(MainWindow *window) {
|
|||
if (this->connected) return;
|
||||
|
||||
EventFrame::connectSignals(window);
|
||||
Project *project = window->editor->project;
|
||||
|
||||
// id
|
||||
this->line_edit_id->disconnect();
|
||||
|
|
@ -629,10 +641,10 @@ void WarpFrame::connectSignals(MainWindow *window) {
|
|||
|
||||
// dest map
|
||||
this->combo_dest_map->disconnect();
|
||||
connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) {
|
||||
connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this](const QString &mapName) {
|
||||
this->warp->setDestinationMap(mapName);
|
||||
this->warp->modify();
|
||||
populateIdNameDropdown(this->combo_dest_warp, project, mapName, Event::Group::Warp);
|
||||
populateIdNameDropdown(this->combo_dest_warp, this->project, mapName, Event::Group::Warp);
|
||||
});
|
||||
connect(window, &MainWindow::mapOpened, this, &WarpFrame::tryInvalidateIdDropdown, Qt::UniqueConnection);
|
||||
|
||||
|
|
@ -646,9 +658,6 @@ void WarpFrame::connectSignals(MainWindow *window) {
|
|||
// warning
|
||||
this->warning->disconnect();
|
||||
connect(this->warning, &QPushButton::clicked, window, &MainWindow::onWarpBehaviorWarningClicked);
|
||||
|
||||
// This frame type displays map names, so when a new map is created we need to repopulate it.
|
||||
connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
void WarpFrame::tryInvalidateIdDropdown(Map *map) {
|
||||
|
|
@ -681,7 +690,7 @@ void WarpFrame::populate(Project *project) {
|
|||
const QSignalBlocker blocker(this);
|
||||
EventFrame::populate(project);
|
||||
|
||||
populateDropdown(this->combo_dest_map, project->mapNames);
|
||||
populateMapNameDropdown(this->combo_dest_map, project);
|
||||
populateIdNameDropdown(this->combo_dest_warp, project, this->warp->getDestinationMap(), Event::Group::Warp);
|
||||
}
|
||||
|
||||
|
|
@ -1102,7 +1111,6 @@ void HealLocationFrame::connectSignals(MainWindow *window) {
|
|||
if (this->connected) return;
|
||||
|
||||
EventFrame::connectSignals(window);
|
||||
Project *project = window->editor->project;
|
||||
|
||||
this->line_edit_id->disconnect();
|
||||
connect(this->line_edit_id, &QLineEdit::textChanged, [this](const QString &text) {
|
||||
|
|
@ -1111,10 +1119,10 @@ void HealLocationFrame::connectSignals(MainWindow *window) {
|
|||
});
|
||||
|
||||
this->combo_respawn_map->disconnect();
|
||||
connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) {
|
||||
connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this](const QString &mapName) {
|
||||
this->healLocation->setRespawnMapName(mapName);
|
||||
this->healLocation->modify();
|
||||
populateIdNameDropdown(this->combo_respawn_npc, project, mapName, Event::Group::Object);
|
||||
populateIdNameDropdown(this->combo_respawn_npc, this->project, mapName, Event::Group::Object);
|
||||
});
|
||||
connect(window, &MainWindow::mapOpened, this, &HealLocationFrame::tryInvalidateIdDropdown, Qt::UniqueConnection);
|
||||
|
||||
|
|
@ -1123,9 +1131,6 @@ void HealLocationFrame::connectSignals(MainWindow *window) {
|
|||
this->healLocation->setRespawnNPC(text);
|
||||
this->healLocation->modify();
|
||||
});
|
||||
|
||||
// This frame type displays map names, so when a new map is created we need to repopulate it.
|
||||
connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
void HealLocationFrame::tryInvalidateIdDropdown(Map *map) {
|
||||
|
|
@ -1158,7 +1163,7 @@ void HealLocationFrame::populate(Project *project) {
|
|||
EventFrame::populate(project);
|
||||
|
||||
if (projectConfig.healLocationRespawnDataEnabled) {
|
||||
populateDropdown(this->combo_respawn_map, project->mapNames);
|
||||
populateMapNameDropdown(this->combo_respawn_map, project);
|
||||
populateIdNameDropdown(this->combo_respawn_npc, project, this->healLocation->getRespawnMapName(), Event::Group::Object);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ void ProjectSettingsEditor::connectSignals() {
|
|||
connect(ui->button_BGsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_BGsIcon); });
|
||||
connect(ui->button_HealLocationsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_HealLocationsIcon); });
|
||||
connect(ui->button_PokemonIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_PokemonIcon); });
|
||||
connect(ui->button_EventsTabIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_EventsTabIcon); });
|
||||
|
||||
|
||||
// Display a warning if a mask value overlaps with another mask in its group.
|
||||
|
|
@ -113,6 +114,20 @@ void ProjectSettingsEditor::initUi() {
|
|||
ui->comboBox_BaseGameVersion->addItems(ProjectConfig::versionStrings);
|
||||
ui->comboBox_AttributesSize->addItems({"1", "2", "4"});
|
||||
|
||||
ui->comboBox_EventsTabIcon->addItem("Automatic", "");
|
||||
ui->comboBox_EventsTabIcon->addItem("Brendan (Emerald)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeemerald, 0));
|
||||
ui->comboBox_EventsTabIcon->addItem("Brendan (R/S)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeruby, 0));
|
||||
ui->comboBox_EventsTabIcon->addItem("May (Emerald)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeemerald, 1));
|
||||
ui->comboBox_EventsTabIcon->addItem("May (R/S)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeruby, 1));
|
||||
ui->comboBox_EventsTabIcon->addItem("Red", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokefirered, 0));
|
||||
ui->comboBox_EventsTabIcon->addItem("Green", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokefirered, 1));
|
||||
ui->comboBox_EventsTabIcon->addItem("Custom", "Custom");
|
||||
connect(ui->comboBox_EventsTabIcon, QOverload<int>::of(&NoScrollComboBox::currentIndexChanged), [this](int index) {
|
||||
bool usingCustom = (index == ui->comboBox_EventsTabIcon->findText("Custom"));
|
||||
ui->lineEdit_EventsTabIcon->setVisible(usingCustom);
|
||||
ui->button_EventsTabIcon->setVisible(usingCustom);
|
||||
});
|
||||
|
||||
// Validate that the border metatiles text is a comma-separated list of metatile values
|
||||
static const QString regex_Hex = "(0[xX])?[A-Fa-f0-9]+";
|
||||
static const QRegularExpression expression_HexList(QString("^(%1,)*%1$").arg(regex_Hex)); // Comma-separated list of hex values
|
||||
|
|
@ -520,6 +535,15 @@ void ProjectSettingsEditor::refresh() {
|
|||
}
|
||||
this->setWarpBehaviorsList(behaviorNames);
|
||||
|
||||
int index = ui->comboBox_EventsTabIcon->findData(projectConfig.eventsTabIconPath);
|
||||
if (index < 0) {
|
||||
index = ui->comboBox_EventsTabIcon->findData("Custom");
|
||||
ui->lineEdit_EventsTabIcon->setText(projectConfig.eventsTabIconPath);
|
||||
} else {
|
||||
ui->lineEdit_EventsTabIcon->setText("");
|
||||
}
|
||||
ui->comboBox_EventsTabIcon->setCurrentIndex(index);
|
||||
|
||||
this->refreshing = false; // Allow signals
|
||||
}
|
||||
|
||||
|
|
@ -608,6 +632,16 @@ void ProjectSettingsEditor::save() {
|
|||
for (auto i = this->editedPokemonIconPaths.cbegin(), end = this->editedPokemonIconPaths.cend(); i != end; i++)
|
||||
projectConfig.setPokemonIconPath(i.key(), i.value());
|
||||
|
||||
QString eventsTabIconPath;
|
||||
QVariant data = ui->comboBox_EventsTabIcon->currentData();
|
||||
if (data.isValid() && data.canConvert<QString>()) {
|
||||
eventsTabIconPath = data.toString();
|
||||
if (eventsTabIconPath == "Custom") {
|
||||
eventsTabIconPath = ui->lineEdit_EventsTabIcon->text();
|
||||
}
|
||||
}
|
||||
projectConfig.eventsTabIconPath = eventsTabIconPath;
|
||||
|
||||
projectConfig.save();
|
||||
userConfig.save();
|
||||
porymapConfig.save();
|
||||
|
|
|
|||