mirror of
https://github.com/huderlem/porymap.git
synced 2026-08-11 03:15:46 -05:00
Merge pull request #692 from GriffinRichards/utility
Add utility file, more project load speed improvements
This commit is contained in:
@@ -109,6 +109,7 @@ In addition to these files, there are some specific symbol and macro names that
|
||||
``define_attribute_encounter``, ``METATILE_ATTRIBUTE_ENCOUNTER_TYPE``, name used to extract setting from ``symbol_attribute_table``
|
||||
``define_metatile_label_prefix``, ``METATILE_``, expected prefix for metatile label macro names
|
||||
``define_heal_locations_prefix``, ``HEAL_LOCATION_``, default prefix for heal location macro names
|
||||
``define_layout_prefix``, ``LAYOUT_``, default prefix for layout macro names
|
||||
``define_map_prefix``, ``MAP_``, expected prefix for map macro names
|
||||
``define_map_dynamic``, ``DYNAMIC``, macro name after prefix for Dynamic maps
|
||||
``define_map_empty``, ``UNDEFINED``, macro name after prefix for empty maps
|
||||
|
||||
@@ -220,6 +220,7 @@ enum ProjectIdentifier {
|
||||
define_attribute_encounter,
|
||||
define_metatile_label_prefix,
|
||||
define_heal_locations_prefix,
|
||||
define_layout_prefix,
|
||||
define_map_prefix,
|
||||
define_map_dynamic,
|
||||
define_map_empty,
|
||||
|
||||
@@ -45,7 +45,8 @@ public:
|
||||
void setConstantName(const QString &constantName) { m_constantName = constantName; }
|
||||
QString constantName() const { return m_constantName; }
|
||||
|
||||
static QString mapConstantFromName(QString mapName, bool includePrefix = true);
|
||||
static QString mapConstantFromName(const QString &name);
|
||||
QString expectedConstantName() const { return Map::mapConstantFromName(m_name); }
|
||||
|
||||
void setLayout(Layout *layout);
|
||||
Layout* layout() const { return m_layout; }
|
||||
|
||||
@@ -20,9 +20,7 @@ public:
|
||||
Layout() {}
|
||||
Layout(const Layout &other);
|
||||
|
||||
static QString layoutConstantFromName(QString mapName);
|
||||
static QString defaultSuffix();
|
||||
|
||||
static QString layoutConstantFromName(const QString &name);
|
||||
|
||||
bool loaded = false;
|
||||
bool hasUnsavedDataChanges = false;
|
||||
|
||||
@@ -54,9 +54,9 @@ public:
|
||||
QString readCIncbin(const QString &text, const QString &label);
|
||||
QMap<QString, QString> readCIncbinMulti(const QString &filepath);
|
||||
QStringList readCIncbinArray(const QString &filename, const QString &label);
|
||||
QMap<QString, int> readCDefinesByRegex(const QString &filename, const QStringList ®exList, QString *error = nullptr);
|
||||
QMap<QString, int> readCDefinesByName(const QString &filename, const QStringList &names, QString *error = nullptr);
|
||||
QStringList readCDefineNames(const QString &filename, const QStringList ®exList, QString *error = nullptr);
|
||||
QMap<QString, int> readCDefinesByRegex(const QString &filename, const QSet<QString> ®exList, QString *error = nullptr);
|
||||
QMap<QString, int> readCDefinesByName(const QString &filename, const QSet<QString> &names, QString *error = nullptr);
|
||||
QStringList readCDefineNames(const QString &filename, const QSet<QString> ®exList, QString *error = nullptr);
|
||||
tsl::ordered_map<QString, QHash<QString, QString>> readCStructs(const QString &, const QString & = "", const QHash<int, QString>& = {});
|
||||
QList<QStringList> getLabelMacros(const QList<QStringList>&, const QString&);
|
||||
QStringList getLabelValues(const QList<QStringList>&, const QString&);
|
||||
@@ -101,10 +101,10 @@ private:
|
||||
QMap<QString,QString> expressions; // Map of all define names encountered to their expressions
|
||||
QStringList filteredNames; // List of define names that matched the search text, in the order that they were encountered
|
||||
};
|
||||
ParsedDefines readCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error);
|
||||
QMap<QString, int> evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error);
|
||||
bool defineNameMatchesFilter(const QString &name, const QStringList &filterList) const;
|
||||
bool defineNameMatchesFilter(const QString &name, const QList<QRegularExpression> &filterList) const;
|
||||
ParsedDefines readCDefines(const QString &filename, const QSet<QString> &filterList, bool useRegex, QString *error);
|
||||
QMap<QString, int> evaluateCDefines(const QString &filename, const QSet<QString> &filterList, bool useRegex, QString *error);
|
||||
bool defineNameMatchesFilter(const QString &name, const QSet<QString> &filterList) const;
|
||||
bool defineNameMatchesFilter(const QString &name, const QSet<QRegularExpression> &filterList) const;
|
||||
|
||||
static const QRegularExpression re_incScriptLabel;
|
||||
static const QRegularExpression re_globalIncScriptLabel;
|
||||
|
||||
14
include/core/utility.h
Normal file
14
include/core/utility.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#ifndef UTILITY_H
|
||||
#define UTILITY_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace Util {
|
||||
void numericalModeSort(QStringList &list);
|
||||
int roundUp(int numToRound, int multiple);
|
||||
QString toDefineCase(QString input);
|
||||
QString toHexString(uint32_t value, int minLength = 0);
|
||||
}
|
||||
|
||||
#endif // UTILITY_H
|
||||
@@ -51,6 +51,7 @@ public:
|
||||
QStringList itemNames;
|
||||
QStringList flagNames;
|
||||
QStringList varNames;
|
||||
QStringList speciesNames;
|
||||
QStringList movementTypes;
|
||||
QStringList mapTypes;
|
||||
QStringList mapBattleScenes;
|
||||
@@ -142,8 +143,8 @@ public:
|
||||
QVector<poryjson::Json::object> extraEncounterGroups;
|
||||
|
||||
bool readSpeciesIconPaths();
|
||||
QPixmap getSpeciesIcon(const QString &species) const;
|
||||
QMap<QString, QString> speciesToIconPath;
|
||||
QString getDefaultSpeciesIconPath(const QString &species);
|
||||
QPixmap getSpeciesIcon(const QString &species);
|
||||
|
||||
void addNewMapsec(const QString &idName);
|
||||
void removeMapsec(const QString &idName);
|
||||
@@ -251,12 +252,11 @@ public:
|
||||
static QString getEmptyMapsecName();
|
||||
static QString getMapGroupPrefix();
|
||||
|
||||
static void numericalModeSort(QStringList &list);
|
||||
|
||||
private:
|
||||
QMap<QString, QString> mapSectionDisplayNames;
|
||||
QMap<QString, qint64> modifiedFileTimestamps;
|
||||
QMap<QString, QString> facingDirections;
|
||||
QMap<QString, QString> speciesToIconPath;
|
||||
|
||||
struct EventGraphics
|
||||
{
|
||||
@@ -277,6 +277,8 @@ private:
|
||||
void ignoreWatchedFileTemporarily(QString filepath);
|
||||
void recordFileChange(const QString &filepath);
|
||||
|
||||
QString findSpeciesIconPath(const QStringList &names) const;
|
||||
|
||||
int maxEventsPerGroup;
|
||||
int maxObjectEvents;
|
||||
static int num_tiles_primary;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "selectablepixmapitem.h"
|
||||
#include "paletteutil.h"
|
||||
#include "imageproviders.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include <memory>
|
||||
using std::shared_ptr;
|
||||
@@ -66,7 +67,7 @@ public:
|
||||
}
|
||||
|
||||
virtual QString info() const {
|
||||
return QString("Tile: 0x") + QString("%1 ").arg(this->id(), 4, 16, QChar('0')).toUpper();
|
||||
return QString("Tile: %1 ").arg(Util::toHexString(this->id(), 4));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ SOURCES += src/core/advancemapparser.cpp \
|
||||
src/core/parseutil.cpp \
|
||||
src/core/tile.cpp \
|
||||
src/core/tileset.cpp \
|
||||
src/core/utility.cpp \
|
||||
src/core/validator.cpp \
|
||||
src/core/regionmap.cpp \
|
||||
src/core/wildmoninfo.cpp \
|
||||
@@ -162,6 +163,7 @@ HEADERS += include/core/advancemapparser.h \
|
||||
include/core/parseutil.h \
|
||||
include/core/tile.h \
|
||||
include/core/tileset.h \
|
||||
include/core/utility.h \
|
||||
include/core/validator.h \
|
||||
include/core/regionmap.h \
|
||||
include/core/wildmoninfo.h \
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "shortcut.h"
|
||||
#include "map.h"
|
||||
#include "validator.h"
|
||||
#include "utility.h"
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFormLayout>
|
||||
@@ -101,6 +102,7 @@ const QMap<ProjectIdentifier, QPair<QString, QString>> ProjectConfig::defaultIde
|
||||
{ProjectIdentifier::define_attribute_encounter, {"define_attribute_encounter", "METATILE_ATTRIBUTE_ENCOUNTER_TYPE"}},
|
||||
{ProjectIdentifier::define_metatile_label_prefix, {"define_metatile_label_prefix", "METATILE_"}},
|
||||
{ProjectIdentifier::define_heal_locations_prefix, {"define_heal_locations_prefix", "HEAL_LOCATION_"}},
|
||||
{ProjectIdentifier::define_layout_prefix, {"define_layout_prefix", "LAYOUT_"}},
|
||||
{ProjectIdentifier::define_map_prefix, {"define_map_prefix", "MAP_"}},
|
||||
{ProjectIdentifier::define_map_dynamic, {"define_map_dynamic", "DYNAMIC"}},
|
||||
{ProjectIdentifier::define_map_empty, {"define_map_empty", "UNDEFINED"}},
|
||||
@@ -876,16 +878,16 @@ QMap<QString, QString> ProjectConfig::getKeyValueMap() {
|
||||
map.insert("tilesets_have_is_compressed", QString::number(this->tilesetsHaveIsCompressed));
|
||||
map.insert("set_transparent_pixels_black", QString::number(this->setTransparentPixelsBlack));
|
||||
map.insert("metatile_attributes_size", QString::number(this->metatileAttributesSize));
|
||||
map.insert("metatile_behavior_mask", "0x" + QString::number(this->metatileBehaviorMask, 16).toUpper());
|
||||
map.insert("metatile_terrain_type_mask", "0x" + QString::number(this->metatileTerrainTypeMask, 16).toUpper());
|
||||
map.insert("metatile_encounter_type_mask", "0x" + QString::number(this->metatileEncounterTypeMask, 16).toUpper());
|
||||
map.insert("metatile_layer_type_mask", "0x" + QString::number(this->metatileLayerTypeMask, 16).toUpper());
|
||||
map.insert("block_metatile_id_mask", "0x" + QString::number(this->blockMetatileIdMask, 16).toUpper());
|
||||
map.insert("block_collision_mask", "0x" + QString::number(this->blockCollisionMask, 16).toUpper());
|
||||
map.insert("block_elevation_mask", "0x" + QString::number(this->blockElevationMask, 16).toUpper());
|
||||
map.insert("unused_tile_normal", "0x" + QString::number(this->unusedTileNormal, 16).toUpper());
|
||||
map.insert("unused_tile_covered", "0x" + QString::number(this->unusedTileCovered, 16).toUpper());
|
||||
map.insert("unused_tile_split", "0x" + QString::number(this->unusedTileSplit, 16).toUpper());
|
||||
map.insert("metatile_behavior_mask", Util::toHexString(this->metatileBehaviorMask));
|
||||
map.insert("metatile_terrain_type_mask", Util::toHexString(this->metatileTerrainTypeMask));
|
||||
map.insert("metatile_encounter_type_mask", Util::toHexString(this->metatileEncounterTypeMask));
|
||||
map.insert("metatile_layer_type_mask", Util::toHexString(this->metatileLayerTypeMask));
|
||||
map.insert("block_metatile_id_mask", Util::toHexString(this->blockMetatileIdMask));
|
||||
map.insert("block_collision_mask", Util::toHexString(this->blockCollisionMask));
|
||||
map.insert("block_elevation_mask", Util::toHexString(this->blockElevationMask));
|
||||
map.insert("unused_tile_normal", Util::toHexString(this->unusedTileNormal));
|
||||
map.insert("unused_tile_covered", Util::toHexString(this->unusedTileCovered));
|
||||
map.insert("unused_tile_split", Util::toHexString(this->unusedTileSplit));
|
||||
map.insert("enable_map_allow_flags", QString::number(this->mapAllowFlagsEnabled));
|
||||
map.insert("event_icon_path_object", this->eventIconPaths[Event::Group::Object]);
|
||||
map.insert("event_icon_path_warp", this->eventIconPaths[Event::Group::Warp]);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "map.h"
|
||||
#include "imageproviders.h"
|
||||
#include "scripting.h"
|
||||
|
||||
#include "utility.h"
|
||||
#include "editcommands.h"
|
||||
|
||||
#include <QTime>
|
||||
@@ -56,14 +56,9 @@ void Map::setLayout(Layout *layout) {
|
||||
}
|
||||
}
|
||||
|
||||
QString Map::mapConstantFromName(QString mapName, bool includePrefix) {
|
||||
// Transform map names of the form 'GraniteCave_B1F` into map constants like 'MAP_GRANITE_CAVE_B1F'.
|
||||
static const QRegularExpression caseChange("([a-z])([A-Z])");
|
||||
QString nameWithUnderscores = mapName.replace(caseChange, "\\1_\\2");
|
||||
const QString prefix = includePrefix ? projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) : "";
|
||||
QString withMapAndUppercase = prefix + nameWithUnderscores.toUpper();
|
||||
static const QRegularExpression underscores("_+");
|
||||
return withMapAndUppercase.replace(underscores, "_");
|
||||
// We don't enforce this for existing maps, but for creating new maps we need to formulaically generate a new MAP_NAME ID.
|
||||
QString Map::mapConstantFromName(const QString &name) {
|
||||
return projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) + Util::toDefineCase(name);
|
||||
}
|
||||
|
||||
int Map::getWidth() const {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "scripting.h"
|
||||
#include "imageproviders.h"
|
||||
#include "utility.h"
|
||||
|
||||
Layout::Layout(const Layout &other) : Layout() {
|
||||
copyFrom(&other);
|
||||
@@ -32,13 +33,8 @@ void Layout::copyFrom(const Layout *other) {
|
||||
this->border = other->border;
|
||||
}
|
||||
|
||||
QString Layout::layoutConstantFromName(QString mapName) {
|
||||
// Transform map names of the form 'GraniteCave_B1F` into layout constants like 'LAYOUT_GRANITE_CAVE_B1F'.
|
||||
static const QRegularExpression caseChange("([a-z])([A-Z])");
|
||||
QString nameWithUnderscores = mapName.replace(caseChange, "\\1_\\2");
|
||||
QString withMapAndUppercase = "LAYOUT_" + nameWithUnderscores.toUpper();
|
||||
static const QRegularExpression underscores("_+");
|
||||
return withMapAndUppercase.replace(underscores, "_");
|
||||
QString Layout::layoutConstantFromName(const QString &name) {
|
||||
return projectConfig.getIdentifier(ProjectIdentifier::define_layout_prefix) + Util::toDefineCase(name);
|
||||
}
|
||||
|
||||
Layout::Settings Layout::settings() const {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "metatile.h"
|
||||
#include "tileset.h"
|
||||
#include "project.h"
|
||||
#include "utility.h"
|
||||
|
||||
// Stores how each attribute should be laid out for all metatiles, according to the vanilla games.
|
||||
// Used to set default config values and import maps with AdvanceMap.
|
||||
@@ -42,7 +43,7 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
|
||||
|
||||
static int numMetatileIdChars = 4;
|
||||
QString Metatile::getMetatileIdString(uint16_t metatileId) {
|
||||
return "0x" + QString("%1").arg(metatileId, numMetatileIdChars, 16, QChar('0')).toUpper();
|
||||
return Util::toHexString(metatileId, numMetatileIdChars);
|
||||
};
|
||||
|
||||
QString Metatile::getMetatileIdStrings(const QList<uint16_t> metatileIds) {
|
||||
@@ -127,8 +128,8 @@ void Metatile::setLayout(Project * project) {
|
||||
if (behaviorMask && !project->metatileBehaviorMapInverse.isEmpty()) {
|
||||
uint32_t maxBehavior = project->metatileBehaviorMapInverse.lastKey();
|
||||
if (packer.clamp(maxBehavior) != maxBehavior)
|
||||
logWarn(QString("Metatile Behavior mask '0x%1' is insufficient to contain all available options.")
|
||||
.arg(QString::number(behaviorMask, 16).toUpper()));
|
||||
logWarn(QString("Metatile Behavior mask '%1' is insufficient to contain all available options.")
|
||||
.arg(Util::toHexString(behaviorMask)));
|
||||
}
|
||||
attributePackers.insert(Metatile::Attr::Behavior, packer);
|
||||
|
||||
@@ -136,8 +137,8 @@ void Metatile::setLayout(Project * project) {
|
||||
packer.setMask(terrainTypeMask);
|
||||
const uint32_t maxTerrainType = NUM_METATILE_TERRAIN_TYPES - 1;
|
||||
if (terrainTypeMask && packer.clamp(maxTerrainType) != maxTerrainType) {
|
||||
logWarn(QString("Metatile Terrain Type mask '0x%1' is insufficient to contain all %2 available options.")
|
||||
.arg(QString::number(terrainTypeMask, 16).toUpper())
|
||||
logWarn(QString("Metatile Terrain Type mask '%1' is insufficient to contain all %2 available options.")
|
||||
.arg(Util::toHexString(terrainTypeMask))
|
||||
.arg(maxTerrainType + 1));
|
||||
}
|
||||
attributePackers.insert(Metatile::Attr::TerrainType, packer);
|
||||
@@ -146,8 +147,8 @@ void Metatile::setLayout(Project * project) {
|
||||
packer.setMask(encounterTypeMask);
|
||||
const uint32_t maxEncounterType = NUM_METATILE_ENCOUNTER_TYPES - 1;
|
||||
if (encounterTypeMask && packer.clamp(maxEncounterType) != maxEncounterType) {
|
||||
logWarn(QString("Metatile Encounter Type mask '0x%1' is insufficient to contain all %2 available options.")
|
||||
.arg(QString::number(encounterTypeMask, 16).toUpper())
|
||||
logWarn(QString("Metatile Encounter Type mask '%1' is insufficient to contain all %2 available options.")
|
||||
.arg(Util::toHexString(encounterTypeMask))
|
||||
.arg(maxEncounterType + 1));
|
||||
}
|
||||
attributePackers.insert(Metatile::Attr::EncounterType, packer);
|
||||
@@ -156,8 +157,8 @@ void Metatile::setLayout(Project * project) {
|
||||
packer.setMask(layerTypeMask);
|
||||
const uint32_t maxLayerType = NUM_METATILE_LAYER_TYPES - 1;
|
||||
if (layerTypeMask && packer.clamp(maxLayerType) != maxLayerType) {
|
||||
logWarn(QString("Metatile Layer Type mask '0x%1' is insufficient to contain all %2 available options.")
|
||||
.arg(QString::number(layerTypeMask, 16).toUpper())
|
||||
logWarn(QString("Metatile Layer Type mask '%1' is insufficient to contain all %2 available options.")
|
||||
.arg(Util::toHexString(layerTypeMask))
|
||||
.arg(maxLayerType + 1));
|
||||
}
|
||||
attributePackers.insert(Metatile::Attr::LayerType, packer);
|
||||
|
||||
@@ -368,11 +368,11 @@ QStringList ParseUtil::readCIncbinArray(const QString &filename, const QString &
|
||||
return paths;
|
||||
}
|
||||
|
||||
bool ParseUtil::defineNameMatchesFilter(const QString &name, const QStringList &filterList) const {
|
||||
bool ParseUtil::defineNameMatchesFilter(const QString &name, const QSet<QString> &filterList) const {
|
||||
return filterList.contains(name);
|
||||
}
|
||||
|
||||
bool ParseUtil::defineNameMatchesFilter(const QString &name, const QList<QRegularExpression> &filterList) const {
|
||||
bool ParseUtil::defineNameMatchesFilter(const QString &name, const QSet<QRegularExpression> &filterList) const {
|
||||
for (auto filter : filterList) {
|
||||
if (filter.match(name).hasMatch())
|
||||
return true;
|
||||
@@ -380,7 +380,7 @@ bool ParseUtil::defineNameMatchesFilter(const QString &name, const QList<QRegula
|
||||
return false;
|
||||
}
|
||||
|
||||
ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error) {
|
||||
ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const QSet<QString> &filterList, bool useRegex, QString *error) {
|
||||
ParsedDefines result;
|
||||
this->file = filename;
|
||||
|
||||
@@ -402,10 +402,10 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const
|
||||
return result;
|
||||
|
||||
// If necessary, construct regular expressions from filter list
|
||||
QList<QRegularExpression> filterList_Regex;
|
||||
QSet<QRegularExpression> filterList_Regex;
|
||||
if (useRegex) {
|
||||
for (auto filter : filterList) {
|
||||
filterList_Regex.append(QRegularExpression(filter));
|
||||
filterList_Regex.insert(QRegularExpression(filter));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,7 +463,7 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const
|
||||
}
|
||||
|
||||
// Read all the define names and their expressions in the specified file, then evaluate the ones matching the search text (and any they depend on).
|
||||
QMap<QString, int> ParseUtil::evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error) {
|
||||
QMap<QString, int> ParseUtil::evaluateCDefines(const QString &filename, const QSet<QString> &filterList, bool useRegex, QString *error) {
|
||||
ParsedDefines defines = readCDefines(filename, filterList, useRegex, error);
|
||||
|
||||
// Evaluate defines
|
||||
@@ -483,19 +483,19 @@ QMap<QString, int> ParseUtil::evaluateCDefines(const QString &filename, const QS
|
||||
}
|
||||
|
||||
// Find and evaluate a specific set of defines with known names.
|
||||
QMap<QString, int> ParseUtil::readCDefinesByName(const QString &filename, const QStringList &names, QString *error) {
|
||||
QMap<QString, int> ParseUtil::readCDefinesByName(const QString &filename, const QSet<QString> &names, QString *error) {
|
||||
return evaluateCDefines(filename, names, false, error);
|
||||
}
|
||||
|
||||
// Find and evaluate an unknown list of defines with a known name pattern.
|
||||
QMap<QString, int> ParseUtil::readCDefinesByRegex(const QString &filename, const QStringList ®exList, QString *error) {
|
||||
QMap<QString, int> ParseUtil::readCDefinesByRegex(const QString &filename, const QSet<QString> ®exList, QString *error) {
|
||||
return evaluateCDefines(filename, regexList, true, error);
|
||||
}
|
||||
|
||||
// Find an unknown list of defines with a known name pattern.
|
||||
// Similar to readCDefinesByRegex, but for cases where we only need to show a list of define names.
|
||||
// We can skip evaluating any expressions (and by extension skip reporting any errors from this process).
|
||||
QStringList ParseUtil::readCDefineNames(const QString &filename, const QStringList ®exList, QString *error) {
|
||||
QStringList ParseUtil::readCDefineNames(const QString &filename, const QSet<QString> ®exList, QString *error) {
|
||||
return readCDefines(filename, regexList, true, error).filteredNames;
|
||||
}
|
||||
|
||||
|
||||
44
src/core/utility.cpp
Normal file
44
src/core/utility.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "utility.h"
|
||||
|
||||
#include <QCollator>
|
||||
#include <QRegularExpression>
|
||||
|
||||
// Sometimes we want to sort names alphabetically to make them easier to find in large combo box lists.
|
||||
// QStringList::sort (as of writing) can only sort numbers in lexical order, which has an undesirable
|
||||
// effect (e.g. 'ROUTE_1, ROUTE_10, ROUTE_2,...' instead of 'ROUTE_1, ROUTE_2,... ROUTE_10').
|
||||
// We can use QCollator to sort these lists with better handling for numbers.
|
||||
void Util::numericalModeSort(QStringList &list) {
|
||||
static QCollator collator;
|
||||
collator.setNumericMode(true);
|
||||
std::sort(list.begin(), list.end(), collator);
|
||||
}
|
||||
|
||||
int Util::roundUp(int numToRound, int multiple) {
|
||||
if (multiple <= 0)
|
||||
return numToRound;
|
||||
|
||||
int remainder = abs(numToRound) % multiple;
|
||||
if (remainder == 0)
|
||||
return numToRound;
|
||||
|
||||
if (numToRound < 0)
|
||||
return -(abs(numToRound) - remainder);
|
||||
else
|
||||
return numToRound + multiple - remainder;
|
||||
}
|
||||
|
||||
// Ex: input 'GraniteCave_B1F' returns 'GRANITE_CAVE_B1F'.
|
||||
QString Util::toDefineCase(QString input) {
|
||||
static const QRegularExpression re_CaseChange("([a-z])([A-Z])");
|
||||
input.replace(re_CaseChange, "\\1_\\2");
|
||||
|
||||
// Remove sequential underscores
|
||||
static const QRegularExpression re_Underscores("_+");
|
||||
input.replace(re_Underscores, "_");
|
||||
|
||||
return input.toUpper();
|
||||
}
|
||||
|
||||
QString Util::toHexString(uint32_t value, int minLength) {
|
||||
return "0x" + QString("%1").arg(value, minLength, 16, QChar('0')).toUpper();
|
||||
}
|
||||
@@ -987,7 +987,7 @@ QString Editor::getMetatileDisplayMessage(uint16_t metatileId) {
|
||||
if (label.size())
|
||||
message += QString(" \"%1\"").arg(label);
|
||||
if (metatile && metatile->behavior() != 0) { // Skip MB_NORMAL
|
||||
const QString behaviorStr = this->project->metatileBehaviorMapInverse.value(metatile->behavior(), "0x" + QString::number(metatile->behavior(), 16));
|
||||
const QString behaviorStr = this->project->metatileBehaviorMapInverse.value(metatile->behavior(), Util::toHexString(metatile->behavior()));
|
||||
message += QString(", Behavior: %1").arg(behaviorStr);
|
||||
}
|
||||
return message;
|
||||
|
||||
247
src/project.cpp
247
src/project.cpp
@@ -10,6 +10,7 @@
|
||||
#include "filedialog.h"
|
||||
#include "validator.h"
|
||||
#include "orderedjson.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QJsonArray>
|
||||
@@ -312,7 +313,7 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t
|
||||
map->setNeedsHealLocation(settings.canFlyTo);
|
||||
|
||||
// Generate a unique MAP constant.
|
||||
map->setConstantName(toUniqueIdentifier(Map::mapConstantFromName(map->name())));
|
||||
map->setConstantName(toUniqueIdentifier(map->expectedConstantName()));
|
||||
|
||||
// Make sure we keep the order of the map names the same as in the map group order.
|
||||
int mapNamePos;
|
||||
@@ -1495,7 +1496,7 @@ bool Project::readTilesetMetatileLabels() {
|
||||
QString metatileLabelsFilename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_labels);
|
||||
fileWatcher.addPath(root + "/" + metatileLabelsFilename);
|
||||
|
||||
const QStringList regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix))};
|
||||
const QSet<QString> regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix))};
|
||||
QMap<QString, int> defines = parser.readCDefinesByRegex(metatileLabelsFilename, regexList);
|
||||
|
||||
for (QString label : defines.keys()) {
|
||||
@@ -2064,8 +2065,8 @@ bool Project::readTilesetLabels() {
|
||||
}
|
||||
}
|
||||
|
||||
numericalModeSort(this->primaryTilesetLabels);
|
||||
numericalModeSort(this->secondaryTilesetLabels);
|
||||
Util::numericalModeSort(this->primaryTilesetLabels);
|
||||
Util::numericalModeSort(this->secondaryTilesetLabels);
|
||||
|
||||
bool success = true;
|
||||
if (this->secondaryTilesetLabels.isEmpty()) {
|
||||
@@ -2087,7 +2088,7 @@ bool Project::readFieldmapProperties() {
|
||||
const QString numPalsTotalName = projectConfig.getIdentifier(ProjectIdentifier::define_pals_total);
|
||||
const QString maxMapSizeName = projectConfig.getIdentifier(ProjectIdentifier::define_map_size);
|
||||
const QString numTilesPerMetatileName = projectConfig.getIdentifier(ProjectIdentifier::define_tiles_per_metatile);
|
||||
const QStringList names = {
|
||||
const QSet<QString> names = {
|
||||
numTilesPrimaryName,
|
||||
numTilesTotalName,
|
||||
numMetatilesPrimaryName,
|
||||
@@ -2171,7 +2172,7 @@ bool Project::readFieldmapMasks() {
|
||||
const QString elevationMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_elevation);
|
||||
const QString behaviorMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_behavior);
|
||||
const QString layerTypeMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_layer);
|
||||
const QStringList searchNames = {
|
||||
const QSet<QString> searchNames = {
|
||||
metatileIdMaskName,
|
||||
collisionMaskName,
|
||||
elevationMaskName,
|
||||
@@ -2196,10 +2197,10 @@ bool Project::readFieldmapMasks() {
|
||||
return false;
|
||||
*value = static_cast<uint16_t>(it.value());
|
||||
if (*value != it.value()){
|
||||
logWarn(QString("Value for %1 truncated from '0x%2' to '0x%3'")
|
||||
logWarn(QString("Value for %1 truncated from '%2' to '%3'")
|
||||
.arg(name)
|
||||
.arg(QString::number(it.value(), 16).toUpper())
|
||||
.arg(QString::number(*value, 16).toUpper()));
|
||||
.arg(Util::toHexString(it.value()))
|
||||
.arg(Util::toHexString(*value)));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
@@ -2339,7 +2340,7 @@ bool Project::readRegionMapSections() {
|
||||
if (!this->mapSectionIdNames.contains(defaultName)) {
|
||||
this->mapSectionIdNames.append(defaultName);
|
||||
}
|
||||
numericalModeSort(this->mapSectionIdNames);
|
||||
Util::numericalModeSort(this->mapSectionIdNames);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -2363,7 +2364,7 @@ void Project::addNewMapsec(const QString &idName) {
|
||||
}
|
||||
|
||||
this->mapSectionIdNames.append(idName);
|
||||
numericalModeSort(this->mapSectionIdNames);
|
||||
Util::numericalModeSort(this->mapSectionIdNames);
|
||||
|
||||
this->hasUnsavedDataChanges = true;
|
||||
|
||||
@@ -2428,44 +2429,40 @@ bool Project::readHealLocations() {
|
||||
}
|
||||
|
||||
bool Project::readItemNames() {
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_items)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_items);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
this->itemNames = parser.readCDefineNames(filename, regexList, &error);
|
||||
this->itemNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_items)}, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read item constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Project::readFlagNames() {
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_flags)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_flags);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
this->flagNames = parser.readCDefineNames(filename, regexList, &error);
|
||||
this->flagNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_flags)}, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read flag constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Project::readVarNames() {
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_vars)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_vars);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
this->varNames = parser.readCDefineNames(filename, regexList, &error);
|
||||
this->varNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_vars)}, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read var constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Project::readMovementTypes() {
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_movement_types)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_event_movement);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
this->movementTypes = parser.readCDefineNames(filename, regexList, &error);
|
||||
this->movementTypes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_movement_types)}, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read movement type constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
@@ -2482,33 +2479,30 @@ bool Project::readInitialFacingDirections() {
|
||||
}
|
||||
|
||||
bool Project::readMapTypes() {
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_map_types)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
this->mapTypes = parser.readCDefineNames(filename, regexList, &error);
|
||||
this->mapTypes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_map_types)}, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read map type constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Project::readMapBattleScenes() {
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_battle_scenes)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
this->mapBattleScenes = parser.readCDefineNames(filename, regexList, &error);
|
||||
this->mapBattleScenes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_battle_scenes)}, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read map battle scene constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Project::readWeatherNames() {
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_weather)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
this->weatherNames = parser.readCDefineNames(filename, regexList, &error);
|
||||
this->weatherNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_weather)}, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read weather constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
@@ -2518,11 +2512,10 @@ bool Project::readCoordEventWeatherNames() {
|
||||
if (!projectConfig.eventWeatherTriggerEnabled)
|
||||
return true;
|
||||
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_coord_event_weather)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
this->coordEventWeatherNames = parser.readCDefineNames(filename, regexList, &error);
|
||||
this->coordEventWeatherNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_coord_event_weather)}, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read coord event weather constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
@@ -2532,33 +2525,30 @@ bool Project::readSecretBaseIds() {
|
||||
if (!projectConfig.eventSecretBaseEnabled)
|
||||
return true;
|
||||
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_secret_bases)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_secret_bases);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
this->secretBaseIds = parser.readCDefineNames(filename, regexList, &error);
|
||||
this->secretBaseIds = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_secret_bases)}, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read secret base id constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Project::readBgEventFacingDirections() {
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_sign_facing_directions)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_event_bg);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
this->bgEventFacingDirections = parser.readCDefineNames(filename, regexList, &error);
|
||||
this->bgEventFacingDirections = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_sign_facing_directions)}, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read bg event facing direction constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Project::readTrainerTypes() {
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_trainer_types)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_trainer_types);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
this->trainerTypes = parser.readCDefineNames(filename, regexList, &error);
|
||||
this->trainerTypes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_trainer_types)}, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read trainer type constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
@@ -2568,11 +2558,10 @@ bool Project::readMetatileBehaviors() {
|
||||
this->metatileBehaviorMap.clear();
|
||||
this->metatileBehaviorMapInverse.clear();
|
||||
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)};
|
||||
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_behaviors);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
QMap<QString, int> defines = parser.readCDefinesByRegex(filename, regexList, &error);
|
||||
QMap<QString, int> defines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)}, &error);
|
||||
if (defines.isEmpty() && projectConfig.metatileBehaviorMask) {
|
||||
// Not having any metatile behavior names is ok (their values will be displayed instead)
|
||||
// but if the user's metatiles can have nonzero values then warn them, as they likely want names.
|
||||
@@ -2591,27 +2580,25 @@ bool Project::readMetatileBehaviors() {
|
||||
}
|
||||
|
||||
bool Project::readSongNames() {
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_music)};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_songs);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
this->songNames = parser.readCDefineNames(filename, regexList, &error);
|
||||
this->songNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_music)}, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read song names from '%1': %2").arg(filename).arg(error));
|
||||
|
||||
// Song names don't have a very useful order (esp. if we include SE_* values), so sort them alphabetically.
|
||||
// The default song should be the first in the list, not the first alphabetically, so save that before sorting.
|
||||
this->defaultSong = this->songNames.value(0, "0");
|
||||
numericalModeSort(this->songNames);
|
||||
Util::numericalModeSort(this->songNames);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Project::readObjEventGfxConstants() {
|
||||
const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)};
|
||||
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_events);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QString error;
|
||||
this->gfxDefines = parser.readCDefinesByRegex(filename, regexList, &error);
|
||||
this->gfxDefines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)}, &error);
|
||||
if (!error.isEmpty())
|
||||
logWarn(QString("Failed to read object event graphics constants from '%1': %2").arg(filename).arg(error));
|
||||
return true;
|
||||
@@ -2937,101 +2924,122 @@ QPixmap Project::getEventPixmap(Event::Group group) {
|
||||
|
||||
bool Project::readSpeciesIconPaths() {
|
||||
this->speciesToIconPath.clear();
|
||||
this->speciesNames.clear();
|
||||
|
||||
// Read map of species constants to icon names
|
||||
const QString srcfilename = projectConfig.getFilePath(ProjectFilePath::pokemon_icon_table);
|
||||
fileWatcher.addPath(root + "/" + srcfilename);
|
||||
fileWatcher.addPath(this->root + "/" + srcfilename);
|
||||
const QString tableName = projectConfig.getIdentifier(ProjectIdentifier::symbol_pokemon_icon_table);
|
||||
const QMap<QString, QString> monIconNames = parser.readNamedIndexCArray(srcfilename, tableName);
|
||||
|
||||
// Read map of icon names to filepaths
|
||||
const QString incfilename = projectConfig.getFilePath(ProjectFilePath::data_pokemon_gfx);
|
||||
fileWatcher.addPath(root + "/" + incfilename);
|
||||
const QMap<QString, QString> iconIncbins = parser.readCIncbinMulti(incfilename);
|
||||
|
||||
// Read species constants. If this fails we can get them from the icon table (but we shouldn't rely on it).
|
||||
const QStringList regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix))};
|
||||
const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix);
|
||||
const QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_species);
|
||||
fileWatcher.addPath(root + "/" + constantsFilename);
|
||||
QStringList speciesNames = parser.readCDefineNames(constantsFilename, regexList);
|
||||
if (speciesNames.isEmpty())
|
||||
speciesNames = monIconNames.keys();
|
||||
fileWatcher.addPath(this->root + "/" + constantsFilename);
|
||||
this->speciesNames = parser.readCDefineNames(constantsFilename, {QString("\\b%1").arg(speciesPrefix)});
|
||||
if (this->speciesNames.isEmpty()) {
|
||||
this->speciesNames = monIconNames.keys();
|
||||
}
|
||||
this->speciesNames.sort();
|
||||
|
||||
// For each species, use the information gathered above to find the icon image.
|
||||
bool missingIcons = false;
|
||||
for (auto species : speciesNames) {
|
||||
QString path = QString();
|
||||
if (monIconNames.contains(species) && iconIncbins.contains(monIconNames.value(species))) {
|
||||
// We have the icon filepath from the icon table
|
||||
path = QString("%1/%2").arg(root).arg(this->fixGraphicPath(iconIncbins[monIconNames.value(species)]));
|
||||
} else {
|
||||
// Failed to read icon filepath from the icon table, check filepaths where icons are normally located.
|
||||
// Try to use the icon name (if we have it) to determine the directory, then try the species name.
|
||||
// The name permuting is overkill, but it's making up for some of the fragility in the way we find icon paths.
|
||||
QStringList possibleDirNames;
|
||||
if (monIconNames.contains(species)) {
|
||||
// Ex: For 'gMonIcon_QuestionMark' try 'question_mark'
|
||||
static const QRegularExpression re("([a-z])([A-Z0-9])");
|
||||
QString iconName = monIconNames.value(species);
|
||||
iconName = iconName.mid(iconName.indexOf("_") + 1); // jump past prefix ('gMonIcon')
|
||||
possibleDirNames.append(iconName.replace(re, "\\1_\\2").toLower());
|
||||
}
|
||||
|
||||
// Ex: For 'SPECIES_FOO_BAR_BAZ' try 'foo_bar_baz'
|
||||
possibleDirNames.append(species.mid(8).toLower());
|
||||
|
||||
// Permute paths with underscores.
|
||||
// Ex: Try 'foo_bar/baz', 'foo/bar_baz', 'foobarbaz', 'foo_bar', and 'foo'
|
||||
QStringList permutedNames;
|
||||
for (auto dir : possibleDirNames) {
|
||||
if (!dir.contains("_")) continue;
|
||||
for (int i = dir.indexOf("_"); i > -1; i = dir.indexOf("_", i + 1)) {
|
||||
QString temp = dir;
|
||||
permutedNames.prepend(temp.replace(i, 1, "/"));
|
||||
permutedNames.append(dir.left(i)); // Prepend the others so the most generic name ('foo') ends up last
|
||||
// If we successfully found the species icon table we can use this data to get the filepath for each species icon.
|
||||
// For any species not in the table, or if we failed to find the table at all, we will have to predict where the icon file is.
|
||||
// That can require checking a lot of files (especially for projects with many species), so to save time on startup we only
|
||||
// do this on request in Project::getDefaultSpeciesIconPath.
|
||||
if (!monIconNames.isEmpty()) {
|
||||
const QString iconGraphicsFile = projectConfig.getFilePath(ProjectFilePath::data_pokemon_gfx);
|
||||
fileWatcher.addPath(this->root + "/" + iconGraphicsFile);
|
||||
QMap<QString, QString> iconNameToFilepath = parser.readCIncbinMulti(iconGraphicsFile);
|
||||
|
||||
for (auto i = monIconNames.constBegin(); i != monIconNames.constEnd(); i++) {
|
||||
QString path;
|
||||
QString species = i.key();
|
||||
QString iconName = i.value();
|
||||
if (iconNameToFilepath.contains(iconName)) {
|
||||
path = fixGraphicPath(iconNameToFilepath.value(iconName));
|
||||
} else {
|
||||
// We have an icon name for this species, but we haven't found its filepath.
|
||||
// Try to find the icon file using the full icon name, and the icon name if we assume it has a prefix.
|
||||
// Ex: For 'gMonIcon_QuestionMark' search for files by permuting through directories using 'question_mark' and 'g_mon_icon_question_mark.
|
||||
static const QRegularExpression re_caseChange("([a-z])([A-Z0-9])");
|
||||
QStringList dirNames;
|
||||
if (iconName.contains("_")) {
|
||||
QString iconNameNoPrefix = iconName.mid(iconName.indexOf("_") + 1);
|
||||
dirNames.append(iconNameNoPrefix.replace(re_caseChange, "\\1_\\2").toLower());
|
||||
}
|
||||
permutedNames.prepend(dir.remove("_"));
|
||||
QString iconNameWithPrefix = iconName; // Leave iconName unchanged by .replace
|
||||
dirNames.append(iconNameWithPrefix.replace(re_caseChange, "\\1_\\2").toLower());
|
||||
path = iconNameToFilepath[iconName] = findSpeciesIconPath(dirNames);
|
||||
}
|
||||
possibleDirNames.append(permutedNames);
|
||||
|
||||
possibleDirNames.removeDuplicates();
|
||||
for (auto dir : possibleDirNames) {
|
||||
if (dir.isEmpty()) continue;
|
||||
const QString stdPath = QString("%1/%2%3/icon.png")
|
||||
.arg(root)
|
||||
.arg(projectConfig.getFilePath(ProjectFilePath::pokemon_gfx))
|
||||
.arg(dir);
|
||||
if (QFile::exists(stdPath)) {
|
||||
// Icon found at a normal filepath
|
||||
path = stdPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.isEmpty() && projectConfig.getPokemonIconPath(species).isEmpty()) {
|
||||
// Failed to find icon, this species will use a placeholder icon.
|
||||
logWarn(QString("Failed to find Pokémon icon for '%1'").arg(species));
|
||||
missingIcons = true;
|
||||
if (!path.isEmpty()) {
|
||||
this->speciesToIconPath.insert(species, QString("%1/%2").arg(this->root).arg(path));
|
||||
}
|
||||
}
|
||||
this->speciesToIconPath.insert(species, path);
|
||||
}
|
||||
|
||||
// Logging this alongside every warning (if there are multiple) is obnoxious, just do it once at the end.
|
||||
if (missingIcons) logInfo("Pokémon icon filepaths can be specified under 'Options->Project Settings'");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QPixmap Project::getSpeciesIcon(const QString &species) const {
|
||||
QString Project::getDefaultSpeciesIconPath(const QString &species) {
|
||||
if (this->speciesToIconPath.contains(species)) {
|
||||
// We already know the icon path for this species (either because we read it from the project, or we found it already).
|
||||
return this->speciesToIconPath.value(species);
|
||||
}
|
||||
if (!this->speciesNames.contains(species)) {
|
||||
// Don't bother searching for a path if we don't recognize the species name.
|
||||
return QString();
|
||||
}
|
||||
|
||||
// Ex: For 'SPECIES_FOO_BAR_BAZ' search for files by permuting through directories using 'foo_bar_baz'.
|
||||
const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix);
|
||||
const QString path = findSpeciesIconPath({species.mid(speciesPrefix.length()).toLower()});
|
||||
this->speciesToIconPath.insert(species, path);
|
||||
|
||||
// We failed to find a default icon path, this species will use a placeholder icon.
|
||||
// If the user has no custom icon path for this species, tell them they can provide one.
|
||||
if (path.isEmpty() && projectConfig.getPokemonIconPath(species).isEmpty()) {
|
||||
logWarn(QString("Failed to find Pokémon icon for '%1'. The filepath can be specified under 'Options->Project Settings'").arg(species));
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// The name permuting in here is overkill, but it's making up for some of the fragility in the way we find pokémon icon paths.
|
||||
// For pokeemerald-expansion in particular this function is solely responsible for finding pokémon icons, because they have no icon table.
|
||||
QString Project::findSpeciesIconPath(const QStringList &names) const {
|
||||
QStringList possibleDirNames = names;
|
||||
|
||||
// Permute paths with underscores.
|
||||
// Ex: For a base name of 'foo_bar_baz', try 'foo_bar/baz', 'foo/bar_baz', 'foobarbaz', 'foo_bar', and 'foo'.
|
||||
QStringList permutedNames;
|
||||
for (auto dir : possibleDirNames) {
|
||||
if (!dir.contains("_")) continue;
|
||||
for (int i = dir.indexOf("_"); i > -1; i = dir.indexOf("_", i + 1)) {
|
||||
QString temp = dir;
|
||||
permutedNames.prepend(temp.replace(i, 1, "/"));
|
||||
permutedNames.append(dir.left(i)); // Prepend the others so the most generic name ('foo') ends up last
|
||||
}
|
||||
permutedNames.prepend(dir.remove("_"));
|
||||
}
|
||||
possibleDirNames.append(permutedNames);
|
||||
possibleDirNames.removeDuplicates();
|
||||
|
||||
const QString basePath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::pokemon_gfx));
|
||||
for (const auto &dir : possibleDirNames) {
|
||||
if (dir.isEmpty()) continue;
|
||||
|
||||
const QString path = QString("%1%2/icon.png").arg(basePath).arg(dir);
|
||||
if (QFile::exists(path))
|
||||
return path;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QPixmap Project::getSpeciesIcon(const QString &species) {
|
||||
QPixmap pixmap;
|
||||
if (!QPixmapCache::find(species, &pixmap)) {
|
||||
// Prefer path from config. If not present, use the path parsed from project files
|
||||
QString path = projectConfig.getPokemonIconPath(species);
|
||||
QString path = Project::getExistingFilepath(projectConfig.getPokemonIconPath(species));
|
||||
if (path.isEmpty()) {
|
||||
path = this->speciesToIconPath.value(species);
|
||||
} else {
|
||||
path = Project::getExistingFilepath(path);
|
||||
path = getDefaultSpeciesIconPath(species);
|
||||
}
|
||||
|
||||
QImage img(path);
|
||||
@@ -3210,14 +3218,3 @@ bool Project::hasUnsavedChanges() {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: This belongs in a more general utility file, once we have one.
|
||||
// Sometimes we want to sort names alphabetically to make them easier to find in large combo box lists.
|
||||
// QStringList::sort (as of writing) can only sort numbers in lexical order, which has an undesirable
|
||||
// effect (e.g. MAPSEC_ROUTE_10 comes after MAPSEC_ROUTE_1, rather than MAPSEC_ROUTE_9).
|
||||
// We can use QCollator to sort these lists with better handling for numbers.
|
||||
void Project::numericalModeSort(QStringList &list) {
|
||||
QCollator collator;
|
||||
collator.setNumericMode(true);
|
||||
std::sort(list.begin(), list.end(), collator);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ void SpeciesComboDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
||||
QWidget *SpeciesComboDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const {
|
||||
NoScrollComboBox *editor = new NoScrollComboBox(parent);
|
||||
editor->setFrame(false);
|
||||
editor->addItems(this->project->speciesToIconPath.keys());
|
||||
editor->addItems(this->project->speciesNames);
|
||||
return editor;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "movablerect.h"
|
||||
#include "utility.h"
|
||||
|
||||
MovableRect::MovableRect(bool *enabled, int width, int height, QRgb color)
|
||||
: QGraphicsRectItem(0, 0, width, height)
|
||||
@@ -22,10 +23,6 @@ void MovableRect::updateLocation(int x, int y) {
|
||||
************************************************************************
|
||||
******************************************************************************/
|
||||
|
||||
int roundUp(int numToRound, int multiple) {
|
||||
return (numToRound + multiple - 1) & -multiple;
|
||||
}
|
||||
|
||||
ResizableRect::ResizableRect(QObject *parent, bool *enabled, int width, int height, QRgb color)
|
||||
: QObject(parent),
|
||||
MovableRect(enabled, width * 16, height * 16, color)
|
||||
@@ -117,8 +114,8 @@ void ResizableRect::mousePressEvent(QGraphicsSceneMouseEvent *event) {
|
||||
}
|
||||
|
||||
void ResizableRect::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
|
||||
int dx = roundUp(event->scenePos().x() - this->clickedPos.x(), 16);
|
||||
int dy = roundUp(event->scenePos().y() - this->clickedPos.y(), 16);
|
||||
int dx = Util::roundUp(event->scenePos().x() - this->clickedPos.x(), 16);
|
||||
int dy = Util::roundUp(event->scenePos().y() - this->clickedPos.y(), 16);
|
||||
|
||||
QRect resizedRect = this->clickedRect;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "noscrollcombobox.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include <QCompleter>
|
||||
#include <QLineEdit>
|
||||
@@ -82,7 +83,7 @@ void NoScrollComboBox::setNumberItem(int value)
|
||||
|
||||
void NoScrollComboBox::setHexItem(uint32_t value)
|
||||
{
|
||||
this->setItem(this->findData(value), "0x" + QString::number(value, 16).toUpper());
|
||||
this->setItem(this->findData(value), Util::toHexString(value));
|
||||
}
|
||||
|
||||
void NoScrollComboBox::setClearButtonEnabled(bool enabled) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "noscrollcombobox.h"
|
||||
#include "prefab.h"
|
||||
#include "filedialog.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QFormLayout>
|
||||
@@ -103,7 +104,7 @@ void ProjectSettingsEditor::initUi() {
|
||||
if (project) {
|
||||
ui->comboBox_DefaultPrimaryTileset->addItems(project->primaryTilesetLabels);
|
||||
ui->comboBox_DefaultSecondaryTileset->addItems(project->secondaryTilesetLabels);
|
||||
ui->comboBox_IconSpecies->addItems(project->speciesToIconPath.keys());
|
||||
ui->comboBox_IconSpecies->addItems(project->speciesNames);
|
||||
ui->comboBox_WarpBehaviors->addItems(project->metatileBehaviorMap.keys());
|
||||
}
|
||||
ui->comboBox_BaseGameVersion->addItems(ProjectConfig::versionStrings);
|
||||
@@ -277,11 +278,11 @@ void ProjectSettingsEditor::updatePokemonIconPath(const QString &newSpecies) {
|
||||
if (!project) return;
|
||||
|
||||
// If user was editing a path for a valid species, record filepath text before we wipe it.
|
||||
if (!this->prevIconSpecies.isEmpty() && this->project->speciesToIconPath.contains(this->prevIconSpecies))
|
||||
if (!this->prevIconSpecies.isEmpty() && this->project->speciesNames.contains(this->prevIconSpecies))
|
||||
this->editedPokemonIconPaths[this->prevIconSpecies] = ui->lineEdit_PokemonIcon->text();
|
||||
|
||||
QString editedPath = this->editedPokemonIconPaths.value(newSpecies);
|
||||
QString defaultPath = this->project->speciesToIconPath.value(newSpecies);
|
||||
QString defaultPath = this->project->getDefaultSpeciesIconPath(newSpecies);
|
||||
|
||||
ui->lineEdit_PokemonIcon->setText(this->stripProjectDir(editedPath));
|
||||
ui->lineEdit_PokemonIcon->setPlaceholderText(this->stripProjectDir(defaultPath));
|
||||
@@ -294,7 +295,7 @@ QStringList ProjectSettingsEditor::getWarpBehaviorsList() {
|
||||
|
||||
void ProjectSettingsEditor::setWarpBehaviorsList(QStringList list) {
|
||||
list.removeDuplicates();
|
||||
Project::numericalModeSort(list);
|
||||
Util::numericalModeSort(list);
|
||||
ui->textEdit_WarpBehaviors->setText(list.join("\n"));
|
||||
}
|
||||
|
||||
@@ -566,7 +567,7 @@ void ProjectSettingsEditor::save() {
|
||||
|
||||
// Save pokemon icon paths
|
||||
const QString species = ui->comboBox_IconSpecies->currentText();
|
||||
if (this->project->speciesToIconPath.contains(species))
|
||||
if (this->project->speciesNames.contains(species))
|
||||
this->editedPokemonIconPaths.insert(species, ui->lineEdit_PokemonIcon->text());
|
||||
for (auto i = this->editedPokemonIconPaths.cbegin(), end = this->editedPokemonIconPaths.cend(); i != end; i++)
|
||||
projectConfig.setPokemonIconPath(i.key(), i.value());
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "shortcut.h"
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QDialog>
|
||||
@@ -793,8 +794,7 @@ void RegionMapEditor::onRegionMapTileSelectorSelectedTileChanged(unsigned id) {
|
||||
}
|
||||
|
||||
void RegionMapEditor::onRegionMapTileSelectorHoveredTileChanged(unsigned tileId) {
|
||||
QString message = QString("Tile: 0x") + QString("%1").arg(tileId, 4, 16, QChar('0')).toUpper();
|
||||
this->ui->statusbar->showMessage(message);
|
||||
this->ui->statusbar->showMessage(QString("Tile: %1").arg(Util::toHexString(tileId, 4)));
|
||||
}
|
||||
|
||||
void RegionMapEditor::onRegionMapTileSelectorHoveredTileCleared() {
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
#include "editor.h"
|
||||
#include "movablerect.h"
|
||||
#include "config.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include "ui_resizelayoutpopup.h"
|
||||
|
||||
// TODO: put this in a util file or something
|
||||
extern int roundUp(int, int);
|
||||
|
||||
CheckeredBgScene::CheckeredBgScene(QObject *parent) : QGraphicsScene(parent) { }
|
||||
|
||||
void CheckeredBgScene::drawBackground(QPainter *painter, const QRectF &rect) {
|
||||
@@ -62,7 +60,7 @@ void BoundedPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem
|
||||
QVariant BoundedPixmapItem::itemChange(GraphicsItemChange change, const QVariant &value) {
|
||||
if (change == ItemPositionChange && scene()) {
|
||||
QPointF newPos = value.toPointF();
|
||||
return QPointF(roundUp(newPos.x(), 16), roundUp(newPos.y(), 16));
|
||||
return QPointF(Util::roundUp(newPos.x(), 16), Util::roundUp(newPos.y(), 16));
|
||||
}
|
||||
else
|
||||
return QGraphicsItem::itemChange(change, value);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "filedialog.h"
|
||||
#include "validator.h"
|
||||
#include "eventfilters.h"
|
||||
#include "utility.h"
|
||||
#include <QMessageBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QCloseEvent>
|
||||
@@ -420,9 +421,7 @@ void TilesetEditor::queueMetatileReload(uint16_t metatileId) {
|
||||
}
|
||||
|
||||
void TilesetEditor::onHoveredTileChanged(uint16_t tile) {
|
||||
QString message = QString("Tile: 0x%1")
|
||||
.arg(QString("%1").arg(tile, 3, 16, QChar('0')).toUpper());
|
||||
this->ui->statusbar->showMessage(message);
|
||||
this->ui->statusbar->showMessage(QString("Tile: %1").arg(Util::toHexString(tile, 3)));
|
||||
}
|
||||
|
||||
void TilesetEditor::onHoveredTileCleared() {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "wildmonchart.h"
|
||||
#include "ui_wildmonchart.h"
|
||||
#include "config.h"
|
||||
#include "utility.h"
|
||||
|
||||
static const QString baseWindowTitle = QString("Wild Pokémon Summary Charts");
|
||||
|
||||
@@ -367,13 +368,7 @@ QChart* WildMonChart::createLevelDistributionChart() {
|
||||
series->attachAxis(axisY);
|
||||
|
||||
// We round the y-axis max up to a multiple of 5.
|
||||
auto roundUp = [](int num, int multiple) {
|
||||
auto remainder = num % multiple;
|
||||
if (remainder == 0)
|
||||
return num;
|
||||
return num + multiple - remainder;
|
||||
};
|
||||
axisY->setMax(roundUp(qCeil(axisY->max()), 5));
|
||||
axisY->setMax(Util::roundUp(qCeil(axisY->max()), 5));
|
||||
|
||||
return chart;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ WildMonSearch::WildMonSearch(Project *project, QWidget *parent) :
|
||||
ui->setupUi(this);
|
||||
|
||||
// Set up species combo box
|
||||
ui->comboBox_Search->addItems(project->speciesToIconPath.keys());
|
||||
ui->comboBox_Search->addItems(project->speciesNames);
|
||||
ui->comboBox_Search->setCurrentText(QString());
|
||||
ui->comboBox_Search->lineEdit()->setPlaceholderText(Project::getEmptySpeciesName());
|
||||
connect(ui->comboBox_Search, &QComboBox::currentTextChanged, this, &WildMonSearch::updateResults);
|
||||
|
||||
Reference in New Issue
Block a user