Fix more minor map connection issues

This commit is contained in:
GriffinR 2024-08-09 01:29:28 -04:00
parent 6fbc6d8d86
commit 1686167714
16 changed files with 382 additions and 316 deletions

View file

@ -16,6 +16,9 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">.ConnectionsListItem { border-width: 1px; }</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>

View file

@ -69,7 +69,6 @@ public:
QMap<Event::Group, QList<Event *>> events;
QList<Event *> ownedEvents; // for memory management
QList<MapConnection*> connections;
QList<int> metatileLayerOrder;
QList<float> metatileLayerOpacity;
@ -98,8 +97,11 @@ public:
QStringList getScriptLabels(Event::Group group = Event::Group::None);
void removeEvent(Event *);
void addEvent(Event *);
bool removeConnection(MapConnection *);
void deleteConnections();
QList<MapConnection*> getConnections() const;
void removeConnection(MapConnection *);
void addConnection(MapConnection *);
void loadConnection(MapConnection *);
QPixmap renderConnection(const QString &, MapLayout *);
QPixmap renderBorder(bool ignoreCache = false);
void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false);
@ -129,12 +131,15 @@ private:
void setNewDimensionsBlockdata(int newWidth, int newHeight);
void setNewBorderDimensionsBlockdata(int newWidth, int newHeight);
QList<MapConnection*> connections;
signals:
void modified();
void mapDimensionsChanged(const QSize &size);
void mapNeedsRedrawing();
void openScriptRequested(QString label);
void connectionAdded(MapConnection*);
void connectionRemoved(MapConnection*);
};
#endif // MAP_H

View file

@ -7,24 +7,27 @@
#include <QMap>
class Project;
class Map;
class MapConnection : public QObject
{
Q_OBJECT
public:
MapConnection(const QString &direction, const QString &hostMapName, const QString &targetMapName, int offset = 0);
MapConnection(const QString &targetMapName, const QString &direction, int offset = 0);
Map* parentMap() const { return m_parentMap; }
QString parentMapName() const;
void setParentMap(Map* map, bool mirror = true);
Map* targetMap() const;
QString targetMapName() const { return m_targetMapName; }
void setTargetMapName(const QString &targetMapName, bool mirror = true);
QString direction() const { return m_direction; }
void setDirection(const QString &direction);
QString hostMapName() const { return m_hostMapName; }
void setHostMapName(const QString &hostMapName);
QString targetMapName() const { return m_targetMapName; }
void setTargetMapName(const QString &targetMapName);
void setDirection(const QString &direction, bool mirror = true);
int offset() const { return m_offset; }
void setOffset(int offset);
void setOffset(int offset, bool mirror = true);
bool isMirror(const MapConnection*);
MapConnection* findMirror();
@ -38,25 +41,22 @@ public:
static bool isCardinal(const QString &direction);
static bool isHorizontal(const QString &direction);
static bool isVertical(const QString &direction);
static bool isDiving(const QString &direction);
static QString oppositeDirection(const QString &direction) { return oppositeDirections.value(direction, direction); }
private:
QString m_direction;
QString m_hostMapName;
Map* m_parentMap;
QString m_targetMapName;
QString m_direction;
int m_offset;
bool m_ignoreMirror;
void mirrorDirection(const QString &direction);
void mirrorHostMapName(const QString &hostMapName);
void mirrorTargetMapName(const QString &targetMapName);
void mirrorOffset(int offset);
void markMapEdited();
Map* getMap(const QString& mapName) const;
signals:
void directionChanged(const QString &before, const QString &after);
void parentMapChanged(Map* before, Map* after);
void targetMapNameChanged(const QString &before, const QString &after);
void hostMapNameChanged(const QString &before, const QString &after);
void directionChanged(const QString &before, const QString &after);
void offsetChanged(int before, int after);
};

View file

@ -18,6 +18,7 @@
#include "ui_mainwindow.h"
#include "bordermetatilespixmapitem.h"
#include "connectionpixmapitem.h"
#include "divingmappixmapitem.h"
#include "currentselectedmetatilespixmapitem.h"
#include "collisionpixmapitem.h"
#include "mappixmapitem.h"
@ -76,17 +77,15 @@ public:
void setEditingConnections();
void setMapEditingButtonsEnabled(bool enabled);
void setConnectionsVisibility(bool visible);
void updateDiveEmergeVisibility();
void addConnection(MapConnection* connection, bool addMirror = true);
void removeConnection(MapConnection* connection, bool addMirror = true);
void removeConnectionPixmap(ConnectionPixmapItem* connectionItem);
void updateDivingMapsVisibility();
void addConnection(MapConnection* connection);
void removeConnection(MapConnection* connection);
void removeSelectedConnection();
void addNewWildMonGroup(QWidget *window);
void deleteWildMonGroup();
void updateDiveMap(QString mapName);
void updateEmergeMap(QString mapName);
void selectLastConnection();
void setSelectedConnectionFromMap(QString mapName);
void setSelectedConnection(MapConnection *connection);
void updatePrimaryTileset(QString tilesetLabel, bool forceLoad = false);
void updateSecondaryTileset(QString tilesetLabel, bool forceLoad = false);
void toggleBorderVisibility(bool visible, bool enableScriptCallback = true);
@ -111,10 +110,9 @@ public:
QPointer<QGraphicsScene> scene = nullptr;
QGraphicsPixmapItem *current_view = nullptr;
QPointer<MapPixmapItem> map_item = nullptr;
ConnectionPixmapItem* selected_connection_item = nullptr;
QList<ConnectionPixmapItem*> connection_items;
QGraphicsPixmapItem *dive_map_overlay = nullptr;
QGraphicsPixmapItem *emerge_map_overlay = nullptr;
QPointer<ConnectionPixmapItem> selected_connection_item = nullptr;
QList<QPointer<ConnectionPixmapItem>> connection_items;
QMap<QString, QPointer<DivingMapPixmapItem>> diving_map_items;
QGraphicsPathItem *connection_mask = nullptr;
QPointer<CollisionPixmapItem> collision_item = nullptr;
QGraphicsItemGroup *events_group = nullptr;
@ -179,19 +177,19 @@ private:
void clearCurrentMetatilesSelection();
void clearMapEvents();
void clearMapConnections();
void clearDiveMap();
void clearEmergeMap();
void clearConnectionMask();
void clearMapBorder();
void clearMapGrid();
void clearWildMonTables();
void updateBorderVisibility();
QPoint calculateConnectionPosition(MapConnection *connection, const QPixmap &pixmap);
void removeConnectionPixmap(MapConnection* connection);
void updateConnectionPixmap(ConnectionPixmapItem* connectionItem);
void updateConnectionPixmapPos(ConnectionPixmapItem* connectionItem);
void displayConnection(MapConnection* connection);
void displayDiveEmergeConnection(MapConnection* connection);
void setDiveEmergeMapName(QString mapName, QString direction);
void displayDivingConnection(MapConnection* connection);
void setDivingMapName(QString mapName, QString direction);
void removeDivingMapPixmap(MapConnection* connection);
void updateEncounterFields(EncounterFields newFields);
QString getMovementPermissionText(uint16_t collision, uint16_t elevation);
QString getMetatileDisplayMessage(uint16_t metatileId);
@ -207,7 +205,7 @@ private slots:
void setStraightPathCursorMode(QGraphicsSceneMouseEvent *event);
void mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item);
void mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item);
void setSelectedConnection(ConnectionPixmapItem* connectionItem);
void setSelectedConnectionItem(ConnectionPixmapItem* connectionItem);
void onHoveredMovementPermissionChanged(uint16_t, uint16_t);
void onHoveredMovementPermissionCleared();
void onHoveredMetatileSelectionChanged(uint16_t);
@ -223,7 +221,7 @@ private slots:
signals:
void objectsChanged();
void openConnectedMap(QString, QString);
void openConnectedMap(MapConnection*);
void wildMonDataChanged();
void warpEventDoubleClicked(QString, int, Event::Group);
void currentMetatilesSelectionChanged();

View file

@ -178,7 +178,7 @@ private slots:
void copy();
void paste();
void onOpenConnectedMap(QString, QString);
void onOpenConnectedMap(MapConnection*);
void onMapNeedsRedrawing();
void onTilesetsSaved(QString, QString);
void onWildMonDataChanged();
@ -321,9 +321,7 @@ private:
QStandardItemModel *mapListModel;
QList<QStandardItem*> *mapGroupItemsList;
QMap<QString, QModelIndex> mapListIndexes;
QIcon* mapIcon;
QIcon* mapEditedIcon;
QIcon* mapOpenedIcon;
QIcon mapIcon;
QAction *undoAction = nullptr;
QAction *redoAction = nullptr;
@ -411,7 +409,7 @@ private:
int insertTilesetLabel(QStringList * list, QString label);
void checkForUpdates(bool requestedByUser);
void setDiveEmergeMapVisible(bool visible);
void setDivingMapsVisible(bool visible);
};
enum MapListUserRoles {

View file

@ -0,0 +1,35 @@
#ifndef DIVINGMAPPIXMAPITEM_H
#define DIVINGMAPPIXMAPITEM_H
#include "mapconnection.h"
#include <QGraphicsPixmapItem>
#include <QPointer>
class DivingMapPixmapItem : public QObject, public QGraphicsPixmapItem {
Q_OBJECT
public:
DivingMapPixmapItem(MapConnection* connection)
: QGraphicsPixmapItem(getBasePixmap(connection))
{
m_connection = connection;
setZValue(2);
// Update pixmap if the connected map is swapped.
connect(m_connection, &MapConnection::targetMapNameChanged, this, &DivingMapPixmapItem::updatePixmap);
}
MapConnection* connection() const { return m_connection; }
private:
QPointer<MapConnection> m_connection;
static QPixmap getBasePixmap(MapConnection* connection) {
// If the map is connected to itself then rendering is pointless.
if (!connection || connection->targetMapName() == connection->parentMapName())
return QPixmap();
return connection->getPixmap();
}
void updatePixmap() { setPixmap(getBasePixmap(m_connection)); }
};
#endif // DIVINGMAPPIXMAPITEM_H

View file

@ -23,7 +23,6 @@ public:
private:
Ui::NewMapConnectionDialog *ui;
Map *map;
};
#endif // NEWMAPCONNECTIONDIALOG_H

View file

@ -145,6 +145,7 @@ HEADERS += include/core/block.h \
include/ui/connectionslistitem.h \
include/ui/customscriptseditor.h \
include/ui/customscriptslistitem.h \
include/ui/divingmappixmapitem.h \
include/ui/draggablepixmapitem.h \
include/ui/bordermetatilespixmapitem.h \
include/ui/collisionpixmapitem.h \

View file

@ -17,11 +17,9 @@ Map::Map(QObject *parent) : QObject(parent)
}
Map::~Map() {
// delete all associated events
while (!ownedEvents.isEmpty()) {
Event *last = ownedEvents.takeLast();
if (last) delete last;
}
qDeleteAll(ownedEvents);
ownedEvents.clear();
deleteConnections();
}
void Map::setName(QString mapName) {
@ -219,7 +217,7 @@ QPixmap Map::renderBorder(bool ignoreCache) {
QPixmap Map::renderConnection(const QString &direction, MapLayout * fromLayout) {
if (direction == "dive" || direction == "emerge")
return render();
return render(true);
if (!MapConnection::isCardinal(direction))
return QPixmap();
@ -513,22 +511,57 @@ void Map::addEvent(Event *event) {
if (!ownedEvents.contains(event)) ownedEvents.append(event);
}
bool Map::removeConnection(MapConnection *connection) {
if (connections.removeOne(connection)) {
modify();
return true;
}
return false;
void Map::deleteConnections() {
qDeleteAll(connections);
connections.clear();
}
QList<MapConnection*> Map::getConnections() const {
return connections;
}
void Map::addConnection(MapConnection *connection) {
if (!connection || connections.contains(connection))
return;
connections.append(connection);
// Adding new Dive/Emerge maps replaces an existing one.
if (MapConnection::isDiving(connection->direction())) {
for (auto connectionToReplace : connections) {
if (connectionToReplace->direction() != connection->direction())
continue;
if (porymapConfig.mirrorConnectingMaps) {
auto mirror = connectionToReplace->findMirror();
if (mirror && mirror->parentMap())
mirror->parentMap()->removeConnection(mirror);
delete mirror;
}
removeConnection(connectionToReplace);
delete connectionToReplace;
break;
}
}
loadConnection(connection);
modify();
emit connectionAdded(connection);
}
void Map::loadConnection(MapConnection *connection) {
if (!connection)
return;
connections.append(connection);
connection->setParentMap(this, false);
}
// Caller takes ownership of connection
void Map::removeConnection(MapConnection *connection) {
if (!connections.removeOne(connection))
return;
connection->setParentMap(nullptr, false);
modify();
emit connectionRemoved(connection);
}
void Map::modify() {
emit modified();
}

View file

@ -9,36 +9,32 @@ const QMap<QString, QString> MapConnection::oppositeDirections = {
{"dive", "emerge"}, {"emerge", "dive"}
};
MapConnection::MapConnection(const QString &direction, const QString &hostMapName, const QString &targetMapName, int offset) {
m_direction = direction;
m_hostMapName = hostMapName;
MapConnection::MapConnection(const QString &targetMapName, const QString &direction, int offset) {
m_parentMap = nullptr;
m_targetMapName = targetMapName;
m_direction = direction;
m_offset = offset;
m_ignoreMirror = false;
}
bool MapConnection::isMirror(const MapConnection* other) {
if (!other)
if (!other || !other->m_parentMap)
return false;
return m_hostMapName == other->m_targetMapName
&& m_targetMapName == other->m_hostMapName
return parentMapName() == other->m_targetMapName
&& m_targetMapName == other->parentMapName()
&& m_offset == -other->m_offset
&& m_direction == oppositeDirection(other->m_direction);
}
MapConnection* MapConnection::findMirror() {
if (!porymapConfig.mirrorConnectingMaps || !project || m_ignoreMirror)
return nullptr;
Map *connectedMap = project->getMap(m_targetMapName);
if (!connectedMap)
auto map = targetMap();
if (!map)
return nullptr;
// Find the matching connection in the connected map.
// Note: There is no strict source -> mirror pairing, i.e. we are not guaranteed
// to always get the same MapConnection if there are multiple identical copies.
for (auto connection : connectedMap->connections) {
for (auto connection : map->getConnections()) {
if (this != connection && this->isMirror(connection))
return connection;
}
@ -46,122 +42,114 @@ MapConnection* MapConnection::findMirror() {
}
MapConnection * MapConnection::createMirror() {
if (!porymapConfig.mirrorConnectingMaps)
return nullptr;
return new MapConnection(oppositeDirection(m_direction), m_targetMapName, m_hostMapName, -m_offset);
auto mirror = new MapConnection(parentMapName(), oppositeDirection(m_direction), -m_offset);
mirror->setParentMap(targetMap(), false);
return mirror;
}
void MapConnection::markMapEdited() {
if (project) {
Map * map = project->getMap(m_hostMapName);
if (map) map->modify();
}
if (m_parentMap)
m_parentMap->modify();
}
Map* MapConnection::getMap(const QString& mapName) const {
return project ? project->getMap(mapName) : nullptr;
}
Map* MapConnection::targetMap() const {
return getMap(m_targetMapName);
}
QPixmap MapConnection::getPixmap() {
if (!project)
auto map = targetMap();
if (!map)
return QPixmap();
Map *hostMap = project->getMap(m_hostMapName);
Map *targetMap = project->getMap(m_targetMapName);
if (!hostMap || !targetMap)
return QPixmap();
return targetMap->renderConnection(m_direction, hostMap->layout);
return map->renderConnection(m_direction, m_parentMap ? m_parentMap->layout : nullptr);
}
void MapConnection::setDirection(const QString &direction) {
if (direction == m_direction)
void MapConnection::setParentMap(Map* map, bool mirror) {
if (map == m_parentMap)
return;
mirrorDirection(direction);
auto before = m_direction;
m_direction = direction;
emit directionChanged(before, m_direction);
markMapEdited();
}
void MapConnection::mirrorDirection(const QString &direction) {
MapConnection * mirror = findMirror();
if (!mirror)
return;
mirror->m_ignoreMirror = true;
mirror->setDirection(oppositeDirection(direction));
mirror->m_ignoreMirror = false;
}
void MapConnection::setHostMapName(const QString &hostMapName) {
if (hostMapName == m_hostMapName)
return;
mirrorHostMapName(hostMapName);
auto before = m_hostMapName;
m_hostMapName = hostMapName;
if (project) {
// TODO: This is probably unexpected design, esp because creating a MapConnection doesn't insert to host map
// Reassign connection to new host map
Map * oldMap = project->getMap(before);
Map * newMap = project->getMap(m_hostMapName);
if (oldMap) oldMap->removeConnection(this);
if (newMap) newMap->addConnection(this);
if (mirror && porymapConfig.mirrorConnectingMaps) {
auto connection = findMirror();
if (connection)
connection->setTargetMapName(map ? map->name : QString(), false);
}
emit hostMapNameChanged(before, m_hostMapName);
if (m_parentMap)
m_parentMap->removeConnection(this);
auto before = m_parentMap;
m_parentMap = map;
if (m_parentMap)
m_parentMap->addConnection(this);
emit parentMapChanged(before, m_parentMap);
}
void MapConnection::mirrorHostMapName(const QString &hostMapName) {
MapConnection * mirror = findMirror();
if (!mirror)
return;
mirror->m_ignoreMirror = true;
mirror->setTargetMapName(hostMapName);
mirror->m_ignoreMirror = false;
QString MapConnection::parentMapName() const {
return m_parentMap ? m_parentMap->name : QString();
}
void MapConnection::setTargetMapName(const QString &targetMapName) {
void MapConnection::setTargetMapName(const QString &targetMapName, bool mirror) {
if (targetMapName == m_targetMapName)
return;
mirrorTargetMapName(targetMapName);
if (mirror && porymapConfig.mirrorConnectingMaps) {
auto connection = findMirror();
if (connection)
connection->setParentMap(getMap(targetMapName), false);
}
auto before = m_targetMapName;
m_targetMapName = targetMapName;
emit targetMapNameChanged(before, m_targetMapName);
markMapEdited();
}
void MapConnection::mirrorTargetMapName(const QString &targetMapName) {
MapConnection * mirror = findMirror();
if (!mirror)
void MapConnection::setDirection(const QString &direction, bool mirror) {
if (direction == m_direction)
return;
mirror->m_ignoreMirror = true;
mirror->setHostMapName(targetMapName);
mirror->m_ignoreMirror = false;
if (mirror && porymapConfig.mirrorConnectingMaps) {
auto connection = findMirror();
if (connection)
connection->setDirection(oppositeDirection(direction), false);
}
auto before = m_direction;
m_direction = direction;
emit directionChanged(before, m_direction);
markMapEdited();
}
void MapConnection::setOffset(int offset) {
void MapConnection::setOffset(int offset, bool mirror) {
if (offset == m_offset)
return;
mirrorOffset(offset);
if (mirror && porymapConfig.mirrorConnectingMaps) {
auto connection = findMirror();
if (connection)
connection->setOffset(-offset, false);
}
auto before = m_offset;
m_offset = offset;
emit offsetChanged(before, m_offset);
markMapEdited();
}
void MapConnection::mirrorOffset(int offset) {
MapConnection * mirror = findMirror();
if (!mirror)
return;
mirror->m_ignoreMirror = true;
mirror->setOffset(-offset);
mirror->m_ignoreMirror = false;
}
const QStringList MapConnection::cardinalDirections = {
"up", "down", "left", "right"
};
bool MapConnection::isCardinal(const QString &direction) {
return cardinalDirections.contains(direction);
}
bool MapConnection::isHorizontal(const QString &direction) {
return direction == "left" || direction == "right";
}
@ -170,6 +158,6 @@ bool MapConnection::isVertical(const QString &direction) {
return direction == "up" || direction == "down";
}
bool MapConnection::isCardinal(const QString &direction) {
return cardinalDirections.contains(direction);
bool MapConnection::isDiving(const QString &direction) {
return direction == "dive" || direction == "emerge";
}

View file

@ -741,8 +741,8 @@ void Editor::displayConnection(MapConnection* connection) {
// updated by mirroring and the target map is changing to/from the current map.
connection->disconnect();
if (connection->direction() == "dive" || connection->direction() == "emerge") {
displayDiveEmergeConnection(connection);
if (MapConnection::isDiving(connection->direction())) {
displayDivingConnection(connection);
return;
}
@ -765,10 +765,10 @@ void Editor::displayConnection(MapConnection* connection) {
// Sync the selection highlight between the list UI and the pixmap
connect(pixmapItem, &ConnectionPixmapItem::selectionChanged, [this, listItem, pixmapItem](bool selected) {
listItem->setSelected(selected);
if (selected) setSelectedConnection(pixmapItem);
if (selected) setSelectedConnectionItem(pixmapItem);
});
connect(listItem, &ConnectionsListItem::selected, [this, pixmapItem] {
setSelectedConnection(pixmapItem);
setSelectedConnectionItem(pixmapItem);
});
// Sync edits to 'offset' between the list UI and the pixmap
@ -790,49 +790,47 @@ void Editor::displayConnection(MapConnection* connection) {
// Sync edits to 'map' between the list UI and the pixmap
connect(connection, &MapConnection::targetMapNameChanged, [this, listItem, pixmapItem](QString, QString) {
// The old offset may not make sense, so we reset it
pixmapItem->connection->setOffset(0);
listItem->updateUI();
updateConnectionPixmap(pixmapItem);
});
connect(connection, &MapConnection::hostMapNameChanged, [this, pixmapItem](QString before, QString) {
// A connection was removed from the current map by reassignment. We're deleting the UI elements,
// but not the associated connection (it still exists, but now it belongs to a different map).
// At the moment this is only possible when updating the mirror for a map connected to itself,
// users have no other way (or need) to relocate a map connection like this.
if (before == this->map->name)
removeConnectionPixmap(pixmapItem);
});
// User manually deleting connection via the remove button
connect(listItem, &ConnectionsListItem::removed, [this](MapConnection* connection) { removeConnection(connection); });
// Sync removing the UI elements. They may be removed when deleting connections or when displaying a new map.
connect(connection, &MapConnection::destroyed, [this, pixmapItem] { removeConnectionPixmap(pixmapItem); });
// When the pixmap is deleted, remove its associated list item
connect(pixmapItem, &ConnectionPixmapItem::destroyed, listItem, &ConnectionsListItem::deleteLater);
connection_items.append(pixmapItem);
}
void Editor::addConnection(MapConnection * connection, bool addMirror) {
void Editor::addConnection(MapConnection * connection) {
if (!connection)
return;
if (addMirror) addConnection(connection->createMirror(), false);
this->map->addConnection(connection);
if (porymapConfig.mirrorConnectingMaps)
connection->createMirror();
Map *map = project->getMap(connection->hostMapName());
if (map) map->addConnection(connection);
// TODO: Edit history
}
void Editor::removeConnection(MapConnection* connection, bool removeMirror) {
void Editor::removeConnection(MapConnection* connection) {
if (!connection)
return;
if (removeMirror) removeConnection(connection->findMirror(), false);
if (porymapConfig.mirrorConnectingMaps) {
auto mirror = connection->findMirror();
if (mirror && mirror->parentMap())
mirror->parentMap()->removeConnection(mirror);
delete mirror;
}
Map* map = project->getMap(connection->hostMapName());
if (map) map->removeConnection(connection);
if (connection->parentMap())
connection->parentMap()->removeConnection(connection);
delete connection;
// TODO: Edit history
}
@ -841,20 +839,30 @@ void Editor::removeSelectedConnection() {
removeConnection(selected_connection_item->connection);
}
void Editor::removeConnectionPixmap(ConnectionPixmapItem* pixmapItem) {
if (!pixmapItem)
void Editor::removeConnectionPixmap(MapConnection* connection) {
if (!connection)
return;
int index = connection_items.indexOf(pixmapItem);
if (index >= 0) {
connection_items.removeAt(index);
if (MapConnection::isDiving(connection->direction())) {
removeDivingMapPixmap(connection);
return;
}
int i;
for (i = 0; i < connection_items.length(); i++) {
if (connection_items.at(i)->connection == connection)
break;
}
if (i == connection_items.length())
return; // Connection is not displayed, nothing to do.
auto pixmapItem = connection_items.takeAt(i);
if (pixmapItem == selected_connection_item) {
// This was the selected connection, select the next one up in the list.
selected_connection_item = nullptr;
if (index != 0) index--;
if (connection_items.length() > index)
setSelectedConnection(connection_items.at(index));
}
if (i != 0) i--;
if (connection_items.length() > i)
setSelectedConnectionItem(connection_items.at(i));
}
if (pixmapItem->scene())
@ -863,63 +871,82 @@ void Editor::removeConnectionPixmap(ConnectionPixmapItem* pixmapItem) {
delete pixmapItem;
}
// TODO: Self-connecting a Dive/Emerge map connection will not actually replace any existing Dive/Emerge connection, if there is one.
void Editor::displayDiveEmergeConnection(MapConnection* connection) {
void Editor::displayDivingConnection(MapConnection* connection) {
if (!connection)
return;
const QString direction = connection->direction();
if (!MapConnection::isDiving(direction))
return;
// Note: We only support editing 1 Dive and Emerge connection per map.
// In a vanilla game only the first Dive/Emerge connection is considered, so allowing
// users to have multiple is likely to lead to confusion. In case users have changed
// this we won't delete extra diving connections, but we'll only display the first one.
if (connection->direction() == "dive") {
if (dive_map_overlay) return;
} else if (connection->direction() == "emerge") {
if (emerge_map_overlay) return;
} else {
// Invalid direction
if (diving_map_items.value(direction))
return;
}
// Create image of Dive/Emerge map (unless we'd be rendering the current map on top of itself)
QPixmap pixmap = (connection->targetMapName() == this->map->name) ? QPixmap() : connection->getPixmap();
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap);
// Create map image
auto item = new DivingMapPixmapItem(connection);
scene->addItem(item);
diving_map_items.insert(direction, item);
maskNonVisibleConnectionTiles();
if (connection->direction() == "dive") {
dive_map_overlay = item;
const QSignalBlocker blocker(ui->comboBox_DiveMap);
ui->comboBox_DiveMap->setCurrentText(connection->targetMapName());
connect(connection, &MapConnection::destroyed, this, &Editor::clearDiveMap);
connect(connection, &MapConnection::hostMapNameChanged, this, &Editor::clearDiveMap);
} else {
emerge_map_overlay = item;
const QSignalBlocker blocker(ui->comboBox_EmergeMap);
ui->comboBox_EmergeMap->setCurrentText(connection->targetMapName());
connect(connection, &MapConnection::destroyed, this, &Editor::clearEmergeMap);
connect(connection, &MapConnection::hostMapNameChanged, this, &Editor::clearEmergeMap);
// Display map name in combo box
auto comboBox = (direction == "dive") ? ui->comboBox_DiveMap : ui->comboBox_EmergeMap;
const QSignalBlocker blocker(comboBox);
comboBox->setCurrentText(connection->targetMapName());
}
void Editor::removeDivingMapPixmap(MapConnection *connection) {
if (!connection)
return;
const QString direction = connection->direction();
if (!diving_map_items.contains(direction))
return;
// If the diving map being displayed is different than the one being removed we don't need to do anything.
if (diving_map_items.value(direction)->connection() != connection)
return;
// Delete map image
auto pixmapItem = diving_map_items.take(direction);
if (pixmapItem->scene())
pixmapItem->scene()->removeItem(pixmapItem);
delete pixmapItem;
// Clear map name from combo box
auto comboBox = (direction == "dive") ? ui->comboBox_DiveMap : ui->comboBox_EmergeMap;
const QSignalBlocker blocker(comboBox);
comboBox->setCurrentText("");
// Reveal any previously-hidden connection (because we only ever display one diving map of each type).
// Note: When this occurs as a result of the user clicking the 'X' clear button it seems the QComboBox
// doesn't expect the line edit to be immediately repopulated, and the 'X' doesn't reappear.
// As a workaround we wait before displaying the new text. The wait time is essentially arbitrary.
for (auto i : map->getConnections()) {
if (i->direction() == direction) {
QTimer::singleShot(10, Qt::CoarseTimer, [this, i]() { displayDivingConnection(i); });
break;
}
}
// Update pixmap if the connected map is swapped
connect(connection, &MapConnection::targetMapNameChanged, [this, item, connection] {
item->setPixmap((connection->targetMapName() == this->map->name) ? QPixmap() : connection->getPixmap());
});
}
void Editor::updateDiveMap(QString mapName) {
setDiveEmergeMapName(mapName, "dive");
setDivingMapName(mapName, "dive");
}
void Editor::updateEmergeMap(QString mapName) {
setDiveEmergeMapName(mapName, "emerge");
setDivingMapName(mapName, "emerge");
}
void Editor::setDiveEmergeMapName(QString mapName, QString direction) {
void Editor::setDivingMapName(QString mapName, QString direction) {
// Only the first Dive/Emerge map (if present) is considered, as in-game.
MapConnection* connection = nullptr;
for (MapConnection* conn : map->connections) {
if (conn->direction() == direction) {
connection = conn;
for (auto i : map->getConnections()) {
if (i->direction() == direction) {
connection = i;
break;
}
}
@ -928,31 +955,32 @@ void Editor::setDiveEmergeMapName(QString mapName, QString direction) {
// Update existing connection
if (mapName.isEmpty()) {
removeConnection(connection);
// TODO: Queue up another Dive/Emerge map if there is one
} else {
connection->setTargetMapName(mapName);
}
} else if (!mapName.isEmpty()) {
// Create new connection
addConnection(new MapConnection(direction, map->name, mapName));
addConnection(new MapConnection(mapName, direction));
}
updateDiveEmergeVisibility();
updateDivingMapsVisibility();
maskNonVisibleConnectionTiles();
}
void Editor::updateDiveEmergeVisibility() {
if (dive_map_overlay && emerge_map_overlay) {
void Editor::updateDivingMapsVisibility() {
auto dive = diving_map_items.value("dive");
auto emerge = diving_map_items.value("emerge");
if (dive && emerge) {
// Both connections in use, use separate sliders
ui->stackedWidget_DiveMapOpacity->setCurrentIndex(0);
dive_map_overlay->setOpacity(!porymapConfig.showDiveEmergeMaps ? 0 : static_cast<qreal>(porymapConfig.diveMapOpacity) / 100);
emerge_map_overlay->setOpacity(!porymapConfig.showDiveEmergeMaps ? 0 : static_cast<qreal>(porymapConfig.emergeMapOpacity) / 100);
dive->setOpacity(!porymapConfig.showDiveEmergeMaps ? 0 : static_cast<qreal>(porymapConfig.diveMapOpacity) / 100);
emerge->setOpacity(!porymapConfig.showDiveEmergeMaps ? 0 : static_cast<qreal>(porymapConfig.emergeMapOpacity) / 100);
} else {
// One connection in use (or none), use single slider
ui->stackedWidget_DiveMapOpacity->setCurrentIndex(1);
qreal opacity = !porymapConfig.showDiveEmergeMaps ? 0 : static_cast<qreal>(porymapConfig.diveEmergeMapOpacity) / 100;
if (dive_map_overlay)
dive_map_overlay->setOpacity(opacity);
else if (emerge_map_overlay)
emerge_map_overlay->setOpacity(opacity);
if (dive) dive->setOpacity(opacity);
else if (emerge) emerge->setOpacity(opacity);
}
}
@ -1011,7 +1039,7 @@ void Editor::updateConnectionPixmapPos(ConnectionPixmapItem* pixmapItem) {
maskNonVisibleConnectionTiles();
}
void Editor::setSelectedConnection(ConnectionPixmapItem* pixmapItem) {
void Editor::setSelectedConnectionItem(ConnectionPixmapItem* pixmapItem) {
if (!pixmapItem || pixmapItem == selected_connection_item)
return;
@ -1020,23 +1048,21 @@ void Editor::setSelectedConnection(ConnectionPixmapItem* pixmapItem) {
selected_connection_item->setSelected(true);
}
// TODO: Inaccurate if there are multiple connections from the same map
void Editor::setSelectedConnectionFromMap(QString mapName) {
// Search for the first connection that connects to the given map.
for (ConnectionPixmapItem* item : connection_items) {
if (item->connection->targetMapName() == mapName) {
setSelectedConnection(item);
void Editor::setSelectedConnection(MapConnection *connection) {
if (!connection)
return;
for (auto item : connection_items) {
if (item->connection == connection) {
setSelectedConnectionItem(item);
break;
}
}
}
void Editor::selectLastConnection() {
setSelectedConnection(connection_items.last());
}
void Editor::onMapConnectionDoubleClicked(MapConnection* connection) {
emit openConnectedMap(connection->targetMapName(), connection->hostMapName());
if (connection)
emit openConnectedMap(connection);
}
void Editor::onBorderMetatilesChanged() {
@ -1222,7 +1248,7 @@ bool Editor::setMap(QString map_name) {
// multiple times if set again in the future
if (map) {
map->disconnect(this);
for (auto connection : map->connections)
for (auto connection : map->getConnections())
connection->disconnect();
}
@ -1244,6 +1270,7 @@ bool Editor::setMap(QString map_name) {
connect(map, &Map::mapDimensionsChanged, map_ruler, &MapRuler::setMapDimensions);
connect(map, &Map::openScriptRequested, this, &Editor::openScript);
connect(map, &Map::connectionAdded, this, &Editor::displayConnection);
connect(map, &Map::connectionRemoved, this, &Editor::removeConnectionPixmap);
updateSelectedEvents();
}
@ -1721,56 +1748,38 @@ DraggablePixmapItem *Editor::addMapEvent(Event *event) {
}
void Editor::clearMapConnections() {
for (ConnectionPixmapItem* item : connection_items) {
if (item->scene()) {
for (auto item : connection_items) {
if (item->scene())
item->scene()->removeItem(item);
}
delete item;
}
connection_items.clear();
clearDiveMap();
clearEmergeMap();
selected_connection_item = nullptr;
}
void Editor::clearDiveMap() {
const QSignalBlocker blocker(ui->comboBox_DiveMap);
const QSignalBlocker blocker1(ui->comboBox_DiveMap);
const QSignalBlocker blocker2(ui->comboBox_EmergeMap);
ui->comboBox_DiveMap->setCurrentText("");
if (dive_map_overlay) {
if (dive_map_overlay->scene()){
dive_map_overlay->scene()->removeItem(dive_map_overlay);
}
delete dive_map_overlay;
dive_map_overlay = nullptr;
}
}
void Editor::clearEmergeMap() {
const QSignalBlocker blocker(ui->comboBox_EmergeMap);
ui->comboBox_EmergeMap->setCurrentText("");
if (emerge_map_overlay) {
if (emerge_map_overlay->scene()){
emerge_map_overlay->scene()->removeItem(emerge_map_overlay);
}
delete emerge_map_overlay;
emerge_map_overlay = nullptr;
for (auto item : diving_map_items.values()) {
if (item->scene())
item->scene()->removeItem(item);
delete item;
}
diving_map_items.clear();
selected_connection_item = nullptr;
}
void Editor::displayMapConnections() {
clearMapConnections();
for (MapConnection *connection : map->connections)
for (MapConnection *connection : map->getConnections())
displayConnection(connection);
if (!connection_items.empty())
setSelectedConnection(connection_items.first());
if (!connection_items.isEmpty())
setSelectedConnectionItem(connection_items.first());
updateDiveEmergeVisibility();
updateDivingMapsVisibility();
}
void Editor::clearConnectionMask() {
@ -1801,6 +1810,7 @@ void Editor::maskNonVisibleConnectionTiles() {
QBrush brush(ui->graphicsView_Map->palette().color(QPalette::Active, QPalette::Base));
connection_mask = scene->addPath(mask, pen, brush);
connection_mask->setZValue(3); // Above diving maps
}
void Editor::clearMapBorder() {

View file

@ -363,9 +363,7 @@ void MainWindow::initEditor() {
}
void MainWindow::initMiscHeapObjects() {
mapIcon = new QIcon(QStringLiteral(":/icons/map.ico"));
mapEditedIcon = new QIcon(QStringLiteral(":/icons/map_edited.ico"));
mapOpenedIcon = new QIcon(QStringLiteral(":/icons/map_opened.ico"));
mapIcon = QIcon(QStringLiteral(":/icons/map.ico"));
mapListModel = new QStandardItemModel;
mapGroupItemsList = new QList<QStandardItem*>;
@ -496,7 +494,7 @@ void MainWindow::loadUserSettings() {
ui->horizontalSlider_CollisionZoom->setValue(porymapConfig.collisionZoom);
setTheme(porymapConfig.theme);
setDiveEmergeMapVisible(porymapConfig.showDiveEmergeMaps);
setDivingMapsVisible(porymapConfig.showDiveEmergeMaps);
}
void MainWindow::restoreWindowState() {
@ -755,6 +753,9 @@ void MainWindow::on_action_Close_Project_triggered() {
// setMap, but with a visible error message in case of failure.
// Use when the user is specifically requesting a map to open.
bool MainWindow::userSetMap(QString map_name, bool scrollTreeView) {
if (editor->map && editor->map->name == map_name)
return true; // Already set
if (map_name == DYNAMIC_MAP_NAME) {
QMessageBox msgBox(this);
QString errorMsg = QString("The map '%1' can't be opened, it's a placeholder to indicate the specified map will be set programmatically.").arg(map_name);
@ -807,6 +808,7 @@ bool MainWindow::setMap(QString map_name, bool scrollTreeView) {
connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing);
// Swap the "currently-open" icon from the old map to the new map
if (!userConfig.recentMap.isEmpty() && userConfig.recentMap != map_name)
updateMapListIcon(userConfig.recentMap);
userConfig.recentMap = map_name;
updateMapListIcon(userConfig.recentMap);
@ -1230,7 +1232,7 @@ void MainWindow::sortMapList() {
QStandardItem* MainWindow::createMapItem(QString mapName, int groupNum, int inGroupNum) {
QStandardItem *map = new QStandardItem;
map->setText(QString("[%1.%2] ").arg(groupNum).arg(inGroupNum, 2, 10, QLatin1Char('0')) + mapName);
map->setIcon(*mapIcon);
map->setIcon(mapIcon);
map->setEditable(false);
map->setData(mapName, Qt::UserRole);
map->setData("map_name", MapListUserRoles::TypeRole);
@ -1319,7 +1321,7 @@ void MainWindow::onNewMapCreated() {
setMap(newMapName, true);
// Refresh any combo box that displays map names and persists between maps
// (others combo boxes like for warp destinations are repopulated when the map changes).
// (other combo boxes like for warp destinations are repopulated when the map changes).
int index = this->editor->project->mapNames.indexOf(newMapName);
if (index >= 0) {
const QSignalBlocker blocker1(ui->comboBox_DiveMap);
@ -1544,12 +1546,15 @@ void MainWindow::updateMapListIcon(const QString &mapName) {
if (!item)
return;
static const QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico"));
static const QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico"));
if (editor->map && editor->map->name == mapName) {
item->setIcon(*mapOpenedIcon);
item->setIcon(mapOpenedIcon);
} else if (editor->project->mapCache.value(mapName)->hasUnsavedChanges()) {
item->setIcon(*mapEditedIcon);
item->setIcon(mapEditedIcon);
} else {
item->setIcon(*mapIcon);
item->setIcon(mapIcon);
}
}
@ -2273,14 +2278,14 @@ void MainWindow::eventTabChanged(int index) {
}
void MainWindow::on_actionDive_Emerge_Map_triggered() {
setDiveEmergeMapVisible(ui->actionDive_Emerge_Map->isChecked());
setDivingMapsVisible(ui->actionDive_Emerge_Map->isChecked());
}
void MainWindow::on_groupBox_DiveMapOpacity_toggled(bool on) {
setDiveEmergeMapVisible(on);
setDivingMapsVisible(on);
}
void MainWindow::setDiveEmergeMapVisible(bool visible) {
void MainWindow::setDivingMapsVisible(bool visible) {
// Qt doesn't change the style of disabled sliders, so we do it ourselves
QString stylesheet = visible ? "" : "QSlider::groove:horizontal {border: 1px solid #999999; border-radius: 3px; height: 2px; background: #B1B1B1;}"
"QSlider::handle:horizontal {border: 1px solid #444444; border-radius: 3px; width: 10px; height: 9px; margin: -5px -1px; background: #5C5C5C; }";
@ -2295,7 +2300,7 @@ void MainWindow::setDiveEmergeMapVisible(bool visible) {
ui->actionDive_Emerge_Map->setChecked(visible);
porymapConfig.showDiveEmergeMaps = visible;
this->editor->updateDiveEmergeVisibility();
this->editor->updateDivingMapsVisibility();
}
// Normally a map only has either a Dive map connection or an Emerge map connection,
@ -2304,17 +2309,17 @@ void MainWindow::setDiveEmergeMapVisible(bool visible) {
// modify them independently.
void MainWindow::on_slider_DiveEmergeMapOpacity_valueChanged(int value) {
porymapConfig.diveEmergeMapOpacity = value;
this->editor->updateDiveEmergeVisibility();
this->editor->updateDivingMapsVisibility();
}
void MainWindow::on_slider_DiveMapOpacity_valueChanged(int value) {
porymapConfig.diveMapOpacity = value;
this->editor->updateDiveEmergeVisibility();
this->editor->updateDivingMapsVisibility();
}
void MainWindow::on_slider_EmergeMapOpacity_valueChanged(int value) {
porymapConfig.emergeMapOpacity = value;
this->editor->updateDiveEmergeVisibility();
this->editor->updateDivingMapsVisibility();
}
void MainWindow::on_horizontalSlider_CollisionTransparency_valueChanged(int value) {
@ -2529,9 +2534,9 @@ void MainWindow::clickToolButtonFromEditMode(QString editMode) {
}
}
void MainWindow::onOpenConnectedMap(QString mapName, QString fromMapName) {
if (mapName != fromMapName && userSetMap(mapName, true))
editor->setSelectedConnectionFromMap(fromMapName);
void MainWindow::onOpenConnectedMap(MapConnection *connection) {
if (userSetMap(connection->targetMapName(), true))
editor->setSelectedConnection(connection->findMirror());
}
void MainWindow::onMapNeedsRedrawing() {
@ -2648,12 +2653,14 @@ void MainWindow::showExportMapImageWindow(ImageExporterMode mode) {
openSubWindow(this->mapImageExporter);
}
void MainWindow::on_pushButton_AddConnection_clicked()
{
void MainWindow::on_pushButton_AddConnection_clicked() {
if (!this->editor || !this->editor->map)
return;
auto dialog = new NewMapConnectionDialog(this, this->editor->map, this->editor->project->mapNames);
if(dialog->exec() == QDialog::Accepted) {
if (dialog->exec() == QDialog::Accepted) {
this->editor->addConnection(dialog->result);
this->editor->selectLastConnection();
this->editor->setSelectedConnection(dialog->result);
}
}

View file

@ -196,10 +196,6 @@ Map* Project::loadMap(QString map_name) {
return map;
}
void Project::setNewMapConnections(Map *map) {
map->connections.clear();
}
const QSet<QString> defaultTopLevelMapFields = {
"id",
"name",
@ -368,7 +364,7 @@ bool Project::loadMapData(Map* map) {
}
}
map->connections.clear();
map->deleteConnections();
QJsonArray connectionsArr = mapObj["connections"].toArray();
if (!connectionsArr.isEmpty()) {
for (int i = 0; i < connectionsArr.size(); i++) {
@ -378,8 +374,7 @@ bool Project::loadMapData(Map* map) {
const QString mapConstant = ParseUtil::jsonToQString(connectionObj["map"]);
if (mapConstantsToMapNames.contains(mapConstant)) {
// Successully read map connection
MapConnection *connection = new MapConnection(direction, map->name, mapConstantsToMapNames.value(mapConstant), offset);
map->connections.append(connection);
map->loadConnection(new MapConnection(mapConstantsToMapNames.value(mapConstant), direction, offset));
} else {
logError(QString("Failed to find connected map for map constant '%1'").arg(mapConstant));
}
@ -1280,9 +1275,10 @@ void Project::saveMap(Map *map) {
mapObj["battle_scene"] = map->battle_scene;
// Connections
if (map->connections.length() > 0) {
auto connections = map->getConnections();
if (connections.length() > 0) {
OrderedJson::array connectionsArr;
for (MapConnection* connection : map->connections) {
for (auto connection : connections) {
if (mapNamesToMapConstants.contains(connection->targetMapName())) {
OrderedJson::object connectionObj;
connectionObj["map"] = this->mapNamesToMapConstants.value(connection->targetMapName());
@ -1848,7 +1844,6 @@ Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool
loadLayoutTilesets(newMap->layout);
setNewMapEvents(newMap);
setNewMapConnections(newMap);
return newMap;
}

View file

@ -40,13 +40,13 @@ void ConnectionsListItem::updateUI() {
ui->spinBox_Offset->setValue(this->connection->offset());
}
// TODO: Frame shifts slightly when style changes
void ConnectionsListItem::setSelected(bool selected) {
if (selected == this->isSelected)
return;
this->isSelected = selected;
this->setStyleSheet(selected ? ".ConnectionsListItem { border: 1px solid rgb(255, 0, 255); }" : "");
this->setStyleSheet(selected ? ".ConnectionsListItem { border: 1px solid rgb(255, 0, 255); }"
: ".ConnectionsListItem { border-width: 1px; }");
if (selected)
emit this->selected();
}

View file

@ -238,7 +238,7 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu
visited.insert(cur.map->name);
stitchedMaps.append(cur);
for (MapConnection *connection : cur.map->connections) {
for (MapConnection *connection : cur.map->getConnections()) {
const QString direction = connection->direction();
int x = cur.x;
int y = cur.y;

View file

@ -8,7 +8,6 @@ NewMapConnectionDialog::NewMapConnectionDialog(QWidget *parent, Map* map, const
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
this->map = map;
this->result = nullptr;
ui->comboBox_Direction->setEditable(false);
@ -20,7 +19,7 @@ NewMapConnectionDialog::NewMapConnectionDialog(QWidget *parent, Map* map, const
// Choose default direction
QMap<QString, int> directionCounts;
for (auto connection : map->connections) {
for (auto connection : map->getConnections()) {
directionCounts[connection->direction()]++;
}
QString defaultDirection;
@ -52,11 +51,6 @@ NewMapConnectionDialog::~NewMapConnectionDialog()
}
void NewMapConnectionDialog::accept() {
QString direction = ui->comboBox_Direction->currentText();
QString hostMapName = this->map ? this->map->name : QString();
QString targetMapName = ui->comboBox_Map->currentText();
this->result = new MapConnection(direction, this->map->name, targetMapName);
this->result = new MapConnection(ui->comboBox_Map->currentText(), ui->comboBox_Direction->currentText());
QDialog::accept();
}