From bf5ead848d93c737f4471c3bd770452a7ab3ca8b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 20 Feb 2025 16:31:08 -0500 Subject: [PATCH 1/6] Only load event sprites when requested --- include/core/events.h | 36 ++----- include/project.h | 19 +++- src/core/events.cpp | 93 ++--------------- src/editor.cpp | 6 +- src/project.cpp | 186 ++++++++++++++++++++++++--------- src/ui/draggablepixmapitem.cpp | 2 +- src/ui/mapimageexporter.cpp | 2 +- 7 files changed, 169 insertions(+), 175 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index 265ec541..73e61141 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -41,14 +41,6 @@ public: virtual void visitSign(SignEvent *) = 0; }; -struct EventGraphics -{ - QImage spritesheet; - int spriteWidth; - int spriteHeight; - bool inanimate; -}; - /// /// Event base class -- purely virtual @@ -64,11 +56,7 @@ public: Event& operator=(const Event &other) = delete; protected: - Event() { - this->spriteWidth = 16; - this->spriteHeight = 16; - this->usingSprite = false; - } + Event() {} // public enums & static methods public: @@ -143,8 +131,8 @@ public: int getZ() const { return this->elevation; } int getElevation() const { return this->elevation; } - int getPixelX() const { return (this->x * 16) - qMax(0, (this->spriteWidth - 16) / 2); } - int getPixelY() const { return (this->y * 16) - qMax(0, this->spriteHeight - 16); } + int getPixelX() const { return (this->x * 16) - qMax(0, (pixmap.width() - 16) / 2); } + int getPixelY() const { return (this->y * 16) - qMax(0, pixmap.height() - 16); } virtual EventFrame *getEventFrame(); virtual EventFrame *createEventFrame() = 0; @@ -172,14 +160,8 @@ public: void setPixmapItem(DraggablePixmapItem *item); DraggablePixmapItem *getPixmapItem() const { return this->pixmapItem; } - void setUsingSprite(bool newUsingSprite) { this->usingSprite = newUsingSprite; } - bool getUsingSprite() const { return this->usingSprite; } - - void setSpriteWidth(int newSpriteWidth) { this->spriteWidth = newSpriteWidth; } - int getspriteWidth() const { return this->spriteWidth; } - - void setSpriteHeight(int newSpriteHeight) { this->spriteHeight = newSpriteHeight; } - int getspriteHeight() const { return this->spriteHeight; } + void setUsesDefaultPixmap(bool newUsesDefaultPixmap) { this->usesDefaultPixmap = newUsesDefaultPixmap; } + bool getUsesDefaultPixmap() const { return this->usesDefaultPixmap; } int getEventIndex(); @@ -204,9 +186,7 @@ protected: int y = 0; int elevation = 0; - int spriteWidth = 16; - int spriteHeight = 16; - bool usingSprite = false; + bool usesDefaultPixmap = true; // Some events can have an associated #define name that should be unique to this event. // e.g. object events can have a 'LOCALID', or Heal Locations have a 'HEAL_LOCATION' id. @@ -273,10 +253,6 @@ public: void setFlag(QString newFlag) { this->flag = newFlag; } QString getFlag() const { return this->flag; } -public: - void setFrameFromMovement(QString movement); - void setPixmapFromSpritesheet(EventGraphics * gfx); - protected: QString gfx; diff --git a/include/project.h b/include/project.h index c2707063..38a31f4a 100644 --- a/include/project.h +++ b/include/project.h @@ -45,7 +45,6 @@ public: QStringList layoutIdsMaster; QMap mapLayouts; QMap mapLayoutsMaster; - QMap eventGraphicsMap; QMap gfxDefines; QString defaultSong; QStringList songNames; @@ -68,7 +67,6 @@ public: QMap unusedMetatileLabels; QMap metatileBehaviorMap; QMap metatileBehaviorMapInverse; - QMap facingDirections; ParseUtil parser; QFileSystemWatcher fileWatcher; QSet modifiedFiles; @@ -210,7 +208,10 @@ public: bool readFieldmapMasks(); QMap> readObjEventGfxInfo(); - void setEventPixmap(Event *event, bool forceLoad = false); + QPixmap getEventPixmap(const QString &gfxName, const QString &movementName); + QPixmap getEventPixmap(const QString &gfxName, int frame, bool hFlip); + QPixmap getEventPixmap(Event::Group group); + void loadEventPixmap(Event *event, bool forceLoad = false); QString fixPalettePath(QString path); QString fixGraphicPath(QString path); @@ -254,6 +255,18 @@ public: private: QMap mapSectionDisplayNames; QMap modifiedFileTimestamps; + QMap facingDirections; + + struct EventGraphics + { + QString filepath; + bool loaded = false; + QImage spritesheet; + int spriteWidth = -1; + int spriteHeight = -1; + bool inanimate = false; + }; + QMap eventGraphicsMap; void updateLayout(Layout *); diff --git a/src/core/events.cpp b/src/core/events.cpp index 3d2ef9d2..37e5ec0a 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -107,9 +107,10 @@ Event::Type Event::typeFromString(QString type) { return typeToStringMap.key(type, Event::Type::None); } -void Event::loadPixmap(Project *) { +void Event::loadPixmap(Project *project) { const QPixmap * pixmap = Event::icons.value(this->getEventGroup()); this->pixmap = pixmap ? *pixmap : QPixmap(); + this->usesDefaultPixmap = true; } void Event::clearIcons() { @@ -230,7 +231,6 @@ void ObjectEvent::setDefaultValues(Project *project) { this->setRadiusX(0); this->setRadiusY(0); this->setSightRadiusBerryTreeID("0"); - this->setFrameFromMovement(project->facingDirections.value(this->getMovement())); } const QSet expectedObjectFields = { @@ -257,78 +257,11 @@ QSet ObjectEvent::getExpectedFields() { } void ObjectEvent::loadPixmap(Project *project) { - EventGraphics *eventGfx = project->eventGraphicsMap.value(this->gfx, nullptr); - if (!eventGfx) { - // Invalid gfx constant. - // If this is a number, try to use that instead. - bool ok; - int altGfx = ParseUtil::gameStringToInt(this->gfx, &ok); - if (ok && (altGfx < project->gfxDefines.count())) { - eventGfx = project->eventGraphicsMap.value(project->gfxDefines.key(altGfx, "NULL"), nullptr); - } - } - if (!eventGfx || eventGfx->spritesheet.isNull()) { - // No sprite associated with this gfx constant. - // Use default sprite instead. + this->pixmap = project->getEventPixmap(this->gfx, this->movement); + if (!this->pixmap.isNull()) { + this->usesDefaultPixmap = false; + } else { Event::loadPixmap(project); - this->spriteWidth = 16; - this->spriteHeight = 16; - this->usingSprite = false; - } else { - this->setFrameFromMovement(project->facingDirections.value(this->movement)); - this->setPixmapFromSpritesheet(eventGfx); - } -} - -void ObjectEvent::setPixmapFromSpritesheet(EventGraphics * gfx) -{ - QImage img; - if (gfx->inanimate) { - img = gfx->spritesheet.copy(0, 0, gfx->spriteWidth, gfx->spriteHeight); - } else { - int x = 0; - int y = 0; - - // Get frame's position in spritesheet. - // Assume horizontal layout. If position would exceed sheet width, try vertical layout. - if ((this->frame + 1) * gfx->spriteWidth <= gfx->spritesheet.width()) { - x = this->frame * gfx->spriteWidth; - } else if ((this->frame + 1) * gfx->spriteHeight <= gfx->spritesheet.height()) { - y = this->frame * gfx->spriteHeight; - } - - img = gfx->spritesheet.copy(x, y, gfx->spriteWidth, gfx->spriteHeight); - - // Right-facing sprite is just the left-facing sprite mirrored - if (this->hFlip) { - img = img.transformed(QTransform().scale(-1, 1)); - } - } - // Set first palette color fully transparent. - img.setColor(0, qRgba(0, 0, 0, 0)); - pixmap = QPixmap::fromImage(img); - this->spriteWidth = gfx->spriteWidth; - this->spriteHeight = gfx->spriteHeight; - this->usingSprite = true; -} - -void ObjectEvent::setFrameFromMovement(QString facingDir) { - // defaults - // TODO: read this from a file somewhere? - this->frame = 0; - this->hFlip = false; - if (facingDir == "DIR_NORTH") { - this->frame = 1; - this->hFlip = false; - } else if (facingDir == "DIR_SOUTH") { - this->frame = 0; - this->hFlip = false; - } else if (facingDir == "DIR_WEST") { - this->frame = 2; - this->hFlip = false; - } else if (facingDir == "DIR_EAST") { - this->frame = 2; - this->hFlip = true; } } @@ -430,19 +363,7 @@ void CloneObjectEvent::loadPixmap(Project *project) { this->gfx = project->gfxDefines.key(0, "0"); this->movement = project->movementTypes.value(0, "0"); } - - EventGraphics *eventGfx = project->eventGraphicsMap.value(gfx, nullptr); - if (!eventGfx || eventGfx->spritesheet.isNull()) { - // No sprite associated with this gfx constant. - // Use default sprite instead. - Event::loadPixmap(project); - this->spriteWidth = 16; - this->spriteHeight = 16; - this->usingSprite = false; - } else { - this->setFrameFromMovement(project->facingDirections.value(this->movement)); - this->setPixmapFromSpritesheet(eventGfx); - } + ObjectEvent::loadPixmap(project); } diff --git a/src/editor.cpp b/src/editor.cpp index ecc056f0..ad99dded 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1670,7 +1670,7 @@ void Editor::displayMapEvents() { } DraggablePixmapItem *Editor::addEventPixmapItem(Event *event) { - this->project->setEventPixmap(event); + this->project->loadEventPixmap(event); auto item = new DraggablePixmapItem(event, this); redrawEventPixmapItem(item); this->events_group->addToGroup(item); @@ -1955,13 +1955,13 @@ qreal Editor::getEventOpacity(const Event *event) const { // - On the Events tab, and the event has a custom sprite (1.0) if (this->editMode != EditMode::Events) return porymapConfig.eventOverlayEnabled ? 0.5 : 0.0; - return event->getUsingSprite() ? 1.0 : 0.7; + return event->getUsesDefaultPixmap() ? 0.7 : 1.0; } void Editor::redrawEventPixmapItem(DraggablePixmapItem *item) { if (item && item->event && !item->event->getPixmap().isNull()) { item->setOpacity(getEventOpacity(item->event)); - project->setEventPixmap(item->event, true); + project->loadEventPixmap(item->event, true); item->setPixmap(item->event->getPixmap()); item->setShapeMode(porymapConfig.eventSelectionShapeMode); diff --git a/src/project.cpp b/src/project.cpp index b25b3776..7f9a1ed5 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2664,92 +2664,176 @@ QStringList Project::getEventScriptsFilePaths() const { return filePaths; } -void Project::setEventPixmap(Event *event, bool forceLoad) { +void Project::loadEventPixmap(Event *event, bool forceLoad) { if (event && (event->getPixmap().isNull() || forceLoad)) event->loadPixmap(this); } void Project::clearEventGraphics() { - qDeleteAll(eventGraphicsMap); - eventGraphicsMap.clear(); + qDeleteAll(this->eventGraphicsMap); + this->eventGraphicsMap.clear(); } bool Project::readEventGraphics() { clearEventGraphics(); - fileWatcher.addPaths(QStringList() << root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_pointers) - << root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_info) - << root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_pic_tables) - << root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx)); - const QString pointersFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_pointers); - const QString pointersName = projectConfig.getIdentifier(ProjectIdentifier::symbol_obj_event_gfx_pointers); - QMap pointerHash = parser.readNamedIndexCArray(pointersFilepath, pointersName); + const QString gfxInfoFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_info); + const QString picTablesFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_pic_tables); + const QString gfxFilepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx); + fileWatcher.addPaths({pointersFilepath, gfxInfoFilepath, picTablesFilepath, gfxFilepath}); - QStringList gfxNames = gfxDefines.keys(); + // Read the table mapping OBJ_EVENT_GFX constants to the names of pointers to data about their graphics. + const QString pointersName = projectConfig.getIdentifier(ProjectIdentifier::symbol_obj_event_gfx_pointers); + const QMap pointerMap = parser.readNamedIndexCArray(pointersFilepath, pointersName); // The positions of each of the required members for the gfx info struct. // For backwards compatibility if the struct doesn't use initializers. - static const auto gfxInfoMemberMap = QHash{ + static const QHash gfxInfoMemberMap = { {8, "inanimate"}, {11, "oam"}, {12, "subspriteTables"}, {14, "images"}, }; + // Read the structs containing data about each of the event sprites. + auto gfxInfos = parser.readCStructs(gfxInfoFilepath, "", gfxInfoMemberMap); - QString filepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_info); - auto gfxInfos = parser.readCStructs(filepath, "", gfxInfoMemberMap); + // We need data in both of these files to translate data from the structs above into the path for a .png file. + const QMap picTables = parser.readCArrayMulti(picTablesFilepath); + const QMap graphicIncbins = parser.readCIncbinMulti(gfxFilepath); - QMap picTables = parser.readCArrayMulti(projectConfig.getFilePath(ProjectFilePath::data_obj_event_pic_tables)); - QMap graphicIncbins = parser.readCIncbinMulti(projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx)); + for (auto i = this->gfxDefines.constBegin(); i != this->gfxDefines.constEnd(); i++) { + const QString gfxName = i.key(); - for (QString gfxName : gfxNames) { - QString info_label = pointerHash[gfxName].replace("&", ""); + // Strip the address-of operator to get the pointer's name. We'll use this name to get data about the event's sprite. + // If we don't recognize the name, ignore it. The event will use a default sprite. + QString info_label = pointerMap[gfxName].replace("&", ""); if (!gfxInfos.contains(info_label)) continue; + const QHash gfxInfoAttributes = gfxInfos[info_label]; - auto gfxInfoAttributes = gfxInfos[info_label]; + auto gfx = new EventGraphics; - auto eventGraphics = new EventGraphics; - eventGraphics->inanimate = ParseUtil::gameStringToBool(gfxInfoAttributes.value("inanimate")); - QString pic_label = gfxInfoAttributes.value("images"); - QString dimensions_label = gfxInfoAttributes.value("oam"); - QString subsprites_label = gfxInfoAttributes.value("subspriteTables"); - - QString gfx_label = picTables[pic_label].value(0); + // We need the .png filepath for the event's sprite. This is buried behind a few levels of indirection. + // The 'images' field gives us the name of the table containing the sprite's image data. + // The entries in this table are expected to be in the format (PngSymbolName, ...). + // We extract the symbol name of the .png's INCBIN'd data by looking at the first entry in this table. + // Once we have the .png's symbol name we can get the actual filepath from its INCBIN. + QString gfx_label = picTables[gfxInfoAttributes.value("images")].value(0); static const QRegularExpression re_parens("[\\(\\)]"); gfx_label = gfx_label.section(re_parens, 1, 1); - QString path = graphicIncbins[gfx_label]; + gfx->filepath = fixGraphicPath(graphicIncbins[gfx_label]); - if (!path.isNull()) { - path = fixGraphicPath(path); - eventGraphics->spritesheet = QImage(root + "/" + path); - if (!eventGraphics->spritesheet.isNull()) { - // Infer the sprite dimensions from the OAM labels. - static const QRegularExpression re("\\S+_(\\d+)x(\\d+)"); - QRegularExpressionMatch dimensionMatch = re.match(dimensions_label); - QRegularExpressionMatch oamTablesMatch = re.match(subsprites_label); - if (oamTablesMatch.hasMatch()) { - eventGraphics->spriteWidth = oamTablesMatch.captured(1).toInt(nullptr, 0); - eventGraphics->spriteHeight = oamTablesMatch.captured(2).toInt(nullptr, 0); - } else if (dimensionMatch.hasMatch()) { - eventGraphics->spriteWidth = dimensionMatch.captured(1).toInt(nullptr, 0); - eventGraphics->spriteHeight = dimensionMatch.captured(2).toInt(nullptr, 0); - } else { - eventGraphics->spriteWidth = eventGraphics->spritesheet.width(); - eventGraphics->spriteHeight = eventGraphics->spritesheet.height(); - } - } - } else { - eventGraphics->spritesheet = QImage(); - eventGraphics->spriteWidth = 16; - eventGraphics->spriteHeight = 16; + // Note: gfx has a 'spritesheet' field that will contain a QImage for the event's sprite. + // We don't create this QImage yet. Reading the image now is unnecessary overhead for startup. + // We'll read the image file when the event's sprite is first requested to be drawn. + + // The .png file is expected to be a spritesheet that can have multiple frames. + // We only want to show one frame at a time, so we need to know the dimensions of each frame. + // TODO: Describe different ways we read these. Use width/height? + static const QRegularExpression re("\\S+_(\\d+)x(\\d+)"); + QRegularExpressionMatch dimensionMatch = re.match(gfxInfoAttributes.value("oam")); + QRegularExpressionMatch oamTablesMatch = re.match(gfxInfoAttributes.value("subspriteTables")); + if (oamTablesMatch.hasMatch()) { + gfx->spriteWidth = oamTablesMatch.captured(1).toInt(nullptr, 0); + gfx->spriteHeight = oamTablesMatch.captured(2).toInt(nullptr, 0); + } else if (dimensionMatch.hasMatch()) { + gfx->spriteWidth = dimensionMatch.captured(1).toInt(nullptr, 0); + gfx->spriteHeight = dimensionMatch.captured(2).toInt(nullptr, 0); } - eventGraphicsMap.insert(gfxName, eventGraphics); + + // Inanimate events will only ever use the first frame of their spritesheet. + gfx->inanimate = ParseUtil::gameStringToBool(gfxInfoAttributes.value("inanimate")); + + this->eventGraphicsMap.insert(gfxName, gfx); } return true; } +QPixmap Project::getEventPixmap(const QString &gfxName, const QString &movementName) { + struct FrameData { + int index = 0; + bool hFlip = false; + }; + // TODO: Expose as a setting to users + static const QMap directionToFrameData = { + {"DIR_SOUTH", { .index = 0, .hFlip = false }}, + {"DIR_NORTH", { .index = 1, .hFlip = false }}, + {"DIR_WEST", { .index = 2, .hFlip = false }}, + {"DIR_EAST", { .index = 2, .hFlip = true }}, // East-facing sprite is just the West-facing sprite mirrored + }; + const QString direction = this->facingDirections.value(movementName, "DIR_SOUTH"); + auto frameData = directionToFrameData.value(direction); + return getEventPixmap(gfxName, frameData.index, frameData.hFlip); +} + +QPixmap Project::getEventPixmap(const QString &gfxName, int frame, bool hFlip) { + EventGraphics* gfx = this->eventGraphicsMap.value(gfxName, nullptr); + if (!gfx) { + // Invalid gfx constant. If this is a number, try to use that instead. + bool ok; + int gfxNum = ParseUtil::gameStringToInt(gfxName, &ok); + if (ok && gfxNum < this->gfxDefines.count()) { + gfx = this->eventGraphicsMap.value(this->gfxDefines.key(gfxNum, "NULL"), nullptr); + } + } + if (gfx && !gfx->loaded) { + // This is the first request for this event's sprite. We'll attempt to load it now. + if (!gfx->filepath.isEmpty()) { + gfx->spritesheet = QImage(QString("%1/%2").arg(this->root).arg(gfx->filepath)); + if (gfx->spritesheet.isNull()) { + logWarn(QString("Failed to open '%1' for event's sprite. Event will use a default sprite instead.").arg(gfx->filepath)); + } else { + // If we were unable to find the dimensions of a frame within the spritesheet we'll use the full image dimensions. + if (gfx->spriteWidth <= 0) { + gfx->spriteWidth = gfx->spritesheet.width(); + } + if (gfx->spriteHeight <= 0) { + gfx->spriteHeight = gfx->spritesheet.height(); + } + } + } + // Set this whether we were successful or not, we only need to try to load it once. + gfx->loaded = true; + } + if (!gfx || gfx->spritesheet.isNull()) { + // Either we didn't recognize the gfxName, or we were unable to load the sprite's image. + return QPixmap(); + } + + QImage img; + if (gfx->inanimate) { + img = gfx->spritesheet.copy(0, 0, gfx->spriteWidth, gfx->spriteHeight); + } else { + int x = 0; + int y = 0; + + // Get frame's position in spritesheet. + // Assume horizontal layout. If position would exceed sheet width, try vertical layout. + if ((frame + 1) * gfx->spriteWidth <= gfx->spritesheet.width()) { + x = frame * gfx->spriteWidth; + } else if ((frame + 1) * gfx->spriteHeight <= gfx->spritesheet.height()) { + y = frame * gfx->spriteHeight; + } + + img = gfx->spritesheet.copy(x, y, gfx->spriteWidth, gfx->spriteHeight); + if (hFlip) { + img = img.transformed(QTransform().scale(-1, 1)); + } + } + // Set first palette color fully transparent. + img.setColor(0, qRgba(0, 0, 0, 0)); + QPixmap pixmap = QPixmap::fromImage(img); + + // TODO: Cache? + return pixmap; +} + +QPixmap Project::getEventPixmap(Event::Group) { + // TODO + return QPixmap(); +} + bool Project::readSpeciesIconPaths() { this->speciesToIconPath.clear(); diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index 846cfb7e..f2fdeb91 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -27,7 +27,7 @@ void DraggablePixmapItem::emitPositionChanged() { } void DraggablePixmapItem::updatePixmap() { - editor->project->setEventPixmap(event, true); + editor->project->loadEventPixmap(event, true); this->updatePosition(); editor->redrawEventPixmapItem(this); emit spriteChanged(event->getPixmap()); diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 0237795a..4b11ea95 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -517,7 +517,7 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { || (m_settings.showBGs && group == Event::Group::Bg) || (m_settings.showTriggers && group == Event::Group::Coord) || (m_settings.showHealLocations && group == Event::Group::Heal)) { - m_editor->project->setEventPixmap(event); + m_editor->project->loadEventPixmap(event); eventPainter.drawImage(QPoint(event->getPixelX() + pixelOffset, event->getPixelY() + pixelOffset), event->getPixmap().toImage()); } } From eef9a37d16185f2cd3bc3a7ebbc82992bf550215 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 21 Feb 2025 12:40:06 -0500 Subject: [PATCH 2/6] Cache event pixmaps --- include/core/events.h | 4 ---- src/core/events.cpp | 40 +------------------------------------ src/mainwindow.cpp | 3 --- src/project.cpp | 46 ++++++++++++++++++++++++++++++++++++++----- 4 files changed, 42 insertions(+), 51 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index 73e61141..6ecad0c1 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -107,8 +107,6 @@ public: static Event* create(Event::Type type); - static QMap icons; - // standard public methods public: @@ -171,8 +169,6 @@ public: static QString groupToString(Event::Group group); static QString typeToString(Event::Type type); static Event::Type typeFromString(QString type); - static void clearIcons(); - static void setIcons(); // protected attributes protected: diff --git a/src/core/events.cpp b/src/core/events.cpp index 37e5ec0a..c124e58d 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -4,8 +4,6 @@ #include "project.h" #include "config.h" -QMap Event::icons; - Event* Event::create(Event::Type type) { switch (type) { case Event::Type::Object: return new ObjectEvent(); @@ -108,46 +106,10 @@ Event::Type Event::typeFromString(QString type) { } void Event::loadPixmap(Project *project) { - const QPixmap * pixmap = Event::icons.value(this->getEventGroup()); - this->pixmap = pixmap ? *pixmap : QPixmap(); + this->pixmap = project->getEventPixmap(this->getEventGroup()); this->usesDefaultPixmap = true; } -void Event::clearIcons() { - qDeleteAll(icons); - icons.clear(); -} - -void Event::setIcons() { - clearIcons(); - const int w = 16; - const int h = 16; - static const QPixmap defaultIcons = QPixmap(":/images/Entities_16x16.png"); - - // Custom event icons may be provided by the user. - const int numIcons = qMin(defaultIcons.width() / w, static_cast(Event::Group::None)); - for (int i = 0; i < numIcons; i++) { - Event::Group group = static_cast(i); - QString customIconPath = projectConfig.getEventIconPath(group); - if (customIconPath.isEmpty()) { - // No custom icon specified, use the default icon. - icons[group] = new QPixmap(defaultIcons.copy(i * w, 0, w, h)); - continue; - } - - // Try to load custom icon - QString validPath = Project::getExistingFilepath(customIconPath); - if (!validPath.isEmpty()) customIconPath = validPath; // Otherwise allow it to fail with the original path - const QPixmap customIcon = QPixmap(customIconPath); - if (customIcon.isNull()) { - // Custom icon failed to load, use the default icon. - icons[group] = new QPixmap(defaultIcons.copy(i * w, 0, w, h)); - logWarn(QString("Failed to load custom event icon '%1', using default icon.").arg(customIconPath)); - } else { - icons[group] = new QPixmap(customIcon.scaled(w, h)); - } - } -} Event *ObjectEvent::duplicate() const { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2124bf65..a38505e2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1103,7 +1103,6 @@ bool MainWindow::setProjectUI() { ui->newEventToolButton->newSecretBaseAction->setVisible(projectConfig.eventSecretBaseEnabled); ui->newEventToolButton->newCloneObjectAction->setVisible(projectConfig.eventCloneObjectEnabled); - Event::setIcons(); editor->setCollisionGraphics(); ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation()); ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision()); @@ -1161,8 +1160,6 @@ void MainWindow::clearProjectUI() { delete this->layoutTreeModel; delete this->layoutListProxyModel; resetMapListFilters(); - - Event::clearIcons(); } void MainWindow::scrollMapList(MapTree *list, const QString &itemName) { diff --git a/src/project.cpp b/src/project.cpp index 7f9a1ed5..bd4e9bf9 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -44,6 +44,7 @@ Project::~Project() clearMapLayouts(); clearEventGraphics(); clearHealLocations(); + QPixmapCache::clear(); } void Project::set_root(QString dir) { @@ -2768,6 +2769,12 @@ QPixmap Project::getEventPixmap(const QString &gfxName, const QString &movementN } QPixmap Project::getEventPixmap(const QString &gfxName, int frame, bool hFlip) { + QPixmap pixmap; + const QString cacheKey = QString("EVENT#%1#%2#%3").arg(gfxName).arg(frame).arg(hFlip ? "1" : "0"); + if (QPixmapCache::find(cacheKey, &pixmap)) { + return pixmap; + } + EventGraphics* gfx = this->eventGraphicsMap.value(gfxName, nullptr); if (!gfx) { // Invalid gfx constant. If this is a number, try to use that instead. @@ -2823,15 +2830,44 @@ QPixmap Project::getEventPixmap(const QString &gfxName, int frame, bool hFlip) { } // Set first palette color fully transparent. img.setColor(0, qRgba(0, 0, 0, 0)); - QPixmap pixmap = QPixmap::fromImage(img); - // TODO: Cache? + pixmap = QPixmap::fromImage(img); + QPixmapCache::insert(cacheKey, pixmap); return pixmap; } -QPixmap Project::getEventPixmap(Event::Group) { - // TODO - return QPixmap(); +QPixmap Project::getEventPixmap(Event::Group group) { + if (group == Event::Group::None) + return QPixmap(); + + QPixmap pixmap; + const QString cacheKey = QString("EVENT#%1").arg(Event::groupToString(group)); + if (QPixmapCache::find(cacheKey, &pixmap)) { + return pixmap; + } + + const int defaultWidth = 16; + const int defaultHeight = 16; + static const QPixmap defaultIcons = QPixmap(":/images/Entities_16x16.png"); + QPixmap defaultIcon = QPixmap(defaultIcons.copy(static_cast(group) * defaultWidth, 0, defaultWidth, defaultHeight)); + + // Custom event icons may be provided by the user. + QString customIconPath = projectConfig.getEventIconPath(group); + if (customIconPath.isEmpty()) { + // No custom icon specified, use the default icon. + pixmap = defaultIcon; + } else { + // Try to load custom icon + QString validPath = Project::getExistingFilepath(customIconPath); + if (!validPath.isEmpty()) customIconPath = validPath; // Otherwise allow it to fail with the original path + pixmap = QPixmap(customIconPath); + if (pixmap.isNull()) { + pixmap = defaultIcon; + logWarn(QString("Failed to load custom event icon '%1', using default icon.").arg(customIconPath)); + } + } + QPixmapCache::insert(cacheKey, pixmap); + return pixmap; } bool Project::readSpeciesIconPaths() { From 880e5847c71c80fe5190010a41c18f6387fbe1f9 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 21 Feb 2025 14:38:39 -0500 Subject: [PATCH 3/6] Remove global scripts from autocomplete --- include/config.h | 2 ++ include/project.h | 1 + src/config.cpp | 3 +++ src/core/map.cpp | 7 +++---- src/project.cpp | 22 +++++++++++++++++----- src/ui/eventframes.cpp | 17 ++++++++++++----- 6 files changed, 38 insertions(+), 14 deletions(-) diff --git a/include/config.h b/include/config.h index 720bd8d0..0b8f3ee3 100644 --- a/include/config.h +++ b/include/config.h @@ -79,6 +79,7 @@ public: this->textEditorGotoLine = ""; this->paletteEditorBitDepth = 24; this->projectSettingsTab = 0; + this->loadAllEventScripts = false; this->warpBehaviorWarningDisabled = false; this->eventDeleteWarningDisabled = false; this->eventOverlayEnabled = false; @@ -135,6 +136,7 @@ public: QString textEditorGotoLine; int paletteEditorBitDepth; int projectSettingsTab; + bool loadAllEventScripts; bool warpBehaviorWarningDisabled; bool eventDeleteWarningDisabled; bool eventOverlayEnabled; diff --git a/include/project.h b/include/project.h index 38a31f4a..4a8ec1f7 100644 --- a/include/project.h +++ b/include/project.h @@ -219,6 +219,7 @@ public: static QString getScriptFileExtension(bool usePoryScript); QString getScriptDefaultString(bool usePoryScript, QString mapName) const; QStringList getEventScriptsFilePaths() const; + void insertGlobalScriptLabels(QStringList &scriptLabels) const; QString getDefaultPrimaryTilesetLabel() const; QString getDefaultSecondaryTilesetLabel() const; diff --git a/src/config.cpp b/src/config.cpp index b56baa4c..22c44b19 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -390,6 +390,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { } } else if (key == "project_settings_tab") { this->projectSettingsTab = getConfigInteger(key, value, 0); + } else if (key == "load_all_event_scripts") { + this->loadAllEventScripts = getConfigBool(key, value); } else if (key == "warp_behavior_warning_disabled") { this->warpBehaviorWarningDisabled = getConfigBool(key, value); } else if (key == "event_delete_warning_disabled") { @@ -479,6 +481,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("text_editor_goto_line", this->textEditorGotoLine); map.insert("palette_editor_bit_depth", QString::number(this->paletteEditorBitDepth)); map.insert("project_settings_tab", QString::number(this->projectSettingsTab)); + map.insert("load_all_event_scripts", QString::number(this->loadAllEventScripts)); map.insert("warp_behavior_warning_disabled", QString::number(this->warpBehaviorWarningDisabled)); map.insert("event_delete_warning_disabled", QString::number(this->eventDeleteWarningDisabled)); map.insert("event_overlay_enabled", QString::number(this->eventOverlayEnabled)); diff --git a/src/core/map.cpp b/src/core/map.cpp index d45a4c1f..7945ab42 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -152,13 +152,12 @@ QStringList Map::getScriptLabels(Event::Group group) { scriptLabels = scriptTracker.getScripts(); } - // Add scripts from map's scripts file, and empty names. + // Add labels from the map's scripts file scriptLabels.append(m_scriptsFileLabels); scriptLabels.sort(Qt::CaseInsensitive); - scriptLabels.prepend("0x0"); - scriptLabels.prepend("NULL"); - scriptLabels.removeAll(""); + scriptLabels.removeAll("0"); + scriptLabels.removeAll("0x0"); scriptLabels.removeDuplicates(); return scriptLabels; diff --git a/src/project.cpp b/src/project.cpp index bd4e9bf9..c8f12a34 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2600,16 +2600,28 @@ bool Project::readMiscellaneousConstants() { } bool Project::readEventScriptLabels() { - globalScriptLabels.clear(); - for (const auto &filePath : getEventScriptsFilePaths()) - globalScriptLabels << ParseUtil::getGlobalScriptLabels(filePath); + this->globalScriptLabels.clear(); - globalScriptLabels.sort(Qt::CaseInsensitive); - globalScriptLabels.removeDuplicates(); + if (porymapConfig.loadAllEventScripts) + return true; + + for (const auto &filePath : getEventScriptsFilePaths()) + this->globalScriptLabels << ParseUtil::getGlobalScriptLabels(filePath); + + this->globalScriptLabels.sort(Qt::CaseInsensitive); + this->globalScriptLabels.removeDuplicates(); return true; } +void Project::insertGlobalScriptLabels(QStringList &scriptLabels) const { + if (this->globalScriptLabels.isEmpty()) + return; + scriptLabels.append(this->globalScriptLabels); + scriptLabels.sort(); + scriptLabels.removeDuplicates(); +} + QString Project::fixPalettePath(QString path) { static const QRegularExpression re_gbapal("\\.gbapal$"); path = path.replace(re_gbapal, ".pal"); diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 40a545ab..45085e7c 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -173,12 +173,19 @@ void EventFrame::setActive(bool active) { } void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * project) { - // The script dropdown is populated with scripts used by the map's events and from its scripts file. - if (this->event->getMap()) - combo->addItems(this->event->getMap()->getScriptLabels(this->event->getEventGroup())); + // The script dropdown and autocomplete are populated with scripts used by the map's events and from its scripts file. + if (!this->event->getMap()) + return; - // The dropdown's autocomplete has all script labels across the full project. - auto completer = new QCompleter(project->globalScriptLabels, combo); + QStringList scripts = this->event->getMap()->getScriptLabels(this->event->getEventGroup()); + combo->addItems(scripts); + + // Depending on the settings, the autocomplete may also contain all global scripts. + if (porymapConfig.loadAllEventScripts) { + project->insertGlobalScriptLabels(scripts); + } + + auto completer = new QCompleter(scripts, combo); completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel); completer->setFilterMode(Qt::MatchContains); From 9afaa4ae3ebb768768632aea4d7cae4dfcb325e0 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 21 Feb 2025 15:18:39 -0500 Subject: [PATCH 4/6] Add setting to re-enable global script autocomplete --- forms/preferenceeditor.ui | 498 +++++++++++++++++++--------------- include/project.h | 1 + include/ui/eventframes.h | 4 +- include/ui/preferenceeditor.h | 1 + src/editor.cpp | 1 + src/mainwindow.cpp | 15 +- src/project.cpp | 15 +- src/ui/eventframes.cpp | 3 + src/ui/preferenceeditor.cpp | 6 + 9 files changed, 306 insertions(+), 238 deletions(-) diff --git a/forms/preferenceeditor.ui b/forms/preferenceeditor.ui index 228c21be..2ce37cbe 100644 --- a/forms/preferenceeditor.ui +++ b/forms/preferenceeditor.ui @@ -6,8 +6,8 @@ 0 0 - 522 - 493 + 530 + 432 @@ -19,244 +19,302 @@ 9 - - - Miscellaneous + + + 0 - - - - - If checked, a prompt to reload your project will appear if relevant project files are edited - - - Monitor project files - - - - - - - If checked, Porymap will automatically open your most recently opened project on startup - - - Open recent project on launch - - - - - - - If checked, Porymap will automatically alert you on startup if a new release is available - - - Automatically check for updates - - - - - - - If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted. - - - Disable warning when deleting events with IDs - - - - - - - - - - Event Selection Mode - - - - - - If enabled, an event can be selected by clicking directly on the opaque pixels of its sprite. This may be preferable when events are overlapping. - - - Select by clicking on sprite - - - - - - - If enabled, an event can be selected by clicking anywhere within its sprite dimensions. This may be preferable for events with small or mostly transparent sprites. - - - Select by clicking within bounding rectangle - - - - - - - - - - - 0 - 0 - - - - Application Theme - - - - - - - Preferred Text Editor - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - true - - - - - 0 - 0 - 492 - 327 - + + + General + + + + 12 + + + 12 + + + 12 + + + + + If checked, a prompt to reload your project will appear if relevant project files are edited - - - QLayout::SetMinimumSize - - - - - <html><head/><body><p>When this command is set a button will appear next to the <span style=" font-weight:600; font-style:italic;">Script</span> combo-box in the <span style=" font-weight:600; font-style:italic;">Events</span> tab which executes this command.<span style=" font-weight:600;"> %F</span> will be substituted with the file path of the script and <span style=" font-weight:600;">%L</span> will be substituted with the line number of the script in that file. <span style=" font-weight:600;">%F </span><span style=" font-style:italic;">must</span> be given if <span style=" font-weight:600;">%L</span> is given. If <span style=" font-weight:600;">%F</span> is <span style=" font-style:italic;">not</span> given then the script's file path will be added to the end of the command. If the script can't be found then the current map's scripts file is opened.</p></body></html> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - - - - - Goto Line Command - - - - - + + Monitor project files + + + + + + + If checked, Porymap will automatically open your most recently opened project on startup + + + Open recent project on launch + + + + + + + If checked, Porymap will automatically alert you on startup if a new release is available + + + Automatically check for updates + + + + + + + + 0 + 0 + + + + Application Theme + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + Events + + + + 12 + + + 12 + + + 12 + + + + + If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted. + + + Disable warning when deleting events with IDs + + + + + + + <html><head/><body><p>If checked, the list of suggestions when typing in an Event's Script field will include all global script labels in the project. Enabling this setting will make Porymap's startup slower.</p></body></html> + + + Autocomplete Script labels using all possible scripts + + + + + + + Selection Mode + + + + - The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH). + If enabled, an event can be selected by clicking directly on the opaque pixels of its sprite. This may be preferable when events are overlapping. - - e.g. code %D - - - true - - - - - - <html><head/><body><p>This is the command that is executed when clicking <span style=" font-weight:600; font-style:italic;">Open Project in Text Editor</span> in the <span style=" font-weight:600; font-style:italic;">Tools</span> menu. <span style=" font-weight:600;">%D</span> will be substituted with the project's root directory. If <span style=" font-weight:600;">%D</span> is <span style=" font-style:italic;">not</span> specified then the project directory will be added to the end of the command.</p></body></html> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true + Select by clicking on sprite - - - - Open Directory Command - - - - - + + - The shell command for your preferred text editor to open a file to a specific line number (possibly an absolute path if the program doesn't exist in your PATH). + If enabled, an event can be selected by clicking anywhere within its sprite dimensions. This may be preferable for events with small or mostly transparent sprites. - - e.g. code --goto %F:%L - - - true + + Select by clicking within bounding rectangle - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 15 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + Text Editor + + + + 12 + + + 12 + + + 12 + + + + + QFrame::Shape::NoFrame + + + true + + + + + 0 + 0 + 476 + 343 + + + + + QLayout::SizeConstraint::SetMinimumSize + + + + + <html><head/><body><p>When this command is set a button will appear next to the <span style=" font-weight:600; font-style:italic;">Script</span> combo-box in the <span style=" font-weight:600; font-style:italic;">Events</span> tab which executes this command.<span style=" font-weight:600;"> %F</span> will be substituted with the file path of the script and <span style=" font-weight:600;">%L</span> will be substituted with the line number of the script in that file. <span style=" font-weight:600;">%F </span><span style=" font-style:italic;">must</span> be given if <span style=" font-weight:600;">%L</span> is given. If <span style=" font-weight:600;">%F</span> is <span style=" font-style:italic;">not</span> given then the script's file path will be added to the end of the command. If the script can't be found then the current map's scripts file is opened.</p></body></html> + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + true + + + + + + + Goto Line Command + + + + + + + The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH). + + + e.g. code %D + + + true + + + + + + + <html><head/><body><p>This is the command that is executed when clicking <span style=" font-weight:600; font-style:italic;">Open Project in Text Editor</span> in the <span style=" font-weight:600; font-style:italic;">Tools</span> menu. <span style=" font-weight:600;">%D</span> will be substituted with the project's root directory. If <span style=" font-weight:600;">%D</span> is <span style=" font-style:italic;">not</span> specified then the project directory will be added to the end of the command.</p></body></html> + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + true + + + + + + + Open Directory Command + + + + + + + The shell command for your preferred text editor to open a file to a specific line number (possibly an absolute path if the program doesn't exist in your PATH). + + + e.g. code --goto %F:%L + + + true + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Fixed + + + + 20 + 15 + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/include/project.h b/include/project.h index 4a8ec1f7..bdca7444 100644 --- a/include/project.h +++ b/include/project.h @@ -298,6 +298,7 @@ signals: void mapSectionDisplayNameChanged(const QString &idName, const QString &displayName); void mapSectionIdNamesChanged(const QStringList &idNames); void mapsExcluded(const QStringList &excludedMapNames); + void eventScriptLabelsRead(); }; #endif // PROJECT_H diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index cfde161c..763d6baf 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -31,8 +31,6 @@ public: void invalidateUi(); void invalidateValues(); - void populateScriptDropdown(NoScrollComboBox * combo, Project * project); - virtual void setActive(bool active); public: @@ -59,6 +57,8 @@ protected: bool initialized = false; bool connected = false; + void populateScriptDropdown(NoScrollComboBox * combo, Project * project); + private: Event *event; }; diff --git a/include/ui/preferenceeditor.h b/include/ui/preferenceeditor.h index c59641c2..e4541495 100644 --- a/include/ui/preferenceeditor.h +++ b/include/ui/preferenceeditor.h @@ -23,6 +23,7 @@ public: signals: void preferencesSaved(); void themeChanged(const QString &theme); + void scriptSettingsChanged(bool on); private: Ui::PreferenceEditor *ui; diff --git a/src/editor.cpp b/src/editor.cpp index ad99dded..6c269195 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -13,6 +13,7 @@ #include "customattributesframe.h" #include "validator.h" #include "message.h" +#include "eventframes.h" #include #include #include diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a38505e2..f91cc12e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2719,12 +2719,10 @@ void MainWindow::on_actionOpen_Config_Folder_triggered() { void MainWindow::on_actionPreferences_triggered() { if (!preferenceEditor) { preferenceEditor = new PreferenceEditor(this); - connect(preferenceEditor, &PreferenceEditor::themeChanged, - this, &MainWindow::setTheme); - connect(preferenceEditor, &PreferenceEditor::themeChanged, - editor, &Editor::maskNonVisibleConnectionTiles); - connect(preferenceEditor, &PreferenceEditor::preferencesSaved, - this, &MainWindow::togglePreferenceSpecificUi); + connect(preferenceEditor, &PreferenceEditor::themeChanged, this, &MainWindow::setTheme); + connect(preferenceEditor, &PreferenceEditor::themeChanged, editor, &Editor::maskNonVisibleConnectionTiles); + connect(preferenceEditor, &PreferenceEditor::preferencesSaved, this, &MainWindow::togglePreferenceSpecificUi); + connect(preferenceEditor, &PreferenceEditor::scriptSettingsChanged, editor->project, &Project::readEventScriptLabels); } openSubWindow(preferenceEditor); @@ -2739,8 +2737,9 @@ void MainWindow::togglePreferenceSpecificUi() { if (this->updatePromoter) this->updatePromoter->updatePreferences(); - // Redraw all events to use updated porymapConfig.eventSelectionShapeMode - this->editor->redrawAllEvents(); + // Changes to porymapConfig.loadAllEventScripts or porymapConfig.eventSelectionShapeMode + // require us to repopulate the EventFrames and redraw event pixmaps, respectively. + this->editor->updateEvents(); } void MainWindow::openProjectSettingsEditor(int tab) { diff --git a/src/project.cpp b/src/project.cpp index c8f12a34..7977c41b 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2602,15 +2602,14 @@ bool Project::readMiscellaneousConstants() { bool Project::readEventScriptLabels() { this->globalScriptLabels.clear(); - if (porymapConfig.loadAllEventScripts) - return true; - - for (const auto &filePath : getEventScriptsFilePaths()) - this->globalScriptLabels << ParseUtil::getGlobalScriptLabels(filePath); - - this->globalScriptLabels.sort(Qt::CaseInsensitive); - this->globalScriptLabels.removeDuplicates(); + if (porymapConfig.loadAllEventScripts) { + for (const auto &filePath : getEventScriptsFilePaths()) + this->globalScriptLabels << ParseUtil::getGlobalScriptLabels(filePath); + this->globalScriptLabels.sort(Qt::CaseInsensitive); + this->globalScriptLabels.removeDuplicates(); + } + emit eventScriptLabelsRead(); return true; } diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 45085e7c..82334722 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -195,6 +195,9 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj if (popup) popup->setUniformItemSizes(true); combo->setCompleter(completer); + + // If the project changes the script labels, update the EventFrame. + connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } diff --git a/src/ui/preferenceeditor.cpp b/src/ui/preferenceeditor.cpp index 130e1a62..f9071ec2 100644 --- a/src/ui/preferenceeditor.cpp +++ b/src/ui/preferenceeditor.cpp @@ -56,6 +56,7 @@ void PreferenceEditor::updateFields() { ui->checkBox_OpenRecentProject->setChecked(porymapConfig.reopenOnLaunch); ui->checkBox_CheckForUpdates->setChecked(porymapConfig.checkForUpdates); ui->checkBox_DisableEventWarning->setChecked(porymapConfig.eventDeleteWarningDisabled); + ui->checkBox_AutocompleteAllScripts->setChecked(porymapConfig.loadAllEventScripts); } void PreferenceEditor::saveFields() { @@ -64,6 +65,11 @@ void PreferenceEditor::saveFields() { porymapConfig.theme = theme; emit themeChanged(theme); } + bool loadAllEventScripts = ui->checkBox_AutocompleteAllScripts->isChecked(); + if (loadAllEventScripts != porymapConfig.loadAllEventScripts) { + porymapConfig.loadAllEventScripts = loadAllEventScripts; + emit scriptSettingsChanged(loadAllEventScripts); + } porymapConfig.eventSelectionShapeMode = ui->radioButton_OnSprite->isChecked() ? QGraphicsPixmapItem::MaskShape : QGraphicsPixmapItem::BoundingRectShape; porymapConfig.textEditorOpenFolder = ui->lineEdit_TextEditorOpenFolder->text(); porymapConfig.textEditorGotoLine = ui->lineEdit_TextEditorGotoLine->text(); From 491b003f2f93e248ab70886f1b5d6acc707d750e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 22 Feb 2025 18:05:06 -0500 Subject: [PATCH 5/6] Make use of the dimension fields in ObjectEventGraphicsInfo --- src/project.cpp | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/project.cpp b/src/project.cpp index 7977c41b..41375270 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2702,8 +2702,9 @@ bool Project::readEventGraphics() { // The positions of each of the required members for the gfx info struct. // For backwards compatibility if the struct doesn't use initializers. static const QHash gfxInfoMemberMap = { + {4, "width"}, + {5, "height"}, {8, "inanimate"}, - {11, "oam"}, {12, "subspriteTables"}, {14, "images"}, }; @@ -2742,16 +2743,23 @@ bool Project::readEventGraphics() { // The .png file is expected to be a spritesheet that can have multiple frames. // We only want to show one frame at a time, so we need to know the dimensions of each frame. - // TODO: Describe different ways we read these. Use width/height? - static const QRegularExpression re("\\S+_(\\d+)x(\\d+)"); - QRegularExpressionMatch dimensionMatch = re.match(gfxInfoAttributes.value("oam")); - QRegularExpressionMatch oamTablesMatch = re.match(gfxInfoAttributes.value("subspriteTables")); - if (oamTablesMatch.hasMatch()) { - gfx->spriteWidth = oamTablesMatch.captured(1).toInt(nullptr, 0); - gfx->spriteHeight = oamTablesMatch.captured(2).toInt(nullptr, 0); - } else if (dimensionMatch.hasMatch()) { - gfx->spriteWidth = dimensionMatch.captured(1).toInt(nullptr, 0); - gfx->spriteHeight = dimensionMatch.captured(2).toInt(nullptr, 0); + // The true dimensions are buried in the subsprite data, so we try to infer the dimensions from the name of the 'subspriteTables' symbol. + // If we are unable to do this, we can read the dimensions from the width and height fields. + // This is much more straightforward, but the numbers are not necessarily accurate (one vanilla event sprite, + // the Town Map in FRLG, has width/height values that differ from its true dimensions). + static const QRegularExpression re_dimensions("\\S+_(\\d+)x(\\d+)"); + const QRegularExpressionMatch dimensionsMatch = re_dimensions.match(gfxInfoAttributes.value("subspriteTables")); + if (dimensionsMatch.hasMatch()) { + gfx->spriteWidth = dimensionsMatch.captured(1).toInt(nullptr, 0); + gfx->spriteHeight = dimensionsMatch.captured(2).toInt(nullptr, 0); + } else if (gfxInfoAttributes.contains("width") && gfxInfoAttributes.contains("height")) { + gfx->spriteWidth = gfxInfoAttributes.value("width").toInt(nullptr, 0); + gfx->spriteHeight = gfxInfoAttributes.value("height").toInt(nullptr, 0); + } + // If we fail to get sprite dimensions then they should remain -1, and the sprite will use the full spritesheet as its image. + if (gfx->spriteWidth <= 0 || gfx->spriteHeight <= 0) { + gfx->spriteWidth = -1; + gfx->spriteHeight = -1; } // Inanimate events will only ever use the first frame of their spritesheet. From 7637dc5ad6a9786735c43eedb7e9b5cd7c1ccbae Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 22 Feb 2025 18:47:52 -0500 Subject: [PATCH 6/6] Fix Qt5 build --- src/project.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/project.cpp b/src/project.cpp index 41375270..0b9b2af4 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2720,7 +2720,8 @@ bool Project::readEventGraphics() { // Strip the address-of operator to get the pointer's name. We'll use this name to get data about the event's sprite. // If we don't recognize the name, ignore it. The event will use a default sprite. - QString info_label = pointerMap[gfxName].replace("&", ""); + QString info_label = pointerMap.value(gfxName); + info_label.replace("&", ""); if (!gfxInfos.contains(info_label)) continue; const QHash gfxInfoAttributes = gfxInfos[info_label];