Update script dropdowns when a map's scripts file is edited

This commit is contained in:
GriffinR 2025-04-24 16:19:08 -04:00
parent dedf0d3e57
commit 79ffe668a3
6 changed files with 99 additions and 100 deletions

View File

@ -34,15 +34,6 @@ class HiddenItemEvent;
class SecretBaseEvent;
class HealLocationEvent;
class EventVisitor {
public:
virtual void nothing() { }
virtual void visitObject(ObjectEvent *) = 0;
virtual void visitTrigger(TriggerEvent *) = 0;
virtual void visitSign(SignEvent *) = 0;
};
///
/// Event base class -- purely virtual
///
@ -121,8 +112,6 @@ public:
void modify();
virtual void accept(EventVisitor *) { }
void setX(int newX) { this->x = newX; }
void setY(int newY) { this->y = newY; }
void setZ(int newZ) { this->elevation = newZ; }
@ -150,6 +139,8 @@ public:
virtual QSet<QString> getExpectedFields() = 0;
virtual QStringList getScripts() const { return QStringList(); }
QJsonObject getCustomAttributes() const { return this->customAttributes; }
void setCustomAttributes(const QJsonObject &newCustomAttributes) { this->customAttributes = newCustomAttributes; }
@ -222,8 +213,6 @@ public:
virtual Event *duplicate() const override;
virtual void accept(EventVisitor *visitor) override { visitor->visitObject(this); }
virtual EventFrame *createEventFrame() override;
virtual OrderedJson::object buildEventJson(Project *project) override;
@ -233,6 +222,8 @@ public:
virtual QSet<QString> getExpectedFields() override;
virtual QStringList getScripts() const override { return {getScript()}; }
virtual QPixmap loadPixmap(Project *project) override;
void setGfx(QString newGfx) { this->gfx = newGfx; }
@ -392,8 +383,6 @@ public:
virtual Event *duplicate() const override;
virtual void accept(EventVisitor *visitor) override { visitor->visitTrigger(this); }
virtual EventFrame *createEventFrame() override;
virtual OrderedJson::object buildEventJson(Project *project) override;
@ -403,6 +392,8 @@ public:
virtual QSet<QString> getExpectedFields() override;
virtual QStringList getScripts() const override { return {getScriptLabel()}; }
void setScriptVar(QString newScriptVar) { this->scriptVar = newScriptVar; }
QString getScriptVar() const { return this->scriptVar; }
@ -490,8 +481,6 @@ public:
virtual Event *duplicate() const override;
virtual void accept(EventVisitor *visitor) override { visitor->visitSign(this); }
virtual EventFrame *createEventFrame() override;
virtual OrderedJson::object buildEventJson(Project *project) override;
@ -501,6 +490,8 @@ public:
virtual QSet<QString> getExpectedFields() override;
virtual QStringList getScripts() const override { return {getScriptLabel()}; }
void setFacingDirection(QString newFacingDirection) { this->facingDirection = newFacingDirection; }
QString getFacingDirection() const { return this->facingDirection; }
@ -633,21 +624,4 @@ inline uint qHash(const Event::Group &key, uint seed = 0) {
return qHash(static_cast<int>(key), seed);
}
///
/// Keeps track of scripts
///
class ScriptTracker : public EventVisitor {
public:
virtual void visitObject(ObjectEvent *object) override { this->scripts << object->getScript(); };
virtual void visitTrigger(TriggerEvent *trigger) override { this->scripts << trigger->getScriptLabel(); };
virtual void visitSign(SignEvent *sign) override { this->scripts << sign->getScriptLabel(); };
QStringList getScripts() const { return this->scripts; }
private:
QStringList scripts;
};
#endif // EVENTS_H

View File

@ -13,6 +13,7 @@
#include <QPixmap>
#include <QObject>
#include <QGraphicsPixmapItem>
#include <QFileSystemWatcher>
#include <math.h>
#define DEFAULT_BORDER_WIDTH 2
@ -56,7 +57,7 @@ public:
MapHeader* header() const { return m_header; }
void setSharedEventsMap(const QString &sharedEventsMap) { m_sharedEventsMap = sharedEventsMap; }
void setSharedScriptsMap(const QString &sharedScriptsMap) { m_sharedScriptsMap = sharedScriptsMap; }
void setSharedScriptsMap(const QString &sharedScriptsMap);
QString sharedEventsMap() const { return m_sharedEventsMap; }
QString sharedScriptsMap() const { return m_sharedScriptsMap; }
@ -75,14 +76,15 @@ public:
Event* getEvent(Event::Group group, const QString &idName) const;
QStringList getEventIdNames(Event::Group group) const;
int getNumEvents(Event::Group group = Event::Group::None) const;
QStringList getScriptLabels(Event::Group group = Event::Group::None);
QString getScriptsFilePath() const;
void openScript(QString label);
void removeEvent(Event *);
void addEvent(Event *);
int getIndexOfEvent(Event *) const;
bool hasEvent(Event *) const;
QStringList getScriptLabels(Event::Group group = Event::Group::None);
QString getScriptsFilePath() const;
void openScript(const QString &label);
void deleteConnections();
QList<MapConnection*> getConnections() const { return m_connections; }
MapConnection* getConnection(const QString &direction) const;
@ -108,7 +110,7 @@ private:
QString m_sharedEventsMap = "";
QString m_sharedScriptsMap = "";
QStringList m_scriptsFileLabels;
QStringList m_scriptLabels;
QJsonObject m_customAttributes;
MapHeader *m_header = nullptr;
@ -131,10 +133,14 @@ private:
QList<MapConnection*> m_connections;
QSet<MapConnection*> m_ownedConnections;
QUndoStack *m_editHistory = nullptr;
QPointer<QUndoStack> m_editHistory;
QPointer<QFileSystemWatcher> m_scriptFileWatcher;
void invalidateScripts();
signals:
void modified();
void scriptsModified();
void mapDimensionsChanged(const QSize &size);
void openScriptRequested(QString label);
void connectionAdded(MapConnection*);

View File

@ -56,9 +56,11 @@ protected:
bool populated = false;
bool initialized = false;
bool connected = false;
QPointer<Project> project;
void populateDropdown(NoScrollComboBox * combo, const QStringList &items);
void populateScriptDropdown(NoScrollComboBox * combo, Project * project);
void populateMapNameDropdown(NoScrollComboBox * combo, Project * project);
void populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group);
private:

View File

@ -114,7 +114,7 @@ QString Event::typeToString(Event::Type type) {
}
QPixmap Event::loadPixmap(Project *project) {
this->pixmap = project->getEventPixmap(this->getEventGroup());
this->pixmap = project ? project->getEventPixmap(this->getEventGroup()) : QPixmap();
this->usesDefaultPixmap = true;
return this->pixmap;
}
@ -226,7 +226,7 @@ QSet<QString> ObjectEvent::getExpectedFields() {
}
QPixmap ObjectEvent::loadPixmap(Project *project) {
this->pixmap = project->getEventPixmap(this->gfx, this->movement);
this->pixmap = project ? project->getEventPixmap(this->gfx, this->movement) : QPixmap();
if (!this->pixmap.isNull()) {
this->usesDefaultPixmap = false;
return this->pixmap;
@ -316,7 +316,7 @@ QSet<QString> CloneObjectEvent::getExpectedFields() {
QPixmap CloneObjectEvent::loadPixmap(Project *project) {
// Try to get the targeted object to clone
Map *clonedMap = project->loadMap(this->targetMap);
Map *clonedMap = project ? project->loadMap(this->targetMap) : nullptr;
Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, this->targetID) : nullptr;
if (clonedEvent && clonedEvent->getEventType() == Event::Type::Object) {
@ -324,10 +324,13 @@ QPixmap CloneObjectEvent::loadPixmap(Project *project) {
ObjectEvent *clonedObject = dynamic_cast<ObjectEvent *>(clonedEvent);
this->gfx = clonedObject->getGfx();
this->movement = clonedObject->getMovement();
} else {
} else if (project) {
// Invalid object specified, use default graphics data (as would be shown in-game)
this->gfx = project->gfxDefines.key(0, "0");
this->movement = project->movementTypes.value(0, "0");
} else {
this->gfx = "0";
this->movement = "0";
}
return ObjectEvent::loadPixmap(project);
}

View File

@ -14,6 +14,10 @@
Map::Map(QObject *parent) : QObject(parent)
{
m_editHistory = new QUndoStack(this);
m_scriptFileWatcher = new QFileSystemWatcher(this);
connect(m_scriptFileWatcher, &QFileSystemWatcher::fileChanged, this, &Map::invalidateScripts);
resetEvents();
m_header = new MapHeader(this);
@ -120,35 +124,40 @@ QPixmap Map::renderConnection(const QString &direction, Layout * fromLayout) {
return connectionPixmap.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16);
}
void Map::openScript(QString label) {
void Map::openScript(const QString &label) {
emit openScriptRequested(label);
}
void Map::setSharedScriptsMap(const QString &sharedScriptsMap) {
if (m_sharedScriptsMap == sharedScriptsMap)
return;
m_sharedScriptsMap = sharedScriptsMap;
invalidateScripts();
}
void Map::invalidateScripts() {
m_scriptsLoaded = false;
emit scriptsModified();
}
QStringList Map::getScriptLabels(Event::Group group) {
if (!m_scriptsLoaded) {
m_scriptsFileLabels = ParseUtil::getGlobalScriptLabels(getScriptsFilePath());
const QString scriptsFilePath = getScriptsFilePath();
m_scriptLabels = ParseUtil::getGlobalScriptLabels(scriptsFilePath);
m_scriptsLoaded = true;
// Track the scripts file for changes. Path may have changed, so stop tracking old files.
m_scriptFileWatcher->removePaths(m_scriptFileWatcher->files());
m_scriptFileWatcher->addPath(scriptsFilePath);
}
QStringList scriptLabels;
QStringList scriptLabels = m_scriptLabels;
// Get script labels currently in-use by the map's events
if (group == Event::Group::None) {
ScriptTracker scriptTracker;
for (const auto &event : getEvents()) {
event->accept(&scriptTracker);
}
scriptLabels = scriptTracker.getScripts();
} else {
ScriptTracker scriptTracker;
for (const auto &event : m_events.value(group)) {
event->accept(&scriptTracker);
}
scriptLabels = scriptTracker.getScripts();
// Add script labels currently in-use by the map's events
for (const auto &event : getEvents(group)) {
scriptLabels.append(event->getScripts());
}
// Add labels from the map's scripts file
scriptLabels.append(m_scriptsFileLabels);
scriptLabels.sort(Qt::CaseInsensitive);
scriptLabels.removeAll("");
scriptLabels.removeAll("0");

View File

@ -152,8 +152,12 @@ void EventFrame::initialize() {
this->label_icon->setPixmap(this->event->getPixmap());
}
void EventFrame::populate(Project *) {
void EventFrame::populate(Project *project) {
this->populated = true;
if (this->project && this->project != project) {
this->project->disconnect(this);
}
this->project = project;
}
void EventFrame::invalidateConnections() {
@ -166,6 +170,10 @@ void EventFrame::invalidateUi() {
void EventFrame::invalidateValues() {
this->populated = false;
if (this->isVisible()) {
// Repopulate immediately
this->populate(this->project);
}
}
void EventFrame::setActive(bool active) {
@ -186,14 +194,15 @@ void EventFrame::populateDropdown(NoScrollComboBox * combo, const QStringList &i
void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * project) {
// The script dropdown and autocomplete are populated with scripts used by the map's events and from its scripts file.
if (!this->event->getMap())
Map *map = this->event ? this->event->getMap() : nullptr;
if (!map)
return;
QStringList scripts = this->event->getMap()->getScriptLabels(this->event->getEventGroup());
QStringList scripts = map->getScriptLabels(this->event->getEventGroup());
populateDropdown(combo, scripts);
// Depending on the settings, the autocomplete may also contain all global scripts.
if (porymapConfig.loadAllEventScripts) {
if (project && porymapConfig.loadAllEventScripts) {
project->insertGlobalScriptLabels(scripts);
}
@ -209,14 +218,23 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj
combo->setCompleter(completer);
// If the project changes the script labels, update the EventFrame.
// TODO: At the moment this only happens when the user changes script settings (i.e. when 'porymapConfig.loadAllEventScripts' changes).
// This should ultimately be connected to a file watcher so that we can also update the dropdown when the scripts file changes.
connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
// If the script labels change then we need to update the EventFrame.
if (project) connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
connect(map, &Map::scriptsModified, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
}
void EventFrame::populateMapNameDropdown(NoScrollComboBox * combo, Project * project) {
if (!project)
return;
populateDropdown(combo, project->mapNames);
// This frame type displays map names, so when a new map is created we need to repopulate it.
connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
}
void EventFrame::populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group) {
if (!project->mapNames.contains(mapName))
if (!project || !project->mapNames.contains(mapName))
return;
Map *map = project->loadMap(mapName);
@ -325,7 +343,6 @@ void ObjectFrame::connectSignals(MainWindow *window) {
if (this->connected) return;
EventFrame::connectSignals(window);
Project *project = window->editor->project;
// local id
this->line_edit_local_id->disconnect();
@ -336,18 +353,18 @@ void ObjectFrame::connectSignals(MainWindow *window) {
// sprite update
this->combo_sprite->disconnect();
connect(this->combo_sprite, &QComboBox::currentTextChanged, [this, project](const QString &text) {
connect(this->combo_sprite, &QComboBox::currentTextChanged, [this](const QString &text) {
this->object->setGfx(text);
this->object->getPixmapItem()->render(project);
this->object->getPixmapItem()->render(this->project);
this->object->modify();
});
connect(this->object->getPixmapItem(), &EventPixmapItem::rendered, this->label_icon, &QLabel::setPixmap);
// movement
this->combo_movement->disconnect();
connect(this->combo_movement, &QComboBox::currentTextChanged, [this, project](const QString &text) {
connect(this->combo_movement, &QComboBox::currentTextChanged, [this](const QString &text) {
this->object->setMovement(text);
this->object->getPixmapItem()->render(project);
this->object->getPixmapItem()->render(this->project);
this->object->modify();
});
@ -498,7 +515,6 @@ void CloneObjectFrame::connectSignals(MainWindow *window) {
if (this->connected) return;
EventFrame::connectSignals(window);
Project *project = window->editor->project;
// local id
this->line_edit_local_id->disconnect();
@ -512,26 +528,23 @@ void CloneObjectFrame::connectSignals(MainWindow *window) {
// target map
this->combo_target_map->disconnect();
connect(this->combo_target_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) {
connect(this->combo_target_map, &QComboBox::currentTextChanged, [this](const QString &mapName) {
this->clone->setTargetMap(mapName);
this->clone->getPixmapItem()->render(project);
this->clone->getPixmapItem()->render(this->project);
this->combo_sprite->setCurrentText(this->clone->getGfx());
this->clone->modify();
populateIdNameDropdown(this->combo_target_id, project, mapName, Event::Group::Object);
populateIdNameDropdown(this->combo_target_id, this->project, mapName, Event::Group::Object);
});
connect(window, &MainWindow::mapOpened, this, &CloneObjectFrame::tryInvalidateIdDropdown, Qt::UniqueConnection);
// target id
this->combo_target_id->disconnect();
connect(this->combo_target_id, &QComboBox::currentTextChanged, [this, project](const QString &text) {
connect(this->combo_target_id, &QComboBox::currentTextChanged, [this](const QString &text) {
this->clone->setTargetID(text);
this->clone->getPixmapItem()->render(project);
this->clone->getPixmapItem()->render(this->project);
this->combo_sprite->setCurrentText(this->clone->getGfx());
this->clone->modify();
});
// This frame type displays map names, so when a new map is created we need to repopulate it.
connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
}
void CloneObjectFrame::tryInvalidateIdDropdown(Map *map) {
@ -567,7 +580,7 @@ void CloneObjectFrame::populate(Project *project) {
const QSignalBlocker blocker(this);
EventFrame::populate(project);
populateDropdown(this->combo_target_map, project->mapNames);
populateMapNameDropdown(this->combo_target_map, project);
populateIdNameDropdown(this->combo_target_id, project, this->clone->getTargetMap(), Event::Group::Object);
}
@ -618,7 +631,6 @@ void WarpFrame::connectSignals(MainWindow *window) {
if (this->connected) return;
EventFrame::connectSignals(window);
Project *project = window->editor->project;
// id
this->line_edit_id->disconnect();
@ -629,10 +641,10 @@ void WarpFrame::connectSignals(MainWindow *window) {
// dest map
this->combo_dest_map->disconnect();
connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) {
connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this](const QString &mapName) {
this->warp->setDestinationMap(mapName);
this->warp->modify();
populateIdNameDropdown(this->combo_dest_warp, project, mapName, Event::Group::Warp);
populateIdNameDropdown(this->combo_dest_warp, this->project, mapName, Event::Group::Warp);
});
connect(window, &MainWindow::mapOpened, this, &WarpFrame::tryInvalidateIdDropdown, Qt::UniqueConnection);
@ -646,9 +658,6 @@ void WarpFrame::connectSignals(MainWindow *window) {
// warning
this->warning->disconnect();
connect(this->warning, &QPushButton::clicked, window, &MainWindow::onWarpBehaviorWarningClicked);
// This frame type displays map names, so when a new map is created we need to repopulate it.
connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
}
void WarpFrame::tryInvalidateIdDropdown(Map *map) {
@ -681,7 +690,7 @@ void WarpFrame::populate(Project *project) {
const QSignalBlocker blocker(this);
EventFrame::populate(project);
populateDropdown(this->combo_dest_map, project->mapNames);
populateMapNameDropdown(this->combo_dest_map, project);
populateIdNameDropdown(this->combo_dest_warp, project, this->warp->getDestinationMap(), Event::Group::Warp);
}
@ -1102,7 +1111,6 @@ void HealLocationFrame::connectSignals(MainWindow *window) {
if (this->connected) return;
EventFrame::connectSignals(window);
Project *project = window->editor->project;
this->line_edit_id->disconnect();
connect(this->line_edit_id, &QLineEdit::textChanged, [this](const QString &text) {
@ -1111,10 +1119,10 @@ void HealLocationFrame::connectSignals(MainWindow *window) {
});
this->combo_respawn_map->disconnect();
connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) {
connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this](const QString &mapName) {
this->healLocation->setRespawnMapName(mapName);
this->healLocation->modify();
populateIdNameDropdown(this->combo_respawn_npc, project, mapName, Event::Group::Object);
populateIdNameDropdown(this->combo_respawn_npc, this->project, mapName, Event::Group::Object);
});
connect(window, &MainWindow::mapOpened, this, &HealLocationFrame::tryInvalidateIdDropdown, Qt::UniqueConnection);
@ -1123,9 +1131,6 @@ void HealLocationFrame::connectSignals(MainWindow *window) {
this->healLocation->setRespawnNPC(text);
this->healLocation->modify();
});
// This frame type displays map names, so when a new map is created we need to repopulate it.
connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection);
}
void HealLocationFrame::tryInvalidateIdDropdown(Map *map) {
@ -1158,7 +1163,7 @@ void HealLocationFrame::populate(Project *project) {
EventFrame::populate(project);
if (projectConfig.healLocationRespawnDataEnabled) {
populateDropdown(this->combo_respawn_map, project->mapNames);
populateMapNameDropdown(this->combo_respawn_map, project);
populateIdNameDropdown(this->combo_respawn_npc, project, this->healLocation->getRespawnMapName(), Event::Group::Object);
}
}