Add search feature to wild pokemon tab

This commit is contained in:
GriffinR 2025-01-25 21:50:45 -05:00
parent de78a1172d
commit 3bbb81e436
22 changed files with 520 additions and 155 deletions

View File

@ -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

View File

@ -1740,7 +1740,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>30</height>
<height>16</height>
</rect>
</property>
<property name="sizePolicy">
@ -1834,7 +1834,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>30</height>
<height>16</height>
</rect>
</property>
<property name="sizePolicy">
@ -1928,7 +1928,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>30</height>
<height>16</height>
</rect>
</property>
<property name="sizePolicy">
@ -2028,7 +2028,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>30</height>
<height>16</height>
</rect>
</property>
<property name="sizePolicy">
@ -2122,7 +2122,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>30</height>
<height>16</height>
</rect>
</property>
<property name="sizePolicy">
@ -2700,8 +2700,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>100</width>
<height>30</height>
<width>204</width>
<height>16</height>
</rect>
</property>
<layout class="QVBoxLayout" name="layout_ConnectionsList">
@ -2845,6 +2845,17 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolButton_WildMonSearch">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../resources/images.qrc">
<normaloff>:/icons/magnifier.ico</normaloff>:/icons/magnifier.ico</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>

94
forms/wildmonsearch.ui Normal file
View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WildMonSearch</class>
<widget class="QDialog" name="WildMonSearch">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>547</width>
<height>329</height>
</rect>
</property>
<property name="windowTitle">
<string>Wild Pokémon Search</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="searchBar">
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="speciesIcon">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../resources/images.qrc">:/images/pokemon_icon_placeholder.png</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="NoScrollComboBox" name="comboBox_Search">
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTableWidget" name="table_Results">
<property name="columnCount">
<number>4</number>
</property>
<column/>
<column/>
<column/>
<column/>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>NoScrollComboBox</class>
<extends>QComboBox</extends>
<header>noscrollcombobox.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../resources/images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -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,

View File

@ -5,10 +5,14 @@
#include <QtWidgets>
#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<EncounterField> EncounterFields;
void setDefaultEncounterRate(QString fieldName, int rate);
WildMonInfo getDefaultMonInfo(EncounterField field);
WildMonInfo getDefaultMonInfo(const EncounterField &field);
QVector<double> getWildEncounterPercentages(const EncounterField &field);
void combineEncounters(WildMonInfo &to, WildMonInfo from);
#endif // GUARD_WILDMONINFO_H

View File

@ -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> networkAccessManager = nullptr;
QPointer<AboutPorymap> aboutWindow = nullptr;
QPointer<WildMonChart> wildMonChart = nullptr;
QPointer<WildMonSearch> wildMonSearch = nullptr;
QAction *undoAction = nullptr;
QAction *redoAction = nullptr;

View File

@ -152,6 +152,7 @@ public:
QVector<poryjson::Json::object> extraEncounterGroups;
bool readSpeciesIconPaths();
QPixmap getSpeciesIcon(const QString &species) const;
QMap<QString, QString> 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();

View File

@ -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<double> percentages() const { return this->slotPercentages; }
void resize(int rows, int cols);
WildMonInfo encounterData() const { return m_monInfo; }
EncounterField encounterField() const { return m_encounterField; }
QList<double> percentages() const { return m_slotPercentages; }
private:
WildMonInfo monInfo;
EncounterFields encounterFields;
int fieldIndex;
int numRows = 0;
int numCols = 0;
QVector<int> slotRatios;
QList<QString> groupNames;
QList<double> slotPercentages;
int m_numRows = 0;
int m_numCols = 0;
WildMonInfo m_monInfo;
EncounterField m_encounterField;
QMap<int,QString> m_groupNames;
QList<double> m_slotPercentages;
signals:
void edited();

View File

@ -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<bool> activeTabs;
QVector<QPushButton *> addDeleteTabButtons;
QVector<QPushButton *> copyTabButtons;
QMap<QString,int> fieldNameToIndex;
Editor *editor;
};

View File

@ -0,0 +1,47 @@
#ifndef WILDMONSEARCH_H
#define WILDMONSEARCH_H
#include <QDialog>
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<QString,QMap<int,QString>> percentageStrings;
QMap<QString,QList<RowData>> resultsCache;
void addTableEntry(const RowData &rowData);
QList<RowData> search(const QString &species) const;
void updatePercentageStrings();
void updateResults(const QString &species);
void cellDoubleClicked(int row, int column);
};
#endif // WILDMONSEARCH_H

View File

@ -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 \

BIN
resources/icons/magnifier.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -25,6 +25,7 @@
<file>icons/help.ico</file>
<file>icons/link_broken.ico</file>
<file>icons/link.ico</file>
<file>icons/magnifier.ico</file>
<file>icons/map_edited.ico</file>
<file>icons/map_opened.ico</file>
<file>icons/map.ico</file>

View File

@ -113,6 +113,7 @@ const QMap<ProjectIdentifier, QPair<QString, QString>> 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_"}},

View File

@ -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<QString, int> 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<double> getWildEncounterPercentages(const EncounterField &field) {
QVector<double> 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<double>(field.encounterRates.value(slot, 0)) / static_cast<double>(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<double>(field.encounterRates.value(slot, 0)) / static_cast<double>(groupTotal);
}
}
}
return percentages;
}
void combineEncounters(WildMonInfo &to, WildMonInfo from) {
to.encounterRate = from.encounterRate;

View File

@ -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 {

View File

@ -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<MonTabWidget *>(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;

View File

@ -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<int, int>());
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.

View File

@ -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());
}

View File

@ -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<double>(this->slotRatios[i]) / static_cast<double>(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<double>(this->slotRatios[i]) / static_cast<double>(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;

View File

@ -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<EncounterTableModel *>(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);
}

158
src/ui/wildmonsearch.cpp Normal file
View File

@ -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::RowData> WildMonSearch::search(const QString &species) const {
QList<RowData> 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<int,QString> 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<RowData> 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());
}