diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst
index 67bba635..fc720c22 100644
--- a/docsrc/manual/project-files.rst
+++ b/docsrc/manual/project-files.rst
@@ -122,6 +122,7 @@ In addition to these files, there are some specific symbol and macro names that
``define_map_section_prefix``, ``MAPSEC_``, expected prefix for location macro names
``define_map_section_empty``, ``NONE``, macro name after prefix for empty region map sections
``define_species_prefix``, ``SPECIES_``, expected prefix for species macro names
+ ``define_species_empty``, ``NONE``, macro name after prefix for the default species
``regex_behaviors``, ``\bMB_``, regex to find metatile behavior macro names
``regex_obj_event_gfx``, ``\bOBJ_EVENT_GFX_``, regex to find Object Event graphics ID macro names
``regex_items``, ``\bITEM_(?!(B_)?USE_)``, regex to find item macro names
diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui
index 39f59836..dfea9752 100644
--- a/forms/mainwindow.ui
+++ b/forms/mainwindow.ui
@@ -1740,7 +1740,7 @@
0
0
100
- 30
+ 16
@@ -1834,7 +1834,7 @@
0
0
100
- 30
+ 16
@@ -1928,7 +1928,7 @@
0
0
100
- 30
+ 16
@@ -2028,7 +2028,7 @@
0
0
100
- 30
+ 16
@@ -2122,7 +2122,7 @@
0
0
100
- 30
+ 16
@@ -2700,8 +2700,8 @@
0
0
- 100
- 30
+ 204
+ 16
@@ -2845,6 +2845,17 @@
+ -
+
+
+ ...
+
+
+
+ :/icons/magnifier.ico:/icons/magnifier.ico
+
+
+
diff --git a/forms/wildmonsearch.ui b/forms/wildmonsearch.ui
new file mode 100644
index 00000000..117f7242
--- /dev/null
+++ b/forms/wildmonsearch.ui
@@ -0,0 +1,94 @@
+
+
+ WildMonSearch
+
+
+
+ 0
+ 0
+ 547
+ 329
+
+
+
+ Wild Pokémon Search
+
+
+ -
+
+
+ QFrame::Shape::NoFrame
+
+
+ QFrame::Shadow::Raised
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ :/images/pokemon_icon_placeholder.png
+
+
+ Qt::AlignmentFlag::AlignCenter
+
+
+
+ -
+
+
+ true
+
+
+ QComboBox::InsertPolicy::NoInsert
+
+
+
+
+
+
+ -
+
+
+ 4
+
+
+
+
+
+
+
+
+
+
+
+ NoScrollComboBox
+ QComboBox
+
+
+
+
+
+
+
+
diff --git a/include/config.h b/include/config.h
index 926e93c6..502ee1ec 100644
--- a/include/config.h
+++ b/include/config.h
@@ -221,6 +221,7 @@ enum ProjectIdentifier {
define_map_section_prefix,
define_map_section_empty,
define_species_prefix,
+ define_species_empty,
regex_behaviors,
regex_obj_event_gfx,
regex_items,
diff --git a/include/core/wildmoninfo.h b/include/core/wildmoninfo.h
index d0aa29dc..3c94fb17 100644
--- a/include/core/wildmoninfo.h
+++ b/include/core/wildmoninfo.h
@@ -5,10 +5,14 @@
#include
#include "orderedmap.h"
-struct WildPokemon {
- int minLevel = 5;
- int maxLevel = 5;
- QString species = "SPECIES_NONE"; // TODO: Use define_species_prefix
+class WildPokemon {
+public:
+ WildPokemon();
+ WildPokemon(int minLevel, int maxLevel, const QString &species);
+
+ int minLevel;
+ int maxLevel;
+ QString species;
};
struct WildMonInfo {
@@ -30,7 +34,8 @@ struct EncounterField {
typedef QVector EncounterFields;
void setDefaultEncounterRate(QString fieldName, int rate);
-WildMonInfo getDefaultMonInfo(EncounterField field);
+WildMonInfo getDefaultMonInfo(const EncounterField &field);
+QVector getWildEncounterPercentages(const EncounterField &field);
void combineEncounters(WildMonInfo &to, WildMonInfo from);
#endif // GUARD_WILDMONINFO_H
diff --git a/include/mainwindow.h b/include/mainwindow.h
index 1ee612bc..cf37a298 100644
--- a/include/mainwindow.h
+++ b/include/mainwindow.h
@@ -28,6 +28,7 @@
#include "gridsettings.h"
#include "customscriptseditor.h"
#include "wildmonchart.h"
+#include "wildmonsearch.h"
#include "updatepromoter.h"
#include "aboutporymap.h"
#include "mapheaderform.h"
@@ -282,6 +283,7 @@ private slots:
void on_pushButton_DeleteWildMonGroup_clicked();
void on_pushButton_SummaryChart_clicked();
void on_pushButton_ConfigureEncountersJSON_clicked();
+ void on_toolButton_WildMonSearch_clicked();
void on_pushButton_CreatePrefab_clicked();
void on_spinBox_SelectedElevation_valueChanged(int elevation);
void on_spinBox_SelectedCollision_valueChanged(int collision);
@@ -294,6 +296,7 @@ private slots:
void reloadScriptEngine();
void on_actionShow_Grid_triggered();
void on_actionGrid_Settings_triggered();
+ void openWildMonTable(const QString &mapName, const QString &groupName, const QString &fieldName);
public:
Ui::MainWindow *ui;
@@ -321,6 +324,7 @@ private:
QPointer networkAccessManager = nullptr;
QPointer aboutWindow = nullptr;
QPointer wildMonChart = nullptr;
+ QPointer wildMonSearch = nullptr;
QAction *undoAction = nullptr;
QAction *redoAction = nullptr;
diff --git a/include/project.h b/include/project.h
index 9fb4e2ed..aff38f03 100644
--- a/include/project.h
+++ b/include/project.h
@@ -152,6 +152,7 @@ public:
QVector extraEncounterGroups;
bool readSpeciesIconPaths();
+ QPixmap getSpeciesIcon(const QString &species) const;
QMap speciesToIconPath;
void addNewMapsec(const QString &idName);
@@ -238,6 +239,7 @@ public:
static QString getEmptyMapDefineName();
static QString getDynamicMapDefineName();
static QString getDynamicMapName();
+ static QString getEmptySpeciesName();
static int getNumTilesPrimary();
static int getNumTilesTotal();
static int getNumMetatilesPrimary();
diff --git a/include/ui/encountertablemodel.h b/include/ui/encountertablemodel.h
index b2a39ad8..6fe54a91 100644
--- a/include/ui/encountertablemodel.h
+++ b/include/ui/encountertablemodel.h
@@ -14,7 +14,7 @@ class EncounterTableModel : public QAbstractTableModel {
Q_OBJECT
public:
- EncounterTableModel(WildMonInfo monInfo, EncounterFields allFields, int fieldIndex, QObject *parent = nullptr);
+ EncounterTableModel(const WildMonInfo &monInfo, const EncounterField &field, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
@@ -28,22 +28,17 @@ public:
Slot, Group, Species, MinLevel, MaxLevel, EncounterChance, SlotRatio, EncounterRate, Count
};
- WildMonInfo encounterData() const { return this->monInfo; }
- EncounterField encounterField() const { return this->encounterFields.at(this->fieldIndex); }
- QList percentages() const { return this->slotPercentages; }
- void resize(int rows, int cols);
+ WildMonInfo encounterData() const { return m_monInfo; }
+ EncounterField encounterField() const { return m_encounterField; }
+ QList percentages() const { return m_slotPercentages; }
private:
- WildMonInfo monInfo;
- EncounterFields encounterFields;
- int fieldIndex;
-
- int numRows = 0;
- int numCols = 0;
-
- QVector slotRatios;
- QList groupNames;
- QList slotPercentages;
+ int m_numRows = 0;
+ int m_numCols = 0;
+ WildMonInfo m_monInfo;
+ EncounterField m_encounterField;
+ QMap m_groupNames;
+ QList m_slotPercentages;
signals:
void edited();
diff --git a/include/ui/montabwidget.h b/include/ui/montabwidget.h
index 6d916068..8292d28e 100644
--- a/include/ui/montabwidget.h
+++ b/include/ui/montabwidget.h
@@ -27,6 +27,8 @@ public:
void copy(int index);
void paste(int index);
+ void setCurrentField(const QString &fieldName);
+
public slots:
void setTabActive(int index, bool active = true);
void deactivateTab(int tabIndex);
@@ -38,6 +40,7 @@ private:
QVector activeTabs;
QVector addDeleteTabButtons;
QVector copyTabButtons;
+ QMap fieldNameToIndex;
Editor *editor;
};
diff --git a/include/ui/wildmonsearch.h b/include/ui/wildmonsearch.h
new file mode 100644
index 00000000..49e04b57
--- /dev/null
+++ b/include/ui/wildmonsearch.h
@@ -0,0 +1,47 @@
+#ifndef WILDMONSEARCH_H
+#define WILDMONSEARCH_H
+
+#include
+
+class Project;
+
+namespace Ui {
+class WildMonSearch;
+}
+
+class WildMonSearch : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit WildMonSearch(Project *project, QWidget *parent = nullptr);
+ ~WildMonSearch();
+
+ void refresh();
+
+signals:
+ void openWildMonTableRequested(const QString &mapName, const QString &groupName, const QString &fieldName);
+
+private:
+ struct RowData {
+ QString mapName;
+ QString groupName;
+ QString fieldName;
+ QString levelRange;
+ QString chance;
+ };
+
+ Ui::WildMonSearch *ui;
+ Project *const project;
+ QMap> percentageStrings;
+ QMap> resultsCache;
+
+ void addTableEntry(const RowData &rowData);
+ QList search(const QString &species) const;
+ void updatePercentageStrings();
+ void updateResults(const QString &species);
+ void cellDoubleClicked(int row, int column);
+
+};
+
+#endif // WILDMONSEARCH_H
diff --git a/porymap.pro b/porymap.pro
index 8987b5cc..b6758c2e 100644
--- a/porymap.pro
+++ b/porymap.pro
@@ -139,7 +139,8 @@ SOURCES += src/core/advancemapparser.cpp \
src/log.cpp \
src/ui/uintspinbox.cpp \
src/ui/updatepromoter.cpp \
- src/ui/wildmonchart.cpp
+ src/ui/wildmonchart.cpp \
+ src/ui/wildmonsearch.cpp
HEADERS += include/core/advancemapparser.h \
include/core/block.h \
@@ -251,7 +252,8 @@ HEADERS += include/core/advancemapparser.h \
include/log.h \
include/ui/uintspinbox.h \
include/ui/updatepromoter.h \
- include/ui/wildmonchart.h
+ include/ui/wildmonchart.h \
+ include/ui/wildmonsearch.h
FORMS += forms/mainwindow.ui \
forms/colorinputwidget.ui \
@@ -281,7 +283,8 @@ FORMS += forms/mainwindow.ui \
forms/customscriptseditor.ui \
forms/customscriptslistitem.ui \
forms/updatepromoter.ui \
- forms/wildmonchart.ui
+ forms/wildmonchart.ui \
+ forms/wildmonsearch.ui
RESOURCES += \
resources/images.qrc \
diff --git a/resources/icons/magnifier.ico b/resources/icons/magnifier.ico
new file mode 100755
index 00000000..9cbc3618
Binary files /dev/null and b/resources/icons/magnifier.ico differ
diff --git a/resources/images.qrc b/resources/images.qrc
index 888c5d9d..fc1ab757 100644
--- a/resources/images.qrc
+++ b/resources/images.qrc
@@ -25,6 +25,7 @@
icons/help.ico
icons/link_broken.ico
icons/link.ico
+ icons/magnifier.ico
icons/map_edited.ico
icons/map_opened.ico
icons/map.ico
diff --git a/src/config.cpp b/src/config.cpp
index ce78d8b9..2356f7a9 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -113,6 +113,7 @@ const QMap> ProjectConfig::defaultIde
{ProjectIdentifier::define_map_section_prefix, {"define_map_section_prefix", "MAPSEC_"}},
{ProjectIdentifier::define_map_section_empty, {"define_map_section_empty", "NONE"}},
{ProjectIdentifier::define_species_prefix, {"define_species_prefix", "SPECIES_"}},
+ {ProjectIdentifier::define_species_empty, {"define_species_empty", "NONE"}},
// Regex
{ProjectIdentifier::regex_behaviors, {"regex_behaviors", "\\bMB_"}},
{ProjectIdentifier::regex_obj_event_gfx, {"regex_obj_event_gfx", "\\bOBJ_EVENT_GFX_"}},
diff --git a/src/core/wildmoninfo.cpp b/src/core/wildmoninfo.cpp
index ecad908f..5222b9e6 100644
--- a/src/core/wildmoninfo.cpp
+++ b/src/core/wildmoninfo.cpp
@@ -1,12 +1,22 @@
#include "wildmoninfo.h"
#include "montabwidget.h"
+#include "project.h"
+
+WildPokemon::WildPokemon(int minLevel, int maxLevel, const QString &species)
+ : minLevel(minLevel),
+ maxLevel(maxLevel),
+ species(species)
+{}
+
+WildPokemon::WildPokemon() : WildPokemon(5, 5, Project::getEmptySpeciesName())
+{}
QMap defaultEncounterRates;
void setDefaultEncounterRate(QString fieldName, int rate) {
defaultEncounterRates[fieldName] = rate;
}
-WildMonInfo getDefaultMonInfo(EncounterField field) {
+WildMonInfo getDefaultMonInfo(const EncounterField &field) {
WildMonInfo newInfo;
newInfo.active = true;
newInfo.encounterRate = defaultEncounterRates.value(field.name, 1);
@@ -18,6 +28,38 @@ WildMonInfo getDefaultMonInfo(EncounterField field) {
return newInfo;
}
+QVector getWildEncounterPercentages(const EncounterField &field) {
+ QVector percentages(field.encounterRates.size(), 0);
+
+ if (!field.groups.empty()) {
+ // This encounter field is broken up into groups (e.g. for fishing rod types).
+ // Each group's percentages will be relative to the group total, not the overall total.
+ for (auto groupKeyPair : field.groups) {
+ int groupTotal = 0;
+ for (int slot : groupKeyPair.second) {
+ groupTotal += field.encounterRates.value(slot, 0);
+ }
+ if (groupTotal != 0) {
+ for (int slot : groupKeyPair.second) {
+ percentages[slot] = static_cast(field.encounterRates.value(slot, 0)) / static_cast(groupTotal);
+ }
+ }
+ }
+ } else {
+ // This encounter field has a single group, percentages are relative to the overall total.
+ int groupTotal = 0;
+ for (int chance : field.encounterRates) {
+ groupTotal += chance;
+ }
+ if (groupTotal != 0) {
+ for (int slot = 0; slot < percentages.count(); slot++) {
+ percentages[slot] = static_cast(field.encounterRates.value(slot, 0)) / static_cast(groupTotal);
+ }
+ }
+ }
+ return percentages;
+}
+
void combineEncounters(WildMonInfo &to, WildMonInfo from) {
to.encounterRate = from.encounterRate;
diff --git a/src/editor.cpp b/src/editor.cpp
index c22068d2..0b3350e3 100644
--- a/src/editor.cpp
+++ b/src/editor.cpp
@@ -302,7 +302,7 @@ void Editor::addNewWildMonGroup(QWidget *window) {
form.addRow(new QLabel("Group Base Label:"), lineEdit);
lineEdit->setValidator(new IdentifierValidator(lineEdit));
connect(lineEdit, &QLineEdit::textChanged, [this, &lineEdit, &buttonBox](QString text){
- if (this->project->encounterGroupLabels.contains(text)) {
+ if (!this->project->isIdentifierUnique(text)) {
lineEdit->setStyleSheet("QLineEdit { background-color: rgba(255, 0, 0, 25%) }");
buttonBox.button(QDialogButtonBox::Ok)->setDisabled(true);
} else {
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 16d55c2d..bf9a91e8 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -2537,6 +2537,25 @@ void MainWindow::on_pushButton_SummaryChart_clicked() {
openSubWindow(this->wildMonChart);
}
+void MainWindow::on_toolButton_WildMonSearch_clicked() {
+ if (!this->wildMonSearch) {
+ this->wildMonSearch = new WildMonSearch(this->editor->project, this);
+ connect(this->wildMonSearch, &WildMonSearch::openWildMonTableRequested, this, &MainWindow::openWildMonTable);
+ connect(this->editor, &Editor::wildMonTableEdited, this->wildMonSearch, &WildMonSearch::refresh);
+ }
+ openSubWindow(this->wildMonSearch);
+}
+
+void MainWindow::openWildMonTable(const QString &mapName, const QString &groupName, const QString &fieldName) {
+ if (userSetMap(mapName)) {
+ // Switch to the correct main tab, wild encounter group, and wild encounter type tab.
+ on_mainTabBar_tabBarClicked(MainTab::WildPokemon);
+ ui->comboBox_EncounterGroupLabel->setCurrentText(groupName);
+ QWidget *w = ui->stackedWidget_WildMons->currentWidget();
+ if (w) static_cast(w)->setCurrentField(fieldName);
+ }
+}
+
void MainWindow::on_pushButton_ConfigureEncountersJSON_clicked() {
editor->configureEncounterJSON(this);
}
@@ -2984,33 +3003,21 @@ void MainWindow::clearOverlay() {
// delete is happening too late and some of the pointers haven't been cleared by the time we need them to,
// so we nullify them all here anyway.
bool MainWindow::closeSupplementaryWindows() {
- if (this->tilesetEditor && !this->tilesetEditor->close())
- return false;
- this->tilesetEditor = nullptr;
+ #define SAFE_CLOSE(window) \
+ do { \
+ if ((window) && !(window)->close()) \
+ return false; \
+ window = nullptr; \
+ } while (0);
- if (this->regionMapEditor && !this->regionMapEditor->close())
- return false;
- this->regionMapEditor = nullptr;
-
- if (this->mapImageExporter && !this->mapImageExporter->close())
- return false;
- this->mapImageExporter = nullptr;
-
- if (this->shortcutsEditor && !this->shortcutsEditor->close())
- return false;
- this->shortcutsEditor = nullptr;
-
- if (this->preferenceEditor && !this->preferenceEditor->close())
- return false;
- this->preferenceEditor = nullptr;
-
- if (this->customScriptsEditor && !this->customScriptsEditor->close())
- return false;
- this->customScriptsEditor = nullptr;
-
- if (this->wildMonChart && !this->wildMonChart->close())
- return false;
- this->wildMonChart = nullptr;
+ SAFE_CLOSE(this->tilesetEditor);
+ SAFE_CLOSE(this->regionMapEditor);
+ SAFE_CLOSE(this->mapImageExporter);
+ SAFE_CLOSE(this->shortcutsEditor);
+ SAFE_CLOSE(this->preferenceEditor);
+ SAFE_CLOSE(this->customScriptsEditor);
+ SAFE_CLOSE(this->wildMonChart);
+ SAFE_CLOSE(this->wildMonSearch);
if (this->projectSettingsEditor) this->projectSettingsEditor->closeQuietly();
this->projectSettingsEditor = nullptr;
diff --git a/src/project.cpp b/src/project.cpp
index 3abfcfbf..712ee87f 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -1721,11 +1721,11 @@ bool Project::readWildMonData() {
for (OrderedJson subObjectRef : wildMonObj["wild_encounter_groups"].array_items()) {
OrderedJson::object subObject = subObjectRef.object_items();
if (!subObject["for_maps"].bool_value()) {
- extraEncounterGroups.push_back(subObject);
+ this->extraEncounterGroups.push_back(subObject);
continue;
}
- for (OrderedJson field : subObject["fields"].array_items()) {
+ for (const OrderedJson &field : subObject["fields"].array_items()) {
EncounterField encounterField;
OrderedJson::object fieldObj = field.object_items();
encounterField.name = fieldObj["type"].string_value();
@@ -1744,17 +1744,17 @@ bool Project::readWildMonData() {
}
}
encounterRateFrequencyMaps.insert(encounterField.name, QMap());
- wildMonFields.append(encounterField);
+ this->wildMonFields.append(encounterField);
}
auto encounters = subObject["encounters"].array_items();
- for (auto encounter : encounters) {
+ for (const auto &encounter : encounters) {
OrderedJson::object encounterObj = encounter.object_items();
QString mapConstant = encounterObj["map"].string_value();
WildPokemonHeader header;
- for (EncounterField monField : wildMonFields) {
+ for (const EncounterField &monField : this->wildMonFields) {
QString field = monField.name;
if (!encounterObj[field].is_null()) {
OrderedJson::object encounterFieldObj = encounterObj[field].object_items();
@@ -1776,8 +1776,8 @@ bool Project::readWildMonData() {
}
}
}
- wildMonData[mapConstant].insert({encounterObj["base_label"].string_value(), header});
- encounterGroupLabels.append(encounterObj["base_label"].string_value());
+ this->wildMonData[mapConstant].insert({encounterObj["base_label"].string_value(), header});
+ this->encounterGroupLabels.append(encounterObj["base_label"].string_value());
}
}
@@ -1975,6 +1975,8 @@ bool Project::isIdentifierUnique(const QString &identifier) const {
}
if (identifier == getEmptyMapDefineName())
return false;
+ if (this->encounterGroupLabels.contains(identifier))
+ return false;
return true;
}
@@ -2981,6 +2983,31 @@ bool Project::readSpeciesIconPaths() {
return true;
}
+QPixmap Project::getSpeciesIcon(const QString &species) const {
+ 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);
+ if (path.isEmpty()) {
+ path = this->speciesToIconPath.value(species);
+ } else {
+ path = Project::getExistingFilepath(path);
+ }
+
+ QImage img(path);
+ if (img.isNull()) {
+ // No icon for this species, use placeholder
+ static const QPixmap placeholder = QPixmap(QStringLiteral(":images/pokemon_icon_placeholder.png"));
+ pixmap = placeholder;
+ } else {
+ img.setColor(0, qRgba(0, 0, 0, 0));
+ pixmap = QPixmap::fromImage(img).copy(0, 0, 32, 32);
+ QPixmapCache::insert(species, pixmap);
+ }
+ }
+ return pixmap;
+}
+
int Project::getNumTilesPrimary()
{
return Project::num_tiles_primary;
@@ -3067,19 +3094,21 @@ int Project::getMaxObjectEvents()
}
QString Project::getEmptyMapDefineName() {
- const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix);
- return prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_empty);
+ return projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_map_empty);
}
QString Project::getDynamicMapDefineName() {
- const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix);
- return prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_dynamic);
+ return projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_map_dynamic);
}
QString Project::getDynamicMapName() {
return projectConfig.getIdentifier(ProjectIdentifier::symbol_dynamic_map_name);
}
+QString Project::getEmptySpeciesName() {
+ return projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_species_empty);
+}
+
// If the provided filepath is an absolute path to an existing file, return filepath.
// If not, and the provided filepath is a relative path from the project dir to an existing file, return the relative path.
// Otherwise return empty string.
diff --git a/src/ui/encountertabledelegates.cpp b/src/ui/encountertabledelegates.cpp
index 230abc7b..4ffe9e0d 100644
--- a/src/ui/encountertabledelegates.cpp
+++ b/src/ui/encountertabledelegates.cpp
@@ -12,30 +12,8 @@ SpeciesComboDelegate::SpeciesComboDelegate(Project *project, QObject *parent) :
}
void SpeciesComboDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
- QString species = index.data(Qt::DisplayRole).toString();
-
- QPixmap pm;
- if (!QPixmapCache::find(species, &pm)) {
- // Prefer path from config. If not present, use the path parsed from project files
- QString path = projectConfig.getPokemonIconPath(species);
- if (path.isEmpty()) {
- path = this->project->speciesToIconPath.value(species);
- } else {
- path = Project::getExistingFilepath(path);
- }
-
- QImage img(path);
- if (img.isNull()) {
- // No icon for this species, use placeholder
- pm = QPixmap(":images/pokemon_icon_placeholder.png");
- } else {
- img.setColor(0, qRgba(0, 0, 0, 0));
- pm = QPixmap::fromImage(img);
- }
- QPixmapCache::insert(species, pm);
- }
- QPixmap monIcon = pm.copy(0, 0, 32, 32);
-
+ const QString species = index.data(Qt::DisplayRole).toString();
+ const QPixmap monIcon = this->project->getSpeciesIcon(species);
painter->drawText(QRect(option.rect.topLeft() + QPoint(36, 0), option.rect.bottomRight()), Qt::AlignLeft | Qt::AlignVCenter, species);
painter->drawPixmap(QRect(option.rect.topLeft(), QSize(32, 32)), monIcon, monIcon.rect());
}
diff --git a/src/ui/encountertablemodel.cpp b/src/ui/encountertablemodel.cpp
index f7e73996..d3a6f2d3 100644
--- a/src/ui/encountertablemodel.cpp
+++ b/src/ui/encountertablemodel.cpp
@@ -3,52 +3,27 @@
-EncounterTableModel::EncounterTableModel(WildMonInfo info, EncounterFields fields, int index, QObject *parent) : QAbstractTableModel(parent) {
- this->fieldIndex = index;
- this->encounterFields = fields;
- this->monInfo = info;
-
- this->resize(this->monInfo.wildPokemon.size(), ColumnType::Count);
-
- for (int r = 0; r < this->numRows; r++) {
- this->groupNames.append(QString());
- this->slotPercentages.append(0.0);
- this->slotRatios.append(fields[fieldIndex].encounterRates.value(r, 0));
- }
-
- if (!this->encounterFields[this->fieldIndex].groups.empty()) {
- for (auto groupKeyPair : fields[fieldIndex].groups) {
- int groupTotal = 0;
- for (int i : groupKeyPair.second) {
- this->groupNames[i] = groupKeyPair.first;
- groupTotal += this->slotRatios[i];
- }
- for (int i : groupKeyPair.second) {
- this->slotPercentages[i] = static_cast(this->slotRatios[i]) / static_cast(groupTotal);
- }
- }
- } else {
- int groupTotal = 0;
- for (int chance : this->encounterFields[this->fieldIndex].encounterRates) {
- groupTotal += chance;
- }
- for (int i = 0; i < this->slotPercentages.count(); i++) {
- this->slotPercentages[i] = static_cast(this->slotRatios[i]) / static_cast(groupTotal);
+EncounterTableModel::EncounterTableModel(const WildMonInfo &info, const EncounterField &field, QObject *parent)
+ : QAbstractTableModel(parent),
+ m_numRows(info.wildPokemon.size()),
+ m_numCols(ColumnType::Count),
+ m_monInfo(info),
+ m_encounterField(field),
+ m_slotPercentages(getWildEncounterPercentages(field))
+{
+ for (const auto &groupKeyPair : m_encounterField.groups) {
+ for (const auto &slot : groupKeyPair.second) {
+ m_groupNames.insert(slot, groupKeyPair.first);
}
}
}
-void EncounterTableModel::resize(int rows, int cols) {
- this->numRows = rows;
- this->numCols = cols;
-}
-
int EncounterTableModel::rowCount(const QModelIndex &) const {
- return this->numRows;
+ return m_numRows;
}
int EncounterTableModel::columnCount(const QModelIndex &) const {
- return this->numCols;
+ return m_numCols;
}
QVariant EncounterTableModel::data(const QModelIndex &index, int role) const {
@@ -61,26 +36,26 @@ QVariant EncounterTableModel::data(const QModelIndex &index, int role) const {
return row;
case ColumnType::Group:
- return this->groupNames[row];
+ return m_groupNames.value(row);
case ColumnType::Species:
- return this->monInfo.wildPokemon[row].species;
+ return m_monInfo.wildPokemon.value(row).species;
case ColumnType::MinLevel:
- return this->monInfo.wildPokemon[row].minLevel;
+ return m_monInfo.wildPokemon.value(row).minLevel;
case ColumnType::MaxLevel:
- return this->monInfo.wildPokemon[row].maxLevel;
+ return m_monInfo.wildPokemon.value(row).maxLevel;
case ColumnType::EncounterChance:
- return QString::number(this->slotPercentages[row] * 100.0, 'f', 2) + "%";
+ return QString::number(m_slotPercentages.value(row, 0) * 100, 'f', 2) + "%";
case ColumnType::SlotRatio:
- return this->slotRatios[row];
+ return m_encounterField.encounterRates.value(row);
case ColumnType::EncounterRate:
if (row == 0) {
- return this->monInfo.encounterRate;
+ return m_monInfo.encounterRate;
} else {
return QVariant();
}
@@ -92,17 +67,17 @@ QVariant EncounterTableModel::data(const QModelIndex &index, int role) const {
else if (role == Qt::EditRole) {
switch (col) {
case ColumnType::Species:
- return this->monInfo.wildPokemon[row].species;
+ return m_monInfo.wildPokemon.value(row).species;
case ColumnType::MinLevel:
- return this->monInfo.wildPokemon[row].minLevel;
+ return m_monInfo.wildPokemon.value(row).minLevel;
case ColumnType::MaxLevel:
- return this->monInfo.wildPokemon[row].maxLevel;
+ return m_monInfo.wildPokemon.value(row).maxLevel;
case ColumnType::EncounterRate:
if (row == 0) {
- return this->monInfo.encounterRate;
+ return m_monInfo.encounterRate;
} else {
return QVariant();
}
@@ -149,12 +124,13 @@ bool EncounterTableModel::setData(const QModelIndex &index, const QVariant &valu
int row = index.row();
int col = index.column();
+ auto wildMon = &m_monInfo.wildPokemon[row];
switch (col) {
case ColumnType::Species: {
QString species = value.toString();
- if (this->monInfo.wildPokemon[row].species != species) {
- this->monInfo.wildPokemon[row].species = species;
+ if (wildMon->species != species) {
+ wildMon->species = species;
emit edited();
}
break;
@@ -162,9 +138,9 @@ bool EncounterTableModel::setData(const QModelIndex &index, const QVariant &valu
case ColumnType::MinLevel: {
int minLevel = value.toInt();
- if (this->monInfo.wildPokemon[row].minLevel != minLevel) {
- this->monInfo.wildPokemon[row].minLevel = minLevel;
- this->monInfo.wildPokemon[row].maxLevel = qMax(minLevel, this->monInfo.wildPokemon[row].maxLevel);
+ if (wildMon->minLevel != minLevel) {
+ wildMon->minLevel = minLevel;
+ wildMon->maxLevel = qMax(minLevel, wildMon->maxLevel);
emit edited();
}
break;
@@ -172,9 +148,9 @@ bool EncounterTableModel::setData(const QModelIndex &index, const QVariant &valu
case ColumnType::MaxLevel: {
int maxLevel = value.toInt();
- if (this->monInfo.wildPokemon[row].maxLevel != maxLevel) {
- this->monInfo.wildPokemon[row].maxLevel = maxLevel;
- this->monInfo.wildPokemon[row].minLevel = qMin(maxLevel, this->monInfo.wildPokemon[row].minLevel);
+ if (wildMon->maxLevel != maxLevel) {
+ wildMon->maxLevel = maxLevel;
+ wildMon->minLevel = qMin(maxLevel, wildMon->minLevel);
emit edited();
}
break;
@@ -182,8 +158,8 @@ bool EncounterTableModel::setData(const QModelIndex &index, const QVariant &valu
case ColumnType::EncounterRate: {
int encounterRate = value.toInt();
- if (this->monInfo.encounterRate != encounterRate) {
- this->monInfo.encounterRate = encounterRate;
+ if (m_monInfo.encounterRate != encounterRate) {
+ m_monInfo.encounterRate = encounterRate;
emit edited();
}
break;
diff --git a/src/ui/montabwidget.cpp b/src/ui/montabwidget.cpp
index 7b196c75..3a04c1b7 100644
--- a/src/ui/montabwidget.cpp
+++ b/src/ui/montabwidget.cpp
@@ -29,6 +29,8 @@ void MonTabWidget::populate() {
copyTabButtons.resize(fields.size());
copyTabButtons.fill(nullptr);
+ this->fieldNameToIndex.clear();
+
int index = 0;
for (EncounterField field : fields) {
QTableView *table = new QTableView(this);
@@ -45,7 +47,7 @@ void MonTabWidget::populate() {
connect(buttonCopy, &QPushButton::clicked, [=]() {actionCopyTab(index); });
copyTabButtons[index] = buttonCopy;
this->tabBar()->setTabButton(index, QTabBar::LeftSide, buttonCopy);
-
+ this->fieldNameToIndex.insert(field.name, index);
index++;
}
}
@@ -111,7 +113,7 @@ void MonTabWidget::deactivateTab(int tabIndex) {
EncounterTableModel *oldModel = static_cast(speciesTable->model());
WildMonInfo monInfo = oldModel->encounterData();
monInfo.active = false;
- EncounterTableModel *newModel = new EncounterTableModel(monInfo, editor->project->wildMonFields, tabIndex, this);
+ EncounterTableModel *newModel = new EncounterTableModel(monInfo, editor->project->wildMonFields[tabIndex], this);
speciesTable->setModel(newModel);
setTabActive(tabIndex, false);
@@ -120,7 +122,7 @@ void MonTabWidget::deactivateTab(int tabIndex) {
void MonTabWidget::populateTab(int tabIndex, WildMonInfo monInfo) {
QTableView *speciesTable = tableAt(tabIndex);
- EncounterTableModel *model = new EncounterTableModel(monInfo, editor->project->wildMonFields, tabIndex, this);
+ EncounterTableModel *model = new EncounterTableModel(monInfo, editor->project->wildMonFields[tabIndex], this);
connect(model, &EncounterTableModel::edited, editor, &Editor::saveEncounterTabData);
connect(model, &EncounterTableModel::edited, editor, &Editor::wildMonTableEdited);
speciesTable->setModel(model);
@@ -167,3 +169,8 @@ void MonTabWidget::setTabActive(int index, bool active) {
this->copyTabButtons[index]->show();
}
}
+
+void MonTabWidget::setCurrentField(const QString &fieldName) {
+ int index = this->fieldNameToIndex.value(fieldName, -1);
+ if (index >= 0) setCurrentIndex(index);
+}
diff --git a/src/ui/wildmonsearch.cpp b/src/ui/wildmonsearch.cpp
new file mode 100644
index 00000000..684d2947
--- /dev/null
+++ b/src/ui/wildmonsearch.cpp
@@ -0,0 +1,158 @@
+#include "wildmonsearch.h"
+#include "ui_wildmonsearch.h"
+#include "project.h"
+
+enum ResultsColumn {
+ Group,
+ Field,
+ Level,
+ Chance,
+};
+
+enum ResultsDataRole {
+ MapName = Qt::UserRole,
+};
+
+WildMonSearch::WildMonSearch(Project *project, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::WildMonSearch),
+ project(project)
+{
+ setAttribute(Qt::WA_DeleteOnClose);
+ ui->setupUi(this);
+
+ // Set up species combo box
+ ui->comboBox_Search->addItems(project->speciesToIconPath.keys());
+ ui->comboBox_Search->setCurrentText(QString());
+ ui->comboBox_Search->lineEdit()->setPlaceholderText(Project::getEmptySpeciesName());
+ connect(ui->comboBox_Search, &QComboBox::currentTextChanged, this, &WildMonSearch::updateResults);
+
+ // Set up table header
+ static const QStringList labels = {"Group", "Field", "Level", "Chance"};
+ ui->table_Results->setHorizontalHeaderLabels(labels);
+ ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::Group, QHeaderView::Stretch);
+ ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::Field, QHeaderView::ResizeToContents);
+ ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::Level, QHeaderView::ResizeToContents);
+ ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::Chance, QHeaderView::ResizeToContents);
+
+ // Table is read-only
+ ui->table_Results->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ ui->table_Results->setSelectionMode(QAbstractItemView::NoSelection);
+
+ connect(ui->table_Results, &QTableWidget::cellDoubleClicked, this, &WildMonSearch::cellDoubleClicked);
+
+ refresh();
+}
+
+WildMonSearch::~WildMonSearch() {
+ delete ui;
+}
+
+void WildMonSearch::refresh() {
+ this->resultsCache.clear();
+ updatePercentageStrings();
+ updateResults(ui->comboBox_Search->currentText());
+}
+
+void WildMonSearch::addTableEntry(const RowData &rowData) {
+ int row = ui->table_Results->rowCount();
+ ui->table_Results->insertRow(row);
+
+ auto groupItem = new QTableWidgetItem(rowData.groupName);
+ groupItem->setData(ResultsDataRole::MapName, rowData.mapName);
+
+ ui->table_Results->setItem(row, ResultsColumn::Group, groupItem);
+ ui->table_Results->setItem(row, ResultsColumn::Field, new QTableWidgetItem(rowData.fieldName));
+ ui->table_Results->setItem(row, ResultsColumn::Level, new QTableWidgetItem(rowData.levelRange));
+ ui->table_Results->setItem(row, ResultsColumn::Chance, new QTableWidgetItem(rowData.chance));
+}
+
+QList WildMonSearch::search(const QString &species) const {
+ QList results;
+ for (const auto &keyPair : this->project->wildMonData) {
+ QString mapConstant = keyPair.first;
+ for (const auto &grouplLabelPair : this->project->wildMonData[mapConstant]) {
+ QString groupName = grouplLabelPair.first;
+ WildPokemonHeader encounterHeader = this->project->wildMonData[mapConstant][groupName];
+ for (const auto &fieldNamePair : encounterHeader.wildMons) {
+ QString fieldName = fieldNamePair.first;
+ WildMonInfo monInfo = encounterHeader.wildMons[fieldName];
+ for (int slot = 0; slot < monInfo.wildPokemon.length(); slot++) {
+ const WildPokemon wildMon = monInfo.wildPokemon.at(slot);
+ if (wildMon.species == species) {
+ RowData rowData;
+ rowData.groupName = groupName;
+ rowData.fieldName = fieldName;
+ rowData.mapName = this->project->mapConstantsToMapNames.value(mapConstant, mapConstant);
+
+ // If min and max level are the same display a single number, otherwise display a level range.
+ rowData.levelRange = (wildMon.minLevel == wildMon.maxLevel) ? QString::number(wildMon.minLevel)
+ : QString("%1-%2").arg(wildMon.minLevel).arg(wildMon.maxLevel);
+ rowData.chance = this->percentageStrings[fieldName][slot];
+ results.append(rowData);
+ }
+ }
+ }
+ }
+ }
+ return results;
+}
+
+void WildMonSearch::updatePercentageStrings() {
+ this->percentageStrings.clear();
+ for (const EncounterField &monField : this->project->wildMonFields) {
+ QMap slotToPercentString;
+ auto percentages = getWildEncounterPercentages(monField);
+ for (int i = 0; i < percentages.length(); i++) {
+ slotToPercentString.insert(i, QString::number(percentages.at(i) * 100, 'f', 2) + "%");
+ }
+ this->percentageStrings[monField.name] = slotToPercentString;
+ }
+}
+
+void WildMonSearch::updateResults(const QString &species) {
+ ui->speciesIcon->setPixmap(this->project->getSpeciesIcon(species));
+
+ ui->table_Results->clearContents();
+ ui->table_Results->setRowCount(0);
+
+ // Note: Per Qt docs, sorting should be disabled while populating the table to avoid it interfering with insertion order.
+ ui->table_Results->setSortingEnabled(false);
+
+ if (ui->comboBox_Search->findText(species) < 0)
+ return; // Not a species name, no need to search wild encounter data.
+
+ const QList results = this->resultsCache.value(species, search(species));
+ if (results.isEmpty()) {
+ static const RowData noResults = {
+ .groupName = QStringLiteral("Species not found."),
+ .fieldName = QStringLiteral("--"),
+ .levelRange = QStringLiteral("--"),
+ .chance = QStringLiteral("--"),
+ };
+ addTableEntry(noResults);
+ } else {
+ for (const auto &entry : results) {
+ addTableEntry(entry);
+ }
+ }
+
+ // TODO: This does a lexical sort... We might need custom item delegates to get proper numerical sorting in this table.
+ ui->table_Results->setSortingEnabled(true);
+
+ this->resultsCache.insert(species, results);
+}
+
+// Double-clicking row data opens the corresponding map/table on the Wild Pokémon tab.
+void WildMonSearch::cellDoubleClicked(int row, int) {
+ auto groupItem = ui->table_Results->item(row, ResultsColumn::Group);
+ auto fieldItem = ui->table_Results->item(row, ResultsColumn::Field);
+ if (!groupItem || !fieldItem)
+ return;
+
+ const QString mapName = groupItem->data(ResultsDataRole::MapName).toString();
+ if (mapName.isEmpty())
+ return;
+
+ emit openWildMonTableRequested(mapName, groupItem->text(), fieldItem->text());
+}