Merge branch 'dev' of https://github.com/huderlem/porymap into local-id

This commit is contained in:
GriffinR 2025-04-07 16:47:04 -04:00
commit 33c66f272e
28 changed files with 926 additions and 532 deletions

View File

@ -37,7 +37,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
- The max encounter rate is now read from the project, rather than assuming the default value from RSE.
- It's now possible to cancel quitting if there are unsaved changes in sub-windows.
- The triple-layer metatiles setting can now be set automatically using a project constant.
- `Export Map Stitch Image` now shows a preview of the full image, not just the current map.
- `Export Map Stitch Image` and `Export Map Timelapse Image` now show a preview of the full image/gif, not just the current map.
- `Custom Attributes` tables now display numbers using spin boxes. The `type` column was removed, because `value`'s type is now obvious.
- Unrecognized map names in Event or Connections data will no longer be overwritten.
- It's now possible to click on an event's sprite even if a different event's rectangle is overlapping it. The old selection behavior is available via a new setting.
@ -62,6 +62,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
- Fix unsaved changes being ignored when quitting (such as with Cmd+Q on macOS).
- Fix selections with multiple Events not always clearing when making a new selection.
- Fix the new event button not updating correctly when selecting object events.
- Fix duplicated `Hidden Item` events not copying the `Requires Itemfinder` field.
- Fix `About porymap` opening a new window each time it's activated.
- Fix the `Edit History` window not raising to the front when reactivated.
- New maps are now always inserted in map dropdowns at the correct position, rather than at the bottom of the list until the project is reloaded.
@ -81,6 +82,12 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
- Fix a freeze on startup if project values are defined with mismatched parentheses.
- Fix stitched map images sometimes rendering garbage
- Fix the `Reset` button on `Export Map Timelapse Image` not resetting the Timelapse settings.
- Fix events in exported map stitch images being occluded by neighboring maps.
- Fix the map connections in exported map images coming from the map currently open in the editor, rather than the map shown in the export window.
- Fix crash when exporting a map stitch image if a map fails to load.
- Fix possible crash when exporting a timelapse that has events edit history.
- Fix exported timelapses excluding pasted events and certain map size changes.
- Fix exporting a timelapse sometimes altering the state of the current map's edit history.
- Stop sliders in the Palette Editor from creating a bunch of edit history when used.
- Fix scrolling on some containers locking up when the mouse stops over a spin box or combo box.
- Fix some file dialogs returning to an incorrect window when closed.
@ -88,6 +95,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
- Fix bug where layout json and blockdata could be saved separately leading to inconsistent data.
- 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 custom project filepaths not converting Windows file separators.
- 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.

View File

@ -456,7 +456,7 @@ in order to add a new map to the folder.</p>
<dt>Name</dt>
<dd>The name of the new map. This cannot be changed in porymap.</dd>
<dt>Group</dt>
<dd>Which map group the new map will beling to. This cannot be changed in porymap.</dd>
<dd>Which map group the new map will belong to. This cannot be changed in porymap.</dd>
<dt>Map Width</dt>
<dd>The width (in metatiles) of the map. This can be changed in porymap.</dd>
<dt>Map Height</dt>
@ -538,4 +538,4 @@ in order to add a new map to the folder.</p>
</body>
</html>
</html>

View File

@ -31,7 +31,7 @@ Name
The name of the new map. This cannot be changed in porymap.
Group
Which map group the new map will beling to. This cannot be changed in porymap.
Which map group the new map will belong to. This cannot be changed in porymap.
Map Width
The width (in metatiles) of the map. This can be changed in porymap.

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>817</width>
<height>535</height>
<height>556</height>
</rect>
</property>
<property name="windowTitle">
@ -25,6 +25,9 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::FocusPolicy::ClickFocus</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_Options">
<item>
<layout class="QFormLayout" name="formLayout">
@ -37,6 +40,9 @@
</item>
<item row="0" column="1">
<widget class="NoScrollComboBox" name="comboBox_MapSelection">
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::SizeAdjustPolicy::AdjustToContents</enum>
</property>
@ -163,7 +169,7 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="checkBox_Elevation">
<widget class="QCheckBox" name="checkBox_Collision">
<property name="text">
<string>Collision</string>
</property>
@ -188,7 +194,10 @@
</property>
<layout class="QGridLayout" name="gridLayout_7">
<item row="2" column="1">
<widget class="QSpinBox" name="spinBox_TimelapseDelay">
<widget class="NoScrollSpinBox" name="spinBox_TimelapseDelay">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::StrongFocus</enum>
</property>
<property name="specialValueText">
<string/>
</property>
@ -214,7 +223,10 @@
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="spinBox_FrameSkip">
<widget class="NoScrollSpinBox" name="spinBox_FrameSkip">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::StrongFocus</enum>
</property>
<property name="suffix">
<string/>
</property>
@ -250,9 +262,22 @@
</spacer>
</item>
<item>
<widget class="QCheckBox" name="checkBox_ActualSize">
<widget class="QCheckBox" name="checkBox_DisablePreviewUpdates">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the image in the preview window will not be recreated when the settings are changed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Preview actual size</string>
<string>Disable preview updates</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_DisablePreviewScaling">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the image shown in the preview window will not scale to fit into the available space. The aspect ratio of the image will never change.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Disable preview scaling</string>
</property>
</widget>
</item>
@ -263,6 +288,9 @@
<property name="text">
<string>Reset</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
@ -283,6 +311,9 @@
<property name="text">
<string>Cancel</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
@ -290,6 +321,9 @@
<property name="text">
<string>Save</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
@ -323,16 +357,22 @@
</property>
<item row="0" column="0">
<widget class="QScrollArea" name="scrollArea_Preview">
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>469</width>
<height>464</height>
<height>485</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
@ -398,6 +438,11 @@
<extends>QComboBox</extends>
<header>noscrollcombobox.h</header>
</customwidget>
<customwidget>
<class>NoScrollSpinBox</class>
<extends>QSpinBox</extends>
<header>noscrollspinbox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View File

@ -47,6 +47,13 @@ enum CommandId {
#define IDMask_EventType_Trigger (1 << 11)
#define IDMask_EventType_Heal (1 << 12)
#define IDMask_ConnectionDirection_Up (1 << 8)
#define IDMask_ConnectionDirection_Down (1 << 9)
#define IDMask_ConnectionDirection_Left (1 << 10)
#define IDMask_ConnectionDirection_Right (1 << 11)
#define IDMask_ConnectionDirection_Dive (1 << 12)
#define IDMask_ConnectionDirection_Emerge (1 << 13)
/// Implements a command to commit metatile paint actions
/// onto the map using the pencil tool.
class PaintMetatile : public QUndoCommand {
@ -400,7 +407,7 @@ public:
void redo() override;
bool mergeWith(const QUndoCommand *command) override;
int id() const override { return CommandId::ID_MapConnectionMove; }
int id() const override;
private:
MapConnection *connection;
@ -421,7 +428,7 @@ public:
void undo() override;
void redo() override;
int id() const override { return CommandId::ID_MapConnectionChangeDirection; }
int id() const override;
private:
QPointer<MapConnection> connection;
@ -443,7 +450,7 @@ public:
void undo() override;
void redo() override;
int id() const override { return CommandId::ID_MapConnectionChangeMap; }
int id() const override;
private:
QPointer<MapConnection> connection;
@ -465,7 +472,7 @@ public:
void undo() override;
void redo() override;
int id() const override { return CommandId::ID_MapConnectionAdd; }
int id() const override;
private:
Map *map = nullptr;
@ -485,7 +492,7 @@ public:
void undo() override;
void redo() override;
int id() const override { return CommandId::ID_MapConnectionRemove; }
int id() const override;
private:
Map *map = nullptr;

View File

@ -170,6 +170,7 @@ public:
static QString typeToJsonKey(Event::Type type);
static Event::Type typeFromJsonKey(QString type);
static QList<Event::Type> types();
static QList<Event::Group> groups();
// protected attributes
protected:
@ -340,10 +341,12 @@ public:
QString getDestinationWarpID() const { return this->destinationWarpID; }
void setWarningEnabled(bool enabled);
bool getWarningEnabled() const { return this->warningEnabled; }
private:
QString destinationMap;
QString destinationWarpID;
bool warningEnabled = false;
};
@ -622,6 +625,10 @@ private:
};
inline uint qHash(const Event::Group &key, uint seed = 0) {
return qHash(static_cast<int>(key), seed);
}
///
/// Keeps track of scripts

View File

@ -87,7 +87,7 @@ public:
bool hasEvent(Event *) const;
void deleteConnections();
QList<MapConnection*> getConnections() const;
QList<MapConnection*> getConnections() const { return m_connections; }
void removeConnection(MapConnection *);
void addConnection(MapConnection *);
void loadConnection(MapConnection *);

View File

@ -27,6 +27,11 @@ public:
QString direction() const { return m_direction; }
void setDirection(const QString &direction, bool mirror = true);
bool isCardinal() const { return isCardinal(m_direction); }
bool isHorizontal() const { return isHorizontal(m_direction); }
bool isVertical() const { return isVertical(m_direction); }
bool isDiving() const { return isDiving(m_direction); }
int offset() const { return m_offset; }
void setOffset(int offset, bool mirror = true);
@ -36,7 +41,8 @@ public:
MapConnection* findMirror();
MapConnection* createMirror();
QPixmap getPixmap();
QPixmap render() const;
QPoint relativePos(bool clipped = false) const;
static QPointer<Project> project;
static const QMap<QString, QString> oppositeDirections;

View File

@ -96,9 +96,12 @@ public:
int getHeight() const { return height; }
int getBorderWidth() const { return border_width; }
int getBorderHeight() const { return border_height; }
int getBorderDrawWidth() const;
int getBorderDrawHeight() const;
bool isWithinBounds(int x, int y);
bool isWithinBorderBounds(int x, int y);
bool isWithinBounds(int x, int y) const;
bool isWithinBounds(const QRect &rect) const;
bool isWithinBorderBounds(int x, int y) const;
bool getBlock(int x, int y, Block *out);
void setBlock(int x, int y, Block block, bool enableScriptCallback = false);
@ -141,6 +144,8 @@ private:
void setNewDimensionsBlockdata(int newWidth, int newHeight);
void setNewBorderDimensionsBlockdata(int newWidth, int newHeight);
static int getBorderDrawDistance(int dimension, qreal minimum);
signals:
void dimensionsChanged(const QSize &size);
void needsRedrawing();

View File

@ -175,8 +175,6 @@ public:
void eventsView_onMousePress(QMouseEvent *event);
int getBorderDrawDistance(int dimension);
bool selectingEvent = false;
void deleteSelectedEvents();

View File

@ -301,6 +301,10 @@ public:
Ui::MainWindow *ui;
QPointer<Editor> editor = nullptr;
signals:
void mapOpened(Map*);
void layoutOpened(Layout*);
private:
QLabel *label_MapRulerStatus = nullptr;
QPointer<TilesetEditor> tilesetEditor = nullptr;

View File

@ -1,10 +1,9 @@
#ifndef MAPIMAGEEXPORTER_H
#define MAPIMAGEEXPORTER_H
#include "map.h"
#include "editor.h"
#include "project.h"
#include <QDialog>
class QGifImage;
namespace Ui {
class MapImageExporter;
@ -17,21 +16,17 @@ enum ImageExporterMode {
};
struct ImageExporterSettings {
bool showObjects = false;
bool showWarps = false;
bool showBGs = false;
bool showTriggers = false;
bool showHealLocations = false;
bool showUpConnections = false;
bool showDownConnections = false;
bool showLeftConnections = false;
bool showRightConnections = false;
QSet<Event::Group> showEvents;
QSet<QString> showConnections;
bool showGrid = false;
bool showBorder = false;
bool showCollision = false;
bool previewActualSize = false;
bool disablePreviewScaling = false;
bool disablePreviewUpdates = false;
int timelapseSkipAmount = 1;
int timelapseDelayMs = 200;
// Not exposed as a setting in the UI atm (our color input widget has no alpha channel).
QColor fillColor = Qt::transparent;
};
class MapImageExporter : public QDialog
@ -39,32 +34,58 @@ class MapImageExporter : public QDialog
Q_OBJECT
public:
explicit MapImageExporter(QWidget *parent, Editor *editor, ImageExporterMode mode);
explicit MapImageExporter(QWidget *parent, Project *project, Map *map, ImageExporterMode mode = ImageExporterMode::Normal)
: MapImageExporter(parent, project, map, map->layout(), mode) {};
explicit MapImageExporter(QWidget *parent, Project *project, Layout *layout, ImageExporterMode mode = ImageExporterMode::Normal)
: MapImageExporter(parent, project, nullptr, layout, mode) {};
~MapImageExporter();
ImageExporterMode mode() const { return m_mode; }
void setMap(Map *map);
void setLayout(Layout *layout);
private:
explicit MapImageExporter(QWidget *parent, Project *project, Map *map, Layout *layout, ImageExporterMode mode);
Ui::MapImageExporter *ui;
Layout *m_layout = nullptr;
Project *m_project = nullptr;
Map *m_map = nullptr;
Editor *m_editor = nullptr;
Layout *m_layout = nullptr;
QGraphicsScene *m_scene = nullptr;
QPixmap m_preview;
QGifImage *m_timelapseGifImage = nullptr;
QBuffer *m_timelapseBuffer = nullptr;
QMovie *m_timelapseMovie = nullptr;
QGraphicsPixmapItem *m_preview = nullptr;
ImageExporterSettings m_settings;
ImageExporterMode m_mode = ImageExporterMode::Normal;
ImageExporterMode m_originalMode;
void updatePreview();
void setModeSpecificUi();
void setSelectionText(const QString &text);
void updateMapSelection();
void resetSettings();
QString getTitle(ImageExporterMode mode);
QString getDescription(ImageExporterMode mode);
void updatePreview(bool forceUpdate = false);
void scalePreview();
void updateShowBorderState();
bool eventsEnabled();
void setEventGroupEnabled(Event::Group group, bool enable);
bool connectionsEnabled();
void setConnectionDirectionEnabled(const QString &dir, bool enable);
void saveImage();
QPixmap getStitchedImage(QProgressDialog *progress, bool includeBorder);
QGifImage* createTimelapseGifImage(QProgressDialog *progress);
QPixmap getStitchedImage(QProgressDialog *progress);
QPixmap getFormattedMapPixmap();
QPixmap getFormattedMapPixmap(Map *map, bool ignoreBorder = false);
QPixmap getFormattedLayoutPixmap(Layout *layout, bool ignoreBorder = false, bool ignoreGrid = false);
void paintGrid(QPixmap *pixmap, bool ignoreBorder = false);
bool historyItemAppliesToFrame(const QUndoCommand *command);
void paintBorder(QPainter *painter, Layout *layout);
void paintCollision(QPainter *painter, Layout *layout);
void paintConnections(QPainter *painter, const Map *map);
void paintEvents(QPainter *painter, const Map *map);
void paintGrid(QPainter *painter, const Layout *layout = nullptr);
QMargins getMargins(const Map *map);
QPixmap getExpandedPixmap(const QPixmap &pixmap, const QSize &targetSize, const QColor &fillColor);
bool currentHistoryAppliesToFrame(QUndoStack *historyStack);
protected:
virtual void showEvent(QShowEvent *) override;
@ -84,15 +105,16 @@ private slots:
void on_checkBox_ConnectionRight_stateChanged(int state);
void on_checkBox_AllConnections_stateChanged(int state);
void on_checkBox_Elevation_stateChanged(int state);
void on_checkBox_Collision_stateChanged(int state);
void on_checkBox_Grid_stateChanged(int state);
void on_checkBox_Border_stateChanged(int state);
void on_pushButton_Reset_pressed();
void on_spinBox_TimelapseDelay_valueChanged(int delayMs);
void on_spinBox_FrameSkip_valueChanged(int skip);
void on_spinBox_TimelapseDelay_editingFinished();
void on_spinBox_FrameSkip_editingFinished();
void on_checkBox_ActualSize_stateChanged(int state);
void on_checkBox_DisablePreviewScaling_stateChanged(int state);
void on_checkBox_DisablePreviewUpdates_stateChanged(int state);
};
#endif // MAPIMAGEEXPORTER_H

View File

@ -987,7 +987,7 @@ void ProjectConfig::setFilePath(const QString &pathId, const QString &path) {
}
QString ProjectConfig::getCustomFilePath(ProjectFilePath pathId) {
return this->filePaths.value(pathId);
return QDir::cleanPath(this->filePaths.value(pathId));
}
QString ProjectConfig::getCustomFilePath(const QString &pathId) {
@ -995,14 +995,17 @@ QString ProjectConfig::getCustomFilePath(const QString &pathId) {
}
QString ProjectConfig::getFilePath(ProjectFilePath pathId) {
const QString customPath = this->getCustomFilePath(pathId);
QString customPath = this->getCustomFilePath(pathId);
if (!customPath.isEmpty()) {
// A custom filepath has been specified. If the file/folder exists, use that.
const QString absCustomPath = this->projectDir + QDir::separator() + customPath;
if (QFileInfo::exists(absCustomPath)) {
const QString baseDir = this->projectDir + "/";
if (customPath.startsWith(baseDir)) {
customPath.remove(0, baseDir.length());
}
if (QFileInfo::exists(QDir::cleanPath(baseDir + customPath))) {
return customPath;
} else {
logError(QString("Custom project filepath '%1' not found. Using default.").arg(absCustomPath));
logError(QString("Custom project filepath '%1' not found. Using default.").arg(customPath));
}
}
return defaultPaths.contains(pathId) ? defaultPaths[pathId].second : QString();

View File

@ -5,7 +5,7 @@
#include <QDebug>
int getEventTypeMask(QList<Event *> events) {
int getEventTypeMask(const QList<Event *> &events) {
int eventTypeMask = 0;
for (auto event : events) {
Event::Group groupType = event->getEventGroup();
@ -24,6 +24,26 @@ int getEventTypeMask(QList<Event *> events) {
return eventTypeMask;
}
int getConnectionDirectionMask(const QList<QString> &directions) {
int mask = 0;
for (auto direction : directions) {
if (direction == "up") {
mask |= IDMask_ConnectionDirection_Up;
} else if (direction == "down") {
mask |= IDMask_ConnectionDirection_Down;
} else if (direction == "left") {
mask |= IDMask_ConnectionDirection_Left;
} else if (direction == "right") {
mask |= IDMask_ConnectionDirection_Right;
} else if (direction == "dive") {
mask |= IDMask_ConnectionDirection_Dive;
} else if (direction == "emerge") {
mask |= IDMask_ConnectionDirection_Emerge;
}
}
return mask;
}
void renderBlocks(Layout *layout, bool ignoreCache = false) {
layout->layoutItem->draw(ignoreCache);
layout->collisionItem->draw(ignoreCache);
@ -587,6 +607,10 @@ bool MapConnectionMove::mergeWith(const QUndoCommand *command) {
return true;
}
int MapConnectionMove::id() const {
return CommandId::ID_MapConnectionMove | getConnectionDirectionMask({this->connection->direction()});
}
/******************************************************************************
************************************************************************
******************************************************************************/
@ -629,6 +653,10 @@ void MapConnectionChangeDirection::undo() {
QUndoCommand::undo();
}
int MapConnectionChangeDirection::id() const {
return CommandId::ID_MapConnectionChangeDirection | getConnectionDirectionMask({this->oldDirection, this->newDirection});
}
/******************************************************************************
************************************************************************
******************************************************************************/
@ -664,6 +692,10 @@ void MapConnectionChangeMap::undo() {
QUndoCommand::undo();
}
int MapConnectionChangeMap::id() const {
return CommandId::ID_MapConnectionChangeMap | getConnectionDirectionMask({this->connection->direction()});
}
/******************************************************************************
************************************************************************
******************************************************************************/
@ -708,6 +740,10 @@ void MapConnectionAdd::undo() {
QUndoCommand::undo();
}
int MapConnectionAdd::id() const {
return CommandId::ID_MapConnectionAdd | getConnectionDirectionMask({this->connection->direction()});
}
/******************************************************************************
************************************************************************
******************************************************************************/
@ -745,3 +781,7 @@ void MapConnectionRemove::undo() {
QUndoCommand::undo();
}
int MapConnectionRemove::id() const {
return CommandId::ID_MapConnectionRemove | getConnectionDirectionMask({this->connection->direction()});
}

View File

@ -55,17 +55,23 @@ void Event::modify() {
this->map->modify();
}
const QMap<Event::Group, QString> groupToStringMap = {
{Event::Group::Object, "Object"},
{Event::Group::Warp, "Warp"},
{Event::Group::Coord, "Trigger"},
{Event::Group::Bg, "BG"},
{Event::Group::Heal, "Heal Location"},
};
QString Event::groupToString(Event::Group group) {
static const QMap<Event::Group, QString> groupToStringMap = {
{Event::Group::Object, "Object"},
{Event::Group::Warp, "Warp"},
{Event::Group::Coord, "Trigger"},
{Event::Group::Bg, "BG"},
{Event::Group::Heal, "Heal Location"},
};
return groupToStringMap.value(group);
}
QList<Event::Group> Event::groups() {
static QList<Event::Group> groupList = groupToStringMap.keys();
return groupList;
}
// These are the expected key names used in the map.json files.
// We re-use them for key names in the copy/paste JSON data,
const QMap<Event::Type, QString> typeToJsonKeyMap = {
@ -398,7 +404,11 @@ QSet<QString> WarpEvent::getExpectedFields() {
}
void WarpEvent::setWarningEnabled(bool enabled) {
WarpFrame * frame = static_cast<WarpFrame*>(this->getEventFrame());
this->warningEnabled = enabled;
// Don't call getEventFrame here, because it may create the event frame.
// If the frame hasn't been created yet then we have nothing else to do.
auto frame = static_cast<WarpFrame*>(this->eventFrame.data());
if (frame && frame->warning)
frame->warning->setVisible(enabled);
}
@ -615,7 +625,7 @@ Event *HiddenItemEvent::duplicate() const {
copy->setItem(this->getItem());
copy->setFlag(this->getFlag());
copy->setQuantity(this->getQuantity());
copy->setQuantity(this->getQuantity());
copy->setUnderfoot(this->getUnderfoot());
copy->setCustomAttributes(this->getCustomAttributes());

View File

@ -250,10 +250,6 @@ void Map::deleteConnections() {
m_connections.clear();
}
QList<MapConnection*> Map::getConnections() const {
return m_connections;
}
void Map::addConnection(MapConnection *connection) {
if (!connection || m_connections.contains(connection))
return;
@ -261,7 +257,7 @@ void Map::addConnection(MapConnection *connection) {
// Maps should only have one Dive/Emerge connection at a time.
// (Users can technically have more by editing their data manually, but we will only display one at a time)
// Any additional connections being added (this can happen via mirroring) are tracked for deleting but otherwise ignored.
if (MapConnection::isDiving(connection->direction())) {
if (connection->isDiving()) {
for (const auto &i : m_connections) {
if (i->direction() == connection->direction()) {
trackConnection(connection);

View File

@ -60,7 +60,7 @@ Map* MapConnection::targetMap() const {
return getMap(m_targetMapName);
}
QPixmap MapConnection::getPixmap() {
QPixmap MapConnection::render() const {
auto map = targetMap();
if (!map)
return QPixmap();
@ -68,6 +68,28 @@ QPixmap MapConnection::getPixmap() {
return map->renderConnection(m_direction, m_parentMap ? m_parentMap->layout() : nullptr);
}
// Get the position of the target map relative to its parent map.
// For right/down connections this is offset by the dimensions of the parent map.
// For left/up connections this is offset by the dimensions of the target map.
// If 'clipped' is true, only the rendered dimensions of the target map will be used, rather than its full dimensions.
QPoint MapConnection::relativePos(bool clipped) const {
int x = 0, y = 0;
if (m_direction == "right") {
if (m_parentMap) x = m_parentMap->getWidth();
y = m_offset;
} else if (m_direction == "down") {
x = m_offset;
if (m_parentMap) y = m_parentMap->getHeight();
} else if (m_direction == "left") {
if (targetMap()) x = !clipped ? -targetMap()->getWidth() : -targetMap()->getConnectionRect(m_direction).width();
y = m_offset;
} else if (m_direction == "up") {
x = m_offset;
if (targetMap()) y = !clipped ? -targetMap()->getHeight() : -targetMap()->getConnectionRect(m_direction).height();
}
return QPoint(x, y);
}
void MapConnection::setParentMap(Map* map, bool mirror) {
if (map == m_parentMap)
return;

View File

@ -51,14 +51,37 @@ Layout::Settings Layout::settings() const {
return settings;
}
bool Layout::isWithinBounds(int x, int y) {
bool Layout::isWithinBounds(int x, int y) const {
return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight());
}
bool Layout::isWithinBorderBounds(int x, int y) {
bool Layout::isWithinBounds(const QRect &rect) const {
return rect.left() >= 0 && rect.right() < this->getWidth() && rect.top() >= 0 && rect.bottom() < this->getHeight();
}
bool Layout::isWithinBorderBounds(int x, int y) const {
return (x >= 0 && x < this->getBorderWidth() && y >= 0 && y < this->getBorderHeight());
}
int Layout::getBorderDrawWidth() const {
return getBorderDrawDistance(border_width, BORDER_DISTANCE);
}
int Layout::getBorderDrawHeight() const {
return getBorderDrawDistance(border_height, BORDER_DISTANCE);
}
// We need to draw sufficient border blocks to fill the area that gets loaded around the player in-game (BORDER_DISTANCE).
// Note that this is not the same as the player's view distance.
// The result will be some multiple of the input dimension, because we only draw the border in increments of its full width/height.
int Layout::getBorderDrawDistance(int dimension, qreal minimum) {
if (dimension >= minimum)
return dimension;
// Get first multiple of dimension >= the minimum
return dimension * qCeil(minimum / qMax(dimension, 1));
}
bool Layout::getBlock(int x, int y, Block *out) {
if (isWithinBounds(x, y)) {
int i = y * getWidth() + x;

View File

@ -761,7 +761,7 @@ void Editor::displayConnection(MapConnection *connection) {
if (!connection)
return;
if (MapConnection::isDiving(connection->direction())) {
if (connection->isDiving()) {
displayDivingConnection(connection);
return;
}
@ -826,7 +826,7 @@ void Editor::removeConnectionPixmap(MapConnection *connection) {
if (!connection)
return;
if (MapConnection::isDiving(connection->direction())) {
if (connection->isDiving()) {
removeDivingMapPixmap(connection);
return;
}
@ -1673,10 +1673,9 @@ void Editor::clearMapEvents() {
if (events_group->scene()) {
events_group->scene()->removeItem(events_group);
}
for (QGraphicsItem *child : events_group->childItems()) {
events_group->removeFromGroup(child);
delete child;
}
// events_group does not own its children, the childrens' parent
// is set to the group's parent (and our group has no parent).
qDeleteAll(events_group->childItems());
delete events_group;
events_group = nullptr;
}
@ -1797,8 +1796,8 @@ void Editor::displayMapBorder() {
int borderWidth = this->layout->getBorderWidth();
int borderHeight = this->layout->getBorderHeight();
int borderHorzDist = getBorderDrawDistance(borderWidth);
int borderVertDist = getBorderDrawDistance(borderHeight);
int borderHorzDist = this->layout->getBorderDrawWidth();
int borderVertDist = this->layout->getBorderDrawHeight();
QPixmap pixmap = this->layout->renderBorder();
for (int y = -borderVertDist; y < this->layout->getHeight() + borderVertDist; y += borderHeight)
for (int x = -borderHorzDist; x < this->layout->getWidth() + borderHorzDist; x += borderWidth) {
@ -1823,17 +1822,6 @@ void Editor::updateMapConnections() {
item->render(true);
}
int Editor::getBorderDrawDistance(int dimension) {
// Draw sufficient border blocks to fill the player's view (BORDER_DISTANCE)
if (dimension >= BORDER_DISTANCE) {
return dimension;
} else if (dimension) {
return dimension * (BORDER_DISTANCE / dimension + (BORDER_DISTANCE % dimension ? 1 : 0));
} else {
return BORDER_DISTANCE;
}
}
void Editor::toggleGrid(bool checked) {
if (porymapConfig.showGrid == checked)
return;

View File

@ -960,6 +960,9 @@ bool MainWindow::setMap(QString map_name) {
Scripting::cb_MapOpened(map_name);
prefab.updatePrefabUi(editor->layout);
updateTilesetEditor();
emit mapOpened(editor->map);
return true;
}
@ -1013,6 +1016,8 @@ bool MainWindow::setLayout(QString layoutId) {
userConfig.recentMapOrLayout = layoutId;
emit layoutOpened(editor->layout);
return true;
}
@ -2597,12 +2602,23 @@ void MainWindow::on_actionImport_Map_from_Advance_Map_1_92_triggered() {
void MainWindow::showExportMapImageWindow(ImageExporterMode mode) {
if (!editor->project) return;
// If the user is requesting this window again we assume it's for a new
// window (the map/mode may have changed), so delete the old window.
if (this->mapImageExporter)
// If the user is requesting this window again with a different mode
// then we'll recreate the window with the new mode.
if (this->mapImageExporter && this->mapImageExporter->mode() != mode)
delete this->mapImageExporter;
this->mapImageExporter = new MapImageExporter(this, this->editor, mode);
if (!this->mapImageExporter) {
// Open new image export window
if (this->editor->map){
this->mapImageExporter = new MapImageExporter(this, this->editor->project, this->editor->map, mode);
} else if (this->editor->layout) {
this->mapImageExporter = new MapImageExporter(this, this->editor->project, this->editor->layout, mode);
}
if (this->mapImageExporter) {
connect(this, &MainWindow::mapOpened, this->mapImageExporter, &MapImageExporter::setMap);
connect(this, &MainWindow::layoutOpened, this->mapImageExporter, &MapImageExporter::setLayout);
}
}
openSubWindow(this->mapImageExporter);
}

View File

@ -5,7 +5,7 @@
#include <math.h>
ConnectionPixmapItem::ConnectionPixmapItem(MapConnection* connection)
: QGraphicsPixmapItem(connection->getPixmap()),
: QGraphicsPixmapItem(connection->render()),
connection(connection)
{
this->setEditable(true);
@ -28,7 +28,7 @@ void ConnectionPixmapItem::refresh() {
// Render additional visual effects on top of the base map image.
void ConnectionPixmapItem::render(bool ignoreCache) {
if (ignoreCache)
this->basePixmap = this->connection->getPixmap();
this->basePixmap = this->connection->render();
QPixmap pixmap = this->basePixmap.copy(0, 0, this->basePixmap.width(), this->basePixmap.height());
this->setZValue(-1);
@ -63,10 +63,10 @@ QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVari
int newOffset = this->connection->offset();
// Restrict movement to the metatile grid and perpendicular to the connection direction.
if (MapConnection::isVertical(this->connection->direction())) {
if (this->connection->isVertical()) {
x = (round(newPos.x() / this->mWidth) * this->mWidth) - this->originX;
newOffset = x / this->mWidth;
} else if (MapConnection::isHorizontal(this->connection->direction())) {
} else if (this->connection->isHorizontal()) {
y = (round(newPos.y() / this->mHeight) * this->mHeight) - this->originY;
newOffset = y / this->mHeight;
}
@ -87,9 +87,9 @@ void ConnectionPixmapItem::updatePos() {
qreal x = this->originX;
qreal y = this->originY;
if (MapConnection::isVertical(this->connection->direction())) {
if (this->connection->isVertical()) {
x += this->connection->offset() * this->mWidth;
} else if (MapConnection::isHorizontal(this->connection->direction())) {
} else if (this->connection->isHorizontal()) {
y += this->connection->offset() * this->mHeight;
}
@ -98,22 +98,13 @@ void ConnectionPixmapItem::updatePos() {
}
void ConnectionPixmapItem::updateOrigin() {
const Map *parentMap = connection->parentMap();
const Map *targetMap = connection->targetMap();
const QString direction = connection->direction();
int x = 0, y = 0;
if (direction == "right") {
if (parentMap) x = parentMap->getWidth();
} else if (direction == "down") {
if (parentMap) y = parentMap->getHeight();
} else if (direction == "left") {
if (targetMap) x = -targetMap->getConnectionRect(direction).width();
} else if (direction == "up") {
if (targetMap) y = -targetMap->getConnectionRect(direction).height();
if (this->connection->isVertical()) {
this->originX = 0;
this->originY = this->connection->relativePos(true).y() * this->mHeight;
} else if (this->connection->isHorizontal()) {
this->originX = this->connection->relativePos(true).x() * this->mWidth;
this->originY = 0;
}
this->originX = x * this->mWidth;
this->originY = y * this->mHeight;
updatePos();
}

View File

@ -11,7 +11,7 @@
CustomScriptsEditor::CustomScriptsEditor(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::CustomScriptsEditor),
baseDir(userConfig.projectDir + QDir::separator())
baseDir(userConfig.projectDir + "/")
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);

View File

@ -25,7 +25,7 @@ QPixmap DivingMapPixmapItem::getBasePixmap(MapConnection* connection) {
return QPixmap(); // Save some rendering time if it won't be displayed
if (connection->targetMapName() == connection->parentMapName())
return QPixmap(); // If the map is connected to itself then rendering is pointless.
return connection->getPixmap();
return connection->render();
}
void DivingMapPixmapItem::updatePixmap() {

View File

@ -512,7 +512,7 @@ void WarpFrame::setup() {
this->warning = new QPushButton(warningText, this);
this->warning->setFlat(true);
this->warning->setStyleSheet("color: red; text-align: left");
this->warning->setVisible(false);
this->warning->setVisible(this->warp->getWarningEnabled());
l_vbox_warning->addWidget(this->warning);
this->layout_contents->addLayout(l_vbox_warning);

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@ ProjectSettingsEditor::ProjectSettingsEditor(QWidget *parent, Project *project)
QMainWindow(parent),
ui(new Ui::ProjectSettingsEditor),
project(project),
baseDir(projectConfig.projectDir + QDir::separator())
baseDir(projectConfig.projectDir + "/")
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
@ -388,11 +388,13 @@ QString ProjectSettingsEditor::chooseProjectFile(const QString &defaultFilepath)
QString path;
if (defaultFilepath.endsWith("/")){
// Default filepath is a folder, choose a new folder
path = FileDialog::getExistingDirectory(this, "Choose Project File Folder", startDir) + QDir::separator();
path = FileDialog::getExistingDirectory(this, "Choose Project File Folder", startDir) + "/";
} else{
// Default filepath is not a folder, choose a new file
path = FileDialog::getOpenFileName(this, "Choose Project File", startDir);
}
if (path.isEmpty())
return path;
if (!path.startsWith(this->baseDir)){
// Most of Porymap's file-parsing code for project files will assume that filepaths

View File

@ -356,6 +356,15 @@ QVector<QRgb> QGifImage::globalColorTable() const
return d->globalColorTable;
}
/*!
Return canvas size.
*/
QSize QGifImage::getCanvasSize() const
{
Q_D(const QGifImage);
return d->getCanvasSize();
}
/*!
Return background color of the gif canvas. It only makes sense when
global color table is not empty.

View File

@ -52,6 +52,8 @@ public:
int loopCount() const;
void setLoopCount(int loop);
QSize getCanvasSize() const;
int frameCount() const;
QImage frame(int index) const;