Support custom collision graphics
This commit is contained in:
parent
1b9b980121
commit
d5210cf230
12 changed files with 157 additions and 38 deletions
|
@ -97,7 +97,7 @@ Fill Metatile
|
|||
Field name: ``new_map_metatile``
|
||||
|
||||
Elevation
|
||||
This is the elevation that will be used to fill new maps. New maps will be filled with passable collision.
|
||||
This is the elevation that will be used to fill new maps. It will also be the default selection on the Collision tab when a map is opened. New maps will be filled with passable collision.
|
||||
|
||||
Defaults to ``3``.
|
||||
|
||||
|
|
|
@ -38,8 +38,8 @@ protected:
|
|||
virtual void onNewConfigFileCreated() = 0;
|
||||
virtual void setUnreadKeys() = 0;
|
||||
bool getConfigBool(QString key, QString value);
|
||||
int getConfigInteger(QString key, QString value, int min, int max, int defaultValue);
|
||||
uint32_t getConfigUint32(QString key, QString value, uint32_t min, uint32_t max, uint32_t defaultValue);
|
||||
int getConfigInteger(QString key, QString value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0);
|
||||
uint32_t getConfigUint32(QString key, QString value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0);
|
||||
private:
|
||||
bool saveDisabled = false;
|
||||
};
|
||||
|
@ -227,6 +227,10 @@ public:
|
|||
this->tilesetsHaveCallback = true;
|
||||
this->tilesetsHaveIsCompressed = true;
|
||||
this->filePaths.clear();
|
||||
this->eventIconPaths.clear();
|
||||
this->collisionSheetPath = QString();
|
||||
this->collisionSheetWidth = 2;
|
||||
this->collisionSheetHeight = 16;
|
||||
this->readKeys.clear();
|
||||
}
|
||||
static const QMap<ProjectFilePath, std::pair<QString, QString>> defaultPaths;
|
||||
|
@ -299,8 +303,14 @@ public:
|
|||
void setMapAllowFlagsEnabled(bool enabled);
|
||||
void setEventIconPath(Event::Group group, const QString &path);
|
||||
QString getEventIconPath(Event::Group group);
|
||||
void setCollisionMapPath(const QString &path);
|
||||
QString getCollisionMapPath();
|
||||
void setCollisionIconPath(int collision, const QString &path);
|
||||
QString getCollisionIconPath(int collision);
|
||||
void setCollisionSheetPath(const QString &path);
|
||||
QString getCollisionSheetPath();
|
||||
void setCollisionSheetWidth(int width);
|
||||
int getCollisionSheetWidth();
|
||||
void setCollisionSheetHeight(int height);
|
||||
int getCollisionSheetHeight();
|
||||
|
||||
protected:
|
||||
virtual QString getConfigFilepath() override;
|
||||
|
@ -340,7 +350,9 @@ private:
|
|||
uint32_t metatileLayerTypeMask;
|
||||
bool enableMapAllowFlags;
|
||||
QMap<Event::Group, QString> eventIconPaths;
|
||||
QString collisionMapPath;
|
||||
QString collisionSheetPath;
|
||||
int collisionSheetWidth;
|
||||
int collisionSheetHeight;
|
||||
};
|
||||
|
||||
extern ProjectConfig projectConfig;
|
||||
|
|
|
@ -178,7 +178,7 @@ public:
|
|||
static QString eventGroupToString(Event::Group group);
|
||||
static QString eventTypeToString(Event::Type type);
|
||||
static Event::Type eventTypeFromString(QString type);
|
||||
static void initIcons();
|
||||
static void setIcons();
|
||||
|
||||
// protected attributes
|
||||
protected:
|
||||
|
@ -260,8 +260,6 @@ public:
|
|||
void setFrameFromMovement(QString movement);
|
||||
void setPixmapFromSpritesheet(QImage, int, int, bool);
|
||||
|
||||
static const QPixmap * defaultIcon;
|
||||
|
||||
|
||||
protected:
|
||||
QString gfx;
|
||||
|
@ -347,8 +345,6 @@ public:
|
|||
void setDestinationWarpID(QString newDestinationWarpID) { this->destinationWarpID = newDestinationWarpID; }
|
||||
QString getDestinationWarpID() { return this->destinationWarpID; }
|
||||
|
||||
static const QPixmap * defaultIcon;
|
||||
|
||||
private:
|
||||
QString destinationMap;
|
||||
QString destinationWarpID;
|
||||
|
@ -375,8 +371,6 @@ public:
|
|||
virtual void setDefaultValues(Project *project) override = 0;
|
||||
|
||||
virtual QSet<QString> getExpectedFields() override = 0;
|
||||
|
||||
static const QPixmap * defaultIcon;
|
||||
};
|
||||
|
||||
|
||||
|
@ -476,8 +470,6 @@ public:
|
|||
virtual void setDefaultValues(Project *project) override = 0;
|
||||
|
||||
virtual QSet<QString> getExpectedFields() override = 0;
|
||||
|
||||
static const QPixmap * defaultIcon;
|
||||
};
|
||||
|
||||
|
||||
|
@ -633,8 +625,6 @@ public:
|
|||
void setRespawnNPC(uint8_t newRespawnNPC) { this->respawnNPC = newRespawnNPC; }
|
||||
uint8_t getRespawnNPC() { return this->respawnNPC; }
|
||||
|
||||
static const QPixmap * defaultIcon;
|
||||
|
||||
private:
|
||||
int index = -1;
|
||||
QString locationName;
|
||||
|
|
|
@ -138,6 +138,7 @@ public:
|
|||
|
||||
int scaleIndex = 2;
|
||||
qreal collisionOpacity = 0.5;
|
||||
static QList<QList<const QImage*>> collisionIcons;
|
||||
|
||||
void objectsView_onMousePress(QMouseEvent *event);
|
||||
|
||||
|
@ -151,6 +152,7 @@ public:
|
|||
void scaleMapView(int);
|
||||
static void openInTextEditor(const QString &path, int lineNum = 0);
|
||||
bool eventLimitReached(Event::Type type);
|
||||
static void setCollisionGraphics();
|
||||
|
||||
public slots:
|
||||
void openMapScripts() const;
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
class MovementPermissionsSelector: public SelectablePixmapItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MovementPermissionsSelector(QPixmap basePixmap) :
|
||||
SelectablePixmapItem(32, 32, 1, 1),
|
||||
MovementPermissionsSelector(int cellWidth, int cellHeight, QPixmap basePixmap) :
|
||||
SelectablePixmapItem(cellWidth, cellHeight, 1, 1),
|
||||
basePixmap(basePixmap) {
|
||||
setAcceptHoverEvents(true);
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
<file>icons/ui/midnight_branch_more.png</file>
|
||||
<file>images/blank_tileset.png</file>
|
||||
<file>images/collisions.png</file>
|
||||
<file>images/collisions_unknown.png</file>
|
||||
<file>images/Entities_16x16.png</file>
|
||||
<file>icons/clipboard.ico</file>
|
||||
</qresource>
|
||||
|
|
BIN
resources/images/collisions_unknown.png
Normal file
BIN
resources/images/collisions_unknown.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 212 B |
|
@ -628,8 +628,10 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
} else if (key == "enable_triple_layer_metatiles") {
|
||||
this->enableTripleLayerMetatiles = getConfigBool(key, value);
|
||||
} else if (key == "new_map_metatile") {
|
||||
// TODO: Update max
|
||||
this->newMapMetatileId = getConfigUint32(key, value, 0, 1023, 0);
|
||||
} else if (key == "new_map_elevation") {
|
||||
// TODO: Update max
|
||||
this->newMapElevation = getConfigInteger(key, value, 0, 15, 3);
|
||||
} else if (key == "new_map_border_metatiles") {
|
||||
this->newMapBorderMetatileIds.clear();
|
||||
|
@ -652,13 +654,13 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
}
|
||||
this->metatileAttributesSize = size;
|
||||
} else if (key == "metatile_behavior_mask") {
|
||||
this->metatileBehaviorMask = getConfigUint32(key, value, 0, 0xFFFFFFFF, 0);
|
||||
this->metatileBehaviorMask = getConfigUint32(key, value);
|
||||
} else if (key == "metatile_terrain_type_mask") {
|
||||
this->metatileTerrainTypeMask = getConfigUint32(key, value, 0, 0xFFFFFFFF, 0);
|
||||
this->metatileTerrainTypeMask = getConfigUint32(key, value);
|
||||
} else if (key == "metatile_encounter_type_mask") {
|
||||
this->metatileEncounterTypeMask = getConfigUint32(key, value, 0, 0xFFFFFFFF, 0);
|
||||
this->metatileEncounterTypeMask = getConfigUint32(key, value);
|
||||
} else if (key == "metatile_layer_type_mask") {
|
||||
this->metatileLayerTypeMask = getConfigUint32(key, value, 0, 0xFFFFFFFF, 0);
|
||||
this->metatileLayerTypeMask = getConfigUint32(key, value);
|
||||
} else if (key == "enable_map_allow_flags") {
|
||||
this->enableMapAllowFlags = getConfigBool(key, value);
|
||||
#ifdef CONFIG_BACKWARDS_COMPATABILITY
|
||||
|
@ -694,6 +696,14 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
this->eventIconPaths[Event::Group::Bg] = value;
|
||||
} else if (key == "event_icon_path_heal") {
|
||||
this->eventIconPaths[Event::Group::Heal] = value;
|
||||
} else if (key == "collision_sheet_path") {
|
||||
this->collisionSheetPath = value;
|
||||
} else if (key == "collision_sheet_width") {
|
||||
// Max for these two keys is if a user specifies blocks with 1 bit for metatile ID and 15 bits for collision or elevation
|
||||
// TODO: Test to make sure there shouldn't be a stricter limit given the UI
|
||||
this->collisionSheetWidth = getConfigInteger(key, value, 1, 0x7FFF, 2);
|
||||
} else if (key == "collision_sheet_height") {
|
||||
this->collisionSheetHeight = getConfigInteger(key, value, 1, 0x7FFF, 16);
|
||||
} else {
|
||||
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key));
|
||||
}
|
||||
|
@ -766,6 +776,9 @@ QMap<QString, QString> ProjectConfig::getKeyValueMap() {
|
|||
map.insert("event_icon_path_coord", this->eventIconPaths[Event::Group::Coord]);
|
||||
map.insert("event_icon_path_bg", this->eventIconPaths[Event::Group::Bg]);
|
||||
map.insert("event_icon_path_heal", this->eventIconPaths[Event::Group::Heal]);
|
||||
map.insert("collision_sheet_path", this->collisionSheetPath);
|
||||
map.insert("collision_sheet_width", QString::number(this->collisionSheetWidth));
|
||||
map.insert("collision_sheet_height", QString::number(this->collisionSheetHeight));
|
||||
return map;
|
||||
}
|
||||
|
||||
|
@ -1116,15 +1129,35 @@ QString ProjectConfig::getEventIconPath(Event::Group group) {
|
|||
return this->eventIconPaths.value(group);
|
||||
}
|
||||
|
||||
void ProjectConfig::setCollisionMapPath(const QString &path) {
|
||||
this->collisionMapPath = path;
|
||||
// TODO: Expose to project settings editor
|
||||
void ProjectConfig::setCollisionSheetPath(const QString &path) {
|
||||
this->collisionSheetPath = path;
|
||||
this->save();
|
||||
}
|
||||
|
||||
QString ProjectConfig::getCollisionMapPath() {
|
||||
return this->collisionMapPath;
|
||||
QString ProjectConfig::getCollisionSheetPath() {
|
||||
return this->collisionSheetPath;
|
||||
}
|
||||
|
||||
void ProjectConfig::setCollisionSheetWidth(int width) {
|
||||
this->collisionSheetWidth = width;
|
||||
this->save();
|
||||
}
|
||||
|
||||
int ProjectConfig::getCollisionSheetWidth() {
|
||||
return this->collisionSheetWidth;
|
||||
}
|
||||
|
||||
void ProjectConfig::setCollisionSheetHeight(int height) {
|
||||
this->collisionSheetHeight = height;
|
||||
this->save();
|
||||
}
|
||||
|
||||
int ProjectConfig::getCollisionSheetHeight() {
|
||||
return this->collisionSheetHeight;
|
||||
}
|
||||
|
||||
|
||||
UserConfig userConfig;
|
||||
|
||||
QString UserConfig::getConfigFilepath() {
|
||||
|
|
|
@ -131,7 +131,7 @@ void Event::loadPixmap(Project *) {
|
|||
this->pixmap = pixmap ? *pixmap : QPixmap();
|
||||
}
|
||||
|
||||
void Event::initIcons() {
|
||||
void Event::setIcons() {
|
||||
qDeleteAll(icons);
|
||||
icons.clear();
|
||||
|
||||
|
@ -159,9 +159,9 @@ void Event::initIcons() {
|
|||
if (customIcon.isNull()) {
|
||||
// Custom icon failed to load, use the default icon.
|
||||
icons[group] = new QPixmap(defaultIcons.copy(i * w, 0, w, h));
|
||||
logError(QString("Failed to load custom event icon '%1', using default icon.").arg(customIconPath));
|
||||
logWarn(QString("Failed to load custom event icon '%1', using default icon.").arg(customIconPath));
|
||||
} else {
|
||||
icons[group] = new QPixmap(customIcon);
|
||||
icons[group] = new QPixmap(customIcon.scaled(w, h));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,11 @@
|
|||
#include <math.h>
|
||||
|
||||
static bool selectNewEvents = false;
|
||||
static const QPixmap *collisionSheetPixmap = nullptr;
|
||||
static const int movementPermissionsSelectorCellSize = 32;
|
||||
|
||||
// 2D array mapping collision+elevation combos to an icon.
|
||||
QList<QList<const QImage*>> Editor::collisionIcons;
|
||||
|
||||
Editor::Editor(Ui::MainWindow* ui)
|
||||
{
|
||||
|
@ -1484,12 +1489,14 @@ void Editor::displayMovementPermissionSelector() {
|
|||
|
||||
scene_collision_metatiles = new QGraphicsScene;
|
||||
if (!movement_permissions_selector_item) {
|
||||
movement_permissions_selector_item = new MovementPermissionsSelector(QPixmap(":/images/collisions.png").scaled(32 * 2, 32 * 16)); // TODO: Don't assume default
|
||||
movement_permissions_selector_item = new MovementPermissionsSelector(movementPermissionsSelectorCellSize,
|
||||
movementPermissionsSelectorCellSize,
|
||||
collisionSheetPixmap ? *collisionSheetPixmap : QPixmap());
|
||||
connect(movement_permissions_selector_item, &MovementPermissionsSelector::hoveredMovementPermissionChanged,
|
||||
this, &Editor::onHoveredMovementPermissionChanged);
|
||||
connect(movement_permissions_selector_item, &MovementPermissionsSelector::hoveredMovementPermissionCleared,
|
||||
this, &Editor::onHoveredMovementPermissionCleared);
|
||||
movement_permissions_selector_item->select(0, 3);
|
||||
movement_permissions_selector_item->select(0, projectConfig.getNewMapElevation()); // TODO: New map collision config?
|
||||
}
|
||||
|
||||
scene_collision_metatiles->addItem(movement_permissions_selector_item);
|
||||
|
@ -2227,3 +2234,77 @@ void Editor::objectsView_onMousePress(QMouseEvent *event) {
|
|||
}
|
||||
selectingEvent = false;
|
||||
}
|
||||
|
||||
// TODO: Show elevation & collision spinners on collision tab
|
||||
// TODO: Hide selection rect for elevation/collision combos not shown on the image
|
||||
// TODO: Zoom slider
|
||||
// TODO: Bug--Images with transparency allow users to paint metatiles on the Collision tab
|
||||
// Custom collision graphics may be provided by the user.
|
||||
void Editor::setCollisionGraphics() {
|
||||
static const QImage defaultCollisionImgSheet = QImage(":/images/collisions.png");
|
||||
QString customPath = projectConfig.getCollisionSheetPath();
|
||||
|
||||
QImage imgSheet;
|
||||
if (!customPath.isEmpty()) {
|
||||
// Try to load custom collision image
|
||||
QFileInfo info(customPath);
|
||||
if (info.isRelative()) {
|
||||
customPath = QDir::cleanPath(projectConfig.getProjectDir() + QDir::separator() + customPath);
|
||||
}
|
||||
imgSheet = QImage(customPath);
|
||||
if (imgSheet.isNull()) {
|
||||
// Custom collision image failed to load, use default
|
||||
logWarn(QString("Failed to load custom collision image '%1', using default.").arg(customPath));
|
||||
imgSheet = defaultCollisionImgSheet;
|
||||
}
|
||||
} else {
|
||||
// No custom collision image specified, use the default.
|
||||
imgSheet = defaultCollisionImgSheet;
|
||||
}
|
||||
|
||||
// Like the vanilla collision image, users are not required to provide an image that gives an icon for every elevation/collision combination.
|
||||
// Instead they tell us how many are provided in their image by specifying the number of columns and rows.
|
||||
const int imgColumns = projectConfig.getCollisionSheetWidth();
|
||||
const int imgRows = projectConfig.getCollisionSheetHeight();
|
||||
|
||||
// Create a pixmap for the selector on the Collision tab
|
||||
delete collisionSheetPixmap;
|
||||
collisionSheetPixmap = new QPixmap(QPixmap::fromImage(imgSheet)
|
||||
.scaled(movementPermissionsSelectorCellSize * imgColumns,
|
||||
movementPermissionsSelectorCellSize * imgRows));
|
||||
|
||||
for (auto sublist : collisionIcons)
|
||||
qDeleteAll(sublist);
|
||||
collisionIcons.clear();
|
||||
|
||||
// Chop up the collision image sheet into separate icon images to be displayed on the map.
|
||||
// Any icons for elevation/collision combinations that aren't provided by the image sheet are also created now.
|
||||
const int w = 16;
|
||||
const int h = 16;
|
||||
const int numCollisions = 4; // TODO: Read value from elsewhere
|
||||
const int numElevations = 16; // TODO: Read value from elsewhere
|
||||
imgSheet = imgSheet.scaled(w * imgColumns, h * imgRows);
|
||||
for (int collision = 0; collision < numCollisions; collision++) {
|
||||
// If (collision >= imgColumns) here, it's a valid collision value, but it is not represented with an icon on the image sheet.
|
||||
// In this case we just use the rightmost collision icon. This is mostly to support the vanilla case, where technically 0-3
|
||||
// are valid collision values, but 1-3 have the same meaning, so the vanilla collision selector image only has 2 columns.
|
||||
int x = ((collision < imgColumns) ? collision : imgColumns) * w;
|
||||
|
||||
QList<const QImage*> sublist;
|
||||
for (int elevation = 0; elevation < numElevations; elevation++) {
|
||||
if (elevation < imgRows) {
|
||||
// This elevation has an icon on the image sheet, add it to the list
|
||||
int y = elevation * h;
|
||||
sublist.append(new QImage(imgSheet.copy(x, y, w, h)));
|
||||
} else {
|
||||
// This is a valid elevation value, but it has no icon on the image sheet.
|
||||
// Give it a placeholder "?" icon (white if passable, red otherwise)
|
||||
static const QImage placeholder = QImage(":/images/collisions_unknown.png");
|
||||
static const QImage * placeholder_White = new QImage(placeholder.copy(0, 0, w, h));
|
||||
static const QImage * placeholder_Red = new QImage(placeholder.copy(w, 0, w, h));
|
||||
sublist.append(x == 0 ? placeholder_White : placeholder_Red);
|
||||
}
|
||||
}
|
||||
collisionIcons.append(sublist);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -391,6 +391,9 @@ void MainWindow::setProjectSpecificUIVisibility()
|
|||
bool floorNumEnabled = projectConfig.getFloorNumberEnabled();
|
||||
ui->spinBox_FloorNumber->setVisible(floorNumEnabled);
|
||||
ui->label_FloorNumber->setVisible(floorNumEnabled);
|
||||
|
||||
Event::setIcons();
|
||||
Editor::setCollisionGraphics();
|
||||
}
|
||||
|
||||
void MainWindow::mapSortOrder_changed(QAction *action)
|
||||
|
@ -514,7 +517,6 @@ bool MainWindow::openProject(QString dir) {
|
|||
this->setProjectSpecificUIVisibility();
|
||||
this->newMapDefaultsSet = false;
|
||||
|
||||
Event::initIcons();
|
||||
Scripting::init(this);
|
||||
bool already_open = isProjectOpen() && (editor->project->root == dir);
|
||||
if (!already_open) {
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
#include "config.h"
|
||||
#include "imageproviders.h"
|
||||
#include "log.h"
|
||||
#include "editor.h"
|
||||
#include <QPainter>
|
||||
|
||||
QImage getCollisionMetatileImage(Block block) {
|
||||
return getCollisionMetatileImage(block.collision, block.elevation);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
QImage getCollisionMetatileImage(int collision, int elevation) {
|
||||
static const QImage collisionImage(":/images/collisions.png");
|
||||
int x = (collision != 0) * 16;
|
||||
int y = elevation * 16;
|
||||
return collisionImage.copy(x, y, 16, 16);
|
||||
const QImage * image = Editor::collisionIcons.at(collision).at(elevation);
|
||||
return image ? *image : QImage();
|
||||
}
|
||||
|
||||
QImage getMetatileImage(
|
||||
|
|
Loading…
Reference in a new issue