diff --git a/forms/newdefinedialog.ui b/forms/newdefinedialog.ui new file mode 100644 index 00000000..c816c6e3 --- /dev/null +++ b/forms/newdefinedialog.ui @@ -0,0 +1,89 @@ + + + NewDefineDialog + + + + 0 + 0 + 252 + 124 + + + + true + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Name + + + + + + + true + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + + + + + + + + Value + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + + diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index 4cf06515..05598b8f 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -1582,19 +1582,21 @@ 499 - - - - - ... + + + + + Qt::Orientation::Horizontal - - - :/icons/help.ico:/icons/help.ico + + + 40 + 20 + - + - + @@ -1626,7 +1628,7 @@ 0 0 544 - 437 + 338 @@ -1646,6 +1648,34 @@ + + + + <html><head/><body><p>Add additional C files containing #defines or enums. These will be used to resolve unknown symbols during project launch.</p></body></html> + + + Add Global Constants File... + + + + :/icons/add.ico:/icons/add.ico + + + + + + + ... + + + + :/icons/help.ico:/icons/help.ico + + + + + + @@ -1671,8 +1701,8 @@ 499 - - + + ... @@ -1683,7 +1713,7 @@ - + @@ -1715,7 +1745,7 @@ 0 0 544 - 437 + 421 @@ -1735,6 +1765,36 @@ + + + + <html><head/><body><p>Add an additional #define name and expression. This may be used to evaluate other #defines during project launch.</p></body></html> + + + Add Global Constant... + + + + :/icons/add.ico:/icons/add.ico + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + diff --git a/include/config.h b/include/config.h index 67a8b507..26d2a353 100644 --- a/include/config.h +++ b/include/config.h @@ -345,6 +345,8 @@ public: this->unusedTileCovered = 0x0000; this->unusedTileSplit = 0x0000; this->maxEventsPerGroup = 255; + this->globalConstantsFilepaths.clear(); + this->globalConstants.clear(); this->identifiers.clear(); this->readKeys.clear(); } @@ -417,6 +419,8 @@ public: QMargins playerViewDistance; QList warpBehaviors; int maxEventsPerGroup; + QStringList globalConstantsFilepaths; + QMap globalConstants; protected: virtual QString getConfigFilepath() override; diff --git a/include/core/parseutil.h b/include/core/parseutil.h index e02d0504..aae86a88 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -43,7 +43,7 @@ class ParseUtil { public: ParseUtil(); - void set_root(const QString &dir); + void setRoot(const QString &dir) { this->root = dir; } static QString readTextFile(const QString &path, QString *error = nullptr); bool cacheFile(const QString &path, QString *error = nullptr); void clearFileCache() { this->fileCache.clear(); } @@ -55,9 +55,13 @@ public: QString readCIncbin(const QString &text, const QString &label); QMap readCIncbinMulti(const QString &filepath); QStringList readCIncbinArray(const QString &filename, const QString &label); - QMap readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); - QMap readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); + QHash readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); + QHash readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); + void loadGlobalCDefinesFromFile(const QString &filename, QString *error = nullptr); + void loadGlobalCDefines(const QMap &defines); + void loadGlobalCDefines(const QHash &defines); + void resetCDefines(); OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); @@ -90,8 +94,20 @@ private: QString curDefine; QHash fileCache; QHash errorMap; - int evaluateDefine(const QString&, const QString &, QMap*, QMap*); - QList tokenizeExpression(QString, QMap*, QMap*); + + // The maps of define names to values/expressions that are available while parsing C defines. + // As the parser reads and evaluates more defines it will update these maps accordingly. + QHash knownDefineValues; + QHash knownDefineExpressions; + + // Maps of special define names to values/expressions that take precedence over defines encountered while parsing. + // Some (like 'TRUE'/'FALSE') are always present in these maps, others may be specified by the user with 'loadGlobalCDefines' / 'loadGlobalCDefinesFromFile'. + QHash globalDefineValues; + QHash globalDefineExpressions; + + int evaluateDefine(const QString &identifier, bool *ok = nullptr); + int evaluateExpression(const QString &expression); + QList tokenizeExpression(QString expression); QList generatePostfix(const QList &tokens); int evaluatePostfix(const QList &postfix); void recordError(const QString &message); @@ -100,11 +116,11 @@ private: QString createErrorMessage(const QString &message, const QString &expression); struct ParsedDefines { - QMap expressions; // Map of all define names encountered to their expressions + QHash expressions; // Map of all define names encountered to their expressions QStringList filteredNames; // List of define names that matched the search text, in the order that they were encountered }; ParsedDefines readCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); - QMap evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); + QHash evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; QString loadTextFile(const QString &path, QString *error = nullptr); diff --git a/include/project.h b/include/project.h index 1031640b..2d2b5fd1 100644 --- a/include/project.h +++ b/include/project.h @@ -76,7 +76,7 @@ public: int maxEncounterRate; bool wildEncountersLoaded; - void set_root(QString); + void setRoot(const QString&); void clearMaps(); void clearTilesetCache(); @@ -203,6 +203,7 @@ public: bool readEventGraphics(); bool readFieldmapProperties(); bool readFieldmapMasks(); + bool readGlobalConstants(); QMap> readObjEventGfxInfo(); QPixmap getEventPixmap(const QString &gfxName, const QString &movementName); diff --git a/include/ui/newdefinedialog.h b/include/ui/newdefinedialog.h new file mode 100644 index 00000000..2107dcec --- /dev/null +++ b/include/ui/newdefinedialog.h @@ -0,0 +1,32 @@ +#ifndef NEWDEFINEDIALOG_H +#define NEWDEFINEDIALOG_H + +#include +#include + +namespace Ui { +class NewDefineDialog; +} + +class NewDefineDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewDefineDialog(QWidget *parent = nullptr); + ~NewDefineDialog(); + + virtual void accept() override; + +signals: + void createdDefine(const QString &name, const QString &expression); + +private: + Ui::NewDefineDialog *ui; + + bool validateName(bool allowEmpty = false); + void onNameChanged(const QString &name); + void dialogButtonClicked(QAbstractButton *button); +}; + +#endif // NEWDEFINEDIALOG_H diff --git a/include/ui/projectsettingseditor.h b/include/ui/projectsettingseditor.h index e4a6ae94..579aec21 100644 --- a/include/ui/projectsettingseditor.h +++ b/include/ui/projectsettingseditor.h @@ -67,6 +67,12 @@ private: void setWarpBehaviorsList(QStringList list); void openFilesHelp(); void openIdentifiersHelp(); + void addNewGlobalConstantsFilepath(); + void addGlobalConstantsFilepath(const QString &filepath); + QStringList getGlobalConstantsFilepaths(); + void addNewGlobalConstant(); + void addGlobalConstant(const QString &name, const QString &expression); + QMap getGlobalConstants(); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/porymap.pro b/porymap.pro index 263bc61b..ff894f39 100644 --- a/porymap.pro +++ b/porymap.pro @@ -104,6 +104,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/metatileselector.cpp \ src/ui/movablerect.cpp \ src/ui/movementpermissionsselector.cpp \ + src/ui/newdefinedialog.cpp \ src/ui/neweventtoolbutton.cpp \ src/ui/newlayoutdialog.cpp \ src/ui/newlayoutform.cpp \ @@ -217,6 +218,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/metatileselector.h \ include/ui/movablerect.h \ include/ui/movementpermissionsselector.h \ + include/ui/newdefinedialog.h \ include/ui/neweventtoolbutton.h \ include/ui/newlayoutdialog.h \ include/ui/newlayoutform.h \ @@ -272,6 +274,7 @@ FORMS += forms/mainwindow.ui \ forms/loadingscreen.ui \ forms/mapheaderform.ui \ forms/maplisttoolbar.ui \ + forms/newdefinedialog.ui \ forms/newlayoutdialog.ui \ forms/newlayoutform.ui \ forms/newlocationdialog.ui \ diff --git a/src/config.cpp b/src/config.cpp index 7e7422c7..0e22840e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -234,7 +234,7 @@ void KeyValueConfigBase::load() { continue; } - this->parseConfigKeyValue(match.captured("key").trimmed().toLower(), match.captured("value").trimmed()); + this->parseConfigKeyValue(match.captured("key").trimmed(), match.captured("value").trimmed()); } this->setUnreadKeys(); @@ -840,6 +840,10 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } + } else if (key.startsWith("global_constant/")) { + this->globalConstants.insert(key.mid(QStringLiteral("global_constant/").length()), value); + } else if (key == "global_constants_filepaths") { + this->globalConstantsFilepaths = value.split(",", Qt::SkipEmptyParts); } else if (key == "prefabs_filepath") { this->prefabFilepath = value; } else if (key == "prefabs_import_prompted") { @@ -863,7 +867,7 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "event_icon_path_heal") { this->eventIconPaths[Event::Group::Heal] = value; } else if (key.startsWith("pokemon_icon_path/")) { - this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()).toUpper(), value); + this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()), value); } else if (key == "collision_sheet_path") { this->collisionSheetPath = value; } else if (key == "collision_sheet_width") { @@ -970,12 +974,16 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("event_icon_path_coord", this->eventIconPaths[Event::Group::Coord]); map.insert("event_icon_path_bg", this->eventIconPaths[Event::Group::Bg]); map.insert("event_icon_path_heal", this->eventIconPaths[Event::Group::Heal]); - for (auto i = this->pokemonIconPaths.cbegin(), end = this->pokemonIconPaths.cend(); i != end; i++){ - const QString path = i.value(); - if (!path.isEmpty()) map.insert("pokemon_icon_path/" + i.key(), path); + for (auto it = this->pokemonIconPaths.constBegin(); it != this->pokemonIconPaths.constEnd(); it++) { + const QString path = it.value(); + if (!path.isEmpty()) map.insert("pokemon_icon_path/" + it.key(), path); } - for (auto i = this->identifiers.cbegin(), end = this->identifiers.cend(); i != end; i++) { - map.insert("ident/"+defaultIdentifiers.value(i.key()).first, i.value()); + for (auto it = this->globalConstants.constBegin(); it != this->globalConstants.constEnd(); it++) { + map.insert("global_constant/" + it.key(), it.value()); + } + map.insert("global_constants_filepaths", this->globalConstantsFilepaths.join(",")); + for (auto it = this->identifiers.constBegin(); it != this->identifiers.constEnd(); it++) { + map.insert("ident/"+defaultIdentifiers.value(it.key()).first, it.value()); } map.insert("collision_sheet_path", this->collisionSheetPath); map.insert("collision_sheet_width", QString::number(this->collisionSheetSize.width())); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 4e048140..cba8c223 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -16,26 +16,8 @@ const QRegularExpression ParseUtil::re_poryScriptLabel("\\b(script)(\\((global|l const QRegularExpression ParseUtil::re_globalPoryScriptLabel("\\b(script)(\\((global)\\))?\\s*\\b(?