From ac8db41299d04a88f41c3c25b7d882d87be0c5b8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 16 Feb 2025 15:31:20 -0500 Subject: [PATCH] Add event group limit --- forms/projectsettingseditor.ui | 23 +++++++++ include/config.h | 2 + include/editor.h | 5 +- include/mainwindow.h | 1 - include/project.h | 5 +- src/config.cpp | 3 ++ src/editor.cpp | 81 +++++++++++++++++++------------- src/mainwindow.cpp | 49 +++++-------------- src/project.cpp | 19 +++++--- src/ui/projectsettingseditor.cpp | 3 ++ 10 files changed, 107 insertions(+), 84 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index da006eff..a088d87e 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -1341,6 +1341,29 @@ + + + + + + + Maximum Events per Event group + + + + + + + <html><head/><body><p>Maps cannot have more than this number of events in each event group. Object events are additionally limited by 'define_obj_event_count' on the Identifiers tab.</p></body></html> + + + 1 + + + + + + diff --git a/include/config.h b/include/config.h index 7f263da0..d0c2ff9f 100644 --- a/include/config.h +++ b/include/config.h @@ -319,6 +319,7 @@ public: this->unusedTileNormal = 0x3014; this->unusedTileCovered = 0x0000; this->unusedTileSplit = 0x0000; + this->maxEventsPerGroup = 255; this->identifiers.clear(); this->readKeys.clear(); } @@ -388,6 +389,7 @@ public: int collisionSheetWidth; int collisionSheetHeight; QList warpBehaviors; + int maxEventsPerGroup; protected: virtual QString getConfigFilepath() override; diff --git a/include/editor.h b/include/editor.h index 689d2f4f..e2e09c42 100644 --- a/include/editor.h +++ b/include/editor.h @@ -110,9 +110,9 @@ public: DraggablePixmapItem *addEventPixmapItem(Event *event); void removeEventPixmapItem(Event *event); - bool eventLimitReached(Map *, Event::Type); + bool canAddEvents(const QList &events); void selectMapEvent(DraggablePixmapItem *item, bool toggle = false); - DraggablePixmapItem *addNewEvent(Event::Type type); + Event *addNewEvent(Event::Type type); void updateSelectedEvents(); void duplicateSelectedEvents(); void redrawAllEvents(); @@ -185,7 +185,6 @@ public: void shouldReselectEvents(); void scaleMapView(int); static void openInTextEditor(const QString &path, int lineNum = 0); - bool eventLimitReached(Event::Type type); void setCollisionGraphics(); public slots: diff --git a/include/mainwindow.h b/include/mainwindow.h index 805899a1..412362c5 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -218,7 +218,6 @@ private slots: void on_actionMove_triggered(); void on_actionMap_Shift_triggered(); - void addNewEvent(Event::Type type); void tryAddEventTab(QWidget * tab); void displayEventTabs(); void updateSelectedEvents(); diff --git a/include/project.h b/include/project.h index 97b9a698..c2707063 100644 --- a/include/project.h +++ b/include/project.h @@ -245,7 +245,7 @@ public: static int getMapDataSize(int width, int height); static bool mapDimensionsValid(int width, int height); bool calculateDefaultMapSize(); - static int getMaxObjectEvents(); + int getMaxEvents(Event::Group group); static QString getEmptyMapsecName(); static QString getMapGroupPrefix(); @@ -263,6 +263,8 @@ private: void ignoreWatchedFileTemporarily(QString filepath); void recordFileChange(const QString &filepath); + int maxEventsPerGroup; + int maxObjectEvents; static int num_tiles_primary; static int num_tiles_total; static int num_metatiles_primary; @@ -270,7 +272,6 @@ private: static int num_pals_total; static int max_map_data_size; static int default_map_dimension; - static int max_object_events; signals: void fileChanged(const QString &filepath); diff --git a/src/config.cpp b/src/config.cpp index b22972a4..89ab50f9 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -803,6 +803,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { const QStringList behaviorList = value.split(",", Qt::SkipEmptyParts); for (auto s : behaviorList) this->warpBehaviors.append(getConfigUint32(key, s)); + } else if (key == "max_events_per_group") { + this->maxEventsPerGroup = getConfigInteger(key, value, 1, INT_MAX, 255); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -898,6 +900,7 @@ QMap ProjectConfig::getKeyValueMap() { for (const auto &value : this->warpBehaviors) warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper()); map.insert("warp_behaviors", warpBehaviorStrs.join(",")); + map.insert("max_events_per_group", QString::number(this->maxEventsPerGroup)); return map; } diff --git a/src/editor.cpp b/src/editor.cpp index f654b805..a7ce2390 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1330,17 +1330,13 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } else { // Left-clicking while in paint mode will add a new event of the // type of the first currently selected events. - // Disallow adding heal locations, deleting them is not possible yet Event::Type eventType = Event::Type::Object; if (this->selected_events->size() > 0) eventType = this->selected_events->first()->event->getEventType(); - DraggablePixmapItem *newEvent = addNewEvent(eventType); - if (newEvent) { - newEvent->move(pos.x(), pos.y()); - emit eventsChanged(); - selectMapEvent(newEvent); - } + Event* event = addNewEvent(eventType); + if (event && event->getPixmapItem()) + event->getPixmapItem()->moveTo(pos); } } else if (eventEditAction == EditAction::Select) { // do nothing here, at least for now @@ -2083,47 +2079,66 @@ void Editor::selectedEventIndexChanged(int index, Event::Group eventGroup) { } } -void Editor::duplicateSelectedEvents() { - if (!selected_events || !selected_events->length() || !map || !current_view || this->getEditingLayout()) - return; +bool Editor::canAddEvents(const QList &events) { + if (!this->project || !this->map) + return false; - QList selectedEvents; - for (int i = 0; i < selected_events->length(); i++) { - Event *original = selected_events->at(i)->event; - Event::Type eventType = original->getEventType(); - if (eventLimitReached(eventType)) { - logWarn(QString("Skipping duplication, the map limit for events of type '%1' has been reached.").arg(Event::typeToString(eventType))); - continue; + QMap newEventCounts; + for (const auto &event : events) { + Event::Group group = event->getEventGroup(); + int maxEvents = this->project->getMaxEvents(group); + if (this->map->getNumEvents(group) + newEventCounts[group]++ >= maxEvents) { + return false; } - Event *duplicate = original->duplicate(); - duplicate->setX(duplicate->getX() + 1); - duplicate->setY(duplicate->getY() + 1); - selectedEvents.append(duplicate); } - map->commit(new EventDuplicate(this, map, selectedEvents)); + return true; } -DraggablePixmapItem *Editor::addNewEvent(Event::Type type) { - if (!project || !map || eventLimitReached(type)) +void Editor::duplicateSelectedEvents() { + if (!selected_events || !selected_events->length() || !project || !map || !current_view || this->getEditingLayout()) + return; + + QList duplicatedEvents; + for (int i = 0; i < selected_events->length(); i++) { + duplicatedEvents.append(selected_events->at(i)->event->duplicate()); + } + if (!canAddEvents(duplicatedEvents)) { + WarningMessage::show(QStringLiteral("Unable to duplicate, the maximum number of events would be exceeded."), ui->graphicsView_Map); + qDeleteAll(duplicatedEvents); + return; + } + this->map->commit(new EventDuplicate(this, this->map, duplicatedEvents)); +} + +Event *Editor::addNewEvent(Event::Type type) { + if (!this->project || !this->map) return nullptr; + Event::Group group = Event::typeToGroup(type); + int maxEvents = this->project->getMaxEvents(group); + if (this->map->getNumEvents(group) >= maxEvents) { + WarningMessage::show(QString("The maximum number of %1 events (%2) has been reached.").arg(Event::groupToString(group)).arg(maxEvents), ui->graphicsView_Map); + return nullptr; + } + Event *event = Event::create(type); if (!event) return nullptr; event->setMap(this->map); event->setDefaultValues(this->project); - map->commit(new EventCreate(this, map, event)); - return event->getPixmapItem(); -} -// Currently only object events have an explicit limit -bool Editor::eventLimitReached(Event::Type event_type) { - if (project && map) { - if (Event::typeToGroup(event_type) == Event::Group::Object) - return map->getNumEvents(Event::Group::Object) >= project->getMaxObjectEvents(); + // This will add the event to the map, create the event pixmap item, and select the event. + this->map->commit(new EventCreate(this, this->map, event)); + + auto pixmapItem = event->getPixmapItem(); + if (pixmapItem) { + auto halfSize = ui->graphicsView_Map->size() / 2; + auto centerPos = ui->graphicsView_Map->mapToScene(halfSize.width(), halfSize.height()); + pixmapItem->moveTo(Metatile::coordFromPixmapCoord(centerPos)); } - return false; + + return event; } void Editor::deleteSelectedEvents() { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 785c77af..5c7e5cdb 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -264,8 +264,6 @@ void MainWindow::initCustomUI() { } void MainWindow::initExtraSignals() { - // other signals - connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this, &MainWindow::addNewEvent); connect(ui->tabWidget_EventType, &QTabWidget::currentChanged, this, &MainWindow::eventTabChanged); // Change pages on wild encounter groups @@ -343,6 +341,7 @@ void MainWindow::initEditor() { connect(this->editor, &Editor::wildMonTableEdited, [this] { this->markMapEdited(); }); connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated); + connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this->editor, &Editor::addNewEvent); connect(ui->toolButton_deleteEvent, &QAbstractButton::clicked, this->editor, &Editor::deleteSelectedEvents); this->loadUserSettings(); @@ -1304,7 +1303,7 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { logInfo(QString("Created a new map named %1.").arg(newMap->name())); if (newMap->needsHealLocation()) { - addNewEvent(Event::Type::HealLocation); + this->editor->addNewEvent(Event::Type::HealLocation); } // TODO: Creating a new map shouldn't be automatically saved. @@ -1750,14 +1749,7 @@ void MainWindow::paste() { QJsonArray events = pasteObject["events"].toArray(); for (QJsonValue event : events) { // paste the event to the map - const QString typeString = event["event_type"].toString(); - Event::Type type = Event::typeFromString(typeString); - - if (this->editor->eventLimitReached(type)) { - logWarn(QString("Cannot paste event, the limit for type '%1' has been reached.").arg(typeString)); - continue; - } - + Event::Type type = Event::typeFromString(event["event_type"].toString()); Event *pasteEvent = Event::create(type); if (!pasteEvent) continue; @@ -1766,12 +1758,16 @@ void MainWindow::paste() { pasteEvent->setMap(this->editor->map); newEvents.append(pasteEvent); } + if (newEvents.empty()) + return; - if (!newEvents.empty()) { - editor->map->commit(new EventPaste(this->editor, editor->map, newEvents)); - updateEvents(); + if (!this->editor->canAddEvents(newEvents)) { + WarningMessage::show(QStringLiteral("Unable to paste, the maximum number of events would be exceeded."), this); + qDeleteAll(newEvents); + return; } - + this->editor->map->commit(new EventPaste(this->editor, this->editor->map, newEvents)); + updateEvents(); break; } } @@ -1983,29 +1979,6 @@ void MainWindow::resetMapViewScale() { editor->scaleMapView(0); } -void MainWindow::addNewEvent(Event::Type type) { - if (editor && editor->project) { - DraggablePixmapItem *item = editor->addNewEvent(type); - if (item) { - auto halfSize = ui->graphicsView_Map->size() / 2; - auto centerPos = ui->graphicsView_Map->mapToScene(halfSize.width(), halfSize.height()); - item->moveTo(Metatile::coordFromPixmapCoord(centerPos)); - updateEvents(); - editor->selectMapEvent(item); - } else { - WarningMessage msgBox(QStringLiteral("Failed to add new event."), this); - if (Event::typeToGroup(type) == Event::Group::Object) { - msgBox.setInformativeText(QString("The limit for object events (%1) has been reached.\n\n" - "This limit can be adjusted with %2 in '%3'.") - .arg(editor->project->getMaxObjectEvents()) - .arg(projectConfig.getIdentifier(ProjectIdentifier::define_obj_event_count)) - .arg(projectConfig.getFilePath(ProjectFilePath::constants_global))); - } - msgBox.exec(); - } - } -} - void MainWindow::tryAddEventTab(QWidget * tab) { auto group = getEventGroupFromTabWidget(tab); if (editor->map->getNumEvents(group)) diff --git a/src/project.cpp b/src/project.cpp index 21dc5199..b25b3776 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -30,7 +30,6 @@ int Project::num_pals_primary = 6; int Project::num_pals_total = 13; int Project::max_map_data_size = 10240; // 0x2800 int Project::default_map_dimension = 20; -int Project::max_object_events = 64; Project::Project(QObject *parent) : QObject(parent) @@ -2578,21 +2577,22 @@ bool Project::readMiscellaneousConstants() { fileWatcher.addPath(root + "/" + filename); QMap defines = parser.readCDefinesByName(filename, {maxObjectEventsName}); + this->maxObjectEvents = 64; // Default value auto it = defines.find(maxObjectEventsName); if (it != defines.end()) { if (it.value() > 0) { - Project::max_object_events = it.value(); + this->maxObjectEvents = it.value(); } else { logWarn(QString("Value for '%1' is %2, must be greater than 0. Using default (%3) instead.") .arg(maxObjectEventsName) .arg(it.value()) - .arg(Project::max_object_events)); + .arg(this->maxObjectEvents)); } } else { logWarn(QString("Value for '%1' not found. Using default (%2) instead.") .arg(maxObjectEventsName) - .arg(Project::max_object_events)); + .arg(this->maxObjectEvents)); } return true; @@ -2943,9 +2943,14 @@ bool Project::calculateDefaultMapSize(){ return true; } -int Project::getMaxObjectEvents() -{ - return Project::max_object_events; +// Object events have their own limit specified by ProjectIdentifier::define_obj_event_count. +// The default value for this is 64. All events (object events included) are also limited by +// the data types of the event counters in the project. This would normally be u8, so the limit is 255. +// We let the users tell us this limit in case they change these data types. +int Project::getMaxEvents(Event::Group group) { + if (group == Event::Group::Object) + return qMin(this->maxObjectEvents, projectConfig.maxEventsPerGroup); + return projectConfig.maxEventsPerGroup; } QString Project::getEmptyMapDefineName() { diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 1250e8dd..86614482 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -135,6 +135,7 @@ void ProjectSettingsEditor::initUi() { ui->spinBox_UnusedTileNormal->setMaximum(Tile::maxValue); ui->spinBox_UnusedTileCovered->setMaximum(Tile::maxValue); ui->spinBox_UnusedTileSplit->setMaximum(Tile::maxValue); + ui->spinBox_MaxEvents->setMaximum(INT_MAX); // The values for some of the settings we provide in this window can be determined using constants in the user's projects. // If the user has these constants we disable these settings in the UI -- they can modify them using their constants. @@ -464,6 +465,7 @@ void ProjectSettingsEditor::refresh() { ui->spinBox_UnusedTileNormal->setValue(projectConfig.unusedTileNormal); ui->spinBox_UnusedTileCovered->setValue(projectConfig.unusedTileCovered); ui->spinBox_UnusedTileSplit->setValue(projectConfig.unusedTileSplit); + ui->spinBox_MaxEvents->setValue(projectConfig.maxEventsPerGroup); // Set (and sync) border metatile IDs this->setBorderMetatileIds(false, projectConfig.newMapBorderMetatileIds); @@ -538,6 +540,7 @@ void ProjectSettingsEditor::save() { projectConfig.unusedTileNormal = ui->spinBox_UnusedTileNormal->value(); projectConfig.unusedTileCovered = ui->spinBox_UnusedTileCovered->value(); projectConfig.unusedTileSplit = ui->spinBox_UnusedTileSplit->value(); + projectConfig.maxEventsPerGroup = ui->spinBox_MaxEvents->value(); // Save line edit settings projectConfig.prefabFilepath = ui->lineEdit_PrefabsPath->text();