Clean up custom Block layouts

This commit is contained in:
GriffinR 2023-12-16 03:36:26 -05:00
parent 5a3907bf56
commit 60fb1a246e
16 changed files with 268 additions and 164 deletions

View file

@ -21,7 +21,7 @@
<item>
<widget class="QTabWidget" name="mainTabs">
<property name="currentIndex">
<number>1</number>
<number>0</number>
</property>
<widget class="QWidget" name="tab_General">
<attribute name="title">
@ -370,7 +370,7 @@
<x>0</x>
<y>0</y>
<width>531</width>
<height>545</height>
<height>587</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_16">
@ -603,20 +603,6 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_CollisionMask">
<property name="text">
<string>Collision</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_ElevationMask">
<property name="text">
<string>Elevation</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="UIntHexSpinBox" name="spinBox_MetatileIdMask" native="true">
<property name="toolTip">
@ -624,6 +610,13 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_CollisionMask">
<property name="text">
<string>Collision</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="UIntHexSpinBox" name="spinBox_CollisionMask" native="true">
<property name="toolTip">
@ -631,6 +624,13 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_ElevationMask">
<property name="text">
<string>Elevation</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="UIntHexSpinBox" name="spinBox_ElevationMask" native="true">
<property name="toolTip">
@ -638,6 +638,19 @@
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="label_OverlapWarningBlocks">
<property name="styleSheet">
<string notr="true">color : red;</string>
</property>
<property name="text">
<string>These masks have overlapping bits. This may result in unexpected value changes.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -731,7 +744,7 @@
<x>0</x>
<y>0</y>
<width>528</width>
<height>522</height>
<height>568</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_12">
@ -799,10 +812,17 @@
<string>Metatiles</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="4" column="0">
<widget class="QLabel" name="label_LayerTypeMask">
<property name="text">
<string>Layer Type mask</string>
<item row="4" column="1">
<widget class="UIntHexSpinBox" name="spinBox_LayerTypeMask" native="true">
<property name="toolTip">
<string>The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled.</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="UIntHexSpinBox" name="spinBox_BehaviorMask" native="true">
<property name="toolTip">
<string>The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled.</string>
</property>
</widget>
</item>
@ -816,24 +836,33 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="NoScrollComboBox" name="comboBox_AttributesSize">
<property name="editable">
<bool>false</bool>
<item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="UIntHexSpinBox" name="spinBox_EncounterTypeMask" native="true">
<property name="toolTip">
<string>The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled.</string>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
</property>
</widget>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_BehaviorMask">
<item row="5" column="0">
<widget class="QLabel" name="label_EncounterTypeMask">
<property name="text">
<string>Behavior mask</string>
<string>Encounter Type mask</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="checkBox_EnableTripleLayerMetatiles">
<property name="text">
<string>Enable Triple Layer Metatiles</string>
</property>
</widget>
</item>
@ -844,18 +873,46 @@
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<item row="3" column="0">
<widget class="QLabel" name="label_BehaviorMask">
<property name="text">
<string>Behavior mask</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>15</height>
</size>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="QLabel" name="label_OverlapWarningMetatiles">
<property name="styleSheet">
<string notr="true">color : red;</string>
</property>
</spacer>
<property name="text">
<string>These masks have overlapping bits. This may result in unexpected value changes.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="UIntHexSpinBox" name="spinBox_EncounterTypeMask" native="true">
<property name="toolTip">
<string>The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled.</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_LayerTypeMask">
<property name="text">
<string>Layer Type mask</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="NoScrollComboBox" name="comboBox_AttributesSize">
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_TerrainTypeMask">
@ -864,33 +921,21 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_EncounterTypeMask">
<property name="text">
<string>Encounter Type mask</string>
<item row="9" column="0">
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="UIntHexSpinBox" name="spinBox_BehaviorMask" native="true">
<property name="toolTip">
<string>The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled.</string>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="UIntHexSpinBox" name="spinBox_LayerTypeMask" native="true">
<property name="toolTip">
<string>The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled.</string>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>1</height>
</size>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="checkBox_EnableTripleLayerMetatiles">
<property name="text">
<string>Enable Triple Layer Metatiles</string>
</property>
</widget>
</spacer>
</item>
</layout>
</widget>

View file

@ -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<uint16_t> metatileIds);
QList<uint16_t> getNewMapBorderMetatileIds();
QString getDefaultPrimaryTileset();

View file

@ -2,7 +2,6 @@
#define BITPACKER_H
#include <QList>
//#include <cstdint>
class BitPacker
{

View file

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

View file

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

View file

@ -216,6 +216,7 @@ public:
QString getDefaultSecondaryTilesetLabel();
void setImportExportPath(QString filename);
void applyParsedLimits();
static int getNumTilesPrimary();
static int getNumTilesTotal();

View file

@ -3,6 +3,7 @@
#include <QMainWindow>
#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<UIntSpinBox*> 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

View file

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

View file

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

View file

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

View file

@ -82,73 +82,66 @@ uint32_t Metatile::getDefaultAttributesMask(BaseGameVersion version, Metatile::A
return vanillaPackers.value(attr).mask();
}
bool Metatile::doMasksOverlap(QList<uint32_t> 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<int, uint32_t> maxMasks = {
uint32_t Metatile::getMaxAttributesMask() {
static const QHash<int, uint32_t> 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);
}

View file

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

View file

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

View file

@ -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<NoScrollComboBox *>()){
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<uint16_t> 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<UIntSpinBox*> masks) {
// Find any overlapping masks
QMap<int, bool> 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<UIntSpinBox*>{
ui->spinBox_MetatileIdMask,
ui->spinBox_CollisionMask,
ui->spinBox_ElevationMask,
};
this->updateMaskOverlapWarning(ui->label_OverlapWarningBlocks, masks);
}
void ProjectSettingsEditor::updateAttributeMaskOverlapWarning() {
const auto masks = QList<UIntSpinBox*>{
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<QString, uint32_t> limits {
{"1", 0xFF},

View file

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

View file

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