Merge branch 'master' into new-map-dialog
This commit is contained in:
commit
72f37d8983
11 changed files with 216 additions and 68 deletions
|
@ -18,6 +18,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
|
|||
- Add support for defining project values with `enum` where `#define` was expected.
|
||||
- Add button to enable editing map groups including renaming groups and rearranging the maps within them.
|
||||
- Add buttons to hide and show empty folders in each map tree view.
|
||||
- Add a setting to specify the tile values to use for the unused metatile layer.
|
||||
|
||||
### Changed
|
||||
- Edits to map connections now have Undo/Redo and can be viewed in exported timelapses.
|
||||
|
@ -63,6 +64,8 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
|
|||
- Fix crash when saving tilesets with fewer palettes than the maximum.
|
||||
- Fix projects not opening on Windows if the project filepath contains certain characters.
|
||||
- Fix exported tile images containing garbage pixels after the end of the tiles.
|
||||
- Fix fully transparent pixels rendering with the incorrect color.
|
||||
- Fix the values for some config fields shuffling their order every save.
|
||||
|
||||
## [5.4.1] - 2024-03-21
|
||||
### Fixed
|
||||
|
|
|
@ -369,7 +369,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>559</width>
|
||||
<height>548</height>
|
||||
<height>560</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_16">
|
||||
|
@ -602,7 +602,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_MetatileIdMask" native="true">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_MetatileIdMask">
|
||||
<property name="toolTip">
|
||||
<string>The mask used to read/write metatile IDs in map data.</string>
|
||||
</property>
|
||||
|
@ -616,7 +616,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_CollisionMask" native="true">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_CollisionMask">
|
||||
<property name="toolTip">
|
||||
<string>The mask used to read/write collision values in map data.</string>
|
||||
</property>
|
||||
|
@ -630,7 +630,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_ElevationMask" native="true">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_ElevationMask">
|
||||
<property name="toolTip">
|
||||
<string>The mask used to read/write elevation values in map data.</string>
|
||||
</property>
|
||||
|
@ -742,7 +742,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>559</width>
|
||||
<height>568</height>
|
||||
<height>798</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_12">
|
||||
|
@ -775,6 +775,86 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_TransparentPixelRendering">
|
||||
<property name="title">
|
||||
<string>Transparent Pixel Rendering</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_22">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButton_RenderBlack">
|
||||
<property name="toolTip">
|
||||
<string>Fully transparent pixels will be rendered as black pixels (the Pokémon games do this by default)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Render as black</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButton_RenderFirstPalColor">
|
||||
<property name="toolTip">
|
||||
<string>Fully transparent pixels will be rendered using the first palette color (this the default behavior for the GBA)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Render using first palette color</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_UnusedLayerRendering">
|
||||
<property name="title">
|
||||
<string>Unused Layer Rendering</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_UnusedTileNorma">
|
||||
<property name="text">
|
||||
<string>Normal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_UnusedTileNormal">
|
||||
<property name="toolTip">
|
||||
<string>This raw tile value will be used to fill the unused bottom layer of Normal metatiles</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_UnusedTileCovered">
|
||||
<property name="text">
|
||||
<string>Covered</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_UnusedTileCovered">
|
||||
<property name="toolTip">
|
||||
<string>This raw tile value will be used to fill the unused top layer of Covered metatiles</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_UnusedTileSplit">
|
||||
<property name="text">
|
||||
<string>Split</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_UnusedTileSplit">
|
||||
<property name="toolTip">
|
||||
<string>This raw tile value will be used to fill the unused middle layer of Split metatiles</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="frame_WarningTilesetsTab">
|
||||
<property name="styleSheet">
|
||||
|
@ -810,14 +890,14 @@
|
|||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="4" column="1">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_LayerTypeMask" native="true">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_LayerTypeMask">
|
||||
<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">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_BehaviorMask">
|
||||
<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>
|
||||
|
@ -864,7 +944,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_TerrainTypeMask" native="true">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_TerrainTypeMask">
|
||||
<property name="toolTip">
|
||||
<string>The mask used to read/write Terrain Type from the metatile's attributes data. If 0, this attribute is disabled.</string>
|
||||
</property>
|
||||
|
@ -891,7 +971,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_EncounterTypeMask" native="true">
|
||||
<widget class="UIntHexSpinBox" name="spinBox_EncounterTypeMask">
|
||||
<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>
|
||||
|
@ -1549,10 +1629,15 @@
|
|||
<header>noscrollspinbox.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>UIntHexSpinBox</class>
|
||||
<extends>QWidget</extends>
|
||||
<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"/>
|
||||
|
|
|
@ -303,6 +303,7 @@ public:
|
|||
this->prefabImportPrompted = false;
|
||||
this->tilesetsHaveCallback = true;
|
||||
this->tilesetsHaveIsCompressed = true;
|
||||
this->setTransparentPixelsBlack = true;
|
||||
this->filePaths.clear();
|
||||
this->eventIconPaths.clear();
|
||||
this->pokemonIconPaths.clear();
|
||||
|
@ -312,6 +313,9 @@ public:
|
|||
this->blockMetatileIdMask = 0x03FF;
|
||||
this->blockCollisionMask = 0x0C00;
|
||||
this->blockElevationMask = 0xF000;
|
||||
this->unusedTileNormal = 0x3014;
|
||||
this->unusedTileCovered = 0x0000;
|
||||
this->unusedTileSplit = 0x0000;
|
||||
this->identifiers.clear();
|
||||
this->readKeys.clear();
|
||||
}
|
||||
|
@ -339,7 +343,7 @@ public:
|
|||
QString getEventIconPath(Event::Group group);
|
||||
void setPokemonIconPath(const QString &species, const QString &path);
|
||||
QString getPokemonIconPath(const QString &species);
|
||||
QHash<QString, QString> getPokemonIconPaths();
|
||||
QMap<QString, QString> getPokemonIconPaths();
|
||||
|
||||
BaseGameVersion baseGameVersion;
|
||||
QString projectDir;
|
||||
|
@ -364,6 +368,7 @@ public:
|
|||
bool prefabImportPrompted;
|
||||
bool tilesetsHaveCallback;
|
||||
bool tilesetsHaveIsCompressed;
|
||||
bool setTransparentPixelsBlack;
|
||||
int metatileAttributesSize;
|
||||
uint32_t metatileBehaviorMask;
|
||||
uint32_t metatileTerrainTypeMask;
|
||||
|
@ -372,11 +377,14 @@ public:
|
|||
uint16_t blockMetatileIdMask;
|
||||
uint16_t blockCollisionMask;
|
||||
uint16_t blockElevationMask;
|
||||
uint16_t unusedTileNormal;
|
||||
uint16_t unusedTileCovered;
|
||||
uint16_t unusedTileSplit;
|
||||
bool mapAllowFlagsEnabled;
|
||||
QString collisionSheetPath;
|
||||
int collisionSheetWidth;
|
||||
int collisionSheetHeight;
|
||||
QSet<uint32_t> warpBehaviors;
|
||||
QList<uint32_t> warpBehaviors;
|
||||
|
||||
protected:
|
||||
virtual QString getConfigFilepath() override;
|
||||
|
@ -390,7 +398,7 @@ private:
|
|||
QMap<ProjectIdentifier, QString> identifiers;
|
||||
QMap<ProjectFilePath, QString> filePaths;
|
||||
QMap<Event::Group, QString> eventIconPaths;
|
||||
QHash<QString, QString> pokemonIconPaths;
|
||||
QMap<QString, QString> pokemonIconPaths;
|
||||
};
|
||||
|
||||
extern ProjectConfig projectConfig;
|
||||
|
|
|
@ -19,6 +19,8 @@ public:
|
|||
uint16_t rawValue() const;
|
||||
|
||||
static int getIndexInTileset(int);
|
||||
|
||||
static const uint16_t maxValue;
|
||||
};
|
||||
|
||||
inline bool operator==(const Tile &a, const Tile &b) {
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
QImage getCollisionMetatileImage(Block);
|
||||
QImage getCollisionMetatileImage(int, int);
|
||||
QImage getMetatileImage(uint16_t, Tileset*, Tileset*, QList<int>, QList<float>, bool useTruePalettes = false);
|
||||
QImage getMetatileImage(Metatile*, Tileset*, Tileset*, QList<int>, QList<float>, bool useTruePalettes = false);
|
||||
QImage getMetatileImage(uint16_t, Tileset*, Tileset*, const QList<int>&, const QList<float>&, bool useTruePalettes = false);
|
||||
QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList<int>&, const QList<float>&, bool useTruePalettes = false);
|
||||
QImage getTileImage(uint16_t, Tileset*, Tileset*);
|
||||
QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false);
|
||||
QImage getGreyscaleTileImage(uint16_t tile, Tileset *primaryTileset, Tileset *secondaryTileset);
|
||||
|
|
|
@ -36,7 +36,7 @@ private:
|
|||
bool projectNeedsReload = false;
|
||||
bool refreshing = false;
|
||||
const QString baseDir;
|
||||
QHash<QString, QString> editedPokemonIconPaths;
|
||||
QMap<QString, QString> editedPokemonIconPaths;
|
||||
QString prevIconSpecies;
|
||||
|
||||
void initUi();
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include <QAction>
|
||||
#include <QAbstractButton>
|
||||
|
||||
const QSet<uint32_t> defaultWarpBehaviors_RSE = {
|
||||
const QList<uint32_t> defaultWarpBehaviors_RSE = {
|
||||
0x0E, // MB_MOSSDEEP_GYM_WARP
|
||||
0x0F, // MB_MT_PYRE_HOLE
|
||||
0x1B, // MB_STAIRS_OUTSIDE_ABANDONED_SHIP
|
||||
|
@ -48,7 +48,7 @@ const QSet<uint32_t> defaultWarpBehaviors_RSE = {
|
|||
0x9D, // MB_SECRET_BASE_SPOT_TREE_RIGHT_OPEN
|
||||
};
|
||||
|
||||
const QSet<uint32_t> defaultWarpBehaviors_FRLG = {
|
||||
const QList<uint32_t> defaultWarpBehaviors_FRLG = {
|
||||
0x60, // MB_CAVE_DOOR
|
||||
0x61, // MB_LADDER
|
||||
0x62, // MB_EAST_ARROW_WARP
|
||||
|
@ -724,6 +724,12 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
this->blockCollisionMask = getConfigUint32(key, value, 0, Block::maxValue);
|
||||
} else if (key == "block_elevation_mask") {
|
||||
this->blockElevationMask = getConfigUint32(key, value, 0, Block::maxValue);
|
||||
} else if (key == "unused_tile_normal") {
|
||||
this->unusedTileNormal = getConfigUint32(key, value, 0, Tile::maxValue);
|
||||
} else if (key == "unused_tile_covered") {
|
||||
this->unusedTileCovered = getConfigUint32(key, value, 0, Tile::maxValue);
|
||||
} else if (key == "unused_tile_split") {
|
||||
this->unusedTileSplit = getConfigUint32(key, value, 0, Tile::maxValue);
|
||||
} else if (key == "enable_map_allow_flags") {
|
||||
this->mapAllowFlagsEnabled = getConfigBool(key, value);
|
||||
#ifdef CONFIG_BACKWARDS_COMPATABILITY
|
||||
|
@ -756,6 +762,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
this->tilesetsHaveCallback = getConfigBool(key, value);
|
||||
} else if (key == "tilesets_have_is_compressed") {
|
||||
this->tilesetsHaveIsCompressed = getConfigBool(key, value);
|
||||
} else if (key == "set_transparent_pixels_black") {
|
||||
this->setTransparentPixelsBlack = getConfigBool(key, value);
|
||||
} else if (key == "event_icon_path_object") {
|
||||
this->eventIconPaths[Event::Group::Object] = value;
|
||||
} else if (key == "event_icon_path_warp") {
|
||||
|
@ -777,9 +785,9 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
} else if (key == "warp_behaviors") {
|
||||
this->warpBehaviors.clear();
|
||||
value.remove(" ");
|
||||
QStringList behaviorList = value.split(",", Qt::SkipEmptyParts);
|
||||
const QStringList behaviorList = value.split(",", Qt::SkipEmptyParts);
|
||||
for (auto s : behaviorList)
|
||||
this->warpBehaviors.insert(getConfigUint32(key, s));
|
||||
this->warpBehaviors.append(getConfigUint32(key, s));
|
||||
} else {
|
||||
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key));
|
||||
}
|
||||
|
@ -843,6 +851,7 @@ QMap<QString, QString> ProjectConfig::getKeyValueMap() {
|
|||
}
|
||||
map.insert("tilesets_have_callback", QString::number(this->tilesetsHaveCallback));
|
||||
map.insert("tilesets_have_is_compressed", QString::number(this->tilesetsHaveIsCompressed));
|
||||
map.insert("set_transparent_pixels_black", QString::number(this->setTransparentPixelsBlack));
|
||||
map.insert("metatile_attributes_size", QString::number(this->metatileAttributesSize));
|
||||
map.insert("metatile_behavior_mask", "0x" + QString::number(this->metatileBehaviorMask, 16).toUpper());
|
||||
map.insert("metatile_terrain_type_mask", "0x" + QString::number(this->metatileTerrainTypeMask, 16).toUpper());
|
||||
|
@ -851,6 +860,9 @@ QMap<QString, QString> ProjectConfig::getKeyValueMap() {
|
|||
map.insert("block_metatile_id_mask", "0x" + QString::number(this->blockMetatileIdMask, 16).toUpper());
|
||||
map.insert("block_collision_mask", "0x" + QString::number(this->blockCollisionMask, 16).toUpper());
|
||||
map.insert("block_elevation_mask", "0x" + QString::number(this->blockElevationMask, 16).toUpper());
|
||||
map.insert("unused_tile_normal", "0x" + QString::number(this->unusedTileNormal, 16).toUpper());
|
||||
map.insert("unused_tile_covered", "0x" + QString::number(this->unusedTileCovered, 16).toUpper());
|
||||
map.insert("unused_tile_split", "0x" + QString::number(this->unusedTileSplit, 16).toUpper());
|
||||
map.insert("enable_map_allow_flags", QString::number(this->mapAllowFlagsEnabled));
|
||||
map.insert("event_icon_path_object", this->eventIconPaths[Event::Group::Object]);
|
||||
map.insert("event_icon_path_warp", this->eventIconPaths[Event::Group::Warp]);
|
||||
|
@ -868,7 +880,7 @@ QMap<QString, QString> ProjectConfig::getKeyValueMap() {
|
|||
map.insert("collision_sheet_width", QString::number(this->collisionSheetWidth));
|
||||
map.insert("collision_sheet_height", QString::number(this->collisionSheetHeight));
|
||||
QStringList warpBehaviorStrs;
|
||||
for (auto value : this->warpBehaviors)
|
||||
for (const auto &value : this->warpBehaviors)
|
||||
warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper());
|
||||
map.insert("warp_behaviors", warpBehaviorStrs.join(","));
|
||||
|
||||
|
@ -1020,7 +1032,7 @@ QString ProjectConfig::getPokemonIconPath(const QString &species) {
|
|||
return this->pokemonIconPaths.value(species);
|
||||
}
|
||||
|
||||
QHash<QString, QString> ProjectConfig::getPokemonIconPaths() {
|
||||
QMap<QString, QString> ProjectConfig::getPokemonIconPaths() {
|
||||
return this->pokemonIconPaths;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
#include "tile.h"
|
||||
#include "project.h"
|
||||
#include "bitpacker.h"
|
||||
|
||||
// Upper limit for raw value (i.e., uint16_t max).
|
||||
const uint16_t Tile::maxValue = 0xFFFF;
|
||||
|
||||
// At the moment these are fixed, and not exposed to the user.
|
||||
// We're only using them for convenience when converting between raw values.
|
||||
// The actual job of clamping Tile's members to correct values is handled by the widths in the bit field.
|
||||
const BitPacker bitsTileId = BitPacker(0x03FF);
|
||||
const BitPacker bitsXFlip = BitPacker(0x0400);
|
||||
const BitPacker bitsYFlip = BitPacker(0x0800);
|
||||
const BitPacker bitsPalette = BitPacker(0xF000);
|
||||
|
||||
Tile::Tile() :
|
||||
tileId(0),
|
||||
|
@ -16,18 +28,17 @@
|
|||
{ }
|
||||
|
||||
Tile::Tile(uint16_t raw) :
|
||||
tileId(raw & 0x3FF),
|
||||
xflip((raw >> 10) & 1),
|
||||
yflip((raw >> 11) & 1),
|
||||
palette((raw >> 12) & 0xF)
|
||||
tileId(bitsTileId.unpack(raw)),
|
||||
xflip(bitsXFlip.unpack(raw)),
|
||||
yflip(bitsYFlip.unpack(raw)),
|
||||
palette(bitsPalette.unpack(raw))
|
||||
{ }
|
||||
|
||||
uint16_t Tile::rawValue() const {
|
||||
return static_cast<uint16_t>(
|
||||
(this->tileId & 0x3FF)
|
||||
| ((this->xflip & 1) << 10)
|
||||
| ((this->yflip & 1) << 11)
|
||||
| ((this->palette & 0xF) << 12));
|
||||
return bitsTileId.pack(this->tileId)
|
||||
| bitsXFlip.pack(this->xflip)
|
||||
| bitsYFlip.pack(this->yflip)
|
||||
| bitsPalette.pack(this->palette);
|
||||
}
|
||||
|
||||
int Tile::getIndexInTileset(int tileId) {
|
||||
|
|
|
@ -2165,19 +2165,29 @@ bool Project::readFieldmapProperties() {
|
|||
fileWatcher.addPath(root + "/" + filename);
|
||||
const QMap<QString, int> defines = parser.readCDefinesByName(filename, names);
|
||||
|
||||
auto loadDefine = [defines](const QString name, int * dest) {
|
||||
auto loadDefine = [defines](const QString name, int * dest, int min, int max) {
|
||||
auto it = defines.find(name);
|
||||
if (it != defines.end()) {
|
||||
*dest = it.value();
|
||||
if (*dest < min) {
|
||||
logWarn(QString("Value for tileset property '%1' (%2) is below the minimum (%3). Defaulting to minimum.").arg(name).arg(*dest).arg(min));
|
||||
*dest = min;
|
||||
} else if (*dest > max) {
|
||||
logWarn(QString("Value for tileset property '%1' (%2) is above the maximum (%3). Defaulting to maximum.").arg(name).arg(*dest).arg(max));
|
||||
*dest = max;
|
||||
}
|
||||
} else {
|
||||
logWarn(QString("Value for tileset property '%1' not found. Using default (%2) instead.").arg(name).arg(*dest));
|
||||
}
|
||||
};
|
||||
loadDefine(numTilesPrimaryName, &Project::num_tiles_primary);
|
||||
loadDefine(numTilesTotalName, &Project::num_tiles_total);
|
||||
loadDefine(numMetatilesPrimaryName, &Project::num_metatiles_primary);
|
||||
loadDefine(numPalsPrimaryName, &Project::num_pals_primary);
|
||||
loadDefine(numPalsTotalName, &Project::num_pals_total);
|
||||
loadDefine(numPalsTotalName, &Project::num_pals_total, 2, INT_MAX); // In reality the max would be 16, but as far as Porymap is concerned it doesn't matter.
|
||||
loadDefine(numTilesTotalName, &Project::num_tiles_total, 2, 1024); // 1024 is fixed because we store tile IDs in a 10-bit field.
|
||||
loadDefine(numPalsPrimaryName, &Project::num_pals_primary, 1, Project::num_pals_total - 1);
|
||||
loadDefine(numTilesPrimaryName, &Project::num_tiles_primary, 1, Project::num_tiles_total - 1);
|
||||
|
||||
// This maximum is overly generous, because until we parse the appropriate masks from the project
|
||||
// we don't actually know what the maximum number of metatiles is.
|
||||
loadDefine(numMetatilesPrimaryName, &Project::num_metatiles_primary, 1, 0xFFFF - 1);
|
||||
|
||||
auto it = defines.find(maxMapSizeName);
|
||||
if (it != defines.end()) {
|
||||
|
@ -3102,12 +3112,12 @@ void Project::applyParsedLimits() {
|
|||
Block::setLayout();
|
||||
Metatile::setLayout(this);
|
||||
|
||||
Project::num_metatiles_primary = qMin(Project::num_metatiles_primary, Block::getMaxMetatileId() + 1);
|
||||
Project::num_metatiles_primary = qMin(qMax(Project::num_metatiles_primary, 1), Block::getMaxMetatileId() + 1);
|
||||
projectConfig.defaultMetatileId = qMin(projectConfig.defaultMetatileId, Block::getMaxMetatileId());
|
||||
projectConfig.defaultElevation = qMin(projectConfig.defaultElevation, Block::getMaxElevation());
|
||||
projectConfig.defaultCollision = qMin(projectConfig.defaultCollision, Block::getMaxCollision());
|
||||
projectConfig.collisionSheetHeight = qMin(projectConfig.collisionSheetHeight, Block::getMaxElevation() + 1);
|
||||
projectConfig.collisionSheetWidth = qMin(projectConfig.collisionSheetWidth, Block::getMaxCollision() + 1);
|
||||
projectConfig.collisionSheetHeight = qMin(qMax(projectConfig.collisionSheetHeight, 1), Block::getMaxElevation() + 1);
|
||||
projectConfig.collisionSheetWidth = qMin(qMax(projectConfig.collisionSheetWidth, 1), Block::getMaxCollision() + 1);
|
||||
}
|
||||
|
||||
bool Project::hasUnsavedChanges() {
|
||||
|
|
|
@ -17,8 +17,8 @@ QImage getMetatileImage(
|
|||
uint16_t metatileId,
|
||||
Tileset *primaryTileset,
|
||||
Tileset *secondaryTileset,
|
||||
QList<int> layerOrder,
|
||||
QList<float> layerOpacity,
|
||||
const QList<int> &layerOrder,
|
||||
const QList<float> &layerOpacity,
|
||||
bool useTruePalettes)
|
||||
{
|
||||
Metatile* metatile = Tileset::getMetatile(metatileId, primaryTileset, secondaryTileset);
|
||||
|
@ -34,8 +34,8 @@ QImage getMetatileImage(
|
|||
Metatile *metatile,
|
||||
Tileset *primaryTileset,
|
||||
Tileset *secondaryTileset,
|
||||
QList<int> layerOrder,
|
||||
QList<float> layerOpacity,
|
||||
const QList<int> &layerOrder,
|
||||
const QList<float> &layerOpacity,
|
||||
bool useTruePalettes)
|
||||
{
|
||||
QImage metatile_image(16, 16, QImage::Format_RGBA8888);
|
||||
|
@ -43,10 +43,16 @@ QImage getMetatileImage(
|
|||
metatile_image.fill(Qt::magenta);
|
||||
return metatile_image;
|
||||
}
|
||||
metatile_image.fill(Qt::black);
|
||||
|
||||
QList<QList<QRgb>> palettes = Tileset::getBlockPalettes(primaryTileset, secondaryTileset, useTruePalettes);
|
||||
|
||||
// We need to fill the metatile image with something so that if any transparent
|
||||
// tile pixels line up across layers we will still have something to render.
|
||||
// The GBA renders transparent pixels using palette 0 color 0. We have this color,
|
||||
// but all 3 games actually overwrite it with black when loading the tileset palettes,
|
||||
// so we have a setting to choose between these two behaviors.
|
||||
metatile_image.fill(projectConfig.setTransparentPixelsBlack ? QColor("black") : QColor(palettes.value(0).value(0)));
|
||||
|
||||
QPainter metatile_painter(&metatile_image);
|
||||
const int numLayers = 3; // When rendering, metatiles always have 3 layers
|
||||
uint32_t layerType = metatile->layerType();
|
||||
|
@ -54,7 +60,6 @@ QImage getMetatileImage(
|
|||
for (int y = 0; y < 2; y++)
|
||||
for (int x = 0; x < 2; x++) {
|
||||
int l = layerOrder.size() >= numLayers ? layerOrder[layer] : layer;
|
||||
int bottomLayer = layerOrder.size() >= numLayers ? layerOrder[0] : 0;
|
||||
|
||||
// Get the tile to render next
|
||||
Tile tile;
|
||||
|
@ -63,25 +68,25 @@ QImage getMetatileImage(
|
|||
tile = metatile->tiles.value(tileOffset + (l * 4));
|
||||
} else {
|
||||
// "Vanilla" metatiles only have 8 tiles, but render 12.
|
||||
// The remaining 4 tiles are rendered either as tile 0 or 0x3014 (tile 20, palette 3) depending on layer type.
|
||||
// The remaining 4 tiles are rendered using user-specified tiles depending on layer type.
|
||||
switch (layerType)
|
||||
{
|
||||
default:
|
||||
case METATILE_LAYER_MIDDLE_TOP:
|
||||
if (l == 0)
|
||||
tile = Tile(0x3014);
|
||||
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:
|
||||
if (l == 2)
|
||||
tile = Tile();
|
||||
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:
|
||||
if (l == 1)
|
||||
tile = Tile();
|
||||
tile = Tile(projectConfig.unusedTileSplit);
|
||||
else // Tiles are on layers 0 and 2
|
||||
tile = metatile->tiles.value(tileOffset + ((l == 0 ? 0 : 1) * 4));
|
||||
break;
|
||||
|
@ -91,18 +96,14 @@ QImage getMetatileImage(
|
|||
QImage tile_image = getTileImage(tile.tileId, primaryTileset, secondaryTileset);
|
||||
if (tile_image.isNull()) {
|
||||
// Some metatiles specify tiles that are outside the valid range.
|
||||
// These are treated as completely transparent, so they can be skipped without
|
||||
// being drawn unless they're on the bottom layer, in which case we need
|
||||
// a placeholder because garbage will be drawn otherwise.
|
||||
if (l == bottomLayer) {
|
||||
metatile_painter.fillRect(x * 8, y * 8, 8, 8, palettes.value(0).value(0));
|
||||
}
|
||||
// The way the GBA will render these depends on what's in memory (which Porymap can't know)
|
||||
// so we treat them as if they were transparent.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Colorize the metatile tiles with its palette.
|
||||
if (tile.palette < palettes.length()) {
|
||||
QList<QRgb> palette = palettes.value(tile.palette);
|
||||
const QList<QRgb> palette = palettes.value(tile.palette);
|
||||
for (int j = 0; j < palette.length(); j++) {
|
||||
tile_image.setColor(j, palette.value(j));
|
||||
}
|
||||
|
@ -121,12 +122,10 @@ QImage getMetatileImage(
|
|||
}
|
||||
}
|
||||
|
||||
// The top layer of the metatile has its first color displayed at transparent.
|
||||
if (l != bottomLayer) {
|
||||
QColor color(tile_image.color(0));
|
||||
color.setAlpha(0);
|
||||
tile_image.setColor(0, color.rgba());
|
||||
}
|
||||
// Color 0 is displayed as transparent.
|
||||
QColor color(tile_image.color(0));
|
||||
color.setAlpha(0);
|
||||
tile_image.setColor(0, color.rgba());
|
||||
|
||||
metatile_painter.drawImage(origin, tile_image.mirrored(tile.xflip, tile.yflip));
|
||||
}
|
||||
|
@ -144,7 +143,7 @@ QImage getTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondary
|
|||
return tileset->tiles.value(index, QImage());
|
||||
}
|
||||
|
||||
QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, QList<QRgb> palette) {
|
||||
QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList<QRgb> &palette) {
|
||||
QImage tileImage = getTileImage(tileId, primaryTileset, secondaryTileset);
|
||||
if (tileImage.isNull()) {
|
||||
tileImage = QImage(8, 8, QImage::Format_RGBA8888);
|
||||
|
|
|
@ -82,6 +82,8 @@ void ProjectSettingsEditor::connectSignals() {
|
|||
}
|
||||
for (auto checkBox : ui->centralwidget->findChildren<QCheckBox *>())
|
||||
connect(checkBox, &QCheckBox::stateChanged, this, &ProjectSettingsEditor::markEdited);
|
||||
for (auto radioButton : ui->centralwidget->findChildren<QRadioButton *>())
|
||||
connect(radioButton, &QRadioButton::toggled, this, &ProjectSettingsEditor::markEdited);
|
||||
for (auto lineEdit : ui->centralwidget->findChildren<QLineEdit *>())
|
||||
connect(lineEdit, &QLineEdit::textEdited, this, &ProjectSettingsEditor::markEdited);
|
||||
for (auto spinBox : ui->centralwidget->findChildren<NoScrollSpinBox *>())
|
||||
|
@ -130,6 +132,9 @@ void ProjectSettingsEditor::initUi() {
|
|||
ui->spinBox_MetatileIdMask->setMaximum(Block::maxValue);
|
||||
ui->spinBox_CollisionMask->setMaximum(Block::maxValue);
|
||||
ui->spinBox_ElevationMask->setMaximum(Block::maxValue);
|
||||
ui->spinBox_UnusedTileNormal->setMaximum(Tile::maxValue);
|
||||
ui->spinBox_UnusedTileCovered->setMaximum(Tile::maxValue);
|
||||
ui->spinBox_UnusedTileSplit->setMaximum(Tile::maxValue);
|
||||
|
||||
// The values for some of the settings we provide in this window can be determined using constants in the user's projects.
|
||||
// If the user has these constants we disable these settings in the UI -- they can modify them using their constants.
|
||||
|
@ -437,6 +442,12 @@ void ProjectSettingsEditor::refresh() {
|
|||
ui->checkBox_OutputIsCompressed->setChecked(projectConfig.tilesetsHaveIsCompressed);
|
||||
ui->checkBox_DisableWarning->setChecked(porymapConfig.warpBehaviorWarningDisabled);
|
||||
|
||||
// Radio buttons
|
||||
if (projectConfig.setTransparentPixelsBlack)
|
||||
ui->radioButton_RenderBlack->setChecked(true);
|
||||
else
|
||||
ui->radioButton_RenderFirstPalColor->setChecked(true);
|
||||
|
||||
// Set spin box values
|
||||
ui->spinBox_Elevation->setValue(projectConfig.defaultElevation);
|
||||
ui->spinBox_Collision->setValue(projectConfig.defaultCollision);
|
||||
|
@ -450,6 +461,9 @@ void ProjectSettingsEditor::refresh() {
|
|||
ui->spinBox_MetatileIdMask->setValue(projectConfig.blockMetatileIdMask & ui->spinBox_MetatileIdMask->maximum());
|
||||
ui->spinBox_CollisionMask->setValue(projectConfig.blockCollisionMask & ui->spinBox_CollisionMask->maximum());
|
||||
ui->spinBox_ElevationMask->setValue(projectConfig.blockElevationMask & ui->spinBox_ElevationMask->maximum());
|
||||
ui->spinBox_UnusedTileNormal->setValue(projectConfig.unusedTileNormal);
|
||||
ui->spinBox_UnusedTileCovered->setValue(projectConfig.unusedTileCovered);
|
||||
ui->spinBox_UnusedTileSplit->setValue(projectConfig.unusedTileSplit);
|
||||
|
||||
// Set (and sync) border metatile IDs
|
||||
this->setBorderMetatileIds(false, projectConfig.newMapBorderMetatileIds);
|
||||
|
@ -470,7 +484,7 @@ void ProjectSettingsEditor::refresh() {
|
|||
|
||||
// Set warp behaviors
|
||||
QStringList behaviorNames;
|
||||
for (auto value : projectConfig.warpBehaviors) {
|
||||
for (const auto &value : projectConfig.warpBehaviors) {
|
||||
if (project->metatileBehaviorMapInverse.contains(value))
|
||||
behaviorNames.append(project->metatileBehaviorMapInverse.value(value));
|
||||
}
|
||||
|
@ -506,6 +520,7 @@ void ProjectSettingsEditor::save() {
|
|||
projectConfig.tilesetsHaveCallback = ui->checkBox_OutputCallback->isChecked();
|
||||
projectConfig.tilesetsHaveIsCompressed = ui->checkBox_OutputIsCompressed->isChecked();
|
||||
porymapConfig.warpBehaviorWarningDisabled = ui->checkBox_DisableWarning->isChecked();
|
||||
projectConfig.setTransparentPixelsBlack = ui->radioButton_RenderBlack->isChecked();
|
||||
|
||||
// Save spin box settings
|
||||
projectConfig.defaultElevation = ui->spinBox_Elevation->value();
|
||||
|
@ -520,6 +535,9 @@ void ProjectSettingsEditor::save() {
|
|||
projectConfig.blockMetatileIdMask = ui->spinBox_MetatileIdMask->value();
|
||||
projectConfig.blockCollisionMask = ui->spinBox_CollisionMask->value();
|
||||
projectConfig.blockElevationMask = ui->spinBox_ElevationMask->value();
|
||||
projectConfig.unusedTileNormal = ui->spinBox_UnusedTileNormal->value();
|
||||
projectConfig.unusedTileCovered = ui->spinBox_UnusedTileCovered->value();
|
||||
projectConfig.unusedTileSplit = ui->spinBox_UnusedTileSplit->value();
|
||||
|
||||
// Save line edit settings
|
||||
projectConfig.prefabFilepath = ui->lineEdit_PrefabsPath->text();
|
||||
|
@ -536,9 +554,9 @@ void ProjectSettingsEditor::save() {
|
|||
|
||||
// Save warp behaviors
|
||||
projectConfig.warpBehaviors.clear();
|
||||
QStringList behaviorNames = this->getWarpBehaviorsList();
|
||||
const QStringList behaviorNames = this->getWarpBehaviorsList();
|
||||
for (auto name : behaviorNames)
|
||||
projectConfig.warpBehaviors.insert(project->metatileBehaviorMap.value(name));
|
||||
projectConfig.warpBehaviors.append(project->metatileBehaviorMap.value(name));
|
||||
|
||||
// Save border metatile IDs
|
||||
projectConfig.newMapBorderMetatileIds = this->getBorderMetatileIds(ui->checkBox_EnableCustomBorderSize->isChecked());
|
||||
|
|
Loading…
Reference in a new issue