switch to using own event system

This commit is contained in:
2026-06-13 17:42:16 +02:00
parent ed17664ef1
commit 5317f35198
49 changed files with 611 additions and 300 deletions

View File

@@ -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<SchematicChoiceOption>`. 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<FireEvent>` 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<Event>)` — synchronous dispatch to all handlers of the event's type.
- `addEvent(shared_ptr<Event>)` — 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<T>` template, still owned by the sim.
### EventHandler
`EventHandler<T>` 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<const T>)`.
`CombinedEventHandler<Ts...>` 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<WeaponFiredEvent>` 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.

View File

@@ -259,9 +259,9 @@ void ArenaSimulation::tick()
m_aiSystem->tickSalvageBehavior(m_admin, *m_scrapSystem, *m_buildingSystem);
// Combat resolution (tick step 8).
std::vector<FireEvent> fireEvents;
m_combatSystem->tick(m_currentTick, m_admin, *m_buildingSystem, fireEvents);
m_fireEvents.insert(m_fireEvents.end(), fireEvents.begin(), fireEvents.end());
std::vector<WeaponFiredEvent> 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<FireEvent> ArenaSimulation::drainFireEvents()
std::vector<WeaponFiredEvent> ArenaSimulation::drainWeaponFiredEvents()
{
std::vector<FireEvent> result;
result.swap(m_fireEvents);
std::vector<WeaponFiredEvent> result;
result.swap(m_weaponFiredEvents);
return result;
}

View File

@@ -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<FireEvent> drainFireEvents();
std::vector<WeaponFiredEvent> drainWeaponFiredEvents();
ArenaStatus status() const;
bool isFinished() const;
@@ -104,7 +104,7 @@ private:
int m_winnerTeam;
std::atomic<bool> m_stopRequested;
std::vector<FireEvent> m_fireEvents;
std::vector<WeaponFiredEvent> m_weaponFiredEvents;
mutable std::mutex m_statusMutex;
ArenaStatus m_status;

View File

@@ -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<GameSpeedChangedEvent>(multiplier));
}
double ArenaView::gameSpeed() const
@@ -93,34 +102,17 @@ void ArenaView::onFrame()
}
}
// Emit fire events via EventManager
{
const std::vector<FireEvent> fires = m_sim->drainFireEvents();
for (const FireEvent& fe : fires)
const std::vector<WeaponFiredEvent> fires = m_sim->drainWeaponFiredEvents();
for (const WeaponFiredEvent& fe : fires)
{
float maxRadius = 0.125f;
if (m_sim->admin().isValid(fe.target)
&& m_sim->admin().hasAll<StationBodyComponent>(fe.target))
{
const StationBodyComponent& sb = m_sim->admin().get<StationBodyComponent>(fe.target);
const int shorter = std::min(sb.footprint.width(),
sb.footprint.height());
maxRadius = shorter / 2.0f;
}
std::uniform_real_distribution<float> angleDist(0.0f, 6.28318530f);
std::uniform_real_distribution<float> 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<WeaponFiredEvent>(fe));
}
}
// Expire old beams
{
std::vector<ActiveBeam> 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<const WeaponFiredEvent> event)
{
float maxRadius = 0.125f;
if (m_sim->admin().isValid(event->target)
&& m_sim->admin().hasAll<StationBodyComponent>(event->target))
{
const StationBodyComponent& sb = m_sim->admin().get<StationBodyComponent>(event->target);
const int shorter = std::min(sb.footprint.width(),
sb.footprint.height());
maxRadius = shorter / 2.0f;
}
std::uniform_real_distribution<float> angleDist(0.0f, 6.28318530f);
std::uniform_real_distribution<float> 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));
}
}

View File

@@ -9,7 +9,8 @@
#include <QTimer>
#include <QVector2D>
#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<WeaponFiredEvent>
{
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<const WeaponFiredEvent> 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;
};

View File

@@ -3,8 +3,13 @@
#include <QHBoxLayout>
#include <QVBoxLayout>
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<ArenaInspectRequestedEvent>(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<ArenaStartRequestedEvent>(m_arenaIndex));
});
titleRow->addWidget(m_startButton);
outerLayout->addLayout(titleRow);

View File

@@ -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;

View File

@@ -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<ArenaSimulation>(
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<const ArenaStartRequestedEvent> event)
{
startArena(event->arenaIndex);
}
void BalancingWindow::handleEvent(std::shared_ptr<const ArenaInspectRequestedEvent> event)
{
inspectArena(event->arenaIndex);
}
void BalancingWindow::handleEvent(std::shared_ptr<const InspectWindowClosedEvent> /*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;

View File

@@ -10,15 +10,22 @@
#include <QTimer>
#include <QWidget>
#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<ArenaStartRequestedEvent,
ArenaInspectRequestedEvent,
InspectWindowClosedEvent>
{
Q_OBJECT
@@ -30,15 +37,20 @@ public:
QWidget* parent = nullptr);
~BalancingWindow() override;
private:
void handleEvent(std::shared_ptr<const ArenaStartRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const ArenaInspectRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const InspectWindowClosedEvent> 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();

View File

@@ -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<InspectWindowClosedEvent>());
event->accept();
}
@@ -176,11 +176,11 @@ void InspectWindow::onSpeedButton(int index)
}
}
void InspectWindow::onSpeedChanged(double multiplier)
void InspectWindow::handleEvent(std::shared_ptr<const GameSpeedChangedEvent> 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<std::size_t>(i)]->setChecked(active);
}
}

View File

@@ -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<EntitySelectedEvent>
public CombinedEventHandler<EntitySelectedEvent,
GameSpeedChangedEvent>
{
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<const EntitySelectedEvent> event) override;
void handleEvent(std::shared_ptr<const GameSpeedChangedEvent> event) override;
private slots:
void onSpeedButton(int index);
void onSpeedChanged(double multiplier);
void pollStatus();
private:

View File

@@ -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

View File

@@ -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;
};

View File

@@ -21,7 +21,7 @@ CombatSystem::CombatSystem(const GameConfig& config)
void CombatSystem::tick(Tick currentTick,
EntityAdmin& admin,
BuildingSystem& /*buildings*/,
std::vector<FireEvent>& outFireEvents)
std::vector<WeaponFiredEvent>& 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<PositionComponent>(owner.owner);
const FactionComponent& faction = admin.get<FactionComponent>(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<FireEvent>& out)
std::vector<WeaponFiredEvent>& 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;

View File

@@ -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<FireEvent>& outFireEvents);
std::vector<WeaponFiredEvent>& outWeaponFiredEvents);
void applyPendingDamage(Tick currentTick, EntityAdmin& admin);
@@ -47,7 +47,7 @@ private:
const FactionComponent& ownFaction,
Tick currentTick,
EntityAdmin& admin,
std::vector<FireEvent>& out);
std::vector<WeaponFiredEvent>& out);
const GameConfig& m_config;
};

View File

@@ -0,0 +1,10 @@
#pragma once
#include "Event.h"
class ArenaInspectRequestedEvent : public Event
{
public:
explicit ArenaInspectRequestedEvent(int arenaIndex) : arenaIndex(arenaIndex) {}
const int arenaIndex;
};

View File

@@ -0,0 +1,10 @@
#pragma once
#include "Event.h"
class ArenaStartRequestedEvent : public Event
{
public:
explicit ArenaStartRequestedEvent(int arenaIndex) : arenaIndex(arenaIndex) {}
const int arenaIndex;
};

View File

@@ -0,0 +1,7 @@
#pragma once
#include "Event.h"
class BlueprintModeExitedEvent : public Event
{
};

View File

@@ -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;
};

View File

@@ -0,0 +1,7 @@
#pragma once
#include "Event.h"
class BuilderModeExitedEvent : public Event
{
};

View File

@@ -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;
};

View File

@@ -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
)

View File

@@ -0,0 +1,10 @@
#pragma once
#include "Event.h"
class DemolishModeChangedEvent : public Event
{
public:
explicit DemolishModeChangedEvent(bool active) : active(active) {}
const bool active;
};

View File

@@ -0,0 +1,7 @@
#pragma once
#include "Event.h"
class DemolishModeToggleRequestedEvent : public Event
{
};

View File

@@ -0,0 +1,7 @@
#pragma once
#include "Event.h"
class EscapeMenuRequestedEvent : public Event
{
};

View File

@@ -0,0 +1,7 @@
#pragma once
#include "Event.h"
class ExitBlueprintModeRequestedEvent : public Event
{
};

View File

@@ -0,0 +1,7 @@
#pragma once
#include "Event.h"
class ExitBuilderModeRequestedEvent : public Event
{
};

View File

@@ -0,0 +1,7 @@
#pragma once
#include "Event.h"
class GameOverEvent : public Event
{
};

View File

@@ -0,0 +1,7 @@
#pragma once
#include "Event.h"
class InspectWindowClosedEvent : public Event
{
};

View File

@@ -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;
};

View File

@@ -0,0 +1,14 @@
#pragma once
#include <vector>
#include "BuildingId.h"
#include "Event.h"
class SelectionChangedEvent : public Event
{
public:
explicit SelectionChangedEvent(std::vector<BuildingId> ids)
: ids(std::move(ids)) {}
const std::vector<BuildingId> ids;
};

View File

@@ -0,0 +1,10 @@
#pragma once
#include "Event.h"
class SpeedChangeRequestedEvent : public Event
{
public:
explicit SpeedChangeRequestedEvent(double multiplier) : multiplier(multiplier) {}
const double multiplier;
};

View File

@@ -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;
};

View File

@@ -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<FireEvent> Simulation::drainFireEvents()
std::vector<WeaponFiredEvent> Simulation::drainWeaponFiredEvents()
{
std::vector<FireEvent> result;
result.swap(m_fireEvents);
std::vector<WeaponFiredEvent> result;
result.swap(m_weaponFiredEvents);
return result;
}

View File

@@ -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<FireEvent> drainFireEvents();
std::vector<WeaponFiredEvent> drainWeaponFiredEvents();
// Returns the pending schematic choices (empty if no drop is pending).
const std::vector<SchematicChoiceOption>& getPendingSchematicChoices() const;
@@ -165,6 +165,6 @@ private:
std::unique_ptr<WaveSystem> m_waveSystem;
std::unique_ptr<CombatSystem> m_combatSystem;
std::vector<FireEvent> m_fireEvents;
std::vector<WeaponFiredEvent> m_weaponFiredEvents;
std::vector<SchematicChoiceOption> m_pendingSchematicChoices;
};

View File

@@ -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<HealthComponent>(player).hp;
std::vector<FireEvent> events;
std::vector<WeaponFiredEvent> 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<WeaponComponent>(wc).cooldownTicks = 3.0f; // override to 3
}
auto enemyFiredIn = [&enemy](const std::vector<FireEvent>& evts)
auto enemyFiredIn = [&enemy](const std::vector<WeaponFiredEvent>& evts)
{
for (const FireEvent& evt : evts)
for (const WeaponFiredEvent& evt : evts)
{
if (evt.shooter == enemy) { return true; }
}
return false;
};
std::vector<FireEvent> events;
std::vector<WeaponFiredEvent> 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<FireEvent> events;
std::vector<WeaponFiredEvent> 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<FireEvent> events = sim.drainFireEvents();
const std::vector<WeaponFiredEvent> 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<FireEvent> events = sim.drainFireEvents();
const std::vector<WeaponFiredEvent> 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<FireEvent> events = sim.drainFireEvents();
const std::vector<WeaponFiredEvent> 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<HealthComponent>(player).hp;
std::vector<FireEvent> events;
std::vector<WeaponFiredEvent> 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<HealthComponent>(player).hp;
std::vector<FireEvent> events;
std::vector<WeaponFiredEvent> 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<FireEvent> events;
std::vector<WeaponFiredEvent> 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<HealthComponent>(player).hp;
std::vector<FireEvent> events;
std::vector<WeaponFiredEvent> events;
f.combat.tick(0, f.admin, f.buildings, events);
f.ships.despawn(enemy);

View File

@@ -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]")

View File

@@ -12,8 +12,11 @@
#include <QScrollArea>
#include <QVBoxLayout>
#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<BuildingId>& 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<ExitBlueprintModeRequestedEvent>());
}
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<ExitBlueprintModeRequestedEvent>());
return;
}
@@ -142,7 +147,8 @@ void BlueprintPanel::onBlueprintButtonClicked(int index)
m_activeIndex = index;
m_blueprintButtons[static_cast<std::size_t>(index)]->setChecked(true);
emit blueprintPlacementRequested(m_blueprints[static_cast<std::size_t>(index)]);
EventManager::getInstance()->sendEventImmediately(
std::make_shared<BlueprintPlacementRequestedEvent>(m_blueprints[static_cast<std::size_t>(index)]));
}
Blueprint BlueprintPanel::createBlueprintFromSelection() const
@@ -312,7 +318,8 @@ void BlueprintPanel::onLoadClicked()
if (m_activeIndex >= 0)
{
emit exitBlueprintModeRequested();
EventManager::getInstance()->sendEventImmediately(
std::make_shared<ExitBlueprintModeRequestedEvent>());
m_activeIndex = -1;
}
@@ -350,3 +357,13 @@ void BlueprintPanel::refreshButtonStates()
canAfford || m_activeIndex == i);
}
}
void BlueprintPanel::handleEvent(std::shared_ptr<const SelectionChangedEvent> event)
{
onSelectionChanged(event->ids);
}
void BlueprintPanel::handleEvent(std::shared_ptr<const BlueprintModeExitedEvent> /*event*/)
{
clearActiveBlueprintButton();
}

View File

@@ -5,10 +5,12 @@
#include <QWidget>
#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<BuildingBlocksChangedEvent>
public CombinedEventHandler<BuildingBlocksChangedEvent,
SelectionChangedEvent,
BlueprintModeExitedEvent>
{
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<BuildingId>& ids);
void clearActiveBlueprintButton();
signals:
void blueprintPlacementRequested(Blueprint blueprint);
void exitBlueprintModeRequested();
private:
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
void handleEvent(std::shared_ptr<const SelectionChangedEvent> event) override;
void handleEvent(std::shared_ptr<const BlueprintModeExitedEvent> event) override;
private slots:
void onCreateClicked();
@@ -44,6 +42,8 @@ private slots:
void onLoadClicked();
private:
void onSelectionChanged(const std::vector<BuildingId>& ids);
void clearActiveBlueprintButton();
Blueprint createBlueprintFromSelection() const;
int computeBlueprintCost(const Blueprint& bp) const;
void rebuildButtons();

View File

@@ -7,7 +7,11 @@
#include <QSignalMapper>
#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<DemolishModeToggleRequestedEvent>());
});
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<int>(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<ExitBuilderModeRequestedEvent>());
return;
}
@@ -107,5 +117,16 @@ void BuildButtonGrid::onBuildButton(int index)
m_activeIndex = index;
m_buttons[static_cast<std::size_t>(index)]->setChecked(true);
emit buildingTypeSelected(m_types[static_cast<std::size_t>(index)]);
EventManager::getInstance()->sendEventImmediately(
std::make_shared<BuildingTypeSelectedEvent>(m_types[static_cast<std::size_t>(index)]));
}
void BuildButtonGrid::handleEvent(std::shared_ptr<const BuilderModeExitedEvent> /*event*/)
{
clearActiveButton();
}
void BuildButtonGrid::handleEvent(std::shared_ptr<const DemolishModeChangedEvent> event)
{
m_demolishButton->setChecked(event->active);
}

View File

@@ -5,28 +5,30 @@
#include <QWidget>
#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<BuilderModeExitedEvent,
DemolishModeChangedEvent>
{
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<const BuilderModeExitedEvent> event) override;
void handleEvent(std::shared_ptr<const DemolishModeChangedEvent> event) override;
private slots:
void onBuildButton(int index);

View File

@@ -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<FireEvent> fires = m_sim->drainFireEvents();
for (const FireEvent& fe : fires)
const std::vector<WeaponFiredEvent> fires = m_sim->drainWeaponFiredEvents();
for (const WeaponFiredEvent& fe : fires)
{
float maxRadius = 0.125f;
if (m_sim->admin().isValid(fe.target)
&& m_sim->admin().hasAll<StationBodyComponent>(fe.target))
{
const StationBodyComponent& sb = m_sim->admin().get<StationBodyComponent>(fe.target);
const int shorter = std::min(sb.footprint.width(), sb.footprint.height());
maxRadius = shorter / 2.0f;
}
std::uniform_real_distribution<float> angleDist(0.0f, 6.28318530f);
std::uniform_real_distribution<float> 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<WeaponFiredEvent>(fe));
}
}
@@ -232,7 +227,8 @@ void GameWorldView::onFrame()
{
m_gameOverShown = true;
m_gameSpeedMultiplier = 0.0;
emit gameOver();
EventManager::getInstance()->sendEventImmediately(
std::make_shared<GameOverEvent>());
}
update();
@@ -1140,7 +1136,8 @@ void GameWorldView::keyPressEvent(QKeyEvent* event)
}
break;
case Qt::Key_Escape:
emit escapeMenuRequested();
EventManager::getInstance()->sendEventImmediately(
std::make_shared<EscapeMenuRequestedEvent>());
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<SelectionChangedEvent>(m_selectedBuildingIds));
m_selectedEntity = hitEntity;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<EntitySelectedEvent>(hitEntity));
@@ -1264,14 +1262,16 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
{
m_selectedBuildingIds = { id };
}
emit selectionChanged(m_selectedBuildingIds);
EventManager::getInstance()->sendEventImmediately(
std::make_shared<SelectionChangedEvent>(m_selectedBuildingIds));
}
else
{
if (!(event->modifiers() & Qt::ControlModifier))
{
m_selectedBuildingIds.clear();
emit selectionChanged(m_selectedBuildingIds);
EventManager::getInstance()->sendEventImmediately(
std::make_shared<SelectionChangedEvent>(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<SelectionChangedEvent>(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<DemolishModeChangedEvent>(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<DemolishModeChangedEvent>(false));
}
void GameWorldView::enterBlueprintMode(Blueprint blueprint)
{
if (m_builderType.has_value()) { exitBuilderMode(); }
m_demolishMode = false;
emit demolishModeChanged(false);
EventManager::getInstance()->sendEventImmediately(
std::make_shared<DemolishModeChangedEvent>(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<BlueprintModeExitedEvent>());
}
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<BuilderModeExitedEvent>());
}
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<DemolishModeChangedEvent>(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<SelectionChangedEvent>(std::vector<BuildingId>{}));
setGameSpeed(1.0);
update();
}
// ---------------------------------------------------------------------------
// Event handlers
// ---------------------------------------------------------------------------
void GameWorldView::handleEvent(std::shared_ptr<const WeaponFiredEvent> event)
{
float maxRadius = 0.125f;
if (m_sim->admin().isValid(event->target)
&& m_sim->admin().hasAll<StationBodyComponent>(event->target))
{
const StationBodyComponent& sb = m_sim->admin().get<StationBodyComponent>(event->target);
const int shorter = std::min(sb.footprint.width(), sb.footprint.height());
maxRadius = shorter / 2.0f;
}
std::uniform_real_distribution<float> angleDist(0.0f, 6.28318530f);
std::uniform_real_distribution<float> 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<const BuildingTypeSelectedEvent> event)
{
enterBuilderMode(event->type);
}
void GameWorldView::handleEvent(std::shared_ptr<const ExitBuilderModeRequestedEvent> /*event*/)
{
exitBuilderMode();
}
void GameWorldView::handleEvent(std::shared_ptr<const DemolishModeToggleRequestedEvent> /*event*/)
{
toggleDemolishMode();
}
void GameWorldView::handleEvent(std::shared_ptr<const BlueprintPlacementRequestedEvent> event)
{
enterBlueprintMode(event->blueprint);
}
void GameWorldView::handleEvent(std::shared_ptr<const ExitBlueprintModeRequestedEvent> /*event*/)
{
exitBlueprintMode();
}
void GameWorldView::handleEvent(std::shared_ptr<const SpeedChangeRequestedEvent> event)
{
setGameSpeed(event->multiplier);
}

View File

@@ -13,10 +13,20 @@
#include <QVector2D>
#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<WeaponFiredEvent,
BuildingTypeSelectedEvent,
ExitBuilderModeRequestedEvent,
DemolishModeToggleRequestedEvent,
BlueprintPlacementRequestedEvent,
ExitBlueprintModeRequestedEvent,
SpeedChangeRequestedEvent>
{
Q_OBJECT
public:
GameWorldView(Simulation* sim, const GameConfig* config,
const VisualsConfig* visuals, QWidget* parent = nullptr);
~GameWorldView() override;
signals:
void selectionChanged(const std::vector<BuildingId>& 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<const WeaponFiredEvent> event) override;
void handleEvent(std::shared_ptr<const BuildingTypeSelectedEvent> event) override;
void handleEvent(std::shared_ptr<const ExitBuilderModeRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const DemolishModeToggleRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const BlueprintPlacementRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const ExitBlueprintModeRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const SpeedChangeRequestedEvent> 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;
};

View File

@@ -8,6 +8,8 @@
#include <QPushButton>
#include <QSignalMapper>
#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<SpeedChangeRequestedEvent>(kSpeeds[index]));
}
}

View File

@@ -26,9 +26,6 @@ public:
explicit HeaderBar(QWidget* parent = nullptr);
~HeaderBar() override;
signals:
void speedChanged(double multiplier);
private slots:
void onSpeedButton(int index);

View File

@@ -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_ptr<const SchematicChoicesAvailableEven
m_gameWorldView->resetFrameTimer();
}
void MainWindow::onEscapeMenuRequested()
void MainWindow::handleEvent(std::shared_ptr<const EscapeMenuRequestedEvent> /*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<const LayoutDialogRequestedEvent> 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<const GameOverEvent> /*event*/)
{
const Tick tick = m_sim->currentTick();
const int totalSeconds = static_cast<int>(ticksToSeconds(tick));

View File

@@ -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<BuildingBlocksChangedEvent,
SchematicChoicesAvailableEvent>
SchematicChoicesAvailableEvent,
GameOverEvent,
EscapeMenuRequestedEvent,
LayoutDialogRequestedEvent>
{
Q_OBJECT
@@ -39,13 +45,11 @@ protected:
private:
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
void handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event) override;
void handleEvent(std::shared_ptr<const GameOverEvent> event) override;
void handleEvent(std::shared_ptr<const EscapeMenuRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const LayoutDialogRequestedEvent> event) override;
void layoutPanels();
private slots:
void onGameOver();
void onEscapeMenuRequested();
void onLayoutDialogRequested(BuildingId shipyardId);
private:
std::string m_configDir;
VisualsConfig m_visuals;

View File

@@ -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<LayoutDialogRequestedEvent>(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<const SelectionChangedEvent> event)
{
onSelectionChanged(event->ids);
}

View File

@@ -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<TickAdvancedEvent, EntitySelectedEvent>
public CombinedEventHandler<TickAdvancedEvent,
EntitySelectedEvent,
SelectionChangedEvent>
{
Q_OBJECT
@@ -39,15 +42,10 @@ public:
QWidget* parent = nullptr);
~SelectedBuildingPanel() override;
signals:
void layoutDialogRequested(BuildingId shipyardId);
public slots:
void onSelectionChanged(const std::vector<BuildingId>& ids);
private:
void handleEvent(std::shared_ptr<const TickAdvancedEvent> event) override;
void handleEvent(std::shared_ptr<const EntitySelectedEvent> event) override;
void handleEvent(std::shared_ptr<const SelectionChangedEvent> event) override;
private slots:
void onRecipeChanged(int comboIndex);
@@ -55,6 +53,7 @@ private slots:
void onSplitterFilterChanged();
private:
void onSelectionChanged(const std::vector<BuildingId>& ids);
void rebuild();
void clearContent();
void buildEmpty();