diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui
index 02ecf34c..26813862 100644
--- a/forms/projectsettingseditor.ui
+++ b/forms/projectsettingseditor.ui
@@ -21,7 +21,7 @@
-
- 1
+ 0
@@ -370,7 +370,7 @@
0
0
531
- 545
+ 587
@@ -603,20 +603,6 @@
- -
-
-
- Collision
-
-
-
- -
-
-
- Elevation
-
-
-
-
@@ -624,6 +610,13 @@
+ -
+
+
+ Collision
+
+
+
-
@@ -631,6 +624,13 @@
+ -
+
+
+ Elevation
+
+
+
-
@@ -638,6 +638,19 @@
+ -
+
+
+ color : red;
+
+
+ These masks have overlapping bits. This may result in unexpected value changes.
+
+
+ true
+
+
+
@@ -731,7 +744,7 @@
0
0
528
- 522
+ 568
@@ -799,10 +812,17 @@
Metatiles
- -
-
-
- Layer Type mask
+
-
+
+
+ The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled.
+
+
+
+ -
+
+
+ The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled.
@@ -816,24 +836,33 @@
- -
-
-
- false
+
-
+
+
+ Qt::Vertical
-
-
- -
-
-
- The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled.
+
+ QSizePolicy::Maximum
-
+
+
+ 20
+ 10
+
+
+
- -
-
+
-
+
- Behavior mask
+ Encounter Type mask
+
+
+
+ -
+
+
+ Enable Triple Layer Metatiles
@@ -844,18 +873,46 @@
- -
-
-
- Qt::Vertical
+
-
+
+
+ Behavior mask
-
-
- 20
- 15
-
+
+
+ -
+
+
+ color : red;
-
+
+ These masks have overlapping bits. This may result in unexpected value changes.
+
+
+ true
+
+
+
+ -
+
+
+ The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled.
+
+
+
+ -
+
+
+ Layer Type mask
+
+
+
+ -
+
+
+ false
+
+
-
@@ -864,33 +921,21 @@
- -
-
-
- Encounter Type mask
+
-
+
+
+ Qt::Vertical
-
-
- -
-
-
- The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled.
+
+ QSizePolicy::MinimumExpanding
-
-
- -
-
-
- The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled.
+
+
+ 20
+ 1
+
-
-
- -
-
-
- Enable Triple Layer Metatiles
-
-
+
diff --git a/include/config.h b/include/config.h
index 7605f0d6..1a6e0ae8 100644
--- a/include/config.h
+++ b/include/config.h
@@ -286,10 +286,10 @@ public:
int getNumTilesInMetatile();
void setDefaultMetatileId(uint16_t metatileId);
uint16_t getDefaultMetatileId();
- void setDefaultElevation(int elevation);
- int getDefaultElevation();
- void setDefaultCollision(int collision);
- int getDefaultCollision();
+ void setDefaultElevation(uint16_t elevation);
+ uint16_t getDefaultElevation();
+ void setDefaultCollision(uint16_t collision);
+ uint16_t getDefaultCollision();
void setNewMapBorderMetatileIds(QList metatileIds);
QList getNewMapBorderMetatileIds();
QString getDefaultPrimaryTileset();
diff --git a/include/core/bitpacker.h b/include/core/bitpacker.h
index e6f6e845..4e3767e4 100644
--- a/include/core/bitpacker.h
+++ b/include/core/bitpacker.h
@@ -2,7 +2,6 @@
#define BITPACKER_H
#include
-//#include
class BitPacker
{
diff --git a/include/core/metatile.h b/include/core/metatile.h
index 5ef86a3e..1570e350 100644
--- a/include/core/metatile.h
+++ b/include/core/metatile.h
@@ -11,7 +11,6 @@
class Project;
-// TODO: Reevaluate enums
enum {
METATILE_LAYER_MIDDLE_TOP,
METATILE_LAYER_BOTTOM_MIDDLE,
@@ -72,6 +71,7 @@ public:
static int getIndexInTileset(int);
static QPoint coordFromPixmapCoord(const QPointF &pixelCoord);
static uint32_t getDefaultAttributesMask(BaseGameVersion version, Metatile::Attr attr);
+ static uint32_t getMaxAttributesMask();
static int getDefaultAttributesSize(BaseGameVersion version);
static void setLayout(Project*);
static QString getMetatileIdString(uint16_t metatileId) {
diff --git a/include/mainwindow.h b/include/mainwindow.h
index 9c9d270b..6c534dee 100644
--- a/include/mainwindow.h
+++ b/include/mainwindow.h
@@ -372,7 +372,7 @@ private:
void initMapSortOrder();
void initShortcuts();
void initExtraShortcuts();
- bool setProjectSpecificUI();
+ void setProjectSpecificUI();
void setWildEncountersUIEnabled(bool enabled);
void loadUserSettings();
void applyMapListFilter(QString filterText);
diff --git a/include/project.h b/include/project.h
index ee727f07..2428aa89 100644
--- a/include/project.h
+++ b/include/project.h
@@ -216,6 +216,7 @@ public:
QString getDefaultSecondaryTilesetLabel();
void setImportExportPath(QString filename);
+ void applyParsedLimits();
static int getNumTilesPrimary();
static int getNumTilesTotal();
diff --git a/include/ui/projectsettingseditor.h b/include/ui/projectsettingseditor.h
index 2510e788..75843886 100644
--- a/include/ui/projectsettingseditor.h
+++ b/include/ui/projectsettingseditor.h
@@ -3,6 +3,7 @@
#include
#include "project.h"
+#include "ui_projectsettingseditor.h"
class NoScrollComboBox;
class QAbstractButton;
@@ -55,6 +56,7 @@ private:
void chooseFile(QLineEdit * filepathEdit, const QString &description, const QString &extensions);
QString stripProjectDir(QString s);
void disableParsedSetting(QWidget * widget, const QString &name, const QString &filepath);
+ void updateMaskOverlapWarning(QLabel * warning, QList masks);
private slots:
void dialogButtonClicked(QAbstractButton *button);
@@ -63,6 +65,8 @@ private slots:
void updatePokemonIconPath(const QString &species);
void markEdited();
void on_mainTabs_tabBarClicked(int index);
+ void updateBlockMaskOverlapWarning();
+ void updateAttributeMaskOverlapWarning();
};
#endif // PROJECTSETTINGSEDITOR_H
diff --git a/src/config.cpp b/src/config.cpp
index ceb7ad01..48bb9e95 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -1043,21 +1043,21 @@ uint16_t ProjectConfig::getDefaultMetatileId() {
return this->defaultMetatileId;
}
-void ProjectConfig::setDefaultElevation(int elevation) {
+void ProjectConfig::setDefaultElevation(uint16_t elevation) {
this->defaultElevation = elevation;
this->save();
}
-int ProjectConfig::getDefaultElevation() {
+uint16_t ProjectConfig::getDefaultElevation() {
return this->defaultElevation;
}
-void ProjectConfig::setDefaultCollision(int collision) {
+void ProjectConfig::setDefaultCollision(uint16_t collision) {
this->defaultCollision = collision;
this->save();
}
-int ProjectConfig::getDefaultCollision() {
+uint16_t ProjectConfig::getDefaultCollision() {
return this->defaultCollision;
}
diff --git a/src/core/bitpacker.cpp b/src/core/bitpacker.cpp
index dd8815bb..f44d8140 100644
--- a/src/core/bitpacker.cpp
+++ b/src/core/bitpacker.cpp
@@ -21,7 +21,7 @@ void BitPacker::setMask(uint32_t mask) {
m_maxValue = (m_setBits.length() >= 32) ? UINT_MAX : ((1 << m_setBits.length()) - 1);
}
-// Given an arbitrary value to set for this bitfield member, returns a bounded value that can later be packed losslessly.
+// Given an arbitrary value to set for this bitfield member, returns a (potentially truncated) value that can later be packed losslessly.
uint32_t BitPacker::clamp(uint32_t value) const {
return (m_maxValue == UINT_MAX) ? value : (value % (m_maxValue + 1));
}
diff --git a/src/core/block.cpp b/src/core/block.cpp
index 1ef1c703..9eab9d13 100644
--- a/src/core/block.cpp
+++ b/src/core/block.cpp
@@ -12,7 +12,7 @@ static BitPacker bitsElevation = BitPacker(0xF000);
Block::Block() :
m_metatileId(0),
m_collision(0),
- m_elevation(0)
+ m_elevation(0)
{ }
Block::Block(uint16_t metatileId, uint16_t collision, uint16_t elevation) :
@@ -46,32 +46,10 @@ uint16_t Block::rawValue() const {
| bitsElevation.pack(m_elevation);
}
-// TODO: After parsing, recalc config (or parsed!) values that depend on max collision/elevation
-/* - newMapMetatileId
- - newMapElevation
- - newMapCollision
- - newMapBorderMetatileIds
- - collisionSheetWidth
- - collisionSheetHeight
- - NUM_METATILES_IN_PRIMARY
- - event elevations
- - metatile labels?
-
-*/
-// TODO: Settings editor -- disable UI & restore after refresh
void Block::setLayout() {
bitsMetatileId.setMask(projectConfig.getBlockMetatileIdMask());
bitsCollision.setMask(projectConfig.getBlockCollisionMask());
bitsElevation.setMask(projectConfig.getBlockElevationMask());
-
- // Some settings may need to be reevaluated based on the layout
- /*uint16_t metatileId = projectConfig.getNewMapMetatileId();
- if (bitsMetatileId.clamp(metatileId) != metatileId)
- projectConfig.setNewMapMetatileId(bitsMetatileId.clamp(metatileId));
- uint16_t metatileId = projectConfig.getNewMapMetatileId();
- if (bitsMetatileId.clamp(metatileId) != metatileId)
- projectConfig.setNewMapMetatileId(bitsMetatileId.clamp(metatileId));*/
-
}
bool Block::operator ==(Block other) const {
diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp
index 937db58f..565dd8f7 100644
--- a/src/core/metatile.cpp
+++ b/src/core/metatile.cpp
@@ -82,73 +82,66 @@ uint32_t Metatile::getDefaultAttributesMask(BaseGameVersion version, Metatile::A
return vanillaPackers.value(attr).mask();
}
-bool Metatile::doMasksOverlap(QList masks) {
- for (int i = 0; i < masks.length(); i++)
- for (int j = i + 1; j < masks.length(); j++) {
- if (masks.at(i) & masks.at(j))
- return true;
- }
- return false;
-}
-
-void Metatile::setLayout(Project * project) {
- // Read masks from the config and limit them based on the specified attribute size.
- const QHash maxMasks = {
+uint32_t Metatile::getMaxAttributesMask() {
+ static const QHash maxMasks = {
{1, 0xFF},
{2, 0xFFFF},
{4, 0xFFFFFFFF},
};
- uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0);
- uint32_t behaviorMask = projectConfig.getMetatileBehaviorMask() & maxMask;
- uint32_t terrainTypeMask = projectConfig.getMetatileTerrainTypeMask() & maxMask;
- uint32_t encounterTypeMask = projectConfig.getMetatileEncounterTypeMask() & maxMask;
- uint32_t layerTypeMask = projectConfig.getMetatileLayerTypeMask() & maxMask;
-
- // TODO: Overlap handling to settings editor; set red text box with similar text warning if overlapping
- // Overlapping masks are technically ok, but probably not intended.
- // Additionally, Porymap will not properly reflect that the values are linked.
- if (doMasksOverlap({behaviorMask, terrainTypeMask, encounterTypeMask, layerTypeMask})) {
- logWarn("Metatile attribute masks are overlapping. This may result in unexpected attribute values.");
- }
+ return maxMasks.value(projectConfig.getMetatileAttributesSize(), 0);
+}
+void Metatile::setLayout(Project * project) {
+ uint32_t behaviorMask = projectConfig.getMetatileBehaviorMask();
+ uint32_t terrainTypeMask = projectConfig.getMetatileTerrainTypeMask();
+ uint32_t encounterTypeMask = projectConfig.getMetatileEncounterTypeMask();
+ uint32_t layerTypeMask = projectConfig.getMetatileLayerTypeMask();
// Calculate mask of bits not used by standard behaviors so we can preserve this data.
uint32_t unusedMask = ~(behaviorMask | terrainTypeMask | encounterTypeMask | layerTypeMask);
- unusedMask &= maxMask;
+ unusedMask &= Metatile::getMaxAttributesMask();
BitPacker packer = BitPacker(unusedMask);
attributePackers.clear();
attributePackers.insert(Metatile::Attr::Unused, unusedMask);
- // TODO: Test displaying 32 bit behavior
- // TODO: Logging masks to hex
// Validate metatile behavior mask
packer.setMask(behaviorMask);
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(behaviorMask));
+ logWarn(QString("Metatile Behavior mask '0x%1' is insufficient to contain all available options.")
+ .arg(QString::number(behaviorMask, 16).toUpper()));
}
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.").arg(terrainTypeMask).arg(maxTerrainType + 1));
+ if (terrainTypeMask && packer.clamp(maxTerrainType) != maxTerrainType) {
+ logWarn(QString("Metatile Terrain Type mask '0x%1' is insufficient to contain all %2 available options.")
+ .arg(QString::number(terrainTypeMask, 16).toUpper())
+ .arg(maxTerrainType + 1));
+ }
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.").arg(encounterTypeMask).arg(maxEncounterType + 1));
+ if (encounterTypeMask && packer.clamp(maxEncounterType) != maxEncounterType) {
+ logWarn(QString("Metatile Encounter Type mask '0x%1' is insufficient to contain all %2 available options.")
+ .arg(QString::number(encounterTypeMask, 16).toUpper())
+ .arg(maxEncounterType + 1));
+ }
attributePackers.insert(Metatile::Attr::EncounterType, packer);
// Validate terrain type mask
packer.setMask(layerTypeMask);
const uint32_t maxLayerType = NUM_METATILE_LAYER_TYPES - 1;
- if (layerTypeMask && packer.clamp(maxLayerType) != maxLayerType)
- logWarn(QString("Metatile Layer Type mask '%1' is insufficient to contain all %2 available options.").arg(layerTypeMask).arg(maxLayerType + 1));
+ if (layerTypeMask && packer.clamp(maxLayerType) != maxLayerType) {
+ logWarn(QString("Metatile Layer Type mask '0x%1' is insufficient to contain all %2 available options.")
+ .arg(QString::number(layerTypeMask, 16).toUpper())
+ .arg(maxLayerType + 1));
+ }
attributePackers.insert(Metatile::Attr::LayerType, packer);
}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 1fec8054..9cfd993e 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -373,7 +373,7 @@ void MainWindow::setWildEncountersUIEnabled(bool enabled) {
}
// Update the UI using information we've read from the user's project files.
-bool MainWindow::setProjectSpecificUI()
+void MainWindow::setProjectSpecificUI()
{
this->setWildEncountersUIEnabled(userConfig.getEncounterJsonActive());
@@ -397,7 +397,6 @@ bool MainWindow::setProjectSpecificUI()
editor->setCollisionGraphics();
ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation());
ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision());
- return true;
}
void MainWindow::mapSortOrder_changed(QAction *action)
@@ -543,7 +542,6 @@ bool MainWindow::openProject(QString dir) {
}
this->projectOpenFailure = !(loadDataStructures()
- && setProjectSpecificUI()
&& populateMapList()
&& setInitialMap());
@@ -948,8 +946,8 @@ bool MainWindow::loadDataStructures() {
&& project->readEventGraphics()
&& project->readSongNames();
- Block::setLayout();
- Metatile::setLayout(project);
+ project->applyParsedLimits();
+ setProjectSpecificUI();
Scripting::populateGlobalObject(this);
return success && loadProjectCombos();
diff --git a/src/project.cpp b/src/project.cpp
index d2992f61..3e487efa 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -27,7 +27,7 @@ using OrderedJsonDoc = poryjson::JsonDoc;
int Project::num_tiles_primary = 512;
int Project::num_tiles_total = 1024;
-int Project::num_metatiles_primary = 512; // TODO: Verify fits within max
+int Project::num_metatiles_primary = 512;
int Project::num_pals_primary = 6;
int Project::num_pals_total = 13;
int Project::max_map_data_size = 10240; // 0x2800
@@ -1865,7 +1865,7 @@ bool Project::readTilesetLabels() {
}
bool Project::readTilesetProperties() {
- static const QStringList definePrefixes{ "\\bNUM_" };
+ QStringList definePrefixes{ "\\bNUM_" };
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap);
fileWatcher.addPath(root + "/" + filename);
QMap defines = parser.readCDefines(filename, definePrefixes);
@@ -1916,7 +1916,7 @@ bool Project::readTilesetProperties() {
// Read data masks for Blocks and metatile attributes.
bool Project::readFieldmapMasks() {
// We're looking for the suffix "_MASK". Technically our "prefix" is the whole define.
- static const QStringList definePrefixes{ "\\b\\w+_MASK" };
+ QStringList definePrefixes{ "\\b\\w+_MASK" };
QString globalFieldmap = projectConfig.getFilePath(ProjectFilePath::global_fieldmap);
fileWatcher.addPath(root + "/" + globalFieldmap);
QMap defines = parser.readCDefines(globalFieldmap, definePrefixes);
@@ -1990,7 +1990,7 @@ bool Project::readFieldmapMasks() {
}
bool Project::readMaxMapDataSize() {
- static const QStringList definePrefixes{ "\\bMAX_" };
+ QStringList definePrefixes{ "\\bMAX_" };
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); // already in fileWatcher from readTilesetProperties
QMap defines = parser.readCDefines(filename, definePrefixes);
@@ -2333,7 +2333,7 @@ bool Project::readMiscellaneousConstants() {
QString filename = projectConfig.getFilePath(ProjectFilePath::constants_global);
fileWatcher.addPath(root + "/" + filename);
- static const QStringList definePrefixes("\\bOBJECT_");
+ QStringList definePrefixes("\\bOBJECT_");
QMap defines = parser.readCDefines(filename, definePrefixes);
auto it = defines.find("OBJECT_EVENT_TEMPLATES_COUNT");
@@ -2705,3 +2705,32 @@ void Project::setImportExportPath(QString filename)
{
this->importExportPath = QFileInfo(filename).absolutePath();
}
+
+// The values of some config fields can limit the values of other config fields
+// (for example, metatile attributes size limits the metatile attribute masks).
+// Others depend on information in the project (for example the default metatile ID
+// can be limited by fieldmap defines)
+// Once we've read data from the project files we can adjust these accordingly.
+void Project::applyParsedLimits() {
+ // Avoid repeatedly writing the config file
+ projectConfig.setSaveDisabled(true);
+
+ uint32_t maxMask = Metatile::getMaxAttributesMask();
+ projectConfig.setMetatileBehaviorMask(projectConfig.getMetatileBehaviorMask() & maxMask);
+ projectConfig.setMetatileTerrainTypeMask(projectConfig.getMetatileTerrainTypeMask() & maxMask);
+ projectConfig.setMetatileEncounterTypeMask(projectConfig.getMetatileEncounterTypeMask() & maxMask);
+ projectConfig.setMetatileLayerTypeMask(projectConfig.getMetatileLayerTypeMask() & maxMask);
+
+ Block::setLayout();
+ Metatile::setLayout(this);
+
+ Project::num_metatiles_primary = qMin(Project::num_metatiles_primary, Block::getMaxMetatileId() + 1);
+ projectConfig.setDefaultMetatileId(qMin(projectConfig.getDefaultMetatileId(), Block::getMaxMetatileId()));
+ projectConfig.setDefaultElevation(qMin(projectConfig.getDefaultElevation(), Block::getMaxElevation()));
+ projectConfig.setDefaultCollision(qMin(projectConfig.getDefaultCollision(), Block::getMaxCollision()));
+ projectConfig.setCollisionSheetHeight(qMin(projectConfig.getCollisionSheetHeight(), Block::getMaxElevation() + 1));
+ projectConfig.setCollisionSheetWidth(qMin(projectConfig.getCollisionSheetWidth(), Block::getMaxCollision() + 1));
+
+ projectConfig.setSaveDisabled(false);
+ projectConfig.save();
+}
diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp
index e2387fe8..54452072 100644
--- a/src/ui/projectsettingseditor.cpp
+++ b/src/ui/projectsettingseditor.cpp
@@ -1,5 +1,4 @@
#include "projectsettingseditor.h"
-#include "ui_projectsettingseditor.h"
#include "config.h"
#include "noscrollcombobox.h"
#include "prefab.h"
@@ -55,6 +54,15 @@ void ProjectSettingsEditor::connectSignals() {
connect(ui->button_HealspotsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_HealspotsIcon); });
connect(ui->button_PokemonIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_PokemonIcon); });
+ // Display a warning if a mask value overlaps with another mask in its group.
+ connect(ui->spinBox_MetatileIdMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateBlockMaskOverlapWarning);
+ connect(ui->spinBox_CollisionMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateBlockMaskOverlapWarning);
+ connect(ui->spinBox_ElevationMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateBlockMaskOverlapWarning);
+ connect(ui->spinBox_BehaviorMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateAttributeMaskOverlapWarning);
+ connect(ui->spinBox_LayerTypeMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateAttributeMaskOverlapWarning);
+ connect(ui->spinBox_EncounterTypeMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateAttributeMaskOverlapWarning);
+ connect(ui->spinBox_TerrainTypeMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateAttributeMaskOverlapWarning);
+
// Record that there are unsaved changes if any of the settings are modified
for (auto combo : ui->centralwidget->findChildren()){
if (combo != ui->comboBox_IconSpecies) // Changes to the icon species combo box are just for info display, don't mark as unsaved
@@ -112,7 +120,6 @@ void ProjectSettingsEditor::initUi() {
ui->spinBox_Collision->setMaximum(Block::getMaxCollision());
ui->spinBox_MaxElevation->setMaximum(Block::getMaxElevation());
ui->spinBox_MaxCollision->setMaximum(Block::getMaxCollision());
- //ui->spinBox_MetatileIdMask->setMinimum(0x1);
ui->spinBox_MetatileIdMask->setMaximum(Block::maxValue);
ui->spinBox_CollisionMask->setMaximum(Block::maxValue);
ui->spinBox_ElevationMask->setMaximum(Block::maxValue);
@@ -189,6 +196,47 @@ QList ProjectSettingsEditor::getBorderMetatileIds(bool customSize) {
return metatileIds;
}
+// Show/hide warning for overlapping mask values. These are technically ok, but probably not intended.
+// Additionally, Porymap will not properly reflect that the values are linked.
+void ProjectSettingsEditor::updateMaskOverlapWarning(QLabel * warning, QList masks) {
+ // Find any overlapping masks
+ QMap overlapping;
+ for (int i = 0; i < masks.length(); i++)
+ for (int j = i + 1; j < masks.length(); j++) {
+ if (masks.at(i)->value() & masks.at(j)->value())
+ overlapping[i] = overlapping[j] = true;
+ }
+
+ // It'de nice if we could style this as a persistent red border around the line edit for any
+ // overlapping masks. As it is editing the border undesirably modifies the arrow buttons.
+ // This stylesheet will just highlight the currently selected line edit, which is fine enough.
+ static const QString styleSheet = "QAbstractSpinBox { selection-background-color: rgba(255, 0, 0, 25%) }";
+
+ // Update warning display
+ if (warning) warning->setHidden(overlapping.isEmpty());
+ for (int i = 0; i < masks.length(); i++)
+ masks.at(i)->setStyleSheet(overlapping.contains(i) ? styleSheet : "");
+}
+
+void ProjectSettingsEditor::updateBlockMaskOverlapWarning() {
+ const auto masks = QList{
+ ui->spinBox_MetatileIdMask,
+ ui->spinBox_CollisionMask,
+ ui->spinBox_ElevationMask,
+ };
+ this->updateMaskOverlapWarning(ui->label_OverlapWarningBlocks, masks);
+}
+
+void ProjectSettingsEditor::updateAttributeMaskOverlapWarning() {
+ const auto masks = QList{
+ ui->spinBox_BehaviorMask,
+ ui->spinBox_LayerTypeMask,
+ ui->spinBox_EncounterTypeMask,
+ ui->spinBox_TerrainTypeMask,
+ };
+ this->updateMaskOverlapWarning(ui->label_OverlapWarningMetatiles, masks);
+}
+
void ProjectSettingsEditor::updateAttributeLimits(const QString &attrSize) {
static const QMap limits {
{"1", 0xFF},
diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp
index 34e24b3f..7d680b54 100644
--- a/src/ui/tileseteditor.cpp
+++ b/src/ui/tileseteditor.cpp
@@ -113,7 +113,7 @@ void TilesetEditor::initUi() {
void TilesetEditor::setAttributesUi() {
// Behavior
- if (projectConfig.getMetatileBehaviorMask() != 0) {
+ if (projectConfig.getMetatileBehaviorMask()) {
for (int num : project->metatileBehaviorMapInverse.keys()) {
this->ui->comboBox_metatileBehaviors->addItem(project->metatileBehaviorMapInverse[num], num);
}
diff --git a/src/ui/uintspinbox.cpp b/src/ui/uintspinbox.cpp
index 6579ed6b..789a1662 100644
--- a/src/ui/uintspinbox.cpp
+++ b/src/ui/uintspinbox.cpp
@@ -104,29 +104,37 @@ void UIntSpinBox::onEditFinished() {
QString input = this->lineEdit()->text();
auto state = this->validate(input, pos);
+ if (state == QValidator::Invalid)
+ return;
+
+ auto newValue = m_value;
if (state == QValidator::Acceptable) {
// Valid input
- m_value = this->valueFromText(input);
+ newValue = this->valueFromText(input);
} else if (state == QValidator::Intermediate) {
// User has deleted all the number text.
// If they did this by selecting all text and then hitting delete
// make sure to put the cursor back in front of the prefix.
- m_value = m_minimum;
+ newValue = m_minimum;
this->lineEdit()->setCursorPosition(m_prefix.length());
}
+ if (newValue != m_value) {
+ m_value = newValue;
+ emit this->valueChanged(m_value);
+ }
+ emit this->textChanged(input);
}
-void UIntSpinBox::stepBy(int steps)
-{
- auto new_value = m_value;
- if (steps < 0 && new_value + steps > new_value) {
- new_value = 0;
- } else if (steps > 0 && new_value + steps < new_value) {
- new_value = UINT_MAX;
+void UIntSpinBox::stepBy(int steps) {
+ auto newValue = m_value;
+ if (steps < 0 && newValue + steps > newValue) {
+ newValue = 0;
+ } else if (steps > 0 && newValue + steps < newValue) {
+ newValue = UINT_MAX;
} else {
- new_value += steps;
+ newValue += steps;
}
- this->setValue(new_value);
+ this->setValue(newValue);
}
QString UIntSpinBox::stripped(QString input) const {
@@ -144,8 +152,9 @@ QValidator::State UIntSpinBox::validate(QString &input, int &pos) const {
if (copy.isEmpty())
return QValidator::Intermediate;
- // Editing the prefix (if not deleting all text) is not allowed
- if (pos < m_prefix.length())
+ // Editing the prefix (if not deleting all text) is not allowed.
+ // Nor is editing beyond the maximum value's character limit.
+ if (pos < m_prefix.length() || pos > (m_numChars + m_prefix.length()))
return QValidator::Invalid;
bool ok;