diff --git a/docs/requirements.md b/docs/requirements.md index 3381b7d..fb9eb6d 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -295,7 +295,8 @@ The screen is divided into three vertical sections: +-----------------+-----------------+--------------+ ``` -- REQ-UI-HEADER: The header bar spans the full width above the game world and always shows the elapsed survival time and the current global building blocks stock on the left, and game speed controls on the right. +- REQ-UI-HEADER: The header bar spans the full width above the game world and always shows the elapsed survival time and the current global building blocks stock on the left, the boss wave counter and boss countdown (REQ-UI-BOSS-STATUS) to the left of the speed buttons, and game speed controls on the right. +- REQ-UI-BOSS-STATUS: The header bar displays, to the left of the speed buttons, the current boss wave counter (REQ-WAV-BOSS-COUNTER) and the time remaining on the boss countdown (REQ-WAV-BOSS-COUNTDOWN). The boss wave counter is shown as `Boss Wave #` and the countdown as `Next boss: `, where `` is the remaining seconds formatted as whole minutes and two-digit seconds. Both values update continuously as the simulation runs. - REQ-UI-SPEED: The game speed controls in the header bar are buttons for 0×, 0.5×, 1×, 2×, and 4× speed. The currently active speed is shown as selected. All game simulation (production, movement, threat accumulation, wave timing) scales with the selected speed. 0× pauses the game. - REQ-UI-WORLD-HEIGHT: The game world view occupies 70% of the remaining screen height below the header bar. - REQ-UI-PANEL-HEIGHT: The UI panel occupies the remaining 30% of the screen height, split horizontally into a selected building panel (left), a build button grid (center), and a blueprint panel (right). diff --git a/src/lib/sim/Simulation.cpp b/src/lib/sim/Simulation.cpp index 805382c..37506f1 100644 --- a/src/lib/sim/Simulation.cpp +++ b/src/lib/sim/Simulation.cpp @@ -532,6 +532,16 @@ double Simulation::threatLevel() const return m_waveSystem->threatLevel(); } +int Simulation::bossWaveCounter() const +{ + return m_waveSystem->bossWaveCounter(); +} + +Tick Simulation::bossCountdownTicks() const +{ + return m_waveSystem->bossCountdownTicks(); +} + int Simulation::schematicLevel(const std::string& shipId) const { const std::map::const_iterator it = diff --git a/src/lib/sim/Simulation.h b/src/lib/sim/Simulation.h index 53f19c8..0475986 100644 --- a/src/lib/sim/Simulation.h +++ b/src/lib/sim/Simulation.h @@ -57,6 +57,8 @@ public: int buildingBlocksStock() const; bool isGameOver() const; double threatLevel() const; + int bossWaveCounter() const; + Tick bossCountdownTicks() const; // Schematic state queries. int schematicLevel(const std::string& shipId) const; diff --git a/src/lib/sim/WaveSystem.cpp b/src/lib/sim/WaveSystem.cpp index 962c425..811e20c 100644 --- a/src/lib/sim/WaveSystem.cpp +++ b/src/lib/sim/WaveSystem.cpp @@ -100,6 +100,16 @@ int WaveSystem::generation() const return m_generation; } +int WaveSystem::bossWaveCounter() const +{ + return m_bossWaveCounter; +} + +Tick WaveSystem::bossCountdownTicks() const +{ + return m_bossCountdownTicks; +} + // --------------------------------------------------------------------------- // Private helpers // --------------------------------------------------------------------------- diff --git a/src/lib/sim/WaveSystem.h b/src/lib/sim/WaveSystem.h index 468ff8a..8e23b27 100644 --- a/src/lib/sim/WaveSystem.h +++ b/src/lib/sim/WaveSystem.h @@ -40,6 +40,12 @@ public: // incremented by 1 after each push — REQ-PSH-STATION-STATS). int generation() const; + // Boss wave counter (REQ-WAV-BOSS-COUNTER): current cycle number, starts at 1. + int bossWaveCounter() const; + + // Ticks remaining until the next boss wave fires (REQ-WAV-BOSS-COUNTDOWN). + Tick bossCountdownTicks() const; + private: struct SpawnEntry { diff --git a/src/ui/GameWorldView.cpp b/src/ui/GameWorldView.cpp index 1beacd4..b1bf47d 100644 --- a/src/ui/GameWorldView.cpp +++ b/src/ui/GameWorldView.cpp @@ -240,7 +240,9 @@ void GameWorldView::onFrame() // Emit state update for header bar / build grid emit stateUpdated(m_sim->currentTick(), m_sim->buildingBlocksStock(), - m_gameSpeedMultiplier); + m_gameSpeedMultiplier, + m_sim->bossWaveCounter(), + m_sim->bossCountdownTicks()); // Game over check if (m_sim->isGameOver() && !m_gameOverShown) @@ -1390,7 +1392,9 @@ void GameWorldView::setGameSpeed(double multiplier) m_gameSpeedMultiplier = multiplier; emit stateUpdated(m_sim->currentTick(), m_sim->buildingBlocksStock(), - m_gameSpeedMultiplier); + m_gameSpeedMultiplier, + m_sim->bossWaveCounter(), + m_sim->bossCountdownTicks()); } void GameWorldView::resetForNewGame() diff --git a/src/ui/GameWorldView.h b/src/ui/GameWorldView.h index 208d818..a76501c 100644 --- a/src/ui/GameWorldView.h +++ b/src/ui/GameWorldView.h @@ -47,7 +47,7 @@ public: signals: void selectionChanged(const std::vector& ids); - void stateUpdated(Tick tick, int blocks, double speed); + void stateUpdated(Tick tick, int blocks, double speed, int bossCounter, Tick bossCountdownTicks); void gameOver(); void builderModeExited(); void blueprintModeExited(); diff --git a/src/ui/HeaderBar.cpp b/src/ui/HeaderBar.cpp index 9ad7fd0..0cd37bd 100644 --- a/src/ui/HeaderBar.cpp +++ b/src/ui/HeaderBar.cpp @@ -22,9 +22,11 @@ HeaderBar::HeaderBar(QWidget* parent) m_timeLabel = new QLabel("00:00", this); m_blocksLabel = new QLabel("Blocks: 0", this); + m_bossLabel = new QLabel("Boss Wave #1 Next boss: 5:00", this); layout->addWidget(m_timeLabel); layout->addWidget(m_blocksLabel); layout->addStretch(); + layout->addWidget(m_bossLabel); const char* labels[] = { "0x", "0.5x", "1x", "2x", "4x" }; QSignalMapper* mapper = new QSignalMapper(this); @@ -43,7 +45,8 @@ HeaderBar::HeaderBar(QWidget* parent) setFixedHeight(sizeHint().height()); } -void HeaderBar::onStateUpdated(Tick tick, int buildingBlocks, double gameSpeed) +void HeaderBar::onStateUpdated(Tick tick, int buildingBlocks, double gameSpeed, + int bossCounter, Tick bossCountdownTicks) { const int totalSeconds = static_cast(ticksToSeconds(tick)); const int minutes = totalSeconds / 60; @@ -56,6 +59,16 @@ void HeaderBar::onStateUpdated(Tick tick, int buildingBlocks, double gameSpeed) m_blocksLabel->setText(QString("Blocks: %1").arg(buildingBlocks)); + const int bossSeconds = static_cast(ticksToSeconds( + bossCountdownTicks > 0 ? bossCountdownTicks : 0)); + const int bossMin = bossSeconds / 60; + const int bossSec = bossSeconds % 60; + m_bossLabel->setText( + QString("Boss Wave #%1 Next boss: %2:%3") + .arg(bossCounter) + .arg(bossMin) + .arg(bossSec, 2, 10, QChar('0'))); + for (int i = 0; i < kSpeedCount; ++i) { const bool active = (std::abs(kSpeeds[i] - gameSpeed) < 0.001); diff --git a/src/ui/HeaderBar.h b/src/ui/HeaderBar.h index d4261f9..0a991c4 100644 --- a/src/ui/HeaderBar.h +++ b/src/ui/HeaderBar.h @@ -17,7 +17,8 @@ public: explicit HeaderBar(QWidget* parent = nullptr); public slots: - void onStateUpdated(Tick tick, int buildingBlocks, double gameSpeed); + void onStateUpdated(Tick tick, int buildingBlocks, double gameSpeed, + int bossCounter, Tick bossCountdownTicks); signals: void speedChanged(double multiplier); @@ -28,6 +29,7 @@ private slots: private: QLabel* m_timeLabel; QLabel* m_blocksLabel; + QLabel* m_bossLabel; std::vector m_speedButtons; static const double kSpeeds[];