add boss wave counter and countdown to the title bar

This commit is contained in:
2026-06-03 22:44:56 +02:00
parent b5185b0906
commit 15d8fa4f2c
9 changed files with 54 additions and 6 deletions

View File

@@ -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 #<x>` and the countdown as `Next boss: <M:SS>`, where `<M:SS>` 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).

View File

@@ -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<std::string, SchematicState>::const_iterator it =

View File

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

View File

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

View File

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

View File

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

View File

@@ -47,7 +47,7 @@ public:
signals:
void selectionChanged(const std::vector<BuildingId>& 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();

View File

@@ -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<int>(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<int>(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);

View File

@@ -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<QPushButton*> m_speedButtons;
static const double kSpeeds[];