mirror of
https://github.com/huderlem/porymap.git
synced 2026-08-27 19:16:31 -05:00
Speed improvements for Project::readSpeciesIconPaths
This commit is contained in:
@@ -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);
|
||||
@@ -255,6 +256,7 @@ private:
|
||||
QMap<QString, QString> mapSectionDisplayNames;
|
||||
QMap<QString, qint64> modifiedFileTimestamps;
|
||||
QMap<QString, QString> facingDirections;
|
||||
QMap<QString, QString> speciesToIconPath;
|
||||
|
||||
struct EventGraphics
|
||||
{
|
||||
@@ -275,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;
|
||||
|
||||
167
src/project.cpp
167
src/project.cpp
@@ -2924,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 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, {QString("\\b%1").arg(speciesPrefix)});
|
||||
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(speciesPrefix.length()).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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,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);
|
||||
@@ -278,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));
|
||||
@@ -567,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());
|
||||
|
||||
@@ -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