Merge pull request #706 from GriffinRichards/tileset-types

Read encounter/terrain types from the project
This commit is contained in:
GriffinR 2025-03-27 02:04:41 -04:00 committed by GitHub
commit ec79ee4185
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 437 additions and 269 deletions

View File

@ -6,12 +6,12 @@
<rect>
<x>0</x>
<y>0</y>
<width>727</width>
<height>700</height>
<width>733</width>
<height>784</height>
</rect>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
<enum>Qt::FocusPolicy::ClickFocus</enum>
</property>
<property name="windowTitle">
<string>Tileset Editor</string>
@ -21,7 +21,7 @@
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
@ -34,10 +34,10 @@
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
<enum>QFrame::Shadow::Plain</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
@ -58,15 +58,15 @@
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
<set>Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop</set>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_Metatiles">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>223</width>
<height>593</height>
<width>239</width>
<height>659</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
@ -88,17 +88,17 @@
<item row="0" column="0">
<widget class="NoScrollGraphicsView" name="graphicsView_Metatiles">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -121,7 +121,7 @@
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
@ -129,10 +129,10 @@
</widget>
<widget class="QFrame" name="frame_Editing">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
@ -162,14 +162,14 @@
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
<enum>QLayout::SizeConstraint::SetMinimumSize</enum>
</property>
<property name="leftMargin">
<number>0</number>
@ -195,6 +195,70 @@
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="15" column="0" colspan="4">
<widget class="QLineEdit" name="lineEdit_metatileLabel">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2" colspan="3">
<widget class="NoScrollComboBox" name="comboBox_layerType">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>185</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="14" column="0" colspan="5">
<widget class="QLabel" name="label_metatileLabel">
<property name="text">
<string>Metatile Label (Optional)</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_BottomTop">
<property name="text">
<string>Bottom/Top</string>
</property>
</widget>
</item>
<item row="10" column="0" colspan="5">
<widget class="QLabel" name="label_terrainType">
<property name="text">
<string>Terrain Type</string>
</property>
</widget>
</item>
<item row="0" column="2" colspan="3">
<widget class="QLabel" name="label_layerType">
<property name="text">
<string>Layer Type</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="5">
<widget class="NoScrollComboBox" name="comboBox_metatileBehaviors">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QGraphicsView" name="graphicsView_metatileLayers">
<property name="sizePolicy">
@ -216,10 +280,10 @@
</size>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
</widget>
</item>
@ -231,17 +295,23 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</widget>
</item>
<item row="14" column="0">
<spacer name="verticalSpacer_7">
<item row="1" column="1">
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Maximum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>1</height>
<width>10</width>
<height>20</height>
</size>
</property>
</spacer>
@ -254,16 +324,33 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="12" column="0" colspan="5">
<widget class="QLabel" name="label_metatileLabel">
<property name="text">
<string>Metatile Label (Optional)</string>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</widget>
</item>
<item row="13" column="4">
<item row="2" column="0" colspan="5">
<widget class="QLabel" name="label_metatileBehavior">
<property name="text">
<string>Metatile Behavior</string>
</property>
</widget>
</item>
<item row="12" column="0" colspan="5">
<widget class="QLabel" name="label_rawAttributesValue">
<property name="text">
<string>Raw Attributes Value</string>
</property>
</widget>
</item>
<item row="8" column="0" colspan="5">
<widget class="QLabel" name="label_encounterType">
<property name="text">
<string>Encounter Type</string>
</property>
</widget>
</item>
<item row="15" column="4">
<widget class="QToolButton" name="copyButton_metatileLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Copies the full metatile label to the clipboard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -277,89 +364,21 @@
</property>
</widget>
</item>
<item row="1" column="2" colspan="3">
<widget class="NoScrollComboBox" name="comboBox_layerType">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>185</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="7" column="0" colspan="5">
<widget class="NoScrollComboBox" name="comboBox_metatileBehaviors">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="2" colspan="3">
<widget class="QLabel" name="label_layerType">
<property name="text">
<string>Layer Type</string>
</property>
</widget>
</item>
<item row="13" column="0" colspan="4">
<widget class="QLineEdit" name="lineEdit_metatileLabel">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="5">
<widget class="QLabel" name="label_metatileBehavior">
<property name="text">
<string>Metatile Behavior</string>
</property>
</widget>
</item>
<item row="1" column="1">
<spacer name="horizontalSpacer_5">
<item row="16" column="0">
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>20</height>
<width>20</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
<item row="8" column="0" colspan="5">
<widget class="QLabel" name="label_encounterType">
<property name="text">
<string>Encounter Type</string>
</property>
</widget>
</item>
<item row="10" column="0" colspan="5">
<widget class="QLabel" name="label_terrainType">
<property name="text">
<string>Terrain Type</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_BottomTop">
<property name="text">
<string>Bottom/Top</string>
</property>
</widget>
<item row="13" column="0" colspan="5">
<widget class="UIntHexSpinBox" name="spinBox_rawAttributesValue"/>
</item>
</layout>
</widget>
@ -399,7 +418,7 @@
<item row="1" column="1">
<widget class="QCheckBox" name="checkBox_xFlip">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
<enum>Qt::LayoutDirection::LeftToRight</enum>
</property>
<property name="text">
<string/>
@ -423,10 +442,10 @@
<item row="3" column="0" colspan="2">
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -451,10 +470,10 @@
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
<enum>QFrame::Shadow::Plain</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
@ -497,13 +516,13 @@
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
</widget>
</item>
@ -513,7 +532,7 @@
<item row="6" column="0" colspan="2">
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -535,15 +554,15 @@
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
<set>Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop</set>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_Tiles">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>455</width>
<height>232</height>
<width>445</width>
<height>237</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2">
@ -562,17 +581,17 @@
<item row="0" column="0">
<widget class="NoScrollGraphicsView" name="graphicsView_Tiles">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -589,7 +608,7 @@
<item>
<widget class="QSlider" name="horizontalSlider_TilesZoom">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
@ -604,8 +623,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>727</width>
<height>22</height>
<width>733</width>
<height>37</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@ -648,6 +667,7 @@
<addaction name="actionLayer_Grid"/>
<addaction name="actionMetatile_Grid"/>
<addaction name="actionShow_Tileset_Divider"/>
<addaction name="actionShow_Raw_Metatile_Attributes"/>
<addaction name="separator"/>
<addaction name="actionShow_Counts"/>
<addaction name="actionShow_Unused"/>
@ -808,6 +828,14 @@
<string>Show Tileset Divider</string>
</property>
</action>
<action name="actionShow_Raw_Metatile_Attributes">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Show Raw Metatile Attributes</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
@ -820,6 +848,16 @@
<extends>QGraphicsView</extends>
<header>mapview.h</header>
</customwidget>
<customwidget>
<class>UIntSpinBox</class>
<extends>QAbstractSpinBox</extends>
<header>uintspinbox.h</header>
</customwidget>
<customwidget>
<class>UIntHexSpinBox</class>
<extends>UIntSpinBox</extends>
<header location="global">uintspinbox.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../resources/images.qrc"/>

View File

@ -72,6 +72,7 @@ public:
this->showTilesetEditorMetatileGrid = false;
this->showTilesetEditorLayerGrid = true;
this->showTilesetEditorDivider = false;
this->showTilesetEditorRawAttributes = false;
this->monitorFiles = true;
this->tilesetCheckerboardFill = true;
this->newMapHeaderSectionExpanded = false;
@ -132,6 +133,7 @@ public:
bool showTilesetEditorMetatileGrid;
bool showTilesetEditorLayerGrid;
bool showTilesetEditorDivider;
bool showTilesetEditorRawAttributes;
bool monitorFiles;
bool tilesetCheckerboardFill;
bool newMapHeaderSectionExpanded;
@ -246,6 +248,8 @@ enum ProjectIdentifier {
regex_sign_facing_directions,
regex_trainer_types,
regex_music,
regex_encounter_types,
regex_terrain_types,
regex_gbapal,
regex_bpp,
pals_output_extension,

View File

@ -11,27 +11,6 @@
class Project;
enum {
METATILE_LAYER_MIDDLE_TOP,
METATILE_LAYER_BOTTOM_MIDDLE,
METATILE_LAYER_BOTTOM_TOP,
NUM_METATILE_LAYER_TYPES
};
enum {
ENCOUNTER_NONE,
ENCOUNTER_LAND,
ENCOUNTER_WATER,
NUM_METATILE_ENCOUNTER_TYPES
};
enum {
TERRAIN_NONE,
TERRAIN_GRASS,
TERRAIN_WATER,
TERRAIN_WATERFALL,
NUM_METATILE_TERRAIN_TYPES
};
class Metatile
{
@ -41,6 +20,13 @@ public:
Metatile &operator=(const Metatile &other) = default;
Metatile(const int numTiles);
enum LayerType {
Normal,
Covered,
Split,
Count
};
enum Attr {
Behavior,
TerrainType,

View File

@ -45,7 +45,8 @@ public:
ParseUtil();
void set_root(const QString &dir);
static QString readTextFile(const QString &path, QString *error = nullptr);
void invalidateTextFile(const QString &path);
bool cacheFile(const QString &path, QString *error = nullptr);
void clearFileCache() { this->fileCache.clear(); }
static int textFileLineCount(const QString &path);
QList<QStringList> parseAsm(const QString &filename);
QStringList readCArray(const QString &filename, const QString &label);
@ -87,6 +88,7 @@ private:
QString text;
QString file;
QString curDefine;
QHash<QString, QString> fileCache;
QHash<QString, QStringList> errorMap;
int evaluateDefine(const QString&, const QString &, QMap<QString, int>*, QMap<QString, QString>*);
QList<Token> tokenizeExpression(QString, QMap<QString, int>*, QMap<QString, QString>*);
@ -105,6 +107,8 @@ private:
QMap<QString, int> evaluateCDefines(const QString &filename, const QSet<QString> &filterList, bool useRegex, QString *error);
bool defineNameMatchesFilter(const QString &name, const QSet<QString> &filterList) const;
bool defineNameMatchesFilter(const QString &name, const QSet<QRegularExpression> &filterList) const;
QString loadTextFile(const QString &path, QString *error = nullptr);
QString pathWithRoot(const QString &path);
static const QRegularExpression re_incScriptLabel;
static const QRegularExpression re_globalIncScriptLabel;

View File

@ -60,6 +60,8 @@ public:
QStringList globalScriptLabels;
QStringList mapSectionIdNamesSaveOrder;
QStringList mapSectionIdNames;
QMap<uint32_t, QString> encounterTypeToName;
QMap<uint32_t, QString> terrainTypeToName;
QMap<QString, MapSectionEntry> regionMapEntries;
QMap<QString, QMap<QString, uint16_t>> metatileLabelsMap;
QMap<QString, uint16_t> unusedMetatileLabels;
@ -295,6 +297,7 @@ private:
void ignoreWatchedFileTemporarily(QString filepath);
void recordFileChange(const QString &filepath);
void resetFileCache();
QString findSpeciesIconPath(const QStringList &names) const;

View File

@ -9,6 +9,7 @@
#include "tileseteditortileselector.h"
#include "metatilelayersitem.h"
class NoScrollComboBox;
class Layout;
namespace Ui {
@ -92,19 +93,10 @@ private slots:
void on_actionShow_Tileset_Divider_triggered(bool checked);
void on_actionUndo_triggered();
void on_actionRedo_triggered();
void on_comboBox_metatileBehaviors_currentTextChanged(const QString &arg1);
void on_lineEdit_metatileLabel_editingFinished();
void on_comboBox_layerType_activated(int arg1);
void on_comboBox_encounterType_activated(int arg1);
void on_comboBox_terrainType_activated(int arg1);
void on_actionExport_Primary_Tiles_Image_triggered();
void on_actionExport_Secondary_Tiles_Image_triggered();
void on_actionExport_Primary_Metatiles_Image_triggered();
@ -122,7 +114,7 @@ private slots:
void on_horizontalSlider_TilesZoom_valueChanged(int value);
private:
void setAttributesUi();
void initAttributesUi();
void initMetatileSelector();
void initTileSelector();
void initSelectedTileItem();
@ -148,6 +140,15 @@ private:
bool replaceMetatile(uint16_t metatileId, const Metatile * src, QString label);
void commitMetatileChange(Metatile * prevMetatile);
void commitMetatileAndLabelChange(Metatile * prevMetatile, QString prevLabel);
uint32_t attributeNameToValue(Metatile::Attr attribute, const QString &text, bool *ok);
void commitAttributeFromComboBox(Metatile::Attr attribute, NoScrollComboBox *combo);
void onRawAttributesEdited();
void refreshMetatileAttributes();
void commitMetatileBehavior();
void commitEncounterType();
void commitTerrainType();
void commitLayerType();
void setRawAttributesVisible(bool visible);
Ui::TilesetEditor *ui;
History<MetatileHistoryItem*> metatileHistory;

View File

@ -125,6 +125,8 @@ 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_encounter_types, {"regex_encounter_types", "\\bTILE_ENCOUNTER_"}},
{ProjectIdentifier::regex_terrain_types, {"regex_terrain_types", "\\bTILE_TERRAIN_"}},
{ProjectIdentifier::regex_gbapal, {"regex_gbapal", "\\.gbapal(\\.[\\w]+)?$"}},
{ProjectIdentifier::regex_bpp, {"regex_bpp", "\\.[\\d]+bpp(\\.[\\w]+)?$"}},
// Other
@ -386,6 +388,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
this->showTilesetEditorLayerGrid = getConfigBool(key, value);
} else if (key == "show_tileset_editor_divider") {
this->showTilesetEditorDivider = getConfigBool(key, value);
} else if (key == "show_tileset_editor_raw_attributes") {
this->showTilesetEditorRawAttributes = getConfigBool(key, value);
} else if (key == "monitor_files") {
this->monitorFiles = getConfigBool(key, value);
} else if (key == "tileset_checkerboard_fill") {
@ -495,6 +499,7 @@ QMap<QString, QString> PorymapConfig::getKeyValueMap() {
map.insert("show_tileset_editor_metatile_grid", this->showTilesetEditorMetatileGrid ? "1" : "0");
map.insert("show_tileset_editor_layer_grid", this->showTilesetEditorLayerGrid ? "1" : "0");
map.insert("show_tileset_editor_divider", this->showTilesetEditorDivider ? "1" : "0");
map.insert("show_tileset_editor_raw_attributes", this->showTilesetEditorRawAttributes ? "1" : "0");
map.insert("monitor_files", this->monitorFiles ? "1" : "0");
map.insert("tileset_checkerboard_fill", this->tilesetCheckerboardFill ? "1" : "0");
map.insert("new_map_header_section_expanded", this->newMapHeaderSectionExpanded ? "1" : "0");

View File

@ -10,11 +10,11 @@ static const QMap<Metatile::Attr, BitPacker> attributePackersFRLG = {
{Metatile::Attr::TerrainType, BitPacker(0x00003E00) },
{Metatile::Attr::EncounterType, BitPacker(0x07000000) },
{Metatile::Attr::LayerType, BitPacker(0x60000000) },
//{Metatile::Attr::Unused, BitPacker(0x98FFC000) },
{Metatile::Attr::Unused, BitPacker(0x98FFC000) },
};
static const QMap<Metatile::Attr, BitPacker> attributePackersRSE = {
{Metatile::Attr::Behavior, BitPacker(0x00FF) },
//{Metatile::Attr::Unused, BitPacker(0x0F00) },
{Metatile::Attr::Unused, BitPacker(0x0F00) },
{Metatile::Attr::LayerType, BitPacker(0xF000) },
};
@ -128,34 +128,39 @@ void Metatile::setLayout(Project * project) {
if (behaviorMask && !project->metatileBehaviorMapInverse.isEmpty()) {
uint32_t maxBehavior = project->metatileBehaviorMapInverse.lastKey();
if (packer.clamp(maxBehavior) != maxBehavior)
logWarn(QString("Metatile Behavior mask '%1' is insufficient to contain all available options.")
.arg(Util::toHexString(behaviorMask)));
logWarn(QString("Metatile Behavior mask '%1' is insufficient to contain largest value '%2'.")
.arg(Util::toHexString(behaviorMask))
.arg(Util::toHexString(maxBehavior)));
}
attributePackers.insert(Metatile::Attr::Behavior, packer);
// Validate terrain type mask
packer.setMask(terrainTypeMask);
const uint32_t maxTerrainType = NUM_METATILE_TERRAIN_TYPES - 1;
if (terrainTypeMask && packer.clamp(maxTerrainType) != maxTerrainType) {
logWarn(QString("Metatile Terrain Type mask '%1' is insufficient to contain all %2 available options.")
if (terrainTypeMask && !project->terrainTypeToName.isEmpty()) {
uint32_t maxTerrainType = project->terrainTypeToName.lastKey();
if (packer.clamp(maxTerrainType) != maxTerrainType) {
logWarn(QString("Metatile Terrain Type mask '%1' is insufficient to contain largest value '%2'.")
.arg(Util::toHexString(terrainTypeMask))
.arg(maxTerrainType + 1));
.arg(Util::toHexString(maxTerrainType)));
}
}
attributePackers.insert(Metatile::Attr::TerrainType, packer);
// Validate encounter type mask
packer.setMask(encounterTypeMask);
const uint32_t maxEncounterType = NUM_METATILE_ENCOUNTER_TYPES - 1;
if (encounterTypeMask && packer.clamp(maxEncounterType) != maxEncounterType) {
logWarn(QString("Metatile Encounter Type mask '%1' is insufficient to contain all %2 available options.")
if (encounterTypeMask && !project->encounterTypeToName.isEmpty()) {
uint32_t maxEncounterType = project->encounterTypeToName.lastKey();
if (packer.clamp(maxEncounterType) != maxEncounterType) {
logWarn(QString("Metatile Encounter Type mask '%1' is insufficient to contain largest value '%2'.")
.arg(Util::toHexString(encounterTypeMask))
.arg(maxEncounterType + 1));
.arg(Util::toHexString(maxEncounterType)));
}
}
attributePackers.insert(Metatile::Attr::EncounterType, packer);
// Validate terrain type mask
// Validate layer type mask
packer.setMask(layerTypeMask);
const uint32_t maxLayerType = NUM_METATILE_LAYER_TYPES - 1;
const uint32_t maxLayerType = Metatile::LayerType::Count - 1;
if (layerTypeMask && packer.clamp(maxLayerType) != maxLayerType) {
logWarn(QString("Metatile Layer Type mask '%1' is insufficient to contain all %2 available options.")
.arg(Util::toHexString(layerTypeMask))

View File

@ -37,6 +37,12 @@ void ParseUtil::set_root(const QString &dir) {
this->root = dir;
}
QString ParseUtil::pathWithRoot(const QString &path) {
if (this->root.isEmpty()) return path;
if (path.startsWith(this->root)) return path;
return QString("%1/%2").arg(this->root).arg(path);
}
void ParseUtil::recordError(const QString &message) {
this->errorMap[this->curDefine].append(message);
}
@ -85,6 +91,23 @@ QString ParseUtil::readTextFile(const QString &path, QString *error) {
return text;
}
// Load the specified text file, either from the cache or by reading the file.
// Note that this doesn't insert any parsed files into the file cache, and we don't
// want it to (we read a lot of files only once, storing them all is a waste of memory).
QString ParseUtil::loadTextFile(const QString &path, QString *error) {
auto it = this->fileCache.constFind(path);
if (it != this->fileCache.constEnd()) {
// Load text file from cache
return it.value();
}
return readTextFile(pathWithRoot(path), error);
}
bool ParseUtil::cacheFile(const QString &path, QString *error) {
this->fileCache.insert(path, readTextFile(pathWithRoot(path), error));
return !error || error->isEmpty();
}
int ParseUtil::textFileLineCount(const QString &path) {
const QString text = readTextFile(path);
return text.split('\n').count() + 1;
@ -93,7 +116,7 @@ int ParseUtil::textFileLineCount(const QString &path) {
QList<QStringList> ParseUtil::parseAsm(const QString &filename) {
QList<QStringList> parsed;
this->text = readTextFile(this->root + '/' + filename);
this->text = loadTextFile(filename);
const QStringList lines = removeLineComments(this->text, "@").split('\n');
for (const auto &line : lines) {
const QString trimmedLine = line.trimmed();
@ -295,7 +318,7 @@ QString ParseUtil::readCIncbin(const QString &filename, const QString &label) {
return path;
}
this->text = readTextFile(this->root + "/" + filename);
this->text = loadTextFile(filename);
QRegularExpression re(QString(
"\\b%1\\b"
@ -316,7 +339,7 @@ QMap<QString, QString> ParseUtil::readCIncbinMulti(const QString &filepath) {
QMap<QString, QString> incbinMap;
this->file = filepath;
this->text = readTextFile(this->root + "/" + filepath);
this->text = loadTextFile(filepath);
static const QRegularExpression regex("(?<label>[A-Za-z0-9_]+)\\s*\\[?\\s*\\]?\\s*=\\s*INCBIN_[US][0-9][0-9]?\\(\\s*\\\"(?<path>[^\\\\\"]*)\\\"\\s*\\)");
@ -338,7 +361,7 @@ QStringList ParseUtil::readCIncbinArray(const QString &filename, const QString &
return paths;
}
this->text = readTextFile(this->root + "/" + filename);
this->text = loadTextFile(filename);
bool found = false;
QString arrayText;
@ -388,8 +411,7 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const
return result;
}
QString filepath = this->root + "/" + this->file;
this->text = readTextFile(filepath, error);
this->text = loadTextFile(filename, error);
if (this->text.isNull())
return result;
@ -507,7 +529,7 @@ QStringList ParseUtil::readCArray(const QString &filename, const QString &label)
}
this->file = filename;
this->text = readTextFile(this->root + "/" + filename);
this->text = loadTextFile(filename);
QRegularExpression re(QString(R"(\b%1\b\s*(\[?[^\]]*\])?\s*=\s*\{([^\}]*)\})").arg(label));
QRegularExpressionMatch match = re.match(this->text);
@ -530,7 +552,7 @@ QMap<QString, QStringList> ParseUtil::readCArrayMulti(const QString &filename) {
QMap<QString, QStringList> map;
this->file = filename;
this->text = readTextFile(this->root + "/" + filename);
this->text = loadTextFile(filename);
static const QRegularExpression regex(R"((?<label>\b[A-Za-z0-9_]+\b)\s*(\[[^\]]*\])?\s*=\s*\{(?<body>[^\}]*)\})");
@ -556,7 +578,7 @@ QMap<QString, QStringList> ParseUtil::readCArrayMulti(const QString &filename) {
}
QMap<QString, QString> ParseUtil::readNamedIndexCArray(const QString &filename, const QString &label, QString *error) {
this->text = readTextFile(this->root + "/" + filename, error);
this->text = loadTextFile(filename, error);
QMap<QString, QString> map;
QRegularExpression re_text(QString(R"(\b%1\b\s*(\[?[^\]]*\])?\s*=\s*\{([^\}]*)\})").arg(label));
@ -589,7 +611,7 @@ bool ParseUtil::gameStringToBool(const QString &gameString, bool * ok) {
}
tsl::ordered_map<QString, QHash<QString, QString>> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash<int, QString> &memberMap) {
QString filePath = this->root + "/" + filename;
QString filePath = pathWithRoot(filename);
auto cParser = fex::Parser();
auto tokens = fex::Lexer().LexFile(filePath);
auto topLevelObjects = cParser.ParseTopLevelObjects(tokens);
@ -657,7 +679,7 @@ QStringList ParseUtil::getLabelValues(const QList<QStringList> &list, const QStr
}
bool ParseUtil::tryParseJsonFile(QJsonDocument *out, const QString &filepath, QString *error) {
QFile file(filepath);
QFile file(pathWithRoot(filepath));
if (!file.open(QIODevice::ReadOnly)) {
if (error) *error = file.errorString();
return false;
@ -677,7 +699,7 @@ bool ParseUtil::tryParseJsonFile(QJsonDocument *out, const QString &filepath, QS
}
bool ParseUtil::tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath, QString *error) {
QString jsonTxt = readTextFile(filepath, error);
QString jsonTxt = loadTextFile(filepath, error);
if (error && !error->isEmpty()) {
return false;
}

View File

@ -78,6 +78,7 @@ bool Project::sanityCheck() {
}
bool Project::load() {
resetFileCache();
this->disabledSettingsNames.clear();
bool success = readMapLayouts()
&& readRegionMapSections()
@ -119,6 +120,27 @@ bool Project::load() {
return success;
}
void Project::resetFileCache() {
this->parser.clearFileCache();
const QSet<QString> filepaths = {
// Whenever we load a tileset we'll need to parse some data from these files, so we cache them to avoid the overhead of opening the files.
// We don't know yet whether the project uses C or asm tileset data, so try to cache both (we'll ignore errors from missing files).
projectConfig.getFilePath(ProjectFilePath::tilesets_headers_asm),
projectConfig.getFilePath(ProjectFilePath::tilesets_graphics_asm),
projectConfig.getFilePath(ProjectFilePath::tilesets_metatiles_asm),
projectConfig.getFilePath(ProjectFilePath::tilesets_headers),
projectConfig.getFilePath(ProjectFilePath::tilesets_graphics),
projectConfig.getFilePath(ProjectFilePath::tilesets_metatiles),
// We need separate sets of constants from these files
projectConfig.getFilePath(ProjectFilePath::constants_map_types),
projectConfig.getFilePath(ProjectFilePath::global_fieldmap),
};
for (const auto &path : filepaths) {
this->parser.cacheFile(path);
}
}
QString Project::getProjectTitle() const {
if (!root.isNull()) {
return root.section('/', -1);
@ -189,7 +211,7 @@ void Project::initTopLevelMapFields() {
bool Project::readMapJson(const QString &mapName, QJsonDocument * out) {
const QString mapFilepath = QString("%1%2/map.json").arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)).arg(mapName);
QString error;
if (!parser.tryParseJsonFile(out, QString("%1/%2").arg(this->root).arg(mapFilepath), &error)) {
if (!parser.tryParseJsonFile(out, mapFilepath, &error)) {
logError(QString("Failed to read map data from '%1': %2").arg(mapFilepath).arg(error));
return false;
}
@ -457,12 +479,11 @@ bool Project::readMapLayouts() {
clearMapLayouts();
const QString layoutsFilepath = projectConfig.getFilePath(ProjectFilePath::json_layouts);
const QString fullFilepath = QString("%1/%2").arg(this->root).arg(layoutsFilepath);
fileWatcher.addPath(fullFilepath);
fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(layoutsFilepath));
QJsonDocument layoutsDoc;
QString error;
if (!parser.tryParseJsonFile(&layoutsDoc, fullFilepath, &error)) {
logError(QString("Failed to read map layouts from '%1': %2").arg(fullFilepath).arg(error));
if (!parser.tryParseJsonFile(&layoutsDoc, layoutsFilepath, &error)) {
logError(QString("Failed to read map layouts from '%1': %2").arg(layoutsFilepath).arg(error));
return false;
}
@ -961,9 +982,6 @@ bool Project::loadLayoutTilesets(Layout *layout) {
return true;
}
// TODO: We are parsing the tileset headers file whenever we load a tileset for the first time.
// At a minimum this means we're parsing the file three times per session (twice here for the first map's tilesets, once on launch in Project::readTilesetLabels).
// We can cache the header data instead and only parse it once on launch.
Tileset* Project::loadTileset(QString label, Tileset *tileset) {
auto memberMap = Tileset::getHeaderMemberMap(this->usingAsmTilesets);
if (this->usingAsmTilesets) {
@ -1612,15 +1630,14 @@ bool Project::readWildMonData() {
this->pokemonMaxLevel = qMax(this->pokemonMinLevel, this->pokemonMaxLevel);
// Read encounter data
const QString wildMonJsonBaseFilepath = projectConfig.getFilePath(ProjectFilePath::json_wild_encounters);
QString wildMonJsonFilepath = QString("%1/%2").arg(root).arg(wildMonJsonBaseFilepath);
fileWatcher.addPath(wildMonJsonFilepath);
const QString wildMonJsonFilepath = projectConfig.getFilePath(ProjectFilePath::json_wild_encounters);
fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(wildMonJsonFilepath));
OrderedJson::object wildMonObj;
QString error;
if (!parser.tryParseOrderedJsonFile(&wildMonObj, wildMonJsonFilepath, &error)) {
// Failing to read wild encounters data is not a critical error, the encounter editor will just be disabled
logWarn(QString("Failed to read wild encounters from '%1': %2").arg(wildMonJsonBaseFilepath).arg(error));
logWarn(QString("Failed to read wild encounters from '%1': %2").arg(wildMonJsonFilepath).arg(error));
return true;
}
@ -1746,8 +1763,8 @@ bool Project::readMapGroups() {
this->initTopLevelMapFields();
const QString filepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::json_map_groups);
fileWatcher.addPath(filepath);
const QString filepath = projectConfig.getFilePath(ProjectFilePath::json_map_groups);
fileWatcher.addPath(root + "/" + filepath);
QJsonDocument mapGroupsDoc;
QString error;
if (!parser.tryParseJsonFile(&mapGroupsDoc, filepath, &error)) {
@ -2159,6 +2176,9 @@ bool Project::readFieldmapProperties() {
// Read data masks for Blocks and metatile attributes.
bool Project::readFieldmapMasks() {
this->encounterTypeToName.clear();
this->terrainTypeToName.clear();
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);
@ -2171,7 +2191,7 @@ bool Project::readFieldmapMasks() {
behaviorMaskName,
layerTypeMaskName,
};
QString globalFieldmap = projectConfig.getFilePath(ProjectFilePath::global_fieldmap);
const QString globalFieldmap = projectConfig.getFilePath(ProjectFilePath::global_fieldmap);
fileWatcher.addPath(root + "/" + globalFieldmap);
QMap<QString, int> defines = parser.readCDefinesByName(globalFieldmap, searchNames);
@ -2255,6 +2275,32 @@ bool Project::readFieldmapMasks() {
}
}
}
// Read #defines for encounter and terrain types to populate in the Tileset Editor dropdowns (if necessary)
QString error;
if (projectConfig.metatileEncounterTypeMask) {
QMap<QString, int> defines = parser.readCDefinesByRegex(globalFieldmap, {projectConfig.getIdentifier(ProjectIdentifier::regex_encounter_types)}, &error);
if (!error.isEmpty()) {
logWarn(QString("Failed to read encounter type constants from '%1': %2").arg(globalFieldmap).arg(error));
error = QString();
} else {
for (auto i = defines.constBegin(); i != defines.constEnd(); i++) {
this->encounterTypeToName.insert(static_cast<uint32_t>(i.value()), i.key());
}
}
}
if (projectConfig.metatileTerrainTypeMask) {
QMap<QString, int> defines = parser.readCDefinesByRegex(globalFieldmap, {projectConfig.getIdentifier(ProjectIdentifier::regex_terrain_types)}, &error);
if (!error.isEmpty()) {
logWarn(QString("Failed to read terrain type constants from '%1': %2").arg(globalFieldmap).arg(error));
error = QString();
} else {
for (auto i = defines.constBegin(); i != defines.constEnd(); i++) {
this->terrainTypeToName.insert(static_cast<uint32_t>(i.value()), i.key());
}
}
}
return true;
}
@ -2267,14 +2313,13 @@ bool Project::readRegionMapSections() {
const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix);
QJsonDocument doc;
const QString baseFilepath = projectConfig.getFilePath(ProjectFilePath::json_region_map_entries);
const QString filepath = QString("%1/%2").arg(this->root).arg(baseFilepath);
const QString filepath = projectConfig.getFilePath(ProjectFilePath::json_region_map_entries);
QString error;
if (!parser.tryParseJsonFile(&doc, filepath, &error)) {
logError(QString("Failed to read region map sections from '%1': %2").arg(baseFilepath).arg(error));
logError(QString("Failed to read region map sections from '%1': %2").arg(filepath).arg(error));
return false;
}
fileWatcher.addPath(filepath);
fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(filepath));
QJsonArray mapSections = doc.object()["map_sections"].toArray();
for (int i = 0; i < mapSections.size(); i++) {
@ -2290,13 +2335,13 @@ bool Project::readRegionMapSections() {
// ignoring everything here and then wiping the file's data when we save later.
idField = oldIdField;
} else {
logWarn(QString("Ignoring data for map section %1 in '%2'. Missing required field \"%3\"").arg(i).arg(baseFilepath).arg(idField));
logWarn(QString("Ignoring data for map section %1 in '%2'. Missing required field \"%3\"").arg(i).arg(filepath).arg(idField));
continue;
}
}
const QString idName = ParseUtil::jsonToQString(mapSectionObj[idField]);
if (!idName.startsWith(requiredPrefix)) {
logWarn(QString("Ignoring data for map section '%1' in '%2'. IDs must start with the prefix '%3'").arg(idName).arg(baseFilepath).arg(requiredPrefix));
logWarn(QString("Ignoring data for map section '%1' in '%2'. IDs must start with the prefix '%3'").arg(idName).arg(filepath).arg(requiredPrefix));
continue;
}
@ -2406,21 +2451,20 @@ bool Project::readHealLocations() {
clearHealLocations();
QJsonDocument doc;
const QString baseFilepath = projectConfig.getFilePath(ProjectFilePath::json_heal_locations);
const QString filepath = QString("%1/%2").arg(this->root).arg(baseFilepath);
const QString filepath = projectConfig.getFilePath(ProjectFilePath::json_heal_locations);
QString error;
if (!parser.tryParseJsonFile(&doc, filepath, &error)) {
logError(QString("Failed to read heal locations from '%1': %2").arg(baseFilepath).arg(error));
logError(QString("Failed to read heal locations from '%1': %2").arg(filepath).arg(error));
return false;
}
fileWatcher.addPath(filepath);
fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(filepath));
QJsonArray healLocations = doc.object()["heal_locations"].toArray();
for (int i = 0; i < healLocations.size(); i++) {
QJsonObject healLocationObj = healLocations.at(i).toObject();
static const QString mapField = QStringLiteral("map");
if (!healLocationObj.contains(mapField)) {
logWarn(QString("Ignoring data for heal location %1 in '%2'. Missing required field \"%3\"").arg(i).arg(baseFilepath).arg(mapField));
logWarn(QString("Ignoring data for heal location %1 in '%2'. Missing required field \"%3\"").arg(i).arg(filepath).arg(mapField));
continue;
}

View File

@ -72,19 +72,19 @@ QImage getMetatileImage(
switch (layerType)
{
default:
case METATILE_LAYER_MIDDLE_TOP:
case Metatile::LayerType::Normal:
if (l == 0)
tile = Tile(projectConfig.unusedTileNormal);
else // Tiles are on layers 1 and 2
tile = metatile->tiles.value(tileOffset + ((l - 1) * 4));
break;
case METATILE_LAYER_BOTTOM_MIDDLE:
case Metatile::LayerType::Covered:
if (l == 2)
tile = Tile(projectConfig.unusedTileCovered);
else // Tiles are on layers 0 and 1
tile = metatile->tiles.value(tileOffset + (l * 4));
break;
case METATILE_LAYER_BOTTOM_TOP:
case Metatile::LayerType::Split:
if (l == 1)
tile = Tile(projectConfig.unusedTileSplit);
else // Tiles are on layers 0 and 2

View File

@ -32,6 +32,8 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
this->paletteId = ui->spinBox_paletteSelector->value();
ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider);
ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes);
ui->spinBox_paletteSelector->setMinimum(0);
ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1);
@ -43,7 +45,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
connect(filter, &ActiveWindowFilter::activated, this, &TilesetEditor::onWindowActivated);
this->installEventFilter(filter);
setAttributesUi();
initAttributesUi();
initMetatileSelector();
initMetatileLayersItem();
initTileSelector();
@ -120,7 +122,18 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi
this->initMetatileHistory();
}
void TilesetEditor::setAttributesUi() {
void TilesetEditor::initAttributesUi() {
// Update the metatile's attributes values when the attribute combo boxes are edited.
// We avoid using the 'currentTextChanged' signal here, we want to know when we can clean up the input field and commit changes.
connect(ui->comboBox_metatileBehaviors->lineEdit(), &QLineEdit::editingFinished, this, &TilesetEditor::commitMetatileBehavior);
connect(ui->comboBox_encounterType->lineEdit(), &QLineEdit::editingFinished, this, &TilesetEditor::commitEncounterType);
connect(ui->comboBox_terrainType->lineEdit(), &QLineEdit::editingFinished, this, &TilesetEditor::commitTerrainType);
connect(ui->comboBox_layerType->lineEdit(), &QLineEdit::editingFinished, this, &TilesetEditor::commitLayerType);
connect(ui->comboBox_metatileBehaviors, QOverload<int>::of(&QComboBox::activated), this, &TilesetEditor::commitMetatileBehavior);
connect(ui->comboBox_encounterType, QOverload<int>::of(&QComboBox::activated), this, &TilesetEditor::commitEncounterType);
connect(ui->comboBox_terrainType, QOverload<int>::of(&QComboBox::activated), this, &TilesetEditor::commitTerrainType);
connect(ui->comboBox_layerType, QOverload<int>::of(&QComboBox::activated), this, &TilesetEditor::commitLayerType);
// Behavior
if (projectConfig.metatileBehaviorMask) {
for (auto i = project->metatileBehaviorMapInverse.constBegin(); i != project->metatileBehaviorMapInverse.constEnd(); i++) {
@ -134,11 +147,9 @@ void TilesetEditor::setAttributesUi() {
// Terrain Type
if (projectConfig.metatileTerrainTypeMask) {
this->ui->comboBox_terrainType->addItem("Normal", TERRAIN_NONE);
this->ui->comboBox_terrainType->addItem("Grass", TERRAIN_GRASS);
this->ui->comboBox_terrainType->addItem("Water", TERRAIN_WATER);
this->ui->comboBox_terrainType->addItem("Waterfall", TERRAIN_WATERFALL);
this->ui->comboBox_terrainType->setEditable(false);
for (auto i = project->terrainTypeToName.constBegin(); i != project->terrainTypeToName.constEnd(); i++) {
this->ui->comboBox_terrainType->addItem(i.value(), i.key());
}
this->ui->comboBox_terrainType->setMinimumContentsLength(0);
} else {
this->ui->comboBox_terrainType->setVisible(false);
@ -147,10 +158,9 @@ void TilesetEditor::setAttributesUi() {
// Encounter Type
if (projectConfig.metatileEncounterTypeMask) {
this->ui->comboBox_encounterType->addItem("None", ENCOUNTER_NONE);
this->ui->comboBox_encounterType->addItem("Land", ENCOUNTER_LAND);
this->ui->comboBox_encounterType->addItem("Water", ENCOUNTER_WATER);
this->ui->comboBox_encounterType->setEditable(false);
for (auto i = project->encounterTypeToName.constBegin(); i != project->encounterTypeToName.constEnd(); i++) {
this->ui->comboBox_encounterType->addItem(i.value(), i.key());
}
this->ui->comboBox_encounterType->setMinimumContentsLength(0);
} else {
this->ui->comboBox_encounterType->setVisible(false);
@ -159,9 +169,9 @@ void TilesetEditor::setAttributesUi() {
// Layer Type
if (!projectConfig.tripleLayerMetatilesEnabled) {
this->ui->comboBox_layerType->addItem("Normal - Middle/Top", METATILE_LAYER_MIDDLE_TOP);
this->ui->comboBox_layerType->addItem("Covered - Bottom/Middle", METATILE_LAYER_BOTTOM_MIDDLE);
this->ui->comboBox_layerType->addItem("Split - Bottom/Top", METATILE_LAYER_BOTTOM_TOP);
this->ui->comboBox_layerType->addItem("Normal - Middle/Top", Metatile::LayerType::Normal);
this->ui->comboBox_layerType->addItem("Covered - Bottom/Middle", Metatile::LayerType::Covered);
this->ui->comboBox_layerType->addItem("Split - Bottom/Top", Metatile::LayerType::Split);
this->ui->comboBox_layerType->setEditable(false);
this->ui->comboBox_layerType->setMinimumContentsLength(0);
if (!projectConfig.metatileLayerTypeMask) {
@ -176,9 +186,22 @@ void TilesetEditor::setAttributesUi() {
this->ui->label_layerType->setVisible(false);
this->ui->label_BottomTop->setText("Bottom/Middle/Top");
}
// Raw attributes value
ui->spinBox_rawAttributesValue->setMaximum(Metatile::getMaxAttributesMask());
setRawAttributesVisible(ui->actionShow_Raw_Metatile_Attributes->isChecked());
connect(ui->spinBox_rawAttributesValue, &UIntHexSpinBox::editingFinished, this, &TilesetEditor::onRawAttributesEdited);
connect(ui->actionShow_Raw_Metatile_Attributes, &QAction::toggled, this, &TilesetEditor::setRawAttributesVisible);
this->ui->frame_Properties->adjustSize();
}
void TilesetEditor::setRawAttributesVisible(bool visible) {
porymapConfig.showTilesetEditorRawAttributes = visible;
ui->label_rawAttributesValue->setVisible(visible);
ui->spinBox_rawAttributesValue->setVisible(visible);
}
void TilesetEditor::initMetatileSelector()
{
this->metatileSelector = new TilesetEditorMetatileSelector(this->primaryTileset, this->secondaryTileset, this->layout);
@ -413,10 +436,7 @@ void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) {
this->ui->lineEdit_metatileLabel->setText(labels.owned);
this->ui->lineEdit_metatileLabel->setPlaceholderText(labels.shared);
this->ui->comboBox_metatileBehaviors->setHexItem(this->metatile->behavior());
this->ui->comboBox_layerType->setHexItem(this->metatile->layerType());
this->ui->comboBox_encounterType->setHexItem(this->metatile->encounterType());
this->ui->comboBox_terrainType->setHexItem(this->metatile->terrainType());
refreshMetatileAttributes();
}
void TilesetEditor::queueMetatileReload(uint16_t metatileId) {
@ -536,30 +556,6 @@ void TilesetEditor::on_checkBox_yFlip_stateChanged(int checked)
this->metatileLayersItem->clearLastModifiedCoords();
}
void TilesetEditor::on_comboBox_metatileBehaviors_currentTextChanged(const QString &metatileBehavior)
{
if (this->metatile) {
uint32_t behavior;
if (project->metatileBehaviorMap.contains(metatileBehavior)) {
behavior = project->metatileBehaviorMap[metatileBehavior];
} else {
// Check if user has entered a number value instead
bool ok;
behavior = metatileBehavior.toUInt(&ok, 0);
if (!ok) return;
}
// This function can also be called when the user selects
// a different metatile. Stop this from being considered a change.
if (this->metatile->behavior() == behavior)
return;
Metatile *prevMetatile = new Metatile(*this->metatile);
this->metatile->setBehavior(behavior);
this->commitMetatileChange(prevMetatile);
}
}
void TilesetEditor::setMetatileLabel(QString label)
{
this->ui->lineEdit_metatileLabel->setText(label);
@ -597,32 +593,92 @@ void TilesetEditor::commitMetatileChange(Metatile * prevMetatile)
this->commitMetatileAndLabelChange(prevMetatile, this->ui->lineEdit_metatileLabel->text());
}
void TilesetEditor::on_comboBox_layerType_activated(int layerType)
{
if (this->metatile) {
Metatile *prevMetatile = new Metatile(*this->metatile);
this->metatile->setLayerType(layerType);
this->commitMetatileChange(prevMetatile);
this->metatileSelector->drawSelectedMetatile(); // Changing the layer type can affect how fully transparent metatiles appear
uint32_t TilesetEditor::attributeNameToValue(Metatile::Attr attribute, const QString &text, bool *ok) {
if (ok) *ok = true;
if (attribute == Metatile::Attr::Behavior) {
auto it = project->metatileBehaviorMap.constFind(text);
if (it != project->metatileBehaviorMap.constEnd())
return it.value();
} else if (attribute == Metatile::Attr::EncounterType) {
for (auto i = project->encounterTypeToName.constBegin(); i != project->encounterTypeToName.constEnd(); i++) {
if (i.value() == text) return i.key();
}
} else if (attribute == Metatile::Attr::TerrainType) {
for (auto i = project->terrainTypeToName.constBegin(); i != project->terrainTypeToName.constEnd(); i++) {
if (i.value() == text) return i.key();
}
} else if (attribute == Metatile::Attr::LayerType) {
// The layer type text is not editable, it uses special display names. Just get the index of the display name.
int i = ui->comboBox_layerType->findText(text);
if (i >= 0) return i;
}
return text.toUInt(ok, 0);
}
void TilesetEditor::on_comboBox_encounterType_activated(int encounterType)
{
if (this->metatile) {
void TilesetEditor::commitAttributeFromComboBox(Metatile::Attr attribute, NoScrollComboBox *combo) {
if (!this->metatile)
return;
bool ok;
uint32_t newValue = this->attributeNameToValue(attribute, combo->currentText(), &ok);
if (ok && newValue != this->metatile->getAttribute(attribute)) {
Metatile *prevMetatile = new Metatile(*this->metatile);
this->metatile->setEncounterType(encounterType);
this->metatile->setAttribute(attribute, newValue);
this->commitMetatileChange(prevMetatile);
// When an attribute changes we also need to update the raw value display.
const QSignalBlocker b_RawAttributesValue(ui->spinBox_rawAttributesValue);
ui->spinBox_rawAttributesValue->setValue(this->metatile->getAttributes());
}
// Update the text in the combo box to reflect the final value.
// The text may change if the input text was invalid, the value was too large to fit, or if a number was entered that we know an identifier for.
const QSignalBlocker b(combo);
combo->setHexItem(this->metatile->getAttribute(attribute));
}
void TilesetEditor::on_comboBox_terrainType_activated(int terrainType)
{
if (this->metatile) {
void TilesetEditor::onRawAttributesEdited() {
uint32_t newAttributes = ui->spinBox_rawAttributesValue->value();
if (newAttributes != this->metatile->getAttributes()) {
Metatile *prevMetatile = new Metatile(*this->metatile);
this->metatile->setTerrainType(terrainType);
this->metatile->setAttributes(newAttributes);
this->commitMetatileChange(prevMetatile);
}
refreshMetatileAttributes();
}
void TilesetEditor::refreshMetatileAttributes() {
if (!this->metatile) return;
const QSignalBlocker b_MetatileBehaviors(ui->comboBox_metatileBehaviors);
const QSignalBlocker b_EncounterType(ui->comboBox_encounterType);
const QSignalBlocker b_TerrainType(ui->comboBox_terrainType);
const QSignalBlocker b_LayerType(ui->comboBox_layerType);
const QSignalBlocker b_RawAttributesValue(ui->spinBox_rawAttributesValue);
ui->comboBox_metatileBehaviors->setHexItem(this->metatile->behavior());
ui->comboBox_encounterType->setHexItem(this->metatile->encounterType());
ui->comboBox_terrainType->setHexItem(this->metatile->terrainType());
ui->comboBox_layerType->setHexItem(this->metatile->layerType());
ui->spinBox_rawAttributesValue->setValue(this->metatile->getAttributes());
this->metatileSelector->drawSelectedMetatile();
}
void TilesetEditor::commitMetatileBehavior() {
commitAttributeFromComboBox(Metatile::Attr::Behavior, ui->comboBox_metatileBehaviors);
}
void TilesetEditor::commitEncounterType() {
commitAttributeFromComboBox(Metatile::Attr::EncounterType, ui->comboBox_encounterType);
}
void TilesetEditor::commitTerrainType() {
commitAttributeFromComboBox(Metatile::Attr::TerrainType, ui->comboBox_terrainType);
};
void TilesetEditor::commitLayerType() {
commitAttributeFromComboBox(Metatile::Attr::LayerType, ui->comboBox_layerType);
this->metatileSelector->drawSelectedMetatile(); // Changing the layer type can affect how fully transparent metatiles appear
}
void TilesetEditor::on_actionSave_Tileset_triggered()