Merge branch 'master' into custom-attr
This commit is contained in:
commit
92403b8ab9
32 changed files with 1170 additions and 278 deletions
5
.github/workflows/main.yml
vendored
5
.github/workflows/main.yml
vendored
|
@ -33,7 +33,7 @@ jobs:
|
|||
uses: jurplel/install-qt-action@v2
|
||||
with:
|
||||
version: '5.14.2'
|
||||
modules: 'qtwidgets qtqml'
|
||||
modules: 'qtwidgets qtqml qtcharts'
|
||||
cached: ${{ steps.cache-qt.outputs.cache-hit }}
|
||||
|
||||
- name: Configure
|
||||
|
@ -58,7 +58,8 @@ jobs:
|
|||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: '6.5.*'
|
||||
version: '6.7.*'
|
||||
modules: 'qtcharts'
|
||||
cached: ${{ steps.cache-qt.outputs.cache-hit }}
|
||||
|
||||
- name: Configure
|
||||
|
|
|
@ -10,6 +10,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
|
|||
### Added
|
||||
- Redesigned the Connections tab, adding a number of new features including the option to open or display diving maps and a list UI for easier edit access.
|
||||
- Add a `Close Project` option
|
||||
- Add charts to the `Wild Pokémon` tab that show species and level distributions.
|
||||
- An alert will be displayed when attempting to open a seemingly invalid project.
|
||||
|
||||
### Changed
|
||||
|
@ -17,7 +18,9 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
|
|||
- Changes to the "Mirror to Connecting Maps" setting will now be saved between sessions.
|
||||
- A notice will be displayed when attempting to open the "Dynamic" map, rather than nothing happening.
|
||||
- The base game version is now auto-detected if the project name contains only one of "emerald", "firered/leafgreen", or "ruby/sapphire".
|
||||
- The max encounter rate is now read from the project, rather than assuming the default value from RSE.
|
||||
- It's now possible to cancel quitting if there are unsaved changes in sub-windows.
|
||||
- The triple-layer metatiles setting can now be set automatically using a project constant.
|
||||
|
||||
### Fixed
|
||||
- Fix `Add Region Map...` not updating the region map settings file.
|
||||
|
@ -30,6 +33,8 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
|
|||
- Fix `About porymap` opening a new window each time it's activated.
|
||||
- Fix the `Edit History` window not raising to the front when reactivated.
|
||||
- New maps are now always inserted in map dropdowns at the correct position, rather than at the bottom of the list until the project is reloaded.
|
||||
- Fix invalid species names clearing from wild pokémon data when revisited.
|
||||
- Fix editing wild pokémon data not marking the map as edited.
|
||||
- Fix changes to map connections not marking connected maps as unsaved.
|
||||
- Fix numerous issues related to connecting a map to itself.
|
||||
- Fix incorrect map connections getting selected when opening a map by double-clicking a map connection.
|
||||
|
|
|
@ -67,6 +67,7 @@ The filepath that Porymap expects for each file can be overridden on the ``Files
|
|||
include/fieldmap.h, yes, no, ``constants_fieldmap``, reads tileset related constants
|
||||
src/fieldmap.c, yes, no, ``fieldmap``, reads ``symbol_attribute_table``
|
||||
src/event_object_movement.c, yes, no, ``initial_facing_table``, reads ``symbol_facing_directions``
|
||||
src/wild_encounter.c, yes, no, ``wild_encounter``, reads ``define_max_encounter_rate``
|
||||
src/pokemon_icon.c, yes, no, ``pokemon_icon_table``, reads files in ``symbol_pokemon_icon_table``
|
||||
graphics/pokemon/\*/icon.png, yes, no, ``pokemon_gfx``, to search for Pokémon icons if they aren't found in ``symbol_pokemon_icon_table``
|
||||
|
||||
|
@ -96,11 +97,13 @@ In addition to these files, there are some specific symbol and macro names that
|
|||
``define_obj_event_count``, ``OBJECT_EVENT_TEMPLATES_COUNT``, to limit total Object Events
|
||||
``define_min_level``, ``MIN_LEVEL``, minimum wild encounters level
|
||||
``define_max_level``, ``MAX_LEVEL``, maximum wild encounters level
|
||||
``define_max_encounter_rate``, ``MAX_ENCOUNTER_RATE``, this value / 16 will be the maximum encounter rate on the ``Wild Pokémon`` tab
|
||||
``define_tiles_primary``, ``NUM_TILES_IN_PRIMARY``,
|
||||
``define_tiles_total``, ``NUM_TILES_TOTAL``,
|
||||
``define_metatiles_primary``, ``NUM_METATILES_IN_PRIMARY``, total metatiles are calculated using metatile ID mask
|
||||
``define_pals_primary``, ``NUM_PALS_IN_PRIMARY``,
|
||||
``define_pals_total``, ``NUM_PALS_TOTAL``,
|
||||
``define_tiles_per_metatile``, ``NUM_TILES_PER_METATILE``, to determine if triple-layer metatiles are in use. Values other than 8 or 12 are ignored
|
||||
``define_map_size``, ``MAX_MAP_DATA_SIZE``, to limit map dimensions
|
||||
``define_mask_metatile``, ``MAPGRID_METATILE_ID_MASK``, optionally read to get settings on ``Maps`` tab
|
||||
``define_mask_collision``, ``MAPGRID_COLLISION_MASK``, optionally read to get settings on ``Maps`` tab
|
||||
|
@ -120,6 +123,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_map_section_count``, ``COUNT``, macro name after prefix for total number of region map sections
|
||||
``define_species_prefix``, ``SPECIES_``, expected prefix for species macro names
|
||||
``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
|
||||
|
@ -134,4 +138,3 @@ In addition to these files, there are some specific symbol and macro names that
|
|||
``regex_sign_facing_directions``, ``\bBG_EVENT_PLAYER_FACING_``, regex to find sign facing direction macro names
|
||||
``regex_trainer_types``, ``\bTRAINER_TYPE_``, regex to find trainer type macro names
|
||||
``regex_music``, ``\b(SE|MUS)_``, regex to find music macro names
|
||||
``regex_species``, ``\bSPECIES_``, regex to find species macro names
|
||||
|
|
|
@ -2924,6 +2924,13 @@
|
|||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_SummaryChart">
|
||||
<property name="text">
|
||||
<string>Summary Chart...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_ConfigureEncountersJSON">
|
||||
<property name="text">
|
||||
|
|
245
forms/wildmonchart.ui
Normal file
245
forms/wildmonchart.ui
Normal file
|
@ -0,0 +1,245 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>WildMonChart</class>
|
||||
<widget class="QWidget" name="WildMonChart">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>785</width>
|
||||
<height>492</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Wild Pokémon Summary</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QFrame" name="frame_TopBar">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<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>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_Theme">
|
||||
<property name="text">
|
||||
<string>Theme</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBox_Theme"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="button_Help">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<normaloff>:/icons/help.ico</normaloff>:/icons/help.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tabSpecies">
|
||||
<attribute name="title">
|
||||
<string>Species Distribution</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QChartView" name="chartView_SpeciesDistribution">
|
||||
<property name="renderHints">
|
||||
<set>QPainter::Antialiasing</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabLevels">
|
||||
<attribute name="title">
|
||||
<string>Level Distribution</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<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="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="topMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_Group">
|
||||
<property name="text">
|
||||
<string>Group</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBox_Group">
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
|
||||
</property>
|
||||
<property name="minimumContentsLength">
|
||||
<number>8</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_Species">
|
||||
<property name="title">
|
||||
<string>Individual Mode</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="leftMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_Species">
|
||||
<property name="text">
|
||||
<string>Species</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboBox_Species">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="insertPolicy">
|
||||
<enum>QComboBox::NoInsert</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
|
||||
</property>
|
||||
<property name="minimumContentsLength">
|
||||
<number>12</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QChartView" name="chartView_LevelDistribution">
|
||||
<property name="renderHints">
|
||||
<set>QPainter::Antialiasing</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QChartView</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>QtCharts</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../resources/images.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -78,6 +78,7 @@ public:
|
|||
this->monitorFiles = true;
|
||||
this->tilesetCheckerboardFill = true;
|
||||
this->theme = "default";
|
||||
this->wildMonChartTheme = "";
|
||||
this->textEditorOpenFolder = "";
|
||||
this->textEditorGotoLine = "";
|
||||
this->paletteEditorBitDepth = 24;
|
||||
|
@ -128,6 +129,7 @@ public:
|
|||
bool monitorFiles;
|
||||
bool tilesetCheckerboardFill;
|
||||
QString theme;
|
||||
QString wildMonChartTheme;
|
||||
QString textEditorOpenFolder;
|
||||
QString textEditorGotoLine;
|
||||
int paletteEditorBitDepth;
|
||||
|
@ -137,6 +139,7 @@ public:
|
|||
QDateTime lastUpdateCheckTime;
|
||||
QVersionNumber lastUpdateCheckVersion;
|
||||
QMap<QUrl, QDateTime> rateLimitTimes;
|
||||
QByteArray wildMonChartGeometry;
|
||||
|
||||
protected:
|
||||
virtual QString getConfigFilepath() override;
|
||||
|
@ -192,11 +195,13 @@ enum ProjectIdentifier {
|
|||
define_obj_event_count,
|
||||
define_min_level,
|
||||
define_max_level,
|
||||
define_max_encounter_rate,
|
||||
define_tiles_primary,
|
||||
define_tiles_total,
|
||||
define_metatiles_primary,
|
||||
define_pals_primary,
|
||||
define_pals_total,
|
||||
define_tiles_per_metatile,
|
||||
define_map_size,
|
||||
define_mask_metatile,
|
||||
define_mask_collision,
|
||||
|
@ -216,6 +221,7 @@ enum ProjectIdentifier {
|
|||
define_map_section_prefix,
|
||||
define_map_section_empty,
|
||||
define_map_section_count,
|
||||
define_species_prefix,
|
||||
regex_behaviors,
|
||||
regex_obj_event_gfx,
|
||||
regex_items,
|
||||
|
@ -230,7 +236,6 @@ enum ProjectIdentifier {
|
|||
regex_sign_facing_directions,
|
||||
regex_trainer_types,
|
||||
regex_music,
|
||||
regex_species,
|
||||
};
|
||||
|
||||
enum ProjectFilePath {
|
||||
|
@ -279,6 +284,7 @@ enum ProjectFilePath {
|
|||
global_fieldmap,
|
||||
fieldmap,
|
||||
initial_facing_table,
|
||||
wild_encounter,
|
||||
pokemon_icon_table,
|
||||
pokemon_gfx,
|
||||
};
|
||||
|
|
|
@ -118,6 +118,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
static Event* create(Event::Type type);
|
||||
|
||||
static QMap<Event::Group, const QPixmap*> icons;
|
||||
|
||||
// standard public methods
|
||||
|
|
|
@ -22,9 +22,9 @@ struct WildPokemonHeader {
|
|||
};
|
||||
|
||||
struct EncounterField {
|
||||
QString name;
|
||||
QString name; // Ex: "fishing_mons"
|
||||
QVector<int> encounterRates;
|
||||
tsl::ordered_map<QString, QVector<int>> groups;
|
||||
tsl::ordered_map<QString, QVector<int>> groups; // Ex: "good_rod", {2, 3, 4}
|
||||
};
|
||||
|
||||
typedef QVector<EncounterField> EncounterFields;
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "movablerect.h"
|
||||
#include "cursortilerect.h"
|
||||
#include "mapruler.h"
|
||||
#include "encountertablemodel.h"
|
||||
|
||||
class DraggablePixmapItem;
|
||||
class MetatilesPixmapItem;
|
||||
|
@ -84,6 +85,7 @@ public:
|
|||
void removeSelectedConnection();
|
||||
void addNewWildMonGroup(QWidget *window);
|
||||
void deleteWildMonGroup();
|
||||
EncounterTableModel* getCurrentWildMonTable();
|
||||
void updateDiveMap(QString mapName);
|
||||
void updateEmergeMap(QString mapName);
|
||||
void setSelectedConnection(MapConnection *connection);
|
||||
|
@ -94,8 +96,7 @@ public:
|
|||
Tileset *getCurrentMapPrimaryTileset();
|
||||
|
||||
DraggablePixmapItem *addMapEvent(Event *event);
|
||||
void selectMapEvent(DraggablePixmapItem *object);
|
||||
void selectMapEvent(DraggablePixmapItem *object, bool toggle);
|
||||
void selectMapEvent(DraggablePixmapItem *object, bool toggle = false);
|
||||
DraggablePixmapItem *addNewEvent(Event::Type type);
|
||||
void updateSelectedEvents();
|
||||
void duplicateSelectedEvents();
|
||||
|
@ -223,7 +224,9 @@ private slots:
|
|||
signals:
|
||||
void objectsChanged();
|
||||
void openConnectedMap(MapConnection*);
|
||||
void wildMonDataChanged();
|
||||
void wildMonTableOpened(EncounterTableModel*);
|
||||
void wildMonTableClosed();
|
||||
void wildMonTableEdited();
|
||||
void warpEventDoubleClicked(QString, int, Event::Group);
|
||||
void currentMetatilesSelectionChanged();
|
||||
void mapRulerStatusChanged(const QString &);
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "preferenceeditor.h"
|
||||
#include "projectsettingseditor.h"
|
||||
#include "customscriptseditor.h"
|
||||
#include "wildmonchart.h"
|
||||
#include "updatepromoter.h"
|
||||
#include "aboutporymap.h"
|
||||
|
||||
|
@ -182,7 +183,6 @@ private slots:
|
|||
void onOpenConnectedMap(MapConnection*);
|
||||
void onMapNeedsRedrawing();
|
||||
void onTilesetsSaved(QString, QString);
|
||||
void onWildMonDataChanged();
|
||||
void openNewMapPopupWindow();
|
||||
void onNewMapCreated();
|
||||
void onMapCacheCleared();
|
||||
|
@ -287,6 +287,7 @@ private slots:
|
|||
void on_horizontalSlider_CollisionZoom_valueChanged(int value);
|
||||
void on_pushButton_NewWildMonGroup_clicked();
|
||||
void on_pushButton_DeleteWildMonGroup_clicked();
|
||||
void on_pushButton_SummaryChart_clicked();
|
||||
void on_pushButton_ConfigureEncountersJSON_clicked();
|
||||
void on_pushButton_CreatePrefab_clicked();
|
||||
void on_spinBox_SelectedElevation_valueChanged(int elevation);
|
||||
|
@ -316,6 +317,7 @@ private:
|
|||
QPointer<UpdatePromoter> updatePromoter = nullptr;
|
||||
QPointer<NetworkAccessManager> networkAccessManager = nullptr;
|
||||
QPointer<AboutPorymap> aboutWindow = nullptr;
|
||||
QPointer<WildMonChart> wildMonChart = nullptr;
|
||||
FilterChildrenProxyModel *mapListProxyModel;
|
||||
QStandardItemModel *mapListModel;
|
||||
QList<QStandardItem*> *mapGroupItemsList;
|
||||
|
|
|
@ -39,7 +39,6 @@ public:
|
|||
QMap<QString, int> mapGroups;
|
||||
QList<QStringList> groupedMapNames;
|
||||
QStringList mapNames;
|
||||
QMap<QString, QVariant> miscConstants;
|
||||
QList<HealLocation> healLocations;
|
||||
QMap<QString, int> healLocationNameToValue;
|
||||
QMap<QString, QString> mapConstantsToMapNames;
|
||||
|
@ -79,6 +78,9 @@ public:
|
|||
bool usingAsmTilesets;
|
||||
QString importExportPath;
|
||||
QSet<QString> disabledSettingsNames;
|
||||
int pokemonMinLevel;
|
||||
int pokemonMaxLevel;
|
||||
int maxEncounterRate;
|
||||
bool wildEncountersLoaded;
|
||||
|
||||
void set_root(QString);
|
||||
|
|
|
@ -55,7 +55,8 @@ private slots:
|
|||
void dialogButtonClicked(QAbstractButton *button);
|
||||
void createNewScript();
|
||||
void loadScript();
|
||||
void refreshScripts();
|
||||
bool refreshScripts();
|
||||
void userRefreshScripts();
|
||||
void removeSelectedScripts();
|
||||
void openSelectedScripts();
|
||||
};
|
||||
|
|
|
@ -24,13 +24,7 @@ public:
|
|||
updatePosition();
|
||||
}
|
||||
|
||||
Editor *editor = nullptr;
|
||||
Event *event = nullptr;
|
||||
QGraphicsItemAnimation *pos_anim = nullptr;
|
||||
|
||||
bool active;
|
||||
int last_x;
|
||||
int last_y;
|
||||
|
||||
void updatePosition();
|
||||
void move(int dx, int dy);
|
||||
|
@ -38,6 +32,12 @@ public:
|
|||
void emitPositionChanged();
|
||||
void updatePixmap();
|
||||
|
||||
private:
|
||||
Editor *editor = nullptr;
|
||||
QPoint lastPos;
|
||||
bool active = false;
|
||||
bool releaseSelectionQueued = false;
|
||||
|
||||
signals:
|
||||
void positionChanged(Event *event);
|
||||
void xChanged(int);
|
||||
|
|
|
@ -28,7 +28,9 @@ public:
|
|||
Slot, Group, Species, MinLevel, MaxLevel, EncounterChance, SlotRatio, EncounterRate, Count
|
||||
};
|
||||
|
||||
WildMonInfo encounterData();
|
||||
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);
|
||||
|
||||
private:
|
||||
|
|
|
@ -61,7 +61,7 @@ private:
|
|||
void chooseImageFile(QLineEdit * filepathEdit);
|
||||
void chooseFile(QLineEdit * filepathEdit, const QString &description, const QString &extensions);
|
||||
QString stripProjectDir(QString s);
|
||||
void disableParsedSetting(QWidget * widget, const QString &name, const QString &filepath);
|
||||
bool disableParsedSetting(QWidget * widget, const QString &identifier, const QString &filepath);
|
||||
void updateMaskOverlapWarning(QLabel * warning, QList<UIntSpinBox*> masks);
|
||||
QStringList getWarpBehaviorsList();
|
||||
void setWarpBehaviorsList(QStringList list);
|
||||
|
|
98
include/ui/wildmonchart.h
Normal file
98
include/ui/wildmonchart.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
#ifndef WILDMONCHART_H
|
||||
#define WILDMONCHART_H
|
||||
|
||||
#include "encountertablemodel.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#if __has_include(<QtCharts>)
|
||||
#include <QtCharts>
|
||||
|
||||
namespace Ui {
|
||||
class WildMonChart;
|
||||
}
|
||||
|
||||
class WildMonChart : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WildMonChart(QWidget *parent, const EncounterTableModel *table);
|
||||
~WildMonChart();
|
||||
|
||||
virtual void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
public slots:
|
||||
void setTable(const EncounterTableModel *table);
|
||||
void clearTable();
|
||||
void refresh();
|
||||
|
||||
private:
|
||||
Ui::WildMonChart *ui;
|
||||
const EncounterTableModel *table;
|
||||
|
||||
QStringList groupNames;
|
||||
QStringList groupNamesReversed;
|
||||
QStringList speciesInLegendOrder;
|
||||
QMap<int, QString> tableIndexToGroupName;
|
||||
|
||||
struct LevelRange {
|
||||
int min;
|
||||
int max;
|
||||
};
|
||||
QMap<QString, LevelRange> groupedLevelRanges;
|
||||
|
||||
struct Summary {
|
||||
double speciesFrequency = 0.0;
|
||||
QMap<int, double> levelFrequencies;
|
||||
};
|
||||
typedef QMap<QString, Summary> GroupedData;
|
||||
|
||||
QMap<QString, GroupedData> speciesToGroupedData;
|
||||
QMap<QString, QColor> speciesToColor;
|
||||
|
||||
|
||||
QStringList getSpeciesNamesAlphabetical() const;
|
||||
double getSpeciesFrequency(const QString&, const QString&) const;
|
||||
QMap<int, double> getLevelFrequencies(const QString &, const QString &) const;
|
||||
LevelRange getLevelRange(const QString &, const QString &) const;
|
||||
bool usesGroupLabels() const;
|
||||
|
||||
void clearTableData();
|
||||
void readTable();
|
||||
QChart* createSpeciesDistributionChart();
|
||||
QChart* createLevelDistributionChart();
|
||||
QBarSet* createLevelDistributionBarSet(const QString &, const QString &, bool);
|
||||
void refreshSpeciesDistributionChart();
|
||||
void refreshLevelDistributionChart();
|
||||
|
||||
void saveSpeciesColors(const QList<QBarSet*> &);
|
||||
void applySpeciesColors(const QList<QBarSet*> &);
|
||||
QChart::ChartTheme currentTheme() const;
|
||||
void updateTheme();
|
||||
void limitChartAnimation(QChart*);
|
||||
|
||||
void showHelpDialog();
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
// As of writing our static Qt build for Windows doesn't include the QtCharts module, so we dummy the class out here.
|
||||
// The charts module is additionally excluded from Windows in porymap.pro
|
||||
#define DISABLE_CHARTS_MODULE
|
||||
|
||||
class WildMonChart : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WildMonChart(QWidget *, const EncounterTableModel *) {};
|
||||
~WildMonChart() {};
|
||||
|
||||
public slots:
|
||||
void setTable(const EncounterTableModel *) {};
|
||||
void clearTable() {};
|
||||
void refresh() {};
|
||||
};
|
||||
|
||||
#endif // __has_include(<QtCharts>)
|
||||
|
||||
#endif // WILDMONCHART_H
|
13
porymap.pro
13
porymap.pro
|
@ -6,6 +6,10 @@
|
|||
|
||||
QT += core gui qml network
|
||||
|
||||
!win32 {
|
||||
QT += charts
|
||||
}
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
TARGET = porymap
|
||||
|
@ -112,7 +116,8 @@ SOURCES += src/core/block.cpp \
|
|||
src/settings.cpp \
|
||||
src/log.cpp \
|
||||
src/ui/uintspinbox.cpp \
|
||||
src/ui/updatepromoter.cpp
|
||||
src/ui/updatepromoter.cpp \
|
||||
src/ui/wildmonchart.cpp
|
||||
|
||||
HEADERS += include/core/block.h \
|
||||
include/core/bitpacker.h \
|
||||
|
@ -213,7 +218,8 @@ HEADERS += include/core/block.h \
|
|||
include/settings.h \
|
||||
include/log.h \
|
||||
include/ui/uintspinbox.h \
|
||||
include/ui/updatepromoter.h
|
||||
include/ui/updatepromoter.h \
|
||||
include/ui/wildmonchart.h
|
||||
|
||||
FORMS += forms/mainwindow.ui \
|
||||
forms/connectionslistitem.ui \
|
||||
|
@ -236,7 +242,8 @@ FORMS += forms/mainwindow.ui \
|
|||
forms/customscriptseditor.ui \
|
||||
forms/customscriptslistitem.ui \
|
||||
forms/customattributesdialog.ui \
|
||||
forms/updatepromoter.ui
|
||||
forms/updatepromoter.ui \
|
||||
forms/wildmonchart.ui
|
||||
|
||||
RESOURCES += \
|
||||
resources/images.qrc \
|
||||
|
|
BIN
resources/icons/help.ico
Executable file
BIN
resources/icons/help.ico
Executable file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
|
@ -16,6 +16,7 @@
|
|||
<file>icons/folder_map_opened.ico</file>
|
||||
<file>icons/folder_map.ico</file>
|
||||
<file>icons/folder.ico</file>
|
||||
<file>icons/help.ico</file>
|
||||
<file>icons/map_edited.ico</file>
|
||||
<file>icons/map_opened.ico</file>
|
||||
<file>icons/map.ico</file>
|
||||
|
|
|
@ -85,11 +85,13 @@ const QMap<ProjectIdentifier, QPair<QString, QString>> ProjectConfig::defaultIde
|
|||
{ProjectIdentifier::define_obj_event_count, {"define_obj_event_count", "OBJECT_EVENT_TEMPLATES_COUNT"}},
|
||||
{ProjectIdentifier::define_min_level, {"define_min_level", "MIN_LEVEL"}},
|
||||
{ProjectIdentifier::define_max_level, {"define_max_level", "MAX_LEVEL"}},
|
||||
{ProjectIdentifier::define_max_encounter_rate, {"define_max_encounter_rate", "MAX_ENCOUNTER_RATE"}},
|
||||
{ProjectIdentifier::define_tiles_primary, {"define_tiles_primary", "NUM_TILES_IN_PRIMARY"}},
|
||||
{ProjectIdentifier::define_tiles_total, {"define_tiles_total", "NUM_TILES_TOTAL"}},
|
||||
{ProjectIdentifier::define_metatiles_primary, {"define_metatiles_primary", "NUM_METATILES_IN_PRIMARY"}},
|
||||
{ProjectIdentifier::define_pals_primary, {"define_pals_primary", "NUM_PALS_IN_PRIMARY"}},
|
||||
{ProjectIdentifier::define_pals_total, {"define_pals_total", "NUM_PALS_TOTAL"}},
|
||||
{ProjectIdentifier::define_tiles_per_metatile, {"define_tiles_per_metatile", "NUM_TILES_PER_METATILE"}},
|
||||
{ProjectIdentifier::define_map_size, {"define_map_size", "MAX_MAP_DATA_SIZE"}},
|
||||
{ProjectIdentifier::define_mask_metatile, {"define_mask_metatile", "MAPGRID_METATILE_ID_MASK"}},
|
||||
{ProjectIdentifier::define_mask_collision, {"define_mask_collision", "MAPGRID_COLLISION_MASK"}},
|
||||
|
@ -109,6 +111,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_map_section_count, {"define_map_section_count", "COUNT"}},
|
||||
{ProjectIdentifier::define_species_prefix, {"define_species_prefix", "SPECIES_"}},
|
||||
// Regex
|
||||
{ProjectIdentifier::regex_behaviors, {"regex_behaviors", "\\bMB_"}},
|
||||
{ProjectIdentifier::regex_obj_event_gfx, {"regex_obj_event_gfx", "\\bOBJ_EVENT_GFX_"}},
|
||||
|
@ -124,7 +127,6 @@ const QMap<ProjectIdentifier, QPair<QString, QString>> ProjectConfig::defaultIde
|
|||
{ProjectIdentifier::regex_sign_facing_directions, {"regex_sign_facing_directions", "\\bBG_EVENT_PLAYER_FACING_"}},
|
||||
{ProjectIdentifier::regex_trainer_types, {"regex_trainer_types", "\\bTRAINER_TYPE_"}},
|
||||
{ProjectIdentifier::regex_music, {"regex_music", "\\b(SE|MUS)_"}},
|
||||
{ProjectIdentifier::regex_species, {"regex_species", "\\bSPECIES_"}},
|
||||
};
|
||||
|
||||
const QMap<ProjectFilePath, QPair<QString, QString>> ProjectConfig::defaultPaths = {
|
||||
|
@ -174,6 +176,7 @@ const QMap<ProjectFilePath, QPair<QString, QString>> ProjectConfig::defaultPaths
|
|||
{ProjectFilePath::fieldmap, { "fieldmap", "src/fieldmap.c"}},
|
||||
{ProjectFilePath::pokemon_icon_table, { "pokemon_icon_table", "src/pokemon_icon.c"}},
|
||||
{ProjectFilePath::initial_facing_table, { "initial_facing_table", "src/event_object_movement.c"}},
|
||||
{ProjectFilePath::wild_encounter, { "wild_encounter", "src/wild_encounter.c"}},
|
||||
{ProjectFilePath::pokemon_gfx, { "pokemon_gfx", "graphics/pokemon/"}},
|
||||
};
|
||||
|
||||
|
@ -368,6 +371,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
this->customScriptsEditorGeometry = bytesFromString(value);
|
||||
} else if (key == "custom_scripts_editor_state") {
|
||||
this->customScriptsEditorState = bytesFromString(value);
|
||||
} else if (key == "wild_mon_chart_geometry") {
|
||||
this->wildMonChartGeometry = bytesFromString(value);
|
||||
} else if (key == "metatiles_zoom") {
|
||||
this->metatilesZoom = getConfigInteger(key, value, 10, 100, 30);
|
||||
} else if (key == "collision_zoom") {
|
||||
|
@ -394,6 +399,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
this->tilesetCheckerboardFill = getConfigBool(key, value);
|
||||
} else if (key == "theme") {
|
||||
this->theme = value;
|
||||
} else if (key == "wild_mon_chart_theme") {
|
||||
this->wildMonChartTheme = value;
|
||||
} else if (key == "text_editor_open_directory") {
|
||||
this->textEditorOpenFolder = value;
|
||||
} else if (key == "text_editor_goto_line") {
|
||||
|
@ -453,6 +460,7 @@ QMap<QString, QString> PorymapConfig::getKeyValueMap() {
|
|||
map.insert("project_settings_editor_state", stringFromByteArray(this->projectSettingsEditorState));
|
||||
map.insert("custom_scripts_editor_geometry", stringFromByteArray(this->customScriptsEditorGeometry));
|
||||
map.insert("custom_scripts_editor_state", stringFromByteArray(this->customScriptsEditorState));
|
||||
map.insert("wild_mon_chart_geometry", stringFromByteArray(this->wildMonChartGeometry));
|
||||
map.insert("mirror_connecting_maps", this->mirrorConnectingMaps ? "1" : "0");
|
||||
map.insert("show_dive_emerge_maps", this->showDiveEmergeMaps ? "1" : "0");
|
||||
map.insert("dive_emerge_map_opacity", QString::number(this->diveEmergeMapOpacity));
|
||||
|
@ -472,6 +480,7 @@ QMap<QString, QString> PorymapConfig::getKeyValueMap() {
|
|||
map.insert("monitor_files", this->monitorFiles ? "1" : "0");
|
||||
map.insert("tileset_checkerboard_fill", this->tilesetCheckerboardFill ? "1" : "0");
|
||||
map.insert("theme", this->theme);
|
||||
map.insert("wild_mon_chart_theme", this->wildMonChartTheme);
|
||||
map.insert("text_editor_open_directory", this->textEditorOpenFolder);
|
||||
map.insert("text_editor_goto_line", this->textEditorGotoLine);
|
||||
map.insert("palette_editor_bit_depth", QString::number(this->paletteEditorBitDepth));
|
||||
|
|
|
@ -331,7 +331,7 @@ void EventCreate::redo() {
|
|||
|
||||
// select this event
|
||||
editor->selected_events->clear();
|
||||
editor->selectMapEvent(event->getPixmapItem(), false);
|
||||
editor->selectMapEvent(event->getPixmapItem());
|
||||
}
|
||||
|
||||
void EventCreate::undo() {
|
||||
|
|
|
@ -6,6 +6,21 @@
|
|||
|
||||
QMap<Event::Group, const QPixmap*> Event::icons;
|
||||
|
||||
Event* Event::create(Event::Type type) {
|
||||
switch (type) {
|
||||
case Event::Type::Object: return new ObjectEvent();
|
||||
case Event::Type::CloneObject: return new CloneObjectEvent();
|
||||
case Event::Type::Warp: return new WarpEvent();
|
||||
case Event::Type::Trigger: return new TriggerEvent();
|
||||
case Event::Type::WeatherTrigger: return new WeatherTriggerEvent();
|
||||
case Event::Type::Sign: return new SignEvent();
|
||||
case Event::Type::HiddenItem: return new HiddenItemEvent();
|
||||
case Event::Type::SecretBase: return new SecretBaseEvent();
|
||||
case Event::Type::HealLocation: return new HealLocationEvent();
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Event::~Event() {
|
||||
if (this->eventFrame)
|
||||
this->eventFrame->deleteLater();
|
||||
|
|
108
src/editor.cpp
108
src/editor.cpp
|
@ -7,7 +7,6 @@
|
|||
#include "mapsceneeventfilter.h"
|
||||
#include "metatile.h"
|
||||
#include "montabwidget.h"
|
||||
#include "encountertablemodel.h"
|
||||
#include "editcommands.h"
|
||||
#include "config.h"
|
||||
#include "scripting.h"
|
||||
|
@ -43,6 +42,11 @@ Editor::Editor(Ui::MainWindow* ui)
|
|||
selectNewEvents = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Send signals used for updating the wild pokemon summary chart
|
||||
connect(ui->stackedWidget_WildMons, &QStackedWidget::currentChanged, [this] {
|
||||
emit wildMonTableOpened(getCurrentWildMonTable());
|
||||
});
|
||||
}
|
||||
|
||||
Editor::~Editor()
|
||||
|
@ -79,9 +83,7 @@ void Editor::saveUiFields() {
|
|||
}
|
||||
|
||||
void Editor::setProject(Project * project) {
|
||||
if (this->project) {
|
||||
closeProject();
|
||||
}
|
||||
closeProject();
|
||||
this->project = project;
|
||||
MapConnection::project = project;
|
||||
}
|
||||
|
@ -194,6 +196,7 @@ void Editor::setEditingConnections() {
|
|||
|
||||
void Editor::clearWildMonTables() {
|
||||
QStackedWidget *stack = ui->stackedWidget_WildMons;
|
||||
const QSignalBlocker blocker(stack);
|
||||
|
||||
// delete widgets from previous map data if they exist
|
||||
while (stack->count()) {
|
||||
|
@ -203,6 +206,7 @@ void Editor::clearWildMonTables() {
|
|||
}
|
||||
|
||||
ui->comboBox_EncounterGroupLabel->clear();
|
||||
emit wildMonTableClosed();
|
||||
}
|
||||
|
||||
void Editor::displayWildMonTables() {
|
||||
|
@ -243,8 +247,12 @@ void Editor::displayWildMonTables() {
|
|||
}
|
||||
tabIndex++;
|
||||
}
|
||||
connect(tabWidget, &MonTabWidget::currentChanged, [this] {
|
||||
emit wildMonTableOpened(getCurrentWildMonTable());
|
||||
});
|
||||
}
|
||||
stack->setCurrentIndex(0);
|
||||
emit wildMonTableOpened(getCurrentWildMonTable());
|
||||
}
|
||||
|
||||
void Editor::addNewWildMonGroup(QWidget *window) {
|
||||
|
@ -359,7 +367,7 @@ void Editor::addNewWildMonGroup(QWidget *window) {
|
|||
tabIndex++;
|
||||
}
|
||||
saveEncounterTabData();
|
||||
emit wildMonDataChanged();
|
||||
emit wildMonTableEdited();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -397,7 +405,8 @@ void Editor::deleteWildMonGroup() {
|
|||
project->encounterGroupLabels.remove(i);
|
||||
|
||||
displayWildMonTables();
|
||||
emit wildMonDataChanged();
|
||||
saveEncounterTabData();
|
||||
emit wildMonTableEdited();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -650,7 +659,8 @@ void Editor::configureEncounterJSON(QWidget *window) {
|
|||
|
||||
// Re-draw the tab accordingly.
|
||||
displayWildMonTables();
|
||||
emit wildMonDataChanged();
|
||||
saveEncounterTabData();
|
||||
emit wildMonTableEdited();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -684,6 +694,16 @@ void Editor::saveEncounterTabData() {
|
|||
}
|
||||
}
|
||||
|
||||
EncounterTableModel* Editor::getCurrentWildMonTable() {
|
||||
auto tabWidget = static_cast<MonTabWidget*>(ui->stackedWidget_WildMons->currentWidget());
|
||||
if (!tabWidget) return nullptr;
|
||||
|
||||
auto tableView = tabWidget->tableAt(tabWidget->currentIndex());
|
||||
if (!tableView) return nullptr;
|
||||
|
||||
return static_cast<EncounterTableModel*>(tableView->model());
|
||||
}
|
||||
|
||||
void Editor::updateEncounterFields(EncounterFields newFields) {
|
||||
EncounterFields oldFields = project->wildMonFields;
|
||||
// Go through fields and determine whether we need to update a field.
|
||||
|
@ -1358,7 +1378,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item
|
|||
if (newEvent) {
|
||||
newEvent->move(pos.x(), pos.y());
|
||||
emit objectsChanged();
|
||||
selectMapEvent(newEvent, false);
|
||||
selectMapEvent(newEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1993,10 +2013,6 @@ void Editor::updateSelectedEvents() {
|
|||
emit objectsChanged();
|
||||
}
|
||||
|
||||
void Editor::selectMapEvent(DraggablePixmapItem *object) {
|
||||
selectMapEvent(object, false);
|
||||
}
|
||||
|
||||
void Editor::selectMapEvent(DraggablePixmapItem *object, bool toggle) {
|
||||
if (!selected_events || !object)
|
||||
return;
|
||||
|
@ -2006,10 +2022,10 @@ void Editor::selectMapEvent(DraggablePixmapItem *object, bool toggle) {
|
|||
selected_events->clear();
|
||||
selected_events->append(object);
|
||||
} else if (!selected_events->contains(object)) {
|
||||
// Adding event to selection
|
||||
// Adding event to group selection
|
||||
selected_events->append(object);
|
||||
} else if (selected_events->length() > 1) {
|
||||
// Removing from group selection
|
||||
// Removing event from group selection
|
||||
selected_events->removeOne(object);
|
||||
} else {
|
||||
// Attempting to toggle the only currently-selected event.
|
||||
|
@ -2071,56 +2087,24 @@ void Editor::duplicateSelectedEvents() {
|
|||
}
|
||||
|
||||
DraggablePixmapItem *Editor::addNewEvent(Event::Type type) {
|
||||
Event *event = nullptr;
|
||||
if (!project || !map || eventLimitReached(type))
|
||||
return nullptr;
|
||||
|
||||
if (project && map && !eventLimitReached(type)) {
|
||||
switch (type) {
|
||||
case Event::Type::Object:
|
||||
event = new ObjectEvent();
|
||||
break;
|
||||
case Event::Type::CloneObject:
|
||||
event = new CloneObjectEvent();
|
||||
break;
|
||||
case Event::Type::Warp:
|
||||
event = new WarpEvent();
|
||||
break;
|
||||
case Event::Type::Trigger:
|
||||
event = new TriggerEvent();
|
||||
break;
|
||||
case Event::Type::WeatherTrigger:
|
||||
event = new WeatherTriggerEvent();
|
||||
break;
|
||||
case Event::Type::Sign:
|
||||
event = new SignEvent();
|
||||
break;
|
||||
case Event::Type::HiddenItem:
|
||||
event = new HiddenItemEvent();
|
||||
break;
|
||||
case Event::Type::SecretBase:
|
||||
event = new SecretBaseEvent();
|
||||
break;
|
||||
case Event::Type::HealLocation: {
|
||||
event = new HealLocationEvent();
|
||||
event->setMap(this->map);
|
||||
event->setDefaultValues(this->project);
|
||||
HealLocation healLocation = HealLocation::fromEvent(event);
|
||||
project->healLocations.append(healLocation);
|
||||
((HealLocationEvent *)event)->setIndex(project->healLocations.length());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!event) return nullptr;
|
||||
Event *event = Event::create(type);
|
||||
if (!event)
|
||||
return nullptr;
|
||||
|
||||
event->setMap(this->map);
|
||||
event->setDefaultValues(this->project);
|
||||
event->setMap(this->map);
|
||||
event->setDefaultValues(this->project);
|
||||
|
||||
map->editHistory.push(new EventCreate(this, map, event));
|
||||
return event->getPixmapItem();
|
||||
if (type == Event::Type::HealLocation) {
|
||||
HealLocation healLocation = HealLocation::fromEvent(event);
|
||||
project->healLocations.append(healLocation);
|
||||
((HealLocationEvent *)event)->setIndex(project->healLocations.length());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
map->editHistory.push(new EventCreate(this, map, event));
|
||||
return event->getPixmapItem();
|
||||
}
|
||||
|
||||
// Currently only object events have an explicit limit
|
||||
|
@ -2227,10 +2211,8 @@ void Editor::objectsView_onMousePress(QMouseEvent *event) {
|
|||
|
||||
bool multiSelect = event->modifiers() & Qt::ControlModifier;
|
||||
if (!selectingEvent && !multiSelect && selected_events->length() > 1) {
|
||||
DraggablePixmapItem *first = selected_events->first();
|
||||
selected_events->clear();
|
||||
selected_events->append(first);
|
||||
updateSelectedEvents();
|
||||
// User is clearing group selection by clicking on the background
|
||||
this->selectMapEvent(selected_events->first());
|
||||
}
|
||||
selectingEvent = false;
|
||||
}
|
||||
|
|
|
@ -124,6 +124,10 @@ void MainWindow::initWindow() {
|
|||
ui->actionCheck_for_Updates->setVisible(false);
|
||||
#endif
|
||||
|
||||
#ifdef DISABLE_CHARTS_MODULE
|
||||
ui->pushButton_SummaryChart->setVisible(false);
|
||||
#endif
|
||||
|
||||
setWindowDisabled(true);
|
||||
}
|
||||
|
||||
|
@ -239,10 +243,8 @@ void MainWindow::initExtraSignals() {
|
|||
connect(ui->tabWidget_EventType, &QTabWidget::currentChanged, this, &MainWindow::eventTabChanged);
|
||||
|
||||
// Change pages on wild encounter groups
|
||||
QStackedWidget *stack = ui->stackedWidget_WildMons;
|
||||
QComboBox *labelCombo = ui->comboBox_EncounterGroupLabel;
|
||||
connect(labelCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){
|
||||
stack->setCurrentIndex(index);
|
||||
connect(ui->comboBox_EncounterGroupLabel, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index){
|
||||
ui->stackedWidget_WildMons->setCurrentIndex(index);
|
||||
});
|
||||
|
||||
// Convert the layout of the map tools' frame into an adjustable FlowLayout
|
||||
|
@ -309,7 +311,7 @@ void MainWindow::initEditor() {
|
|||
connect(this->editor, &Editor::openConnectedMap, this, &MainWindow::onOpenConnectedMap);
|
||||
connect(this->editor, &Editor::warpEventDoubleClicked, this, &MainWindow::openWarpMap);
|
||||
connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged);
|
||||
connect(this->editor, &Editor::wildMonDataChanged, this, &MainWindow::onWildMonDataChanged);
|
||||
connect(this->editor, &Editor::wildMonTableEdited, [this] { this->markMapEdited(); });
|
||||
connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged);
|
||||
connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated);
|
||||
connect(ui->toolButton_Open_Scripts, &QToolButton::pressed, this->editor, &Editor::openMapScripts);
|
||||
|
@ -1123,6 +1125,7 @@ void MainWindow::clearProjectUI() {
|
|||
|
||||
// Clear map list
|
||||
mapListModel->clear();
|
||||
mapListIndexes.clear();
|
||||
mapGroupItemsList->clear();
|
||||
}
|
||||
|
||||
|
@ -1139,6 +1142,7 @@ void MainWindow::sortMapList() {
|
|||
|
||||
ui->mapList->setUpdatesEnabled(false);
|
||||
mapListModel->clear();
|
||||
mapListIndexes.clear();
|
||||
mapGroupItemsList->clear();
|
||||
QStandardItem *root = mapListModel->invisibleRootItem();
|
||||
|
||||
|
@ -1771,7 +1775,7 @@ void MainWindow::paste() {
|
|||
editor->metatile_selector_item->setExternalSelection(width, height, metatiles, collisions);
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
case MainTab::Events:
|
||||
{
|
||||
// can only paste events to this tab
|
||||
if (pasteObject["object"].toString() != "events") {
|
||||
|
@ -1783,56 +1787,25 @@ void MainWindow::paste() {
|
|||
QJsonArray events = pasteObject["events"].toArray();
|
||||
for (QJsonValue event : events) {
|
||||
// paste the event to the map
|
||||
Event *pasteEvent = nullptr;
|
||||
|
||||
Event::Type type = Event::eventTypeFromString(event["event_type"].toString());
|
||||
const QString typeString = event["event_type"].toString();
|
||||
Event::Type type = Event::eventTypeFromString(typeString);
|
||||
|
||||
if (this->editor->eventLimitReached(type)) {
|
||||
logWarn(QString("Cannot paste event, the limit for type '%1' has been reached.").arg(event["event_type"].toString()));
|
||||
break;
|
||||
logWarn(QString("Cannot paste event, the limit for type '%1' has been reached.").arg(typeString));
|
||||
continue;
|
||||
}
|
||||
if (type == Event::Type::HealLocation) {
|
||||
logWarn(QString("Cannot paste events of type '%1'").arg(typeString));
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case Event::Type::Object:
|
||||
pasteEvent = new ObjectEvent();
|
||||
pasteEvent->loadFromJson(event["event"].toObject(), this->editor->project);
|
||||
break;
|
||||
case Event::Type::CloneObject:
|
||||
pasteEvent = new CloneObjectEvent();
|
||||
pasteEvent->loadFromJson(event["event"].toObject(), this->editor->project);
|
||||
break;
|
||||
case Event::Type::Warp:
|
||||
pasteEvent = new WarpEvent();
|
||||
pasteEvent->loadFromJson(event["event"].toObject(), this->editor->project);
|
||||
break;
|
||||
case Event::Type::Trigger:
|
||||
pasteEvent = new TriggerEvent();
|
||||
pasteEvent->loadFromJson(event["event"].toObject(), this->editor->project);
|
||||
break;
|
||||
case Event::Type::WeatherTrigger:
|
||||
pasteEvent = new WeatherTriggerEvent();
|
||||
pasteEvent->loadFromJson(event["event"].toObject(), this->editor->project);
|
||||
break;
|
||||
case Event::Type::Sign:
|
||||
pasteEvent = new SignEvent();
|
||||
pasteEvent->loadFromJson(event["event"].toObject(), this->editor->project);
|
||||
break;
|
||||
case Event::Type::HiddenItem:
|
||||
pasteEvent = new HiddenItemEvent();
|
||||
pasteEvent->loadFromJson(event["event"].toObject(), this->editor->project);
|
||||
break;
|
||||
case Event::Type::SecretBase:
|
||||
pasteEvent = new SecretBaseEvent();
|
||||
pasteEvent->loadFromJson(event["event"].toObject(), this->editor->project);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Event *pasteEvent = Event::create(type);
|
||||
if (!pasteEvent)
|
||||
continue;
|
||||
|
||||
if (pasteEvent) {
|
||||
pasteEvent->setMap(this->editor->map);
|
||||
newEvents.append(pasteEvent);
|
||||
}
|
||||
pasteEvent->loadFromJson(event["event"].toObject(), this->editor->project);
|
||||
pasteEvent->setMap(this->editor->map);
|
||||
newEvents.append(pasteEvent);
|
||||
}
|
||||
|
||||
if (!newEvents.empty()) {
|
||||
|
@ -2024,7 +1997,7 @@ void MainWindow::addNewEvent(Event::Type type) {
|
|||
auto centerPos = ui->graphicsView_Map->mapToScene(halfSize.width(), halfSize.height());
|
||||
object->moveTo(Metatile::coordFromPixmapCoord(centerPos));
|
||||
updateObjects();
|
||||
editor->selectMapEvent(object, false);
|
||||
editor->selectMapEvent(object);
|
||||
} else {
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setText("Failed to add new event");
|
||||
|
@ -2572,11 +2545,6 @@ void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryT
|
|||
redrawMapScene();
|
||||
}
|
||||
|
||||
void MainWindow::onWildMonDataChanged() {
|
||||
editor->saveEncounterTabData();
|
||||
markMapEdited();
|
||||
}
|
||||
|
||||
void MainWindow::onMapRulerStatusChanged(const QString &status) {
|
||||
if (status.isEmpty()) {
|
||||
label_MapRulerStatus->hide();
|
||||
|
@ -2669,6 +2637,16 @@ void MainWindow::on_pushButton_DeleteWildMonGroup_clicked() {
|
|||
editor->deleteWildMonGroup();
|
||||
}
|
||||
|
||||
void MainWindow::on_pushButton_SummaryChart_clicked() {
|
||||
if (!this->wildMonChart) {
|
||||
this->wildMonChart = new WildMonChart(this, this->editor->getCurrentWildMonTable());
|
||||
connect(this->editor, &Editor::wildMonTableOpened, this->wildMonChart, &WildMonChart::setTable);
|
||||
connect(this->editor, &Editor::wildMonTableClosed, this->wildMonChart, &WildMonChart::clearTable);
|
||||
connect(this->editor, &Editor::wildMonTableEdited, this->wildMonChart, &WildMonChart::refresh);
|
||||
}
|
||||
openSubWindow(this->wildMonChart);
|
||||
}
|
||||
|
||||
void MainWindow::on_pushButton_ConfigureEncountersJSON_clicked() {
|
||||
editor->configureEncounterJSON(this);
|
||||
}
|
||||
|
@ -3104,6 +3082,10 @@ bool MainWindow::closeSupplementaryWindows() {
|
|||
return false;
|
||||
this->customScriptsEditor = nullptr;
|
||||
|
||||
if (this->wildMonChart && !this->wildMonChart->close())
|
||||
return false;
|
||||
this->wildMonChart = nullptr;
|
||||
|
||||
if (this->projectSettingsEditor) this->projectSettingsEditor->closeQuietly();
|
||||
this->projectSettingsEditor = nullptr;
|
||||
|
||||
|
|
|
@ -113,6 +113,7 @@ bool Project::sanityCheck() {
|
|||
}
|
||||
|
||||
bool Project::load() {
|
||||
this->disabledSettingsNames.clear();
|
||||
bool success = readMapLayouts()
|
||||
&& readRegionMapSections()
|
||||
&& readItemNames()
|
||||
|
@ -1654,15 +1655,43 @@ void Project::deleteFile(QString path) {
|
|||
}
|
||||
|
||||
bool Project::readWildMonData() {
|
||||
extraEncounterGroups.clear();
|
||||
wildMonFields.clear();
|
||||
wildMonData.clear();
|
||||
encounterGroupLabels.clear();
|
||||
this->extraEncounterGroups.clear();
|
||||
this->wildMonFields.clear();
|
||||
this->wildMonData.clear();
|
||||
this->encounterGroupLabels.clear();
|
||||
this->pokemonMinLevel = 0;
|
||||
this->pokemonMaxLevel = 100;
|
||||
this->maxEncounterRate = 2880/16;
|
||||
this->wildEncountersLoaded = false;
|
||||
if (!userConfig.useEncounterJson) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read max encounter rate. The games multiply the encounter rate value in the map data by 16, so our input limit is the max/16.
|
||||
const QString encounterRateFile = projectConfig.getFilePath(ProjectFilePath::wild_encounter);
|
||||
const QString maxEncounterRateName = projectConfig.getIdentifier(ProjectIdentifier::define_max_encounter_rate);
|
||||
|
||||
fileWatcher.addPath(QString("%1/%2").arg(root).arg(encounterRateFile));
|
||||
auto defines = parser.readCDefinesByName(encounterRateFile, {maxEncounterRateName});
|
||||
if (defines.contains(maxEncounterRateName))
|
||||
this->maxEncounterRate = defines.value(maxEncounterRateName)/16;
|
||||
|
||||
// Read min/max level
|
||||
const QString levelRangeFile = projectConfig.getFilePath(ProjectFilePath::constants_pokemon);
|
||||
const QString minLevelName = projectConfig.getIdentifier(ProjectIdentifier::define_min_level);
|
||||
const QString maxLevelName = projectConfig.getIdentifier(ProjectIdentifier::define_max_level);
|
||||
|
||||
fileWatcher.addPath(QString("%1/%2").arg(root).arg(levelRangeFile));
|
||||
defines = parser.readCDefinesByName(levelRangeFile, {minLevelName, maxLevelName});
|
||||
if (defines.contains(minLevelName))
|
||||
this->pokemonMinLevel = defines.value(minLevelName);
|
||||
if (defines.contains(maxLevelName))
|
||||
this->pokemonMaxLevel = defines.value(maxLevelName);
|
||||
|
||||
this->pokemonMinLevel = qMin(this->pokemonMinLevel, this->pokemonMaxLevel);
|
||||
this->pokemonMaxLevel = qMax(this->pokemonMinLevel, this->pokemonMaxLevel);
|
||||
|
||||
// Read encounter data
|
||||
QString wildMonJsonFilepath = QString("%1/%2").arg(root).arg(projectConfig.getFilePath(ProjectFilePath::json_wild_encounters));
|
||||
fileWatcher.addPath(wildMonJsonFilepath);
|
||||
|
||||
|
@ -1960,6 +1989,7 @@ bool Project::readFieldmapProperties() {
|
|||
const QString numPalsPrimaryName = projectConfig.getIdentifier(ProjectIdentifier::define_pals_primary);
|
||||
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 = {
|
||||
numTilesPrimaryName,
|
||||
numTilesTotalName,
|
||||
|
@ -1967,6 +1997,7 @@ bool Project::readFieldmapProperties() {
|
|||
numPalsPrimaryName,
|
||||
numPalsTotalName,
|
||||
maxMapSizeName,
|
||||
numTilesPerMetatileName,
|
||||
};
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
|
@ -2007,6 +2038,22 @@ bool Project::readFieldmapProperties() {
|
|||
.arg(Project::max_map_data_size));
|
||||
}
|
||||
|
||||
it = defines.find(numTilesPerMetatileName);
|
||||
if (it != defines.end()) {
|
||||
// We can determine whether triple-layer metatiles are in-use by reading this constant.
|
||||
// If the constant is missing (or is using a value other than 8 or 12) the user must tell
|
||||
// us whether they're using triple-layer metatiles under Project Settings.
|
||||
static const int numTilesPerLayer = 4;
|
||||
int numTilesPerMetatile = it.value();
|
||||
if (numTilesPerMetatile == 2 * numTilesPerLayer) {
|
||||
projectConfig.tripleLayerMetatilesEnabled = false;
|
||||
this->disabledSettingsNames.insert(numTilesPerMetatileName);
|
||||
} else if (numTilesPerMetatile == 3 * numTilesPerLayer) {
|
||||
projectConfig.tripleLayerMetatilesEnabled = true;
|
||||
this->disabledSettingsNames.insert(numTilesPerMetatileName);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2032,7 +2079,8 @@ bool Project::readFieldmapMasks() {
|
|||
// If users do have the defines we disable them in the settings editor and direct them to their project files.
|
||||
// Record the names we read so we know later which settings to disable.
|
||||
const QStringList defineNames = defines.keys();
|
||||
this->disabledSettingsNames = QSet<QString>(defineNames.constBegin(), defineNames.constEnd());
|
||||
for (auto name : defineNames)
|
||||
this->disabledSettingsNames.insert(name);
|
||||
|
||||
// Read Block masks
|
||||
auto readBlockMask = [defines](const QString name, uint16_t *value) {
|
||||
|
@ -2412,17 +2460,6 @@ bool Project::readObjEventGfxConstants() {
|
|||
}
|
||||
|
||||
bool Project::readMiscellaneousConstants() {
|
||||
miscConstants.clear();
|
||||
if (userConfig.useEncounterJson) {
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_pokemon);
|
||||
const QString minLevelName = projectConfig.getIdentifier(ProjectIdentifier::define_min_level);
|
||||
const QString maxLevelName = projectConfig.getIdentifier(ProjectIdentifier::define_max_level);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
QMap<QString, int> pokemonDefines = parser.readCDefinesByName(filename, {minLevelName, maxLevelName});
|
||||
miscConstants.insert("max_level_define", pokemonDefines.value(maxLevelName) > pokemonDefines.value(minLevelName) ? pokemonDefines.value(maxLevelName) : 100);
|
||||
miscConstants.insert("min_level_define", pokemonDefines.value(minLevelName) < pokemonDefines.value(maxLevelName) ? pokemonDefines.value(minLevelName) : 1);
|
||||
}
|
||||
|
||||
const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_global);
|
||||
const QString maxObjectEventsName = projectConfig.getIdentifier(ProjectIdentifier::define_obj_event_count);
|
||||
fileWatcher.addPath(root + "/" + filename);
|
||||
|
@ -2611,7 +2648,7 @@ bool Project::readSpeciesIconPaths() {
|
|||
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 prefixes = {projectConfig.getIdentifier(ProjectIdentifier::regex_species)};
|
||||
const QStringList prefixes = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix))};
|
||||
const QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_species);
|
||||
fileWatcher.addPath(root + "/" + constantsFilename);
|
||||
QStringList speciesNames = parser.readCDefineNames(constantsFilename, prefixes);
|
||||
|
|
|
@ -27,7 +27,7 @@ CustomScriptsEditor::CustomScriptsEditor(QWidget *parent) :
|
|||
|
||||
connect(ui->button_CreateNewScript, &QAbstractButton::clicked, this, &CustomScriptsEditor::createNewScript);
|
||||
connect(ui->button_LoadScript, &QAbstractButton::clicked, this, &CustomScriptsEditor::loadScript);
|
||||
connect(ui->button_RefreshScripts, &QAbstractButton::clicked, this, &CustomScriptsEditor::refreshScripts);
|
||||
connect(ui->button_RefreshScripts, &QAbstractButton::clicked, this, &CustomScriptsEditor::userRefreshScripts);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &CustomScriptsEditor::dialogButtonClicked);
|
||||
|
||||
this->initShortcuts();
|
||||
|
@ -57,7 +57,7 @@ void CustomScriptsEditor::initShortcuts() {
|
|||
shortcut_load->setObjectName("shortcut_load");
|
||||
shortcut_load->setWhatsThis("Load Script...");
|
||||
|
||||
auto *shortcut_refresh = new Shortcut(QKeySequence(), this, SLOT(refreshScripts()));
|
||||
auto *shortcut_refresh = new Shortcut(QKeySequence(), this, SLOT(userRefreshScripts()));
|
||||
shortcut_refresh->setObjectName("shortcut_refresh");
|
||||
shortcut_refresh->setWhatsThis("Refresh Scripts");
|
||||
|
||||
|
@ -238,14 +238,21 @@ void CustomScriptsEditor::openSelectedScripts() {
|
|||
this->openScript(item);
|
||||
}
|
||||
|
||||
void CustomScriptsEditor::refreshScripts() {
|
||||
// When the user refreshes the scripts we show a little tooltip as feedback.
|
||||
// We don't want this tooltip to display when we refresh programmatically, like when changes are saved.
|
||||
void CustomScriptsEditor::userRefreshScripts() {
|
||||
if (refreshScripts())
|
||||
QToolTip::showText(ui->button_RefreshScripts->mapToGlobal(QPoint(0, 0)), "Refreshed!");
|
||||
}
|
||||
|
||||
bool CustomScriptsEditor::refreshScripts() {
|
||||
if (this->hasUnsavedChanges) {
|
||||
if (this->prompt("Scripts have been modified, save changes and reload the script engine?", QMessageBox::Yes) == QMessageBox::No)
|
||||
return;
|
||||
return false;
|
||||
this->save();
|
||||
}
|
||||
QToolTip::showText(ui->button_RefreshScripts->mapToGlobal(QPoint(0, 0)), "Refreshed!");
|
||||
emit reloadScriptEngine();
|
||||
return true;
|
||||
}
|
||||
|
||||
void CustomScriptsEditor::save() {
|
||||
|
@ -264,6 +271,7 @@ void CustomScriptsEditor::save() {
|
|||
}
|
||||
|
||||
userConfig.setCustomScripts(paths, enabledStates);
|
||||
userConfig.save();
|
||||
this->hasUnsavedChanges = false;
|
||||
this->refreshScripts();
|
||||
}
|
||||
|
|
|
@ -34,11 +34,26 @@ void DraggablePixmapItem::updatePixmap() {
|
|||
}
|
||||
|
||||
void DraggablePixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) {
|
||||
active = true;
|
||||
QPoint pos = Metatile::coordFromPixmapCoord(mouse->scenePos());
|
||||
last_x = pos.x();
|
||||
last_y = pos.y();
|
||||
this->editor->selectMapEvent(this, mouse->modifiers() & Qt::ControlModifier);
|
||||
if (this->active)
|
||||
return;
|
||||
this->active = true;
|
||||
this->lastPos = Metatile::coordFromPixmapCoord(mouse->scenePos());
|
||||
|
||||
bool selectionToggle = mouse->modifiers() & Qt::ControlModifier;
|
||||
if (selectionToggle || !editor->selected_events->contains(this)) {
|
||||
// User is either toggling this selection on/off as part of a group selection,
|
||||
// or they're newly selecting just this item.
|
||||
this->editor->selectMapEvent(this, selectionToggle);
|
||||
} else {
|
||||
// This item is already selected and the user isn't toggling the selection, so there are 4 possibilities:
|
||||
// 1. This is the only selected event, and the selection is pointless.
|
||||
// 2. This is the only selected event, and they want to drag the item around.
|
||||
// 3. There's a group selection, and they want to start a new selection with just this item.
|
||||
// 4. There's a group selection, and they want to drag the group around.
|
||||
// 'selectMapEvent' will immediately clear the rest of the selection, which supports #1-3 but prevents #4.
|
||||
// To support #4 we set the flag below, and we only call 'selectMapEvent' on mouse release if no move occurred.
|
||||
this->releaseSelectionQueued = true;
|
||||
}
|
||||
this->editor->selectingEvent = true;
|
||||
}
|
||||
|
||||
|
@ -57,28 +72,39 @@ void DraggablePixmapItem::moveTo(const QPoint &pos) {
|
|||
}
|
||||
|
||||
void DraggablePixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) {
|
||||
if (active) {
|
||||
QPoint pos = Metatile::coordFromPixmapCoord(mouse->scenePos());
|
||||
if (pos.x() != last_x || pos.y() != last_y) {
|
||||
emit this->editor->map_item->hoveredMapMetatileChanged(pos);
|
||||
QList <Event *> selectedEvents;
|
||||
if (editor->selected_events->contains(this)) {
|
||||
for (DraggablePixmapItem *item : *editor->selected_events) {
|
||||
selectedEvents.append(item->event);
|
||||
}
|
||||
} else {
|
||||
selectedEvents.append(this->event);
|
||||
}
|
||||
editor->map->editHistory.push(new EventMove(selectedEvents, pos.x() - last_x, pos.y() - last_y, currentActionId));
|
||||
last_x = pos.x();
|
||||
last_y = pos.y();
|
||||
if (!this->active)
|
||||
return;
|
||||
|
||||
QPoint pos = Metatile::coordFromPixmapCoord(mouse->scenePos());
|
||||
if (pos == this->lastPos)
|
||||
return;
|
||||
|
||||
QPoint moveDistance = pos - this->lastPos;
|
||||
this->lastPos = pos;
|
||||
emit this->editor->map_item->hoveredMapMetatileChanged(pos);
|
||||
|
||||
QList <Event *> selectedEvents;
|
||||
if (editor->selected_events->contains(this)) {
|
||||
for (DraggablePixmapItem *item : *editor->selected_events) {
|
||||
selectedEvents.append(item->event);
|
||||
}
|
||||
} else {
|
||||
selectedEvents.append(this->event);
|
||||
}
|
||||
editor->map->editHistory.push(new EventMove(selectedEvents, moveDistance.x(), moveDistance.y(), currentActionId));
|
||||
this->releaseSelectionQueued = false;
|
||||
}
|
||||
|
||||
void DraggablePixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *) {
|
||||
active = false;
|
||||
void DraggablePixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) {
|
||||
if (!this->active)
|
||||
return;
|
||||
this->active = false;
|
||||
currentActionId++;
|
||||
if (this->releaseSelectionQueued) {
|
||||
this->releaseSelectionQueued = false;
|
||||
if (Metatile::coordFromPixmapCoord(mouse->scenePos()) == this->lastPos)
|
||||
this->editor->selectMapEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
void DraggablePixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) {
|
||||
|
|
|
@ -50,7 +50,7 @@ QWidget *SpeciesComboDelegate::createEditor(QWidget *parent, const QStyleOptionV
|
|||
void SpeciesComboDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const {
|
||||
QString species = index.data(Qt::EditRole).toString();
|
||||
NoScrollComboBox *combo = static_cast<NoScrollComboBox *>(editor);
|
||||
combo->setCurrentIndex(combo->findText(species));
|
||||
combo->setCurrentText(species);
|
||||
}
|
||||
|
||||
void SpeciesComboDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
|
||||
|
@ -75,12 +75,12 @@ QWidget *SpinBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewIt
|
|||
|
||||
int col = index.column();
|
||||
if (col == EncounterTableModel::ColumnType::MinLevel || col == EncounterTableModel::ColumnType::MaxLevel) {
|
||||
editor->setMinimum(this->project->miscConstants.value("min_level_define").toInt());
|
||||
editor->setMaximum(this->project->miscConstants.value("max_level_define").toInt());
|
||||
editor->setMinimum(this->project->pokemonMinLevel);
|
||||
editor->setMaximum(this->project->pokemonMaxLevel);
|
||||
}
|
||||
else if (col == EncounterTableModel::ColumnType::EncounterRate) {
|
||||
editor->setMinimum(0);
|
||||
editor->setMaximum(180);
|
||||
editor->setMaximum(this->project->maxEncounterRate);
|
||||
}
|
||||
|
||||
return editor;
|
||||
|
|
|
@ -142,45 +142,57 @@ QVariant EncounterTableModel::headerData(int section, Qt::Orientation orientatio
|
|||
}
|
||||
|
||||
bool EncounterTableModel::setData(const QModelIndex &index, const QVariant &value, int role) {
|
||||
if (role == Qt::EditRole) {
|
||||
if (!checkIndex(index))
|
||||
return false;
|
||||
if (role != Qt::EditRole)
|
||||
return false;
|
||||
if (!checkIndex(index))
|
||||
return false;
|
||||
|
||||
int row = index.row();
|
||||
int col = index.column();
|
||||
int row = index.row();
|
||||
int col = index.column();
|
||||
|
||||
switch (col) {
|
||||
case ColumnType::Species:
|
||||
this->monInfo.wildPokemon[row].species = value.toString();
|
||||
break;
|
||||
|
||||
case ColumnType::MinLevel: {
|
||||
int minLevel = value.toInt();
|
||||
this->monInfo.wildPokemon[row].minLevel = minLevel;
|
||||
if (minLevel > this->monInfo.wildPokemon[row].maxLevel)
|
||||
this->monInfo.wildPokemon[row].maxLevel = minLevel;
|
||||
break;
|
||||
switch (col) {
|
||||
case ColumnType::Species: {
|
||||
QString species = value.toString();
|
||||
if (this->monInfo.wildPokemon[row].species != species) {
|
||||
this->monInfo.wildPokemon[row].species = species;
|
||||
emit edited();
|
||||
}
|
||||
|
||||
case ColumnType::MaxLevel: {
|
||||
int maxLevel = value.toInt();
|
||||
this->monInfo.wildPokemon[row].maxLevel = maxLevel;
|
||||
if (maxLevel < this->monInfo.wildPokemon[row].minLevel)
|
||||
this->monInfo.wildPokemon[row].minLevel = maxLevel;
|
||||
break;
|
||||
}
|
||||
|
||||
case ColumnType::EncounterRate:
|
||||
this->monInfo.encounterRate = value.toInt();
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
emit edited();
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
|
||||
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);
|
||||
emit edited();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
emit edited();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ColumnType::EncounterRate: {
|
||||
int encounterRate = value.toInt();
|
||||
if (this->monInfo.encounterRate != encounterRate) {
|
||||
this->monInfo.encounterRate = encounterRate;
|
||||
emit edited();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Qt::ItemFlags EncounterTableModel::flags(const QModelIndex &index) const {
|
||||
|
@ -199,7 +211,3 @@ Qt::ItemFlags EncounterTableModel::flags(const QModelIndex &index) const {
|
|||
}
|
||||
return flags | QAbstractTableModel::flags(index);
|
||||
}
|
||||
|
||||
WildMonInfo EncounterTableModel::encounterData() {
|
||||
return this->monInfo;
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ void MonTabWidget::paste(int index) {
|
|||
WildMonInfo newInfo = getDefaultMonInfo(this->editor->project->wildMonFields.at(index));
|
||||
combineEncounters(newInfo, encounterClipboard);
|
||||
populateTab(index, newInfo);
|
||||
emit editor->wildMonDataChanged();
|
||||
emit editor->wildMonTableEdited();
|
||||
}
|
||||
|
||||
void MonTabWidget::actionCopyTab(int index) {
|
||||
|
@ -88,21 +88,19 @@ void MonTabWidget::actionCopyTab(int index) {
|
|||
}
|
||||
|
||||
void MonTabWidget::actionAddDeleteTab(int index) {
|
||||
clearTableAt(index);
|
||||
if (activeTabs[index]) {
|
||||
// delete tab
|
||||
clearTableAt(index);
|
||||
deactivateTab(index);
|
||||
editor->saveEncounterTabData();
|
||||
emit editor->wildMonDataChanged();
|
||||
}
|
||||
else {
|
||||
// add tab
|
||||
clearTableAt(index);
|
||||
populateTab(index, getDefaultMonInfo(editor->project->wildMonFields.at(index)));
|
||||
editor->saveEncounterTabData();
|
||||
setCurrentIndex(index);
|
||||
emit editor->wildMonDataChanged();
|
||||
}
|
||||
emit editor->wildMonTableEdited();
|
||||
}
|
||||
|
||||
void MonTabWidget::clearTableAt(int tabIndex) {
|
||||
|
@ -130,6 +128,7 @@ void MonTabWidget::populateTab(int tabIndex, WildMonInfo monInfo) {
|
|||
|
||||
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);
|
||||
|
||||
speciesTable->setItemDelegateForColumn(EncounterTableModel::ColumnType::Species, new SpeciesComboDelegate(editor->project, this));
|
||||
|
@ -158,6 +157,8 @@ void MonTabWidget::populateTab(int tabIndex, WildMonInfo monInfo) {
|
|||
}
|
||||
|
||||
QTableView *MonTabWidget::tableAt(int tabIndex) {
|
||||
if (tabIndex < 0)
|
||||
return nullptr;
|
||||
return static_cast<QTableView *>(this->widget(tabIndex));
|
||||
}
|
||||
|
||||
|
|
|
@ -133,53 +133,40 @@ void ProjectSettingsEditor::initUi() {
|
|||
ui->spinBox_CollisionMask->setMaximum(Block::maxValue);
|
||||
ui->spinBox_ElevationMask->setMaximum(Block::maxValue);
|
||||
|
||||
// Some settings can be determined by constants in the project.
|
||||
// We reflect that here by disabling their UI elements.
|
||||
if (project) {
|
||||
const QString maskFilepath = projectConfig.getFilePath(ProjectFilePath::global_fieldmap);
|
||||
const QString attrTableFilepath = projectConfig.getFilePath(ProjectFilePath::fieldmap);
|
||||
const QString metatileIdMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_metatile);
|
||||
const QString collisionMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_collision);
|
||||
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 QString behaviorTableName = projectConfig.getIdentifier(ProjectIdentifier::define_attribute_behavior);
|
||||
const QString layerTypeTableName = projectConfig.getIdentifier(ProjectIdentifier::define_attribute_layer);
|
||||
const QString encounterTypeTableName = projectConfig.getIdentifier(ProjectIdentifier::define_attribute_encounter);
|
||||
const QString terrainTypeTableName = projectConfig.getIdentifier(ProjectIdentifier::define_attribute_terrain);
|
||||
const QString attrTableName = projectConfig.getIdentifier(ProjectIdentifier::symbol_attribute_table);
|
||||
// The values for some of the settings we provide in this window can be determined using constants in the user's projects.
|
||||
// If the user has these constants we disable these settings in the UI -- they can modify them using their constants.
|
||||
const QString globalFieldmapPath = projectConfig.getFilePath(ProjectFilePath::global_fieldmap);
|
||||
const QString constantsFieldmapPath = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap);
|
||||
const QString fieldmapPath = projectConfig.getFilePath(ProjectFilePath::fieldmap);
|
||||
|
||||
// Block masks
|
||||
if (project->disabledSettingsNames.contains(metatileIdMaskName))
|
||||
this->disableParsedSetting(ui->spinBox_MetatileIdMask, metatileIdMaskName, maskFilepath);
|
||||
if (project->disabledSettingsNames.contains(collisionMaskName))
|
||||
this->disableParsedSetting(ui->spinBox_CollisionMask, collisionMaskName, maskFilepath);
|
||||
if (project->disabledSettingsNames.contains(elevationMaskName))
|
||||
this->disableParsedSetting(ui->spinBox_ElevationMask, elevationMaskName, maskFilepath);
|
||||
// Block masks
|
||||
this->disableParsedSetting(ui->spinBox_MetatileIdMask, projectConfig.getIdentifier(ProjectIdentifier::define_mask_metatile), globalFieldmapPath);
|
||||
this->disableParsedSetting(ui->spinBox_CollisionMask, projectConfig.getIdentifier(ProjectIdentifier::define_mask_collision), globalFieldmapPath);
|
||||
this->disableParsedSetting(ui->spinBox_ElevationMask, projectConfig.getIdentifier(ProjectIdentifier::define_mask_elevation), globalFieldmapPath);
|
||||
|
||||
// Behavior mask
|
||||
if (project->disabledSettingsNames.contains(behaviorMaskName))
|
||||
this->disableParsedSetting(ui->spinBox_BehaviorMask, behaviorMaskName, maskFilepath);
|
||||
else if (project->disabledSettingsNames.contains(behaviorTableName))
|
||||
this->disableParsedSetting(ui->spinBox_BehaviorMask, attrTableName, attrTableFilepath);
|
||||
// Behavior mask
|
||||
if (!this->disableParsedSetting(ui->spinBox_BehaviorMask, projectConfig.getIdentifier(ProjectIdentifier::define_mask_behavior), globalFieldmapPath))
|
||||
this->disableParsedSetting(ui->spinBox_BehaviorMask, projectConfig.getIdentifier(ProjectIdentifier::define_attribute_behavior), fieldmapPath);
|
||||
|
||||
// Layer type mask
|
||||
if (project->disabledSettingsNames.contains(layerTypeMaskName))
|
||||
this->disableParsedSetting(ui->spinBox_LayerTypeMask, layerTypeMaskName, maskFilepath);
|
||||
else if (project->disabledSettingsNames.contains(layerTypeTableName))
|
||||
this->disableParsedSetting(ui->spinBox_LayerTypeMask, attrTableName, attrTableFilepath);
|
||||
// Layer type mask
|
||||
if (!this->disableParsedSetting(ui->spinBox_LayerTypeMask, projectConfig.getIdentifier(ProjectIdentifier::define_mask_layer), globalFieldmapPath))
|
||||
this->disableParsedSetting(ui->spinBox_LayerTypeMask, projectConfig.getIdentifier(ProjectIdentifier::define_attribute_layer), fieldmapPath);
|
||||
|
||||
// Encounter and terrain type masks
|
||||
if (project->disabledSettingsNames.contains(encounterTypeTableName))
|
||||
this->disableParsedSetting(ui->spinBox_EncounterTypeMask, attrTableName, attrTableFilepath);
|
||||
if (project->disabledSettingsNames.contains(terrainTypeTableName))
|
||||
this->disableParsedSetting(ui->spinBox_TerrainTypeMask, attrTableName, attrTableFilepath);
|
||||
}
|
||||
// Encounter and terrain type masks
|
||||
this->disableParsedSetting(ui->spinBox_EncounterTypeMask, projectConfig.getIdentifier(ProjectIdentifier::define_attribute_encounter), fieldmapPath);
|
||||
this->disableParsedSetting(ui->spinBox_TerrainTypeMask, projectConfig.getIdentifier(ProjectIdentifier::define_attribute_terrain), fieldmapPath);
|
||||
|
||||
// Tripe layer metatiles
|
||||
this->disableParsedSetting(ui->checkBox_EnableTripleLayerMetatiles, projectConfig.getIdentifier(ProjectIdentifier::define_tiles_per_metatile), constantsFieldmapPath);
|
||||
}
|
||||
|
||||
void ProjectSettingsEditor::disableParsedSetting(QWidget * widget, const QString &name, const QString &filepath) {
|
||||
widget->setEnabled(false);
|
||||
widget->setToolTip(QString("This value has been read from '%1' in %2").arg(name).arg(filepath));
|
||||
bool ProjectSettingsEditor::disableParsedSetting(QWidget * widget, const QString &identifier, const QString &filepath) {
|
||||
if (project && project->disabledSettingsNames.contains(identifier)) {
|
||||
widget->setEnabled(false);
|
||||
widget->setToolTip(QString("This value has been set using '%1' in %2").arg(identifier).arg(filepath));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remember the current settings tab for future sessions
|
||||
|
|
452
src/ui/wildmonchart.cpp
Normal file
452
src/ui/wildmonchart.cpp
Normal file
|
@ -0,0 +1,452 @@
|
|||
#if __has_include(<QtCharts>)
|
||||
#include "wildmonchart.h"
|
||||
#include "ui_wildmonchart.h"
|
||||
#include "config.h"
|
||||
|
||||
static const QString baseWindowTitle = QString("Wild Pokémon Summary Charts");
|
||||
|
||||
static const QList<QPair<QString, QChart::ChartTheme>> themes = {
|
||||
{"Light", QChart::ChartThemeLight},
|
||||
{"Dark", QChart::ChartThemeDark},
|
||||
{"Blue Cerulean", QChart::ChartThemeBlueCerulean},
|
||||
{"Brown Sand", QChart::ChartThemeBrownSand},
|
||||
{"Blue NCS", QChart::ChartThemeBlueNcs},
|
||||
{"High Contrast", QChart::ChartThemeHighContrast},
|
||||
{"Blue Icy", QChart::ChartThemeBlueIcy},
|
||||
{"Qt", QChart::ChartThemeQt},
|
||||
};
|
||||
|
||||
WildMonChart::WildMonChart(QWidget *parent, const EncounterTableModel *table) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::WildMonChart)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setWindowFlags(Qt::Window);
|
||||
|
||||
connect(ui->button_Help, &QAbstractButton::clicked, this, &WildMonChart::showHelpDialog);
|
||||
|
||||
// Changing these settings changes which level distribution chart is shown
|
||||
connect(ui->groupBox_Species, &QGroupBox::clicked, this, &WildMonChart::refreshLevelDistributionChart);
|
||||
connect(ui->comboBox_Species, &QComboBox::currentTextChanged, this, &WildMonChart::refreshLevelDistributionChart);
|
||||
connect(ui->comboBox_Group, &QComboBox::currentTextChanged, this, &WildMonChart::refreshLevelDistributionChart);
|
||||
|
||||
// Set up Theme combo box
|
||||
for (auto i : themes)
|
||||
ui->comboBox_Theme->addItem(i.first, i.second);
|
||||
connect(ui->comboBox_Theme, &QComboBox::currentTextChanged, this, &WildMonChart::updateTheme);
|
||||
|
||||
// User's theme choice is saved in the config
|
||||
int configThemeIndex = ui->comboBox_Theme->findText(porymapConfig.wildMonChartTheme);
|
||||
if (configThemeIndex >= 0) {
|
||||
const QSignalBlocker blocker(ui->comboBox_Theme);
|
||||
ui->comboBox_Theme->setCurrentIndex(configThemeIndex);
|
||||
} else {
|
||||
porymapConfig.wildMonChartTheme = ui->comboBox_Theme->currentText();
|
||||
}
|
||||
|
||||
restoreGeometry(porymapConfig.wildMonChartGeometry);
|
||||
|
||||
setTable(table);
|
||||
};
|
||||
|
||||
WildMonChart::~WildMonChart() {
|
||||
delete ui;
|
||||
};
|
||||
|
||||
void WildMonChart::setTable(const EncounterTableModel *table) {
|
||||
if (this->table == table)
|
||||
return;
|
||||
this->table = table;
|
||||
refresh();
|
||||
}
|
||||
|
||||
void WildMonChart::clearTable() {
|
||||
setTable(nullptr);
|
||||
}
|
||||
|
||||
void WildMonChart::clearTableData() {
|
||||
this->groupNames.clear();
|
||||
this->groupNamesReversed.clear();
|
||||
this->speciesInLegendOrder.clear();
|
||||
this->tableIndexToGroupName.clear();
|
||||
this->groupedLevelRanges.clear();
|
||||
this->speciesToGroupedData.clear();
|
||||
this->speciesToColor.clear();
|
||||
setWindowTitle(baseWindowTitle);
|
||||
|
||||
const QSignalBlocker blocker1(ui->comboBox_Species);
|
||||
const QSignalBlocker blocker2(ui->comboBox_Group);
|
||||
ui->comboBox_Species->clear();
|
||||
ui->comboBox_Group->clear();
|
||||
ui->comboBox_Group->setEnabled(false);
|
||||
}
|
||||
|
||||
// Extract all the data from the table that we need for the charts
|
||||
void WildMonChart::readTable() {
|
||||
clearTableData();
|
||||
if (!this->table)
|
||||
return;
|
||||
|
||||
setWindowTitle(QString("%1 - %2").arg(baseWindowTitle).arg(this->table->encounterField().name));
|
||||
|
||||
// Read data about encounter groups, e.g. for "fishing_mons" we want to know table indexes 2-4 belong to "good_rod"
|
||||
for (auto groupPair : this->table->encounterField().groups) {
|
||||
// Frustratingly when adding categories to the charts they insert bottom-to-top, but the table and combo box
|
||||
// insert top-to-bottom. To keep the order visually the same across displays we keep separate ordered lists.
|
||||
this->groupNames.append(groupPair.first);
|
||||
this->groupNamesReversed.prepend(groupPair.first);
|
||||
for (auto i : groupPair.second)
|
||||
this->tableIndexToGroupName.insert(i, groupPair.first);
|
||||
}
|
||||
// Implicitly 1 unnamed group when none are listed
|
||||
if (this->groupNames.isEmpty()) {
|
||||
this->groupNames.append(QString());
|
||||
this->groupNamesReversed.append(QString());
|
||||
}
|
||||
|
||||
// Read data from the table, combining data for duplicate entries
|
||||
const QList<double> tableFrequencies = this->table->percentages();
|
||||
const QVector<WildPokemon> tablePokemon = this->table->encounterData().wildPokemon;
|
||||
const int numRows = qMin(tableFrequencies.length(), tablePokemon.length());
|
||||
const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix);
|
||||
for (int i = 0; i < numRows; i++) {
|
||||
const double frequency = tableFrequencies.at(i);
|
||||
const WildPokemon pokemon = tablePokemon.at(i);
|
||||
const QString groupName = this->tableIndexToGroupName.value(i);
|
||||
|
||||
// Create species label (strip 'SPECIES_' prefix).
|
||||
QString label = pokemon.species;
|
||||
if (label.startsWith(speciesPrefix))
|
||||
label.remove(0, speciesPrefix.length());
|
||||
|
||||
// Add species/level frequency data
|
||||
Summary *summary = &this->speciesToGroupedData[label][groupName];
|
||||
summary->speciesFrequency += frequency;
|
||||
if (pokemon.minLevel > pokemon.maxLevel)
|
||||
continue; // Invalid
|
||||
int numLevels = pokemon.maxLevel - pokemon.minLevel + 1;
|
||||
for (int level = pokemon.minLevel; level <= pokemon.maxLevel; level++)
|
||||
summary->levelFrequencies[level] += frequency / numLevels;
|
||||
|
||||
// Update level min/max for showing level distribution across a group
|
||||
if (!this->groupedLevelRanges.contains(groupName)) {
|
||||
LevelRange *levelRange = &this->groupedLevelRanges[groupName];
|
||||
levelRange->min = pokemon.minLevel;
|
||||
levelRange->max = pokemon.maxLevel;
|
||||
} else {
|
||||
LevelRange *levelRange = &this->groupedLevelRanges[groupName];
|
||||
if (levelRange->min > pokemon.minLevel)
|
||||
levelRange->min = pokemon.minLevel;
|
||||
if (levelRange->max < pokemon.maxLevel)
|
||||
levelRange->max = pokemon.maxLevel;
|
||||
}
|
||||
}
|
||||
|
||||
// Populate combo boxes
|
||||
const QSignalBlocker blocker1(ui->comboBox_Species);
|
||||
const QSignalBlocker blocker2(ui->comboBox_Group);
|
||||
ui->comboBox_Species->clear();
|
||||
ui->comboBox_Species->addItems(getSpeciesNamesAlphabetical());
|
||||
ui->comboBox_Group->clear();
|
||||
ui->comboBox_Group->addItems(this->groupNames);
|
||||
ui->comboBox_Group->setEnabled(usesGroupLabels());
|
||||
}
|
||||
|
||||
void WildMonChart::refresh() {
|
||||
const QSignalBlocker blocker1(ui->comboBox_Species);
|
||||
const QSignalBlocker blocker2(ui->comboBox_Group);
|
||||
const QString oldSpecies = ui->comboBox_Species->currentText();
|
||||
const QString oldGroup = ui->comboBox_Group->currentText();
|
||||
|
||||
readTable();
|
||||
|
||||
// If the old UI settings are still valid we should restore them
|
||||
int index = ui->comboBox_Species->findText(oldSpecies);
|
||||
if (index >= 0) ui->comboBox_Species->setCurrentIndex(index);
|
||||
|
||||
index = ui->comboBox_Group->findText(oldGroup);
|
||||
if (index >= 0) ui->comboBox_Group->setCurrentIndex(index);
|
||||
|
||||
refreshSpeciesDistributionChart();
|
||||
refreshLevelDistributionChart();
|
||||
}
|
||||
|
||||
void WildMonChart::refreshSpeciesDistributionChart() {
|
||||
if (ui->chartView_SpeciesDistribution->chart())
|
||||
ui->chartView_SpeciesDistribution->chart()->deleteLater();
|
||||
ui->chartView_SpeciesDistribution->setChart(createSpeciesDistributionChart());
|
||||
limitChartAnimation(ui->chartView_SpeciesDistribution->chart());
|
||||
}
|
||||
|
||||
void WildMonChart::refreshLevelDistributionChart() {
|
||||
if (ui->chartView_LevelDistribution->chart())
|
||||
ui->chartView_LevelDistribution->chart()->deleteLater();
|
||||
ui->chartView_LevelDistribution->setChart(createLevelDistributionChart());
|
||||
limitChartAnimation(ui->chartView_LevelDistribution->chart());
|
||||
}
|
||||
|
||||
QStringList WildMonChart::getSpeciesNamesAlphabetical() const {
|
||||
return this->speciesToGroupedData.keys();
|
||||
}
|
||||
|
||||
double WildMonChart::getSpeciesFrequency(const QString &species, const QString &groupName) const {
|
||||
return this->speciesToGroupedData[species][groupName].speciesFrequency;
|
||||
}
|
||||
|
||||
QMap<int, double> WildMonChart::getLevelFrequencies(const QString &species, const QString &groupName) const {
|
||||
return this->speciesToGroupedData[species][groupName].levelFrequencies;
|
||||
}
|
||||
|
||||
WildMonChart::LevelRange WildMonChart::getLevelRange(const QString &species, const QString &groupName) const {
|
||||
const QList<int> levels = getLevelFrequencies(species, groupName).keys();
|
||||
|
||||
LevelRange range;
|
||||
if (levels.isEmpty()) {
|
||||
range.min = 0;
|
||||
range.max = 0;
|
||||
} else {
|
||||
range.min = levels.first();
|
||||
range.max = levels.last();
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
bool WildMonChart::usesGroupLabels() const {
|
||||
return this->groupNames.length() > 1;
|
||||
}
|
||||
|
||||
QChart* WildMonChart::createSpeciesDistributionChart() {
|
||||
QList<QBarSet*> barSets;
|
||||
for (const auto species : getSpeciesNamesAlphabetical()) {
|
||||
// Add encounter chance data
|
||||
auto set = new QBarSet(species);
|
||||
for (auto groupName : this->groupNamesReversed)
|
||||
set->append(getSpeciesFrequency(species, groupName) * 100);
|
||||
|
||||
// We order the bar sets from lowest to highest total, left-to-right.
|
||||
for (int i = 0; i < barSets.length() + 1; i++){
|
||||
if (i >= barSets.length() || barSets.at(i)->sum() > set->sum()) {
|
||||
barSets.insert(i, set);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Show species name and % when hovering over a bar set. This covers some shortfalls in our ability to control the chart design
|
||||
// (i.e. bar segments may be too narrow to see the % label, or colors may be hard to match to the legend).
|
||||
connect(set, &QBarSet::hovered, [set] (bool on, int i) {
|
||||
QString text = on ? QString("%1 (%2%)").arg(set->label()).arg(set->at(i)) : "";
|
||||
QToolTip::showText(QCursor::pos(), text);
|
||||
});
|
||||
}
|
||||
|
||||
// Preserve the order we set earlier so that the legend isn't shuffling around for the other all-species charts.
|
||||
this->speciesInLegendOrder.clear();
|
||||
for (auto set : barSets)
|
||||
this->speciesInLegendOrder.append(set->label());
|
||||
|
||||
// Set up series
|
||||
auto series = new QHorizontalPercentBarSeries();
|
||||
series->setLabelsVisible();
|
||||
series->append(barSets);
|
||||
|
||||
// Set up chart
|
||||
auto chart = new QChart();
|
||||
chart->addSeries(series);
|
||||
chart->setTheme(currentTheme());
|
||||
chart->setAnimationOptions(QChart::SeriesAnimations);
|
||||
chart->legend()->setVisible(true);
|
||||
chart->legend()->setShowToolTips(true);
|
||||
chart->legend()->setAlignment(Qt::AlignBottom);
|
||||
saveSpeciesColors(barSets);
|
||||
|
||||
// X-axis is the % frequency. We're already showing percentages on the bar, so we just display 0/50/100%
|
||||
auto axisX = new QValueAxis();
|
||||
axisX->setLabelFormat("%u%%");
|
||||
axisX->setTickCount(3);
|
||||
chart->addAxis(axisX, Qt::AlignBottom);
|
||||
series->attachAxis(axisX);
|
||||
|
||||
// Y-axis is the names of encounter groups (e.g. Old Rod, Good Rod...)
|
||||
if (usesGroupLabels()) {
|
||||
auto axisY = new QBarCategoryAxis();
|
||||
axisY->setCategories(this->groupNamesReversed);
|
||||
chart->addAxis(axisY, Qt::AlignLeft);
|
||||
series->attachAxis(axisY);
|
||||
}
|
||||
|
||||
return chart;
|
||||
}
|
||||
|
||||
QBarSet* WildMonChart::createLevelDistributionBarSet(const QString &species, const QString &groupName, bool individual) {
|
||||
const double totalFrequency = individual ? getSpeciesFrequency(species, groupName) : 1.0;
|
||||
const QMap<int, double> levelFrequencies = getLevelFrequencies(species, groupName);
|
||||
|
||||
auto set = new QBarSet(species);
|
||||
LevelRange levelRange = individual ? getLevelRange(species, groupName) : this->groupedLevelRanges.value(groupName);
|
||||
for (int i = levelRange.min; i <= levelRange.max; i++) {
|
||||
set->append(levelFrequencies.value(i, 0) / totalFrequency * 100);
|
||||
}
|
||||
|
||||
// Show data when hovering over a bar set. This covers some shortfalls in our ability to control the chart design.
|
||||
connect(set, &QBarSet::hovered, [=] (bool on, int i) {
|
||||
QString text = on ? QString("%1 Lv%2 (%3%)")
|
||||
.arg(individual ? "" : set->label())
|
||||
.arg(QString::number(i + levelRange.min))
|
||||
.arg(set->at(i))
|
||||
: "";
|
||||
QToolTip::showText(QCursor::pos(), text);
|
||||
});
|
||||
|
||||
// Clicking on a bar set in the stacked chart opens its individual chart
|
||||
if (!individual) {
|
||||
connect(set, &QBarSet::clicked, [this, species](int) {
|
||||
const QSignalBlocker blocker1(ui->groupBox_Species);
|
||||
const QSignalBlocker blocker2(ui->comboBox_Species);
|
||||
ui->groupBox_Species->setChecked(true);
|
||||
ui->comboBox_Species->setCurrentText(species);
|
||||
refreshLevelDistributionChart();
|
||||
});
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
QChart* WildMonChart::createLevelDistributionChart() {
|
||||
const QString groupName = ui->comboBox_Group->currentText();
|
||||
|
||||
LevelRange levelRange;
|
||||
QList<QBarSet*> barSets;
|
||||
|
||||
// Create bar sets
|
||||
if (ui->groupBox_Species->isChecked()) {
|
||||
// Species box is active, we just display data for the selected species.
|
||||
const QString species = ui->comboBox_Species->currentText();
|
||||
barSets.append(createLevelDistributionBarSet(species, groupName, true));
|
||||
levelRange = getLevelRange(species, groupName);
|
||||
} else {
|
||||
// Species box is inactive, we display data for all species in the table.
|
||||
for (const auto species : this->speciesInLegendOrder)
|
||||
barSets.append(createLevelDistributionBarSet(species, groupName, false));
|
||||
levelRange = this->groupedLevelRanges.value(groupName);
|
||||
}
|
||||
|
||||
// Set up series
|
||||
auto series = new QStackedBarSeries();
|
||||
//series->setLabelsVisible();
|
||||
series->append(barSets);
|
||||
|
||||
// Set up chart
|
||||
auto chart = new QChart();
|
||||
chart->addSeries(series);
|
||||
chart->setTheme(currentTheme());
|
||||
chart->setAnimationOptions(QChart::SeriesAnimations);
|
||||
chart->legend()->setVisible(true);
|
||||
chart->legend()->setShowToolTips(true);
|
||||
chart->legend()->setAlignment(Qt::AlignBottom);
|
||||
applySpeciesColors(barSets); // Has to happen after theme is set
|
||||
|
||||
// X-axis is the level range.
|
||||
QBarCategoryAxis *axisX = new QBarCategoryAxis();
|
||||
for (int i = levelRange.min; i <= levelRange.max; i++)
|
||||
axisX->append(QString::number(i));
|
||||
chart->addAxis(axisX, Qt::AlignBottom);
|
||||
series->attachAxis(axisX);
|
||||
|
||||
// Y-axis is the % frequency
|
||||
QValueAxis *axisY = new QValueAxis();
|
||||
axisY->setLabelFormat("%u%%");
|
||||
chart->addAxis(axisY, Qt::AlignLeft);
|
||||
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));
|
||||
|
||||
return chart;
|
||||
}
|
||||
|
||||
QChart::ChartTheme WildMonChart::currentTheme() const {
|
||||
return static_cast<QChart::ChartTheme>(ui->comboBox_Theme->currentData().toInt());
|
||||
}
|
||||
|
||||
void WildMonChart::updateTheme() {
|
||||
auto theme = currentTheme();
|
||||
porymapConfig.wildMonChartTheme = ui->comboBox_Theme->currentText();
|
||||
|
||||
// In order to keep the color of each species in the legend consistent across
|
||||
// charts we save species->color mappings. The legend colors are overwritten
|
||||
// when we change themes, so we need to recalculate them. We let the species
|
||||
// distribution chart determine what those mapping are (it always includes every
|
||||
// species in the table) and then we apply those mappings to subsequent charts.
|
||||
QChart *chart = ui->chartView_SpeciesDistribution->chart();
|
||||
if (!chart)
|
||||
return;
|
||||
chart->setTheme(theme);
|
||||
saveSpeciesColors(static_cast<QAbstractBarSeries*>(chart->series().at(0))->barSets());
|
||||
|
||||
chart = ui->chartView_LevelDistribution->chart();
|
||||
if (chart) {
|
||||
chart->setTheme(theme);
|
||||
applySpeciesColors(static_cast<QAbstractBarSeries*>(chart->series().at(0))->barSets());
|
||||
}
|
||||
}
|
||||
|
||||
void WildMonChart::saveSpeciesColors(const QList<QBarSet*> &barSets) {
|
||||
this->speciesToColor.clear();
|
||||
for (auto set : barSets)
|
||||
this->speciesToColor.insert(set->label(), set->color());
|
||||
}
|
||||
|
||||
void WildMonChart::applySpeciesColors(const QList<QBarSet*> &barSets) {
|
||||
for (auto set : barSets)
|
||||
set->setColor(this->speciesToColor.value(set->label()));
|
||||
}
|
||||
|
||||
// Turn off the animation once it's played, otherwise it replays any time the window changes size.
|
||||
void WildMonChart::limitChartAnimation(QChart *chart) {
|
||||
// Chart may be destroyed before the animation finishes
|
||||
QPointer<QChart> safeChart = chart;
|
||||
|
||||
// QChart has no signal for when the animation is finished, so we use a timer to stop the animation.
|
||||
// It is technically possible to get the chart to freeze mid-animation by resizing the window after
|
||||
// the timer starts but before it finishes, but 1. animations are short so this is difficult to do,
|
||||
// and 2. the issue resolves if the window is resized afterwards, so it's probably fine.
|
||||
QTimer::singleShot(chart->animationDuration() + 500, [safeChart] {
|
||||
if (safeChart) safeChart->setAnimationOptions(QChart::NoAnimation);
|
||||
});
|
||||
}
|
||||
|
||||
void WildMonChart::showHelpDialog() {
|
||||
static const QString text = "This window provides some visualizations of the data in your current Wild Pokémon tab";
|
||||
static const QString informative =
|
||||
"The <b>Species Distribution</b> tab shows the cumulative encounter chance for each species "
|
||||
"in the table. In other words, it answers the question \"What is the likelihood of encountering "
|
||||
"each species in a single encounter?\""
|
||||
"<br><br>"
|
||||
"The <b>Level Distribution</b> tab shows the chance of encountering each species at a particular level. "
|
||||
"In the top left under <b>Group</b> you can select which encounter group to show data for. "
|
||||
"In the top right under <b>Species</b> you can select which species to show data for. "
|
||||
"<br><br>"
|
||||
"<b>Individual Mode</b> on the <b>Level Distribution</b> tab toggles whether data is shown for all species in the table. "
|
||||
"The percentages will update to reflect whether you're showing all species or just that individual species. "
|
||||
"In other words, while <b>Individual Mode</b> is checked the chart is answering the question \"If a species x "
|
||||
"is encountered, what is the likelihood that it will be level y\", and while <b>Individual Mode</b> is not checked, "
|
||||
"it answers the question \"For a single encounter, what is the likelihood of encountering a species x at level y.\"";
|
||||
QMessageBox msgBox(QMessageBox::Information, "porymap", text, QMessageBox::Close, this);
|
||||
msgBox.setTextFormat(Qt::RichText);
|
||||
msgBox.setInformativeText(informative);
|
||||
msgBox.exec();
|
||||
}
|
||||
|
||||
void WildMonChart::closeEvent(QCloseEvent *event) {
|
||||
porymapConfig.wildMonChartGeometry = saveGeometry();
|
||||
QWidget::closeEvent(event);
|
||||
}
|
||||
|
||||
#endif // __has_include(<QtCharts>)
|
Loading…
Reference in a new issue