diff --git a/docs/architecture.md b/docs/architecture.md index 9a3b4e0..b0d5dca 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -59,26 +59,43 @@ Simulation types shared across subsystems: - `Item` — `struct Item { ItemType type; }`. Items on belts have no persistent identity across ticks. - `Port` — `struct Port { QPoint tile; Rotation direction; }`. Identifies a belt-adjacent cell and the direction of flow across that cell. - `MovementIntent` — `struct MovementIntent { int priority; QVector2D target; }`. Priority follows the order declared under Movement Arbitration. Cleared at the start of each tick; the highest-priority write wins; `tickMovement` reads the winner. -- `FireEvent` — `struct FireEvent { EntityId shooter; EntityId target; Tick emittedAt; }`. Transient record emitted each time a weapon fires (REQ-SHP-FIRING, REQ-SHP-FIRING-BEAM). Buffered in a sim-owned queue and drained by the renderer; see Sim → UI Events. +- `WeaponFiredEvent` — `struct WeaponFiredEvent : public Event { entt::entity shooter; entt::entity target; Tick emittedAt; }`. Transient record emitted each time a weapon fires (REQ-SHP-FIRING, REQ-SHP-FIRING-BEAM). Buffered in a sim-owned vector during the tick, then drained and re-emitted via EventManager by the UI frame handler; see Sim → UI Events. - `SchematicChoiceOption` — `struct SchematicChoiceOption { string schematicId; SchematicType type; string displayName; bool isNewUnlock; int targetLevel; }`. Describes one option in the schematic choice dialog (REQ-DEF-SCHEMATIC-DROP). Up to three are generated when an enemy station set is destroyed. `SchematicType` is `Ship`, `Module`, or `Recipe`. - `SchematicChoicesAvailableEvent` — EventManager event carrying a `vector`. Sent by the UI each frame when pending choices are detected; handled by `MainWindow` which opens the schematic choice dialog. -## Sim → UI Events +## Event System -The sim owns a per-frame event queue for weapon fires that the UI drains on each render. `FireEvent` records are one-shot signals not derivable from persistent state. Additional one-shot event types can be added here later (e.g., building-complete, unit-death flashes) without changing the pattern. +All inter-component communication — both sim→UI and UI→UI — uses a unified `EventManager`/`EventHandler` system. No custom Qt signals/slots are used for inter-widget communication. -Implementation: a plain `std::vector` owned by `Simulation`. Combat resolution (tick-order step 8) appends to it. The UI calls `simulation.drainFireEvents()` once per rendered frame, which returns the accumulated vector by move and clears the internal one. Beams are tracked by the renderer for 0.3 s of wall time (9 ticks at 30 Hz) using the events' `emittedAt` tick, then discarded. If either the shooter or target entity is gone when the renderer looks them up, the beam is dropped early. +### EventManager -Schematic drops use a different pattern: when an enemy station set is destroyed, the simulation generates up to 3 `SchematicChoiceOption` entries and stores them as pending state. The UI polls `hasSchematicChoicesPending()` each frame and, when true, sends a `SchematicChoicesAvailableEvent` via the EventManager. `MainWindow` handles this event by pausing the game and opening a modal `SchematicChoiceDialog`. The player's selection is fed back via `applySchematicChoice(index)`, which applies the chosen schematic and clears the pending state. +`EventManager` is a singleton (`EventManager::getInstance()`) that routes events to registered handlers. -We deliberately do **not** use `QObject` signals/slots or `QEvent` for the fire-event queue: +- `sendEventImmediately(shared_ptr)` — synchronous dispatch to all handlers of the event's type. +- `addEvent(shared_ptr)` — queues the event for later batch processing. +- `processEvents()` — drains the queue, dispatching each event to its handlers. -- **Determinism.** A plain ordered vector preserves tick-order exactly; the queue is part of per-tick state, inspectable in tests. -- **Sim/UI seam.** The sim exposes pull-style access only; the UI never subscribes into the sim, keeping the simulation/presentation split clean. -- **Headless testability.** Catch2 tests read the queue directly after `tick()`; no event loop, no `QApplication`. -- **Zero overhead.** Sim types remain plain structs — no `QObject`, no moc, no signal dispatch machinery. +The EventManager is thread-safe (mutex-guarded). -If the number of event types grows past a handful, we can wrap them in a small `EventQueue` template, still owned by the sim. +### EventHandler + +`EventHandler` is a CRTP-style template that a class inherits to receive events of type `T`. It provides `registerForEvent()` / `unregisterForEvent()` and requires an override of `handleEvent(shared_ptr)`. + +`CombinedEventHandler` is a variadic template for classes that handle multiple event types. It provides `registerForEvents()` / `unregisterForEvents()` and requires one `handleEvent` override per type. + +### Sim → UI Events + +The simulation layer stays free of EventManager — it uses a plain `std::vector` internally (owned by `CombatSystem`). This preserves determinism, tick-order fidelity, and headless testability (Catch2 tests read the queue directly via `drainWeaponFiredEvents()` after `tick()`). + +The UI frame handler (`GameWorldView::onFrame` / `ArenaView::onFrame`) bridges the gap: each frame it calls `simulation.drainWeaponFiredEvents()`, then re-emits each `WeaponFiredEvent` via `EventManager::sendEventImmediately()`. Subscribers (the same view's `handleEvent(WeaponFiredEvent)`) create `ActiveBeam` records tracked for 0.3 s of wall time, then discarded. If either the shooter or target entity is gone when the renderer looks them up, the beam is dropped early. + +Schematic drops: when an enemy station set is destroyed, the simulation generates up to 3 `SchematicChoiceOption` entries and stores them as pending state. The UI polls `hasSchematicChoicesPending()` each frame and, when true, sends a `SchematicChoicesAvailableEvent` via EventManager. `MainWindow` handles this event by pausing the game and opening a modal `SchematicChoiceDialog`. The player's selection is fed back via `applySchematicChoice(index)`. + +### UI Events + +All UI interactions — building selection, builder/blueprint mode transitions, speed changes, demolish mode, escape menu, layout dialog requests — are communicated via EventManager events rather than Qt signals/slots. Each event is a small struct inheriting `Event` (e.g., `SelectionChangedEvent`, `BuildingTypeSelectedEvent`, `SpeedChangeRequestedEvent`). Widgets register as `CombinedEventHandler` for the events they care about and emit events via `EventManager::sendEventImmediately()`. + +Bidirectional interactions use separate request/notification event types to avoid infinite recursion (e.g., `ExitBuilderModeRequestedEvent` from `BuildButtonGrid` → `GameWorldView`, vs. `BuilderModeExitedEvent` from `GameWorldView` → `BuildButtonGrid`). ## Tick Order @@ -91,7 +108,7 @@ Within a single simulation tick, subsystems run in this fixed order. The order i 5. **Building → belt push** — buildings push items from output buffer onto the belt tile at their output port (REQ-MAT-OUTPUT-PORT). 6. **Belt tick** — advance items along belt tiles; apply splitter routing (REQ-BLD-SPLITTER). 7. **Ship behavior systems** — clear `MovementIntent` on each ship, then run `tickThreatResponse`, `tickScrapCollector`, `tickRepairBehavior`, `tickHomeReturn` in any order (arbitration is via intent priority). -8. **Combat resolution** — ships and defence stations acquire targets, fire, apply damage; queue deaths. Each fire appends a `FireEvent` to the sim's fire-event queue (REQ-SHP-FIRING-BEAM). +8. **Combat resolution** — ships and defence stations acquire targets, fire, apply damage; queue deaths. Each fire appends a `WeaponFiredEvent` to the sim's weapon-fired-event queue (REQ-SHP-FIRING-BEAM). 9. **Deaths & loot** — process queued deaths: drop scrap (REQ-RES-SCRAP-DROP); if a full enemy-defence-station set was destroyed this tick, generate up to 3 schematic choice options (REQ-DEF-SCHEMATIC-DROP) stored as pending state for the UI to present; remove entities. 10. **`tickMovement`** — advance ship positions based on final `MovementIntent`. 11. **Scrap despawn** — decrement scrap timers; remove expired scrap (REQ-RES-SCRAP-DROP). @@ -283,7 +300,7 @@ The game world is rendered by a single `GameWorldView` widget that inherits `QOp ### Threading -Sim and UI run on the same thread for v1. `paintEvent` reads sim state directly without locks. If profiling later justifies moving the sim to a worker thread, the pull-style `drainFireEvents()` / `getPendingSchematicChoices()` / `applySchematicChoice()` / `forEachVisualItem()` APIs already support a clean snapshot-and-render split; a single mutex at the sim boundary would suffice. +Sim and UI run on the same thread for v1. `paintEvent` reads sim state directly without locks. If profiling later justifies moving the sim to a worker thread, the pull-style `drainWeaponFiredEvents()` / `getPendingSchematicChoices()` / `applySchematicChoice()` / `forEachVisualItem()` APIs already support a clean snapshot-and-render split; a single mutex at the sim boundary would suffice. The `ArenaSimulation` used by the balancing tool runs headlessly on a worker thread; fire events accumulate in its internal vector and are only drained when `ArenaView` drives `tickOnce()` on the main thread during interactive inspection. ### Layer Order (back to front) @@ -292,7 +309,7 @@ Sim and UI run on the same thread for v1. `paintEvent` reads sim state directly 3. **Belt items** — 10×10 colored squares emitted by `BeltSystem::forEachVisualItem`. 4. **Scrap** — glyphs at world positions. 5. **Ships** — colored arrows oriented by velocity; color keyed to role (player combat / salvage / repair / enemy). -6. **Laser beams** — lines derived from live `FireEvent`s kept by the renderer for 0.3 s (REQ-SHP-FIRING-BEAM). +6. **Laser beams** — lines derived from live `WeaponFiredEvent`s kept by the renderer for 0.3 s (REQ-SHP-FIRING-BEAM). 7. **Build overlays** — ghost in builder mode (REQ-BLD-GHOST), demolish-mode tint, tile highlight under cursor, box-drag selection rectangle. 8. **Screen-space UI** — screen-anchored elements, drawn after resetting the world-space transform. diff --git a/src/balancing/ArenaSimulation.cpp b/src/balancing/ArenaSimulation.cpp index 76f4aad..397f935 100644 --- a/src/balancing/ArenaSimulation.cpp +++ b/src/balancing/ArenaSimulation.cpp @@ -259,9 +259,9 @@ void ArenaSimulation::tick() m_aiSystem->tickSalvageBehavior(m_admin, *m_scrapSystem, *m_buildingSystem); // Combat resolution (tick step 8). - std::vector fireEvents; - m_combatSystem->tick(m_currentTick, m_admin, *m_buildingSystem, fireEvents); - m_fireEvents.insert(m_fireEvents.end(), fireEvents.begin(), fireEvents.end()); + std::vector weaponFiredEvents; + m_combatSystem->tick(m_currentTick, m_admin, *m_buildingSystem, weaponFiredEvents); + m_weaponFiredEvents.insert(m_weaponFiredEvents.end(), weaponFiredEvents.begin(), weaponFiredEvents.end()); m_combatSystem->applyPendingDamage(m_currentTick, m_admin); // Deaths (tick step 9, simplified). @@ -393,10 +393,10 @@ void ArenaSimulation::tickOnce() } } -std::vector ArenaSimulation::drainFireEvents() +std::vector ArenaSimulation::drainWeaponFiredEvents() { - std::vector result; - result.swap(m_fireEvents); + std::vector result; + result.swap(m_weaponFiredEvents); return result; } diff --git a/src/balancing/ArenaSimulation.h b/src/balancing/ArenaSimulation.h index 84cebb3..1b08640 100644 --- a/src/balancing/ArenaSimulation.h +++ b/src/balancing/ArenaSimulation.h @@ -13,7 +13,7 @@ #include "BuildingId.h" #include "entt/entity/entity.hpp" -#include "FireEvent.h" +#include "WeaponFiredEvent.h" #include "GameConfig.h" #include "Tick.h" @@ -58,7 +58,7 @@ public: void requestStop(); void tickOnce(); - std::vector drainFireEvents(); + std::vector drainWeaponFiredEvents(); ArenaStatus status() const; bool isFinished() const; @@ -104,7 +104,7 @@ private: int m_winnerTeam; std::atomic m_stopRequested; - std::vector m_fireEvents; + std::vector m_weaponFiredEvents; mutable std::mutex m_statusMutex; ArenaStatus m_status; diff --git a/src/balancing/ArenaView.cpp b/src/balancing/ArenaView.cpp index a886270..99697e5 100644 --- a/src/balancing/ArenaView.cpp +++ b/src/balancing/ArenaView.cpp @@ -16,6 +16,7 @@ #include "EventManager.h" #include "FacingComponent.h" #include "FactionComponent.h" +#include "GameSpeedChangedEvent.h" #include "HealthComponent.h" #include "PositionComponent.h" #include "ScrapSystem.h" @@ -45,6 +46,13 @@ ArenaView::ArenaView(ArenaSimulation* sim, const VisualsConfig* visuals, connect(m_renderTimer, &QTimer::timeout, this, &ArenaView::onFrame); m_renderTimer->start(); m_frameTimer.start(); + + registerForEvent(); +} + +ArenaView::~ArenaView() +{ + unregisterForEvent(); } void ArenaView::setGameSpeed(double multiplier) @@ -54,7 +62,8 @@ void ArenaView::setGameSpeed(double multiplier) m_prevNonZeroSpeed = multiplier; } m_gameSpeedMultiplier = multiplier; - emit speedChanged(multiplier); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(multiplier)); } double ArenaView::gameSpeed() const @@ -93,34 +102,17 @@ void ArenaView::onFrame() } } + // Emit fire events via EventManager { - const std::vector fires = m_sim->drainFireEvents(); - for (const FireEvent& fe : fires) + const std::vector fires = m_sim->drainWeaponFiredEvents(); + for (const WeaponFiredEvent& fe : fires) { - float maxRadius = 0.125f; - if (m_sim->admin().isValid(fe.target) - && m_sim->admin().hasAll(fe.target)) - { - const StationBodyComponent& sb = m_sim->admin().get(fe.target); - const int shorter = std::min(sb.footprint.width(), - sb.footprint.height()); - maxRadius = shorter / 2.0f; - } - - std::uniform_real_distribution angleDist(0.0f, 6.28318530f); - std::uniform_real_distribution radiusDist(0.0f, maxRadius); - const float angle = angleDist(m_rng); - const float radius = radiusDist(m_rng); - - ActiveBeam beam; - beam.event = fe; - beam.emittedWallMs = m_wallMs; - beam.targetOffset = QVector2D(radius * std::cos(angle), - radius * std::sin(angle)); - m_activeBeams.push_back(beam); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(fe)); } } + // Expire old beams { std::vector live; for (const ActiveBeam& b : m_activeBeams) @@ -136,12 +128,36 @@ void ArenaView::onFrame() if (m_sim->isFinished() && !m_finishedEmitted) { m_finishedEmitted = true; - emit finished(); } update(); } +void ArenaView::handleEvent(std::shared_ptr event) +{ + float maxRadius = 0.125f; + if (m_sim->admin().isValid(event->target) + && m_sim->admin().hasAll(event->target)) + { + const StationBodyComponent& sb = m_sim->admin().get(event->target); + const int shorter = std::min(sb.footprint.width(), + sb.footprint.height()); + maxRadius = shorter / 2.0f; + } + + std::uniform_real_distribution angleDist(0.0f, 6.28318530f); + std::uniform_real_distribution radiusDist(0.0f, maxRadius); + const float angle = angleDist(m_rng); + const float radius = radiusDist(m_rng); + + ActiveBeam beam; + beam.event = *event; + beam.emittedWallMs = m_wallMs; + beam.targetOffset = QVector2D(radius * std::cos(angle), + radius * std::sin(angle)); + m_activeBeams.push_back(beam); +} + void ArenaView::paintGL() { QPainter painter(this); @@ -414,4 +430,3 @@ void ArenaView::drawBeams(QPainter& painter) worldToWidget(*targetPos + beam.targetOffset)); } } - diff --git a/src/balancing/ArenaView.h b/src/balancing/ArenaView.h index f6c598e..83035a6 100644 --- a/src/balancing/ArenaView.h +++ b/src/balancing/ArenaView.h @@ -9,7 +9,8 @@ #include #include -#include "FireEvent.h" +#include "EventHandler.h" +#include "WeaponFiredEvent.h" #include "entt/entity/entity.hpp" #include "EntitySelectedEvent.h" @@ -20,23 +21,21 @@ class ArenaSimulation; class QPainter; -class ArenaView : public QOpenGLWidget +class ArenaView : public QOpenGLWidget, + public EventHandler { Q_OBJECT public: ArenaView(ArenaSimulation* sim, const VisualsConfig* visuals, QWidget* parent = nullptr); + ~ArenaView() override; void setGameSpeed(double multiplier); double gameSpeed() const; void togglePause(); void stopRendering(); -signals: - void speedChanged(double multiplier); - void finished(); - protected: void paintGL() override; void mousePressEvent(QMouseEvent* event) override; @@ -45,6 +44,8 @@ private slots: void onFrame(); private: + void handleEvent(std::shared_ptr event) override; + void drawTiles(QPainter& painter); void drawBuildings(QPainter& painter); void drawStations(QPainter& painter); @@ -62,7 +63,7 @@ private: struct ActiveBeam { - FireEvent event; + WeaponFiredEvent event; qint64 emittedWallMs; QVector2D targetOffset; }; diff --git a/src/balancing/ArenaWidget.cpp b/src/balancing/ArenaWidget.cpp index 2f9fd3d..d0d01b5 100644 --- a/src/balancing/ArenaWidget.cpp +++ b/src/balancing/ArenaWidget.cpp @@ -3,8 +3,13 @@ #include #include -ArenaWidget::ArenaWidget(const std::string& arenaName, QWidget* parent) +#include "ArenaInspectRequestedEvent.h" +#include "ArenaStartRequestedEvent.h" +#include "EventManager.h" + +ArenaWidget::ArenaWidget(int arenaIndex, const std::string& arenaName, QWidget* parent) : QFrame(parent) + , m_arenaIndex(arenaIndex) , m_running(false) , m_wasFinished(false) { @@ -31,11 +36,17 @@ void ArenaWidget::buildLayout(const std::string& arenaName) titleRow->addStretch(); m_inspectButton = new QPushButton(tr("Inspect"), this); - connect(m_inspectButton, &QPushButton::clicked, this, &ArenaWidget::inspectRequested); + connect(m_inspectButton, &QPushButton::clicked, this, [this]() { + EventManager::getInstance()->sendEventImmediately( + std::make_shared(m_arenaIndex)); + }); titleRow->addWidget(m_inspectButton); m_startButton = new QPushButton(tr("Start"), this); - connect(m_startButton, &QPushButton::clicked, this, &ArenaWidget::startRequested); + connect(m_startButton, &QPushButton::clicked, this, [this]() { + EventManager::getInstance()->sendEventImmediately( + std::make_shared(m_arenaIndex)); + }); titleRow->addWidget(m_startButton); outerLayout->addLayout(titleRow); diff --git a/src/balancing/ArenaWidget.h b/src/balancing/ArenaWidget.h index 515a260..65ec2ec 100644 --- a/src/balancing/ArenaWidget.h +++ b/src/balancing/ArenaWidget.h @@ -14,19 +14,16 @@ class ArenaWidget : public QFrame Q_OBJECT public: - explicit ArenaWidget(const std::string& arenaName, QWidget* parent = nullptr); + ArenaWidget(int arenaIndex, const std::string& arenaName, QWidget* parent = nullptr); void updateStatus(const ArenaStatus& status); void startSimulation(); void resetToGrey(); -signals: - void startRequested(); - void inspectRequested(); - private: void buildLayout(const std::string& arenaName); + int m_arenaIndex; QLabel* m_titleLabel; QLabel* m_team1Header; QLabel* m_team2Header; diff --git a/src/balancing/BalancingWindow.cpp b/src/balancing/BalancingWindow.cpp index 39eac62..3eea5ba 100644 --- a/src/balancing/BalancingWindow.cpp +++ b/src/balancing/BalancingWindow.cpp @@ -48,14 +48,17 @@ BalancingWindow::BalancingWindow(const BalancingConfig& balancingConfig, m_pollTimer = new QTimer(this); connect(m_pollTimer, &QTimer::timeout, this, &BalancingWindow::pollStatuses); m_pollTimer->start(100); + + registerForEvents(); } BalancingWindow::~BalancingWindow() { + unregisterForEvents(); + m_pollTimer->stop(); if (m_inspectWindow) { - m_inspectWindow->disconnect(this); delete m_inspectWindow; m_inspectWindow = nullptr; } @@ -81,16 +84,11 @@ void BalancingWindow::populateArenas(const BalancingConfig& balancingConfig) entry.config = arenaConfig; entry.simulation = std::make_unique( m_gameConfig, arenaConfig, m_nextSeed++); - entry.widget = new ArenaWidget(arenaConfig.name, scrollContent); + entry.widget = new ArenaWidget(index, arenaConfig.name, scrollContent); contentLayout->addWidget(entry.widget); entry.widget->updateStatus(entry.simulation->status()); - connect(entry.widget, &ArenaWidget::startRequested, - this, [this, index]() { startArena(index); }); - connect(entry.widget, &ArenaWidget::inspectRequested, - this, [this, index]() { inspectArena(index); }); - m_arenas.push_back(std::move(entry)); } @@ -158,6 +156,21 @@ void BalancingWindow::startAll() } } +void BalancingWindow::handleEvent(std::shared_ptr event) +{ + startArena(event->arenaIndex); +} + +void BalancingWindow::handleEvent(std::shared_ptr event) +{ + inspectArena(event->arenaIndex); +} + +void BalancingWindow::handleEvent(std::shared_ptr /*event*/) +{ + closeInspectWindow(); +} + void BalancingWindow::startArena(int index) { ArenaEntry& entry = m_arenas[index]; @@ -179,7 +192,6 @@ void BalancingWindow::inspectArena(int index) { if (m_inspectWindow) { - m_inspectWindow->disconnect(this); delete m_inspectWindow; m_inspectWindow = nullptr; @@ -210,8 +222,6 @@ void BalancingWindow::inspectArena(int index) m_inspectWindow = new InspectWindow( m_inspectedSim.get(), &m_gameConfig, &m_visuals, entry.config.name, nullptr); - connect(m_inspectWindow, &InspectWindow::closed, - this, &BalancingWindow::closeInspectWindow); setMainControlsEnabled(false); m_inspectWindow->show(); @@ -224,7 +234,6 @@ void BalancingWindow::closeInspectWindow() return; } - m_inspectWindow->disconnect(this); m_inspectWindow->deleteLater(); m_inspectWindow = nullptr; diff --git a/src/balancing/BalancingWindow.h b/src/balancing/BalancingWindow.h index eb74fb6..b71c716 100644 --- a/src/balancing/BalancingWindow.h +++ b/src/balancing/BalancingWindow.h @@ -10,15 +10,22 @@ #include #include +#include "ArenaInspectRequestedEvent.h" +#include "ArenaStartRequestedEvent.h" #include "ArenaWidget.h" #include "ArenaSimulation.h" #include "BalancingConfig.h" +#include "EventHandler.h" #include "GameConfig.h" +#include "InspectWindowClosedEvent.h" #include "VisualsConfig.h" class InspectWindow; -class BalancingWindow : public QWidget +class BalancingWindow : public QWidget, + public CombinedEventHandler { Q_OBJECT @@ -30,15 +37,20 @@ public: QWidget* parent = nullptr); ~BalancingWindow() override; +private: + void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; + private slots: void pollStatuses(); void reloadConfig(); void startAll(); + +private: void startArena(int index); void inspectArena(int index); void closeInspectWindow(); - -private: void populateArenas(const BalancingConfig& balancingConfig); void stopAllArenas(); void updateButtons(); diff --git a/src/balancing/InspectWindow.cpp b/src/balancing/InspectWindow.cpp index 28ab4b3..d3277a0 100644 --- a/src/balancing/InspectWindow.cpp +++ b/src/balancing/InspectWindow.cpp @@ -10,7 +10,9 @@ #include "ArenaView.h" #include "EntityAdmin.h" +#include "EventManager.h" #include "HealthComponent.h" +#include "InspectWindowClosedEvent.h" #include "ModuleOwnerComponent.h" #include "ShipIdentityComponent.h" #include "ShipStatsCalculator.h" @@ -76,9 +78,6 @@ InspectWindow::InspectWindow(ArenaSimulation* sim, const GameConfig* config, m_arenaView = new ArenaView(sim, visuals, this); mainLayout->addWidget(m_arenaView, 1); - connect(m_arenaView, &ArenaView::speedChanged, - this, &InspectWindow::onSpeedChanged); - // Info panel (bottom) { QWidget* infoPanel = new QWidget(this); @@ -140,19 +139,20 @@ InspectWindow::InspectWindow(ArenaSimulation* sim, const GameConfig* config, setFocusPolicy(Qt::StrongFocus); - registerForEvent(); + registerForEvents(); } InspectWindow::~InspectWindow() { - unregisterForEvent(); + unregisterForEvents(); } void InspectWindow::closeEvent(QCloseEvent* event) { m_arenaView->stopRendering(); m_pollTimer->stop(); - emit closed(); + EventManager::getInstance()->sendEventImmediately( + std::make_shared()); event->accept(); } @@ -176,11 +176,11 @@ void InspectWindow::onSpeedButton(int index) } } -void InspectWindow::onSpeedChanged(double multiplier) +void InspectWindow::handleEvent(std::shared_ptr event) { for (int i = 0; i < kSpeedCount; ++i) { - const bool active = (std::abs(kSpeeds[i] - multiplier) < 0.001); + const bool active = (std::abs(kSpeeds[i] - event->speed) < 0.001); m_speedButtons[static_cast(i)]->setChecked(active); } } diff --git a/src/balancing/InspectWindow.h b/src/balancing/InspectWindow.h index f2b9116..b770668 100644 --- a/src/balancing/InspectWindow.h +++ b/src/balancing/InspectWindow.h @@ -15,13 +15,15 @@ #include "EntitySelectedEvent.h" #include "EventHandler.h" #include "GameConfig.h" +#include "GameSpeedChangedEvent.h" #include "VisualsConfig.h" class ArenaView; class ShipStatsPanel; class InspectWindow : public QWidget, - public EventHandler + public CombinedEventHandler { Q_OBJECT @@ -31,19 +33,16 @@ public: const std::string& arenaName, QWidget* parent = nullptr); ~InspectWindow() override; -signals: - void closed(); - protected: void closeEvent(QCloseEvent* event) override; void keyPressEvent(QKeyEvent* event) override; private: void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; private slots: void onSpeedButton(int index); - void onSpeedChanged(double multiplier); void pollStatus(); private: diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt index 0dad6e8..5bfb1df 100644 --- a/src/lib/core/CMakeLists.txt +++ b/src/lib/core/CMakeLists.txt @@ -6,7 +6,6 @@ SET(HDRS ${CMAKE_CURRENT_SOURCE_DIR}/EntityAdmin.h ${CMAKE_CURRENT_SOURCE_DIR}/Blueprint.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildingId.h - ${CMAKE_CURRENT_SOURCE_DIR}/FireEvent.h ${CMAKE_CURRENT_SOURCE_DIR}/ItemType.h ${CMAKE_CURRENT_SOURCE_DIR}/Item.h ${CMAKE_CURRENT_SOURCE_DIR}/Port.h diff --git a/src/lib/core/FireEvent.h b/src/lib/core/FireEvent.h deleted file mode 100644 index cea1324..0000000 --- a/src/lib/core/FireEvent.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "Tick.h" - -#include "entt/entity/entity.hpp" - -// Transient record emitted each time a weapon fires (REQ-SHP-FIRING, -// REQ-SHP-FIRING-BEAM). Buffered in a sim-owned queue and drained by the -// renderer each frame to draw the 0.3-second laser beam. -struct FireEvent -{ - entt::entity shooter; - entt::entity target; - Tick emittedAt; -}; diff --git a/src/lib/ecs/system/CombatSystem.cpp b/src/lib/ecs/system/CombatSystem.cpp index 42202ee..b474fb1 100644 --- a/src/lib/ecs/system/CombatSystem.cpp +++ b/src/lib/ecs/system/CombatSystem.cpp @@ -21,7 +21,7 @@ CombatSystem::CombatSystem(const GameConfig& config) void CombatSystem::tick(Tick currentTick, EntityAdmin& admin, BuildingSystem& /*buildings*/, - std::vector& outFireEvents) + std::vector& outWeaponFiredEvents) { TRACE(); // All weapons (ships and stations) are child entities linked via ModuleOwnerComponent. @@ -35,7 +35,7 @@ void CombatSystem::tick(Tick currentTick, } const PositionComponent& pos = admin.get(owner.owner); const FactionComponent& faction = admin.get(owner.owner); - resolveWeapon(owner.owner, weapon, pos, faction, currentTick, admin, outFireEvents); + resolveWeapon(owner.owner, weapon, pos, faction, currentTick, admin, outWeaponFiredEvents); }); } @@ -46,7 +46,7 @@ void CombatSystem::resolveWeapon( const FactionComponent& ownFaction, Tick currentTick, EntityAdmin& admin, - std::vector& out) + std::vector& out) { if (weapon.cooldownTicks > 0.0f) { @@ -115,7 +115,7 @@ void CombatSystem::resolveWeapon( m_pendingDamage.push_back({targetEntity, weapon.damage, currentTick + kWeaponImpactDelayTicks}); - FireEvent evt; + WeaponFiredEvent evt; evt.shooter = shipEntity; evt.target = targetEntity; evt.emittedAt = currentTick; diff --git a/src/lib/ecs/system/CombatSystem.h b/src/lib/ecs/system/CombatSystem.h index 771d9cd..2a037dd 100644 --- a/src/lib/ecs/system/CombatSystem.h +++ b/src/lib/ecs/system/CombatSystem.h @@ -7,7 +7,7 @@ #include "Building.h" #include "FactionComponent.h" -#include "FireEvent.h" +#include "WeaponFiredEvent.h" #include "GameConfig.h" #include "PositionComponent.h" #include "Tick.h" @@ -26,7 +26,7 @@ public: void tick(Tick currentTick, EntityAdmin& admin, BuildingSystem& buildings, - std::vector& outFireEvents); + std::vector& outWeaponFiredEvents); void applyPendingDamage(Tick currentTick, EntityAdmin& admin); @@ -47,7 +47,7 @@ private: const FactionComponent& ownFaction, Tick currentTick, EntityAdmin& admin, - std::vector& out); + std::vector& out); const GameConfig& m_config; }; diff --git a/src/lib/eventsystem/event/ArenaInspectRequestedEvent.h b/src/lib/eventsystem/event/ArenaInspectRequestedEvent.h new file mode 100644 index 0000000..a033ecb --- /dev/null +++ b/src/lib/eventsystem/event/ArenaInspectRequestedEvent.h @@ -0,0 +1,10 @@ +#pragma once + +#include "Event.h" + +class ArenaInspectRequestedEvent : public Event +{ +public: + explicit ArenaInspectRequestedEvent(int arenaIndex) : arenaIndex(arenaIndex) {} + const int arenaIndex; +}; diff --git a/src/lib/eventsystem/event/ArenaStartRequestedEvent.h b/src/lib/eventsystem/event/ArenaStartRequestedEvent.h new file mode 100644 index 0000000..3f777fa --- /dev/null +++ b/src/lib/eventsystem/event/ArenaStartRequestedEvent.h @@ -0,0 +1,10 @@ +#pragma once + +#include "Event.h" + +class ArenaStartRequestedEvent : public Event +{ +public: + explicit ArenaStartRequestedEvent(int arenaIndex) : arenaIndex(arenaIndex) {} + const int arenaIndex; +}; diff --git a/src/lib/eventsystem/event/BlueprintModeExitedEvent.h b/src/lib/eventsystem/event/BlueprintModeExitedEvent.h new file mode 100644 index 0000000..8c41549 --- /dev/null +++ b/src/lib/eventsystem/event/BlueprintModeExitedEvent.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Event.h" + +class BlueprintModeExitedEvent : public Event +{ +}; diff --git a/src/lib/eventsystem/event/BlueprintPlacementRequestedEvent.h b/src/lib/eventsystem/event/BlueprintPlacementRequestedEvent.h new file mode 100644 index 0000000..5a863e1 --- /dev/null +++ b/src/lib/eventsystem/event/BlueprintPlacementRequestedEvent.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Blueprint.h" +#include "Event.h" + +class BlueprintPlacementRequestedEvent : public Event +{ +public: + explicit BlueprintPlacementRequestedEvent(Blueprint blueprint) + : blueprint(std::move(blueprint)) {} + const Blueprint blueprint; +}; diff --git a/src/lib/eventsystem/event/BuilderModeExitedEvent.h b/src/lib/eventsystem/event/BuilderModeExitedEvent.h new file mode 100644 index 0000000..f2598f0 --- /dev/null +++ b/src/lib/eventsystem/event/BuilderModeExitedEvent.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Event.h" + +class BuilderModeExitedEvent : public Event +{ +}; diff --git a/src/lib/eventsystem/event/BuildingTypeSelectedEvent.h b/src/lib/eventsystem/event/BuildingTypeSelectedEvent.h new file mode 100644 index 0000000..1cfd5d9 --- /dev/null +++ b/src/lib/eventsystem/event/BuildingTypeSelectedEvent.h @@ -0,0 +1,11 @@ +#pragma once + +#include "BuildingType.h" +#include "Event.h" + +class BuildingTypeSelectedEvent : public Event +{ +public: + explicit BuildingTypeSelectedEvent(BuildingType type) : type(type) {} + const BuildingType type; +}; diff --git a/src/lib/eventsystem/event/CMakeLists.txt b/src/lib/eventsystem/event/CMakeLists.txt index 12ed411..41165c0 100644 --- a/src/lib/eventsystem/event/CMakeLists.txt +++ b/src/lib/eventsystem/event/CMakeLists.txt @@ -7,6 +7,23 @@ SET(HDRS ${CMAKE_CURRENT_SOURCE_DIR}/GameSpeedChangedEvent.h ${CMAKE_CURRENT_SOURCE_DIR}/BossWaveUpdatedEvent.h ${CMAKE_CURRENT_SOURCE_DIR}/SchematicChoicesAvailableEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/SelectionChangedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/GameOverEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/BuilderModeExitedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/BlueprintModeExitedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/EscapeMenuRequestedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/DemolishModeChangedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/BuildingTypeSelectedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/ExitBuilderModeRequestedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/DemolishModeToggleRequestedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/BlueprintPlacementRequestedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/ExitBlueprintModeRequestedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/SpeedChangeRequestedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/LayoutDialogRequestedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/InspectWindowClosedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/ArenaStartRequestedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/ArenaInspectRequestedEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/WeaponFiredEvent.h PARENT_SCOPE ) diff --git a/src/lib/eventsystem/event/DemolishModeChangedEvent.h b/src/lib/eventsystem/event/DemolishModeChangedEvent.h new file mode 100644 index 0000000..401f2ab --- /dev/null +++ b/src/lib/eventsystem/event/DemolishModeChangedEvent.h @@ -0,0 +1,10 @@ +#pragma once + +#include "Event.h" + +class DemolishModeChangedEvent : public Event +{ +public: + explicit DemolishModeChangedEvent(bool active) : active(active) {} + const bool active; +}; diff --git a/src/lib/eventsystem/event/DemolishModeToggleRequestedEvent.h b/src/lib/eventsystem/event/DemolishModeToggleRequestedEvent.h new file mode 100644 index 0000000..4d69f8c --- /dev/null +++ b/src/lib/eventsystem/event/DemolishModeToggleRequestedEvent.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Event.h" + +class DemolishModeToggleRequestedEvent : public Event +{ +}; diff --git a/src/lib/eventsystem/event/EscapeMenuRequestedEvent.h b/src/lib/eventsystem/event/EscapeMenuRequestedEvent.h new file mode 100644 index 0000000..a764309 --- /dev/null +++ b/src/lib/eventsystem/event/EscapeMenuRequestedEvent.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Event.h" + +class EscapeMenuRequestedEvent : public Event +{ +}; diff --git a/src/lib/eventsystem/event/ExitBlueprintModeRequestedEvent.h b/src/lib/eventsystem/event/ExitBlueprintModeRequestedEvent.h new file mode 100644 index 0000000..aca1fca --- /dev/null +++ b/src/lib/eventsystem/event/ExitBlueprintModeRequestedEvent.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Event.h" + +class ExitBlueprintModeRequestedEvent : public Event +{ +}; diff --git a/src/lib/eventsystem/event/ExitBuilderModeRequestedEvent.h b/src/lib/eventsystem/event/ExitBuilderModeRequestedEvent.h new file mode 100644 index 0000000..11e73d3 --- /dev/null +++ b/src/lib/eventsystem/event/ExitBuilderModeRequestedEvent.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Event.h" + +class ExitBuilderModeRequestedEvent : public Event +{ +}; diff --git a/src/lib/eventsystem/event/GameOverEvent.h b/src/lib/eventsystem/event/GameOverEvent.h new file mode 100644 index 0000000..67b3e8b --- /dev/null +++ b/src/lib/eventsystem/event/GameOverEvent.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Event.h" + +class GameOverEvent : public Event +{ +}; diff --git a/src/lib/eventsystem/event/InspectWindowClosedEvent.h b/src/lib/eventsystem/event/InspectWindowClosedEvent.h new file mode 100644 index 0000000..3070bc0 --- /dev/null +++ b/src/lib/eventsystem/event/InspectWindowClosedEvent.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Event.h" + +class InspectWindowClosedEvent : public Event +{ +}; diff --git a/src/lib/eventsystem/event/LayoutDialogRequestedEvent.h b/src/lib/eventsystem/event/LayoutDialogRequestedEvent.h new file mode 100644 index 0000000..44c6df2 --- /dev/null +++ b/src/lib/eventsystem/event/LayoutDialogRequestedEvent.h @@ -0,0 +1,12 @@ +#pragma once + +#include "BuildingId.h" +#include "Event.h" + +class LayoutDialogRequestedEvent : public Event +{ +public: + explicit LayoutDialogRequestedEvent(BuildingId shipyardId) + : shipyardId(shipyardId) {} + const BuildingId shipyardId; +}; diff --git a/src/lib/eventsystem/event/SelectionChangedEvent.h b/src/lib/eventsystem/event/SelectionChangedEvent.h new file mode 100644 index 0000000..2f8401c --- /dev/null +++ b/src/lib/eventsystem/event/SelectionChangedEvent.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "BuildingId.h" +#include "Event.h" + +class SelectionChangedEvent : public Event +{ +public: + explicit SelectionChangedEvent(std::vector ids) + : ids(std::move(ids)) {} + const std::vector ids; +}; diff --git a/src/lib/eventsystem/event/SpeedChangeRequestedEvent.h b/src/lib/eventsystem/event/SpeedChangeRequestedEvent.h new file mode 100644 index 0000000..6613edd --- /dev/null +++ b/src/lib/eventsystem/event/SpeedChangeRequestedEvent.h @@ -0,0 +1,10 @@ +#pragma once + +#include "Event.h" + +class SpeedChangeRequestedEvent : public Event +{ +public: + explicit SpeedChangeRequestedEvent(double multiplier) : multiplier(multiplier) {} + const double multiplier; +}; diff --git a/src/lib/eventsystem/event/WeaponFiredEvent.h b/src/lib/eventsystem/event/WeaponFiredEvent.h new file mode 100644 index 0000000..b93d801 --- /dev/null +++ b/src/lib/eventsystem/event/WeaponFiredEvent.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Event.h" +#include "Tick.h" + +#include "entt/entity/entity.hpp" + +struct WeaponFiredEvent : public Event +{ + WeaponFiredEvent() = default; + WeaponFiredEvent(entt::entity shooter, entt::entity target, Tick emittedAt) + : shooter(shooter), target(target), emittedAt(emittedAt) {} + + entt::entity shooter = entt::null; + entt::entity target = entt::null; + Tick emittedAt = 0; +}; diff --git a/src/lib/sim/Simulation.cpp b/src/lib/sim/Simulation.cpp index eb8fda3..1f73f57 100644 --- a/src/lib/sim/Simulation.cpp +++ b/src/lib/sim/Simulation.cpp @@ -136,7 +136,7 @@ void Simulation::reset(unsigned int seed) m_playerStation2Entity = entt::null; m_currentEnemyStationEntities[0] = entt::null; m_currentEnemyStationEntities[1] = entt::null; - m_fireEvents.clear(); + m_weaponFiredEvents.clear(); m_pendingSchematicChoices.clear(); m_admin.clear(); @@ -246,7 +246,7 @@ void Simulation::tick() // Step 8: combat resolution m_combatSystem->tick(m_currentTick, m_admin, - *m_buildingSystem, m_fireEvents); + *m_buildingSystem, m_weaponFiredEvents); // Step 8b: deferred damage whose impact tick has arrived m_combatSystem->applyPendingDamage(m_currentTick, m_admin); @@ -745,10 +745,10 @@ bool Simulation::isItemUnlocked(const std::string& itemId) const // Drains // --------------------------------------------------------------------------- -std::vector Simulation::drainFireEvents() +std::vector Simulation::drainWeaponFiredEvents() { - std::vector result; - result.swap(m_fireEvents); + std::vector result; + result.swap(m_weaponFiredEvents); return result; } diff --git a/src/lib/sim/Simulation.h b/src/lib/sim/Simulation.h index 1ee859c..98d24ff 100644 --- a/src/lib/sim/Simulation.h +++ b/src/lib/sim/Simulation.h @@ -16,7 +16,7 @@ #include "BuildingType.h" #include "BuildingId.h" #include "EventHandler.h" -#include "FireEvent.h" +#include "WeaponFiredEvent.h" #include "GameConfig.h" #include "Rotation.h" #include "Tick.h" @@ -50,7 +50,7 @@ public: // Returns all fire events accumulated since the last drain, clearing the // internal queue. Call once per rendered frame (REQ-SHP-FIRING-BEAM). - std::vector drainFireEvents(); + std::vector drainWeaponFiredEvents(); // Returns the pending schematic choices (empty if no drop is pending). const std::vector& getPendingSchematicChoices() const; @@ -165,6 +165,6 @@ private: std::unique_ptr m_waveSystem; std::unique_ptr m_combatSystem; - std::vector m_fireEvents; + std::vector m_weaponFiredEvents; std::vector m_pendingSchematicChoices; }; diff --git a/src/test/CombatSystemTest.cpp b/src/test/CombatSystemTest.cpp index 83229f2..84330d8 100644 --- a/src/test/CombatSystemTest.cpp +++ b/src/test/CombatSystemTest.cpp @@ -10,7 +10,7 @@ #include "ConfigLoader.h" #include "EntityAdmin.h" #include "FactionComponent.h" -#include "FireEvent.h" +#include "WeaponFiredEvent.h" #include "HealthComponent.h" #include "HqProxyComponent.h" #include "ModuleOwnerComponent.h" @@ -111,7 +111,7 @@ TEST_CASE("CombatSystem: ship fires when cooldown=0 and target in range", "[comb const float hpBefore = f.admin.get(player).hp; - std::vector events; + std::vector events; f.combat.tick(0, f.admin, f.buildings, events); f.combat.applyPendingDamage(5, f.admin); @@ -135,16 +135,16 @@ TEST_CASE("CombatSystem: cooldown prevents firing before it expires", "[combat]" f.admin.get(wc).cooldownTicks = 3.0f; // override to 3 } - auto enemyFiredIn = [&enemy](const std::vector& evts) + auto enemyFiredIn = [&enemy](const std::vector& evts) { - for (const FireEvent& evt : evts) + for (const WeaponFiredEvent& evt : evts) { if (evt.shooter == enemy) { return true; } } return false; }; - std::vector events; + std::vector events; f.combat.tick(0, f.admin, f.buildings, events); REQUIRE_FALSE(enemyFiredIn(events)); @@ -165,7 +165,7 @@ TEST_CASE("CombatSystem: no fire when target is out of range", "[combat]") const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(500.0f, 0.0f), false); f.wireEnemyTarget(enemy, player); - std::vector events; + std::vector events; f.combat.tick(0, f.admin, f.buildings, events); REQUIRE(events.empty()); } @@ -204,9 +204,9 @@ TEST_CASE("CombatSystem: player station fires at enemy ship in range", "[combat] sim.tick(); - const std::vector events = sim.drainFireEvents(); + const std::vector events = sim.drainWeaponFiredEvents(); bool stationFired = false; - for (const FireEvent& evt : events) + for (const WeaponFiredEvent& evt : events) { if (evt.shooter == stationEntity) { stationFired = true; } } @@ -242,9 +242,9 @@ TEST_CASE("CombatSystem: enemy station fires at player ship in range", "[combat] sim.tick(); - const std::vector events = sim.drainFireEvents(); + const std::vector events = sim.drainWeaponFiredEvents(); bool stationFired = false; - for (const FireEvent& evt : events) + for (const WeaponFiredEvent& evt : events) { if (evt.shooter == stationEntity) { stationFired = true; } } @@ -280,9 +280,9 @@ TEST_CASE("CombatSystem: player ship fires at enemy station in range", "[combat] sim.tick(); - const std::vector events = sim.drainFireEvents(); + const std::vector events = sim.drainWeaponFiredEvents(); bool playerFiredAtStation = false; - for (const FireEvent& evt : events) + for (const WeaponFiredEvent& evt : events) { if (evt.shooter == playerShip && evt.target == stationEntity) { @@ -308,7 +308,7 @@ TEST_CASE("CombatSystem: damage not applied before impact tick", "[combat]") const float hpBefore = f.admin.get(player).hp; - std::vector events; + std::vector events; f.combat.tick(0, f.admin, f.buildings, events); for (Tick t = 1; t < 5; ++t) @@ -330,7 +330,7 @@ TEST_CASE("CombatSystem: damage applied exactly at impact tick", "[combat]") const float hpBefore = f.admin.get(player).hp; - std::vector events; + std::vector events; f.combat.tick(0, f.admin, f.buildings, events); f.combat.applyPendingDamage(5, f.admin); @@ -347,7 +347,7 @@ TEST_CASE("CombatSystem: damage silently dropped if target already dead", "[comb const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false); f.wireEnemyTarget(enemy, player); - std::vector events; + std::vector events; f.combat.tick(0, f.admin, f.buildings, events); f.ships.despawn(player); @@ -370,7 +370,7 @@ TEST_CASE("CombatSystem: damage still applied if shooter already dead", "[combat const float hpBefore = f.admin.get(player).hp; - std::vector events; + std::vector events; f.combat.tick(0, f.admin, f.buildings, events); f.ships.despawn(enemy); diff --git a/src/test/SimulationTest.cpp b/src/test/SimulationTest.cpp index 66b8625..2efb0d8 100644 --- a/src/test/SimulationTest.cpp +++ b/src/test/SimulationTest.cpp @@ -43,22 +43,22 @@ TEST_CASE("Simulation::tick 10 times yields currentTick == 10", "[simulation]") REQUIRE(sim.currentTick() == 10); } -TEST_CASE("Simulation::drainFireEvents returns empty initially", "[simulation]") +TEST_CASE("Simulation::drainWeaponFiredEvents returns empty initially", "[simulation]") { Simulation sim(loadConfig()); - REQUIRE(sim.drainFireEvents().empty()); + REQUIRE(sim.drainWeaponFiredEvents().empty()); } -TEST_CASE("Simulation::drainFireEvents clears queue on drain", "[simulation]") +TEST_CASE("Simulation::drainWeaponFiredEvents clears queue on drain", "[simulation]") { Simulation sim(loadConfig()); // First drain: empty. - sim.drainFireEvents(); + sim.drainWeaponFiredEvents(); // Second drain must also be empty (not a double-return). - REQUIRE(sim.drainFireEvents().empty()); + REQUIRE(sim.drainWeaponFiredEvents().empty()); } TEST_CASE("Simulation::hasSchematicChoicesPending returns false initially", "[simulation]") diff --git a/src/ui/BlueprintPanel.cpp b/src/ui/BlueprintPanel.cpp index bc6d431..63397db 100644 --- a/src/ui/BlueprintPanel.cpp +++ b/src/ui/BlueprintPanel.cpp @@ -12,8 +12,11 @@ #include #include +#include "BlueprintPlacementRequestedEvent.h" #include "BlueprintSerializer.h" #include "BuildingBlocksChangedEvent.h" +#include "EventManager.h" +#include "ExitBlueprintModeRequestedEvent.h" #include "Building.h" #include "BuildingSystem.h" @@ -62,12 +65,12 @@ BlueprintPanel::BlueprintPanel(Simulation* sim, const GameConfig* config, QWidge connect(m_saveBtn, &QPushButton::clicked, this, &BlueprintPanel::onSaveClicked); connect(m_loadBtn, &QPushButton::clicked, this, &BlueprintPanel::onLoadClicked); - registerForEvent(); + registerForEvents(); } BlueprintPanel::~BlueprintPanel() { - unregisterForEvent(); + unregisterForEvents(); } void BlueprintPanel::onSelectionChanged(const std::vector& ids) @@ -114,7 +117,8 @@ void BlueprintPanel::onDeleteBlueprintClicked(int index) if (m_activeIndex == index) { m_activeIndex = -1; - emit exitBlueprintModeRequested(); + EventManager::getInstance()->sendEventImmediately( + std::make_shared()); } else if (m_activeIndex > index) { @@ -131,7 +135,8 @@ void BlueprintPanel::onBlueprintButtonClicked(int index) if (m_activeIndex == index) { clearActiveBlueprintButton(); - emit exitBlueprintModeRequested(); + EventManager::getInstance()->sendEventImmediately( + std::make_shared()); return; } @@ -142,7 +147,8 @@ void BlueprintPanel::onBlueprintButtonClicked(int index) m_activeIndex = index; m_blueprintButtons[static_cast(index)]->setChecked(true); - emit blueprintPlacementRequested(m_blueprints[static_cast(index)]); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(m_blueprints[static_cast(index)])); } Blueprint BlueprintPanel::createBlueprintFromSelection() const @@ -312,7 +318,8 @@ void BlueprintPanel::onLoadClicked() if (m_activeIndex >= 0) { - emit exitBlueprintModeRequested(); + EventManager::getInstance()->sendEventImmediately( + std::make_shared()); m_activeIndex = -1; } @@ -350,3 +357,13 @@ void BlueprintPanel::refreshButtonStates() canAfford || m_activeIndex == i); } } + +void BlueprintPanel::handleEvent(std::shared_ptr event) +{ + onSelectionChanged(event->ids); +} + +void BlueprintPanel::handleEvent(std::shared_ptr /*event*/) +{ + clearActiveBlueprintButton(); +} diff --git a/src/ui/BlueprintPanel.h b/src/ui/BlueprintPanel.h index 563e0a3..3552c11 100644 --- a/src/ui/BlueprintPanel.h +++ b/src/ui/BlueprintPanel.h @@ -5,10 +5,12 @@ #include #include "Blueprint.h" +#include "BlueprintModeExitedEvent.h" #include "BuildingBlocksChangedEvent.h" #include "BuildingId.h" #include "EventHandler.h" #include "GameConfig.h" +#include "SelectionChangedEvent.h" #include "Tick.h" class Simulation; @@ -17,7 +19,9 @@ class QScrollArea; class QVBoxLayout; class BlueprintPanel : public QWidget, - public EventHandler + public CombinedEventHandler { Q_OBJECT @@ -25,16 +29,10 @@ public: BlueprintPanel(Simulation* sim, const GameConfig* config, QWidget* parent = nullptr); ~BlueprintPanel() override; -public slots: - void onSelectionChanged(const std::vector& ids); - void clearActiveBlueprintButton(); - -signals: - void blueprintPlacementRequested(Blueprint blueprint); - void exitBlueprintModeRequested(); - private: void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; private slots: void onCreateClicked(); @@ -44,6 +42,8 @@ private slots: void onLoadClicked(); private: + void onSelectionChanged(const std::vector& ids); + void clearActiveBlueprintButton(); Blueprint createBlueprintFromSelection() const; int computeBlueprintCost(const Blueprint& bp) const; void rebuildButtons(); diff --git a/src/ui/BuildButtonGrid.cpp b/src/ui/BuildButtonGrid.cpp index e30ed6e..64d3768 100644 --- a/src/ui/BuildButtonGrid.cpp +++ b/src/ui/BuildButtonGrid.cpp @@ -7,7 +7,11 @@ #include #include "BuildingType.h" +#include "BuildingTypeSelectedEvent.h" +#include "DemolishModeToggleRequestedEvent.h" #include "DisplayName.h" +#include "EventManager.h" +#include "ExitBuilderModeRequestedEvent.h" BuildButtonGrid::BuildButtonGrid(const GameConfig* config, QWidget* parent) @@ -58,7 +62,17 @@ BuildButtonGrid::BuildButtonGrid(const GameConfig* config, QWidget* parent) m_demolishButton->setCheckable(true); m_demolishButton->setFixedHeight(48); layout->addWidget(m_demolishButton, row, col); - connect(m_demolishButton, SIGNAL(clicked()), this, SIGNAL(demolishModeToggleRequested())); + connect(m_demolishButton, &QPushButton::clicked, this, [this]() { + EventManager::getInstance()->sendEventImmediately( + std::make_shared()); + }); + + registerForEvents(); +} + +BuildButtonGrid::~BuildButtonGrid() +{ + unregisterForEvents(); } void BuildButtonGrid::updateAffordability(int buildingBlocks) @@ -72,11 +86,6 @@ void BuildButtonGrid::updateAffordability(int buildingBlocks) } } -void BuildButtonGrid::setDemolishModeActive(bool active) -{ - m_demolishButton->setChecked(active); -} - void BuildButtonGrid::clearActiveButton() { if (m_activeIndex >= 0 && m_activeIndex < static_cast(m_buttons.size())) @@ -96,7 +105,8 @@ void BuildButtonGrid::onBuildButton(int index) if (m_activeIndex == index) { clearActiveButton(); - emit builderModeExited(); + EventManager::getInstance()->sendEventImmediately( + std::make_shared()); return; } @@ -107,5 +117,16 @@ void BuildButtonGrid::onBuildButton(int index) m_activeIndex = index; m_buttons[static_cast(index)]->setChecked(true); - emit buildingTypeSelected(m_types[static_cast(index)]); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(m_types[static_cast(index)])); +} + +void BuildButtonGrid::handleEvent(std::shared_ptr /*event*/) +{ + clearActiveButton(); +} + +void BuildButtonGrid::handleEvent(std::shared_ptr event) +{ + m_demolishButton->setChecked(event->active); } diff --git a/src/ui/BuildButtonGrid.h b/src/ui/BuildButtonGrid.h index 3caca2a..96513db 100644 --- a/src/ui/BuildButtonGrid.h +++ b/src/ui/BuildButtonGrid.h @@ -5,28 +5,30 @@ #include +#include "BuilderModeExitedEvent.h" #include "BuildingType.h" +#include "DemolishModeChangedEvent.h" +#include "EventHandler.h" #include "GameConfig.h" class QPushButton; -class BuildButtonGrid : public QWidget +class BuildButtonGrid : public QWidget, + public CombinedEventHandler { Q_OBJECT public: BuildButtonGrid(const GameConfig* config, QWidget* parent = nullptr); + ~BuildButtonGrid() override; void updateAffordability(int buildingBlocks); void clearActiveButton(); -signals: - void buildingTypeSelected(BuildingType type); - void builderModeExited(); - void demolishModeToggleRequested(); - -public slots: - void setDemolishModeActive(bool active); +private: + void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; private slots: void onBuildButton(int index); diff --git a/src/ui/GameWorldView.cpp b/src/ui/GameWorldView.cpp index 63ea508..485b7a1 100644 --- a/src/ui/GameWorldView.cpp +++ b/src/ui/GameWorldView.cpp @@ -20,14 +20,17 @@ #include "BeltSystem.h" #include "Building.h" #include "BuildingSystem.h" +#include "DemolishModeChangedEvent.h" #include "EntityHitTest.h" #include "EntitySelectedEvent.h" #include "EventManager.h" #include "FacingComponent.h" #include "FactionComponent.h" +#include "GameOverEvent.h" #include "HealthComponent.h" #include "PositionComponent.h" #include "ScrapSystem.h" +#include "SelectionChangedEvent.h" #include "SensorRangeComponent.h" #include "ShipIdentityComponent.h" #include "ShipSystem.h" @@ -35,8 +38,11 @@ #include "StationBodyComponent.h" #include "SurfaceMask.h" #include "Tick.h" +#include "EscapeMenuRequestedEvent.h" #include "TracePrintRequestedEvent.h" #include "BossWaveUpdatedEvent.h" +#include "BuilderModeExitedEvent.h" +#include "BlueprintModeExitedEvent.h" #include "BuildingBlocksChangedEvent.h" #include "GameSpeedChangedEvent.h" #include "SchematicChoicesAvailableEvent.h" @@ -115,6 +121,13 @@ GameWorldView::GameWorldView(Simulation* sim, const GameConfig* config, connect(m_renderTimer, &QTimer::timeout, this, &GameWorldView::onFrame); m_renderTimer->start(); m_frameTimer.start(); + + registerForEvents(); +} + +GameWorldView::~GameWorldView() +{ + unregisterForEvents(); } void GameWorldView::initializeGL() @@ -137,31 +150,13 @@ void GameWorldView::onFrame() } } - // Drain fire events → active beams + // Emit fire events via EventManager { - const std::vector fires = m_sim->drainFireEvents(); - for (const FireEvent& fe : fires) + const std::vector fires = m_sim->drainWeaponFiredEvents(); + for (const WeaponFiredEvent& fe : fires) { - float maxRadius = 0.125f; - if (m_sim->admin().isValid(fe.target) - && m_sim->admin().hasAll(fe.target)) - { - const StationBodyComponent& sb = m_sim->admin().get(fe.target); - const int shorter = std::min(sb.footprint.width(), sb.footprint.height()); - maxRadius = shorter / 2.0f; - } - - std::uniform_real_distribution angleDist(0.0f, 6.28318530f); - std::uniform_real_distribution radiusDist(0.0f, maxRadius); - const float angle = angleDist(m_rng); - const float radius = radiusDist(m_rng); - - ActiveBeam beam; - beam.event = fe; - beam.emittedWallMs = m_wallMs; - beam.targetOffset = QVector2D(radius * std::cos(angle), - radius * std::sin(angle)); - m_activeBeams.push_back(beam); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(fe)); } } @@ -232,7 +227,8 @@ void GameWorldView::onFrame() { m_gameOverShown = true; m_gameSpeedMultiplier = 0.0; - emit gameOver(); + EventManager::getInstance()->sendEventImmediately( + std::make_shared()); } update(); @@ -1140,7 +1136,8 @@ void GameWorldView::keyPressEvent(QKeyEvent* event) } break; case Qt::Key_Escape: - emit escapeMenuRequested(); + EventManager::getInstance()->sendEventImmediately( + std::make_shared()); break; case Qt::Key_Backspace: toggleDemolishMode(); @@ -1227,7 +1224,8 @@ void GameWorldView::mousePressEvent(QMouseEvent* event) if (hitEntity != entt::null) { m_selectedBuildingIds.clear(); - emit selectionChanged(m_selectedBuildingIds); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(m_selectedBuildingIds)); m_selectedEntity = hitEntity; EventManager::getInstance()->sendEventImmediately( std::make_shared(hitEntity)); @@ -1264,14 +1262,16 @@ void GameWorldView::mousePressEvent(QMouseEvent* event) { m_selectedBuildingIds = { id }; } - emit selectionChanged(m_selectedBuildingIds); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(m_selectedBuildingIds)); } else { if (!(event->modifiers() & Qt::ControlModifier)) { m_selectedBuildingIds.clear(); - emit selectionChanged(m_selectedBuildingIds); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(m_selectedBuildingIds)); } m_boxSelecting = true; m_boxStartTile = tile; @@ -1370,12 +1370,13 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event) if (!found) { m_selectedBuildingIds.push_back(id); } } } - emit selectionChanged(m_selectedBuildingIds); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(m_selectedBuildingIds)); } } // --------------------------------------------------------------------------- -// Slots +// Methods (formerly slots) // --------------------------------------------------------------------------- void GameWorldView::toggleDemolishMode() @@ -1391,7 +1392,8 @@ void GameWorldView::toggleDemolishMode() if (m_blueprintMode.has_value()) { exitBlueprintMode(); } m_demolishMode = true; } - emit demolishModeChanged(m_demolishMode); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(m_demolishMode)); } void GameWorldView::enterBuilderMode(BuildingType type) @@ -1401,14 +1403,16 @@ void GameWorldView::enterBuilderMode(BuildingType type) m_ghostValid = false; m_demolishMode = false; m_blueprintMode.reset(); - emit demolishModeChanged(false); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(false)); } void GameWorldView::enterBlueprintMode(Blueprint blueprint) { if (m_builderType.has_value()) { exitBuilderMode(); } m_demolishMode = false; - emit demolishModeChanged(false); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(false)); m_blueprintGhostTile = m_ghostTile; m_blueprintMode = std::move(blueprint); } @@ -1416,7 +1420,8 @@ void GameWorldView::enterBlueprintMode(Blueprint blueprint) void GameWorldView::exitBlueprintMode() { m_blueprintMode.reset(); - emit blueprintModeExited(); + EventManager::getInstance()->sendEventImmediately( + std::make_shared()); } void GameWorldView::exitBuilderMode() @@ -1424,7 +1429,8 @@ void GameWorldView::exitBuilderMode() m_builderType.reset(); m_beltDragTiles.clear(); m_dragging = false; - emit builderModeExited(); + EventManager::getInstance()->sendEventImmediately( + std::make_shared()); } double GameWorldView::gameSpeed() const @@ -1454,7 +1460,8 @@ void GameWorldView::resetForNewGame() m_ghostValid = false; m_demolishMode = false; m_demolishHoverBuildingId = kInvalidBuildingId; - emit demolishModeChanged(false); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(false)); m_selectedBuildingIds.clear(); m_boxSelecting = false; m_scrollXTiles = 0.0f; @@ -1466,7 +1473,66 @@ void GameWorldView::resetForNewGame() m_lastBlocks = -1; m_lastBossCounter = -1; m_lastBossCountdown = Tick(-1); - emit selectionChanged({}); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(std::vector{})); setGameSpeed(1.0); update(); } + +// --------------------------------------------------------------------------- +// Event handlers +// --------------------------------------------------------------------------- + +void GameWorldView::handleEvent(std::shared_ptr event) +{ + float maxRadius = 0.125f; + if (m_sim->admin().isValid(event->target) + && m_sim->admin().hasAll(event->target)) + { + const StationBodyComponent& sb = m_sim->admin().get(event->target); + const int shorter = std::min(sb.footprint.width(), sb.footprint.height()); + maxRadius = shorter / 2.0f; + } + + std::uniform_real_distribution angleDist(0.0f, 6.28318530f); + std::uniform_real_distribution radiusDist(0.0f, maxRadius); + const float angle = angleDist(m_rng); + const float radius = radiusDist(m_rng); + + ActiveBeam beam; + beam.event = *event; + beam.emittedWallMs = m_wallMs; + beam.targetOffset = QVector2D(radius * std::cos(angle), + radius * std::sin(angle)); + m_activeBeams.push_back(beam); +} + +void GameWorldView::handleEvent(std::shared_ptr event) +{ + enterBuilderMode(event->type); +} + +void GameWorldView::handleEvent(std::shared_ptr /*event*/) +{ + exitBuilderMode(); +} + +void GameWorldView::handleEvent(std::shared_ptr /*event*/) +{ + toggleDemolishMode(); +} + +void GameWorldView::handleEvent(std::shared_ptr event) +{ + enterBlueprintMode(event->blueprint); +} + +void GameWorldView::handleEvent(std::shared_ptr /*event*/) +{ + exitBlueprintMode(); +} + +void GameWorldView::handleEvent(std::shared_ptr event) +{ + setGameSpeed(event->multiplier); +} diff --git a/src/ui/GameWorldView.h b/src/ui/GameWorldView.h index 814659d..c321ac9 100644 --- a/src/ui/GameWorldView.h +++ b/src/ui/GameWorldView.h @@ -13,10 +13,20 @@ #include #include "Blueprint.h" -#include "SchematicChoiceOption.h" +#include "BlueprintModeExitedEvent.h" +#include "BlueprintPlacementRequestedEvent.h" +#include "BuilderModeExitedEvent.h" #include "BuildingType.h" +#include "BuildingTypeSelectedEvent.h" #include "BuildingId.h" -#include "FireEvent.h" +#include "DemolishModeChangedEvent.h" +#include "DemolishModeToggleRequestedEvent.h" +#include "EventHandler.h" +#include "ExitBlueprintModeRequestedEvent.h" +#include "ExitBuilderModeRequestedEvent.h" +#include "WeaponFiredEvent.h" +#include "SchematicChoiceOption.h" +#include "SpeedChangeRequestedEvent.h" #include "entt/entity/entity.hpp" #include "EntitySelectedEvent.h" @@ -38,32 +48,24 @@ struct QPointCompare } }; -class GameWorldView : public QOpenGLWidget +class GameWorldView : public QOpenGLWidget, + public CombinedEventHandler { Q_OBJECT public: GameWorldView(Simulation* sim, const GameConfig* config, const VisualsConfig* visuals, QWidget* parent = nullptr); + ~GameWorldView() override; -signals: - void selectionChanged(const std::vector& ids); - void gameOver(); - void builderModeExited(); - void blueprintModeExited(); - void escapeMenuRequested(); - void demolishModeChanged(bool active); - -public: double gameSpeed() const; void resetFrameTimer(); - -public slots: - void enterBuilderMode(BuildingType type); - void exitBuilderMode(); - void enterBlueprintMode(Blueprint blueprint); - void exitBlueprintMode(); - void toggleDemolishMode(); void setGameSpeed(double multiplier); void resetForNewGame(); @@ -80,6 +82,14 @@ private slots: void onFrame(); private: + void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; + void drawTiles(QPainter& painter); void drawBuildings(QPainter& painter); void drawStations(QPainter& painter); @@ -119,9 +129,15 @@ private: void stepSpeed(int delta); void placeAtTile(QPoint tile); + void enterBuilderMode(BuildingType type); + void exitBuilderMode(); + void enterBlueprintMode(Blueprint blueprint); + void exitBlueprintMode(); + void toggleDemolishMode(); + struct ActiveBeam { - FireEvent event; + WeaponFiredEvent event; qint64 emittedWallMs; QVector2D targetOffset; }; diff --git a/src/ui/HeaderBar.cpp b/src/ui/HeaderBar.cpp index 35dcde3..83cf533 100644 --- a/src/ui/HeaderBar.cpp +++ b/src/ui/HeaderBar.cpp @@ -8,6 +8,8 @@ #include #include +#include "EventManager.h" +#include "SpeedChangeRequestedEvent.h" #include "Tick.h" const double HeaderBar::kSpeeds[] = { 0.0, 0.5, 1.0, 2.0, 10.0 }; @@ -90,6 +92,7 @@ void HeaderBar::onSpeedButton(int index) { if (index >= 0 && index < kSpeedCount) { - emit speedChanged(kSpeeds[index]); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(kSpeeds[index])); } } diff --git a/src/ui/HeaderBar.h b/src/ui/HeaderBar.h index 6bcb1fe..5d5e731 100644 --- a/src/ui/HeaderBar.h +++ b/src/ui/HeaderBar.h @@ -26,9 +26,6 @@ public: explicit HeaderBar(QWidget* parent = nullptr); ~HeaderBar() override; -signals: - void speedChanged(double multiplier); - private slots: void onSpeedButton(int index); diff --git a/src/ui/MainWindow.cpp b/src/ui/MainWindow.cpp index b5bf59b..7173aa3 100644 --- a/src/ui/MainWindow.cpp +++ b/src/ui/MainWindow.cpp @@ -53,52 +53,6 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir, QWidget* p bottomLayout->addWidget(m_buildButtonGrid, 1); bottomLayout->addWidget(m_blueprintPanel, 1); - // Signals: game world → other panels - connect(m_gameWorldView, &GameWorldView::selectionChanged, - m_selectedBuildingPanel, &SelectedBuildingPanel::onSelectionChanged); - - connect(m_gameWorldView, &GameWorldView::gameOver, - this, &MainWindow::onGameOver); - - connect(m_gameWorldView, &GameWorldView::escapeMenuRequested, - this, &MainWindow::onEscapeMenuRequested); - - connect(m_selectedBuildingPanel, &SelectedBuildingPanel::layoutDialogRequested, - this, &MainWindow::onLayoutDialogRequested); - - // Signals: build grid → game world - connect(m_buildButtonGrid, &BuildButtonGrid::buildingTypeSelected, - m_gameWorldView, &GameWorldView::enterBuilderMode); - - connect(m_buildButtonGrid, &BuildButtonGrid::builderModeExited, - m_gameWorldView, &GameWorldView::exitBuilderMode); - - connect(m_gameWorldView, &GameWorldView::builderModeExited, - m_buildButtonGrid, &BuildButtonGrid::clearActiveButton); - - connect(m_buildButtonGrid, &BuildButtonGrid::demolishModeToggleRequested, - m_gameWorldView, &GameWorldView::toggleDemolishMode); - - connect(m_gameWorldView, &GameWorldView::demolishModeChanged, - m_buildButtonGrid, &BuildButtonGrid::setDemolishModeActive); - - // Signals: blueprint panel ↔ game world - connect(m_gameWorldView, &GameWorldView::selectionChanged, - m_blueprintPanel, &BlueprintPanel::onSelectionChanged); - - connect(m_blueprintPanel, &BlueprintPanel::blueprintPlacementRequested, - m_gameWorldView, &GameWorldView::enterBlueprintMode); - - connect(m_blueprintPanel, &BlueprintPanel::exitBlueprintModeRequested, - m_gameWorldView, &GameWorldView::exitBlueprintMode); - - connect(m_gameWorldView, &GameWorldView::blueprintModeExited, - m_blueprintPanel, &BlueprintPanel::clearActiveBlueprintButton); - - // Signals: header bar → game world - connect(m_headerBar, &HeaderBar::speedChanged, - m_gameWorldView, &GameWorldView::setGameSpeed); - m_gameWorldView->setFocus(); connect(qApp, &QApplication::focusChanged, this, [this](QWidget*, QWidget* newWidget) { @@ -191,7 +145,7 @@ void MainWindow::handleEvent(std::shared_ptrresetFrameTimer(); } -void MainWindow::onEscapeMenuRequested() +void MainWindow::handleEvent(std::shared_ptr /*event*/) { const double prevSpeed = m_gameWorldView->gameSpeed(); m_gameWorldView->setGameSpeed(0.0); @@ -235,12 +189,12 @@ void MainWindow::onEscapeMenuRequested() } } -void MainWindow::onLayoutDialogRequested(BuildingId shipyardId) +void MainWindow::handleEvent(std::shared_ptr event) { const double prevSpeed = m_gameWorldView->gameSpeed(); m_gameWorldView->setGameSpeed(0.0); - const Building* b = m_sim->buildings().findBuilding(shipyardId); + const Building* b = m_sim->buildings().findBuilding(event->shipyardId); if (!b) { m_gameWorldView->setGameSpeed(prevSpeed); @@ -272,14 +226,14 @@ void MainWindow::onLayoutDialogRequested(BuildingId shipyardId) this); if (dialog.exec() == QDialog::Accepted && dialog.result().has_value()) { - m_sim->buildings().setShipLayout(shipyardId, *dialog.result()); + m_sim->buildings().setShipLayout(event->shipyardId, *dialog.result()); } m_gameWorldView->setGameSpeed(prevSpeed); m_gameWorldView->resetFrameTimer(); } -void MainWindow::onGameOver() +void MainWindow::handleEvent(std::shared_ptr /*event*/) { const Tick tick = m_sim->currentTick(); const int totalSeconds = static_cast(ticksToSeconds(tick)); diff --git a/src/ui/MainWindow.h b/src/ui/MainWindow.h index 9351ddd..791a54f 100644 --- a/src/ui/MainWindow.h +++ b/src/ui/MainWindow.h @@ -7,7 +7,10 @@ #include "BuildingBlocksChangedEvent.h" #include "BuildingId.h" +#include "EscapeMenuRequestedEvent.h" #include "EventHandler.h" +#include "GameOverEvent.h" +#include "LayoutDialogRequestedEvent.h" #include "SchematicChoicesAvailableEvent.h" #include "ShipLayoutBlueprint.h" #include "Tick.h" @@ -24,7 +27,10 @@ class QResizeEvent; class MainWindow : public QWidget, public CombinedEventHandler + SchematicChoicesAvailableEvent, + GameOverEvent, + EscapeMenuRequestedEvent, + LayoutDialogRequestedEvent> { Q_OBJECT @@ -39,13 +45,11 @@ protected: private: void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; void layoutPanels(); -private slots: - void onGameOver(); - void onEscapeMenuRequested(); - void onLayoutDialogRequested(BuildingId shipyardId); - private: std::string m_configDir; VisualsConfig m_visuals; diff --git a/src/ui/SelectedBuildingPanel.cpp b/src/ui/SelectedBuildingPanel.cpp index 404e163..cc91422 100644 --- a/src/ui/SelectedBuildingPanel.cpp +++ b/src/ui/SelectedBuildingPanel.cpp @@ -16,6 +16,7 @@ #include "DynamicBodyComponent.h" #include "EntityAdmin.h" #include "EntitySelectedEvent.h" +#include "EventManager.h" #include "FactionComponent.h" #include "HealthComponent.h" #include "ModuleOwnerComponent.h" @@ -28,6 +29,7 @@ #include "BuildingSystem.h" #include "BuildingType.h" #include "ItemType.h" +#include "LayoutDialogRequestedEvent.h" #include "ModulesConfig.h" #include "Rotation.h" #include "ShipLayoutPreview.h" @@ -139,7 +141,8 @@ SelectedBuildingPanel::SelectedBuildingPanel(Simulation* sim, connect(m_configureLayoutBtn, &QPushButton::clicked, this, [this]() { if (m_singleBuildingId != kInvalidBuildingId) { - emit layoutDialogRequested(m_singleBuildingId); + EventManager::getInstance()->sendEventImmediately( + std::make_shared(m_singleBuildingId)); } }); connect(m_filterAList, &QListWidget::itemChanged, @@ -861,3 +864,8 @@ void SelectedBuildingPanel::clearEntityDisplay() m_entityStatsPanel->hide(); m_stationStatsLabel->hide(); } + +void SelectedBuildingPanel::handleEvent(std::shared_ptr event) +{ + onSelectionChanged(event->ids); +} diff --git a/src/ui/SelectedBuildingPanel.h b/src/ui/SelectedBuildingPanel.h index 133dcb3..5eecfe3 100644 --- a/src/ui/SelectedBuildingPanel.h +++ b/src/ui/SelectedBuildingPanel.h @@ -15,6 +15,7 @@ #include "EventHandler.h" #include "GameConfig.h" #include "RecipesConfig.h" +#include "SelectionChangedEvent.h" #include "ShipLayout.h" #include "ShipsConfig.h" #include "Tick.h" @@ -30,7 +31,9 @@ class QPushButton; class QVBoxLayout; class SelectedBuildingPanel : public QWidget, - public CombinedEventHandler + public CombinedEventHandler { Q_OBJECT @@ -39,15 +42,10 @@ public: QWidget* parent = nullptr); ~SelectedBuildingPanel() override; -signals: - void layoutDialogRequested(BuildingId shipyardId); - -public slots: - void onSelectionChanged(const std::vector& ids); - private: void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; private slots: void onRecipeChanged(int comboIndex); @@ -55,6 +53,7 @@ private slots: void onSplitterFilterChanged(); private: + void onSelectionChanged(const std::vector& ids); void rebuild(); void clearContent(); void buildEmpty();