implement cost formula for asteroid expansion
This commit is contained in:
@@ -19,9 +19,7 @@ enemy_buffer_width_tiles = 20
|
|||||||
|
|
||||||
[expansion]
|
[expansion]
|
||||||
columns_per_expansion_tiles = 10
|
columns_per_expansion_tiles = 10
|
||||||
# Flat until the escalating cost formula lands (progression_design.md
|
cost_building_blocks_formula = "300 + 50*x + 10*x*x"
|
||||||
# action item 4).
|
|
||||||
cost_building_blocks = 400
|
|
||||||
|
|
||||||
[push]
|
[push]
|
||||||
push_expand_columns_tiles = 10
|
push_expand_columns_tiles = 10
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ enemy_buffer_width_tiles = 15
|
|||||||
|
|
||||||
[expansion]
|
[expansion]
|
||||||
columns_per_expansion_tiles = 10
|
columns_per_expansion_tiles = 10
|
||||||
cost_building_blocks = 200
|
cost_building_blocks_formula = "400 * 2^x"
|
||||||
|
|
||||||
[push]
|
[push]
|
||||||
push_expand_columns_tiles = 20
|
push_expand_columns_tiles = 20
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
|
|||||||
## Asteroid Expansion
|
## Asteroid Expansion
|
||||||
|
|
||||||
- REQ-EXP-UNLOCK: The player can unlock additional asteroid tile columns to the left of the existing asteroid by spending building blocks from the global stock.
|
- REQ-EXP-UNLOCK: The player can unlock additional asteroid tile columns to the left of the existing asteroid by spending building blocks from the global stock.
|
||||||
- REQ-EXP-COST: Each expansion adds `world.toml [expansion].columns_per_expansion` columns and costs `[expansion].cost_building_blocks` building blocks.
|
- REQ-EXP-COST: Each expansion adds `world.toml [expansion].columns_per_expansion` columns. The building block cost of an expansion is defined by the formula `world.toml [expansion].cost_building_blocks_formula`, where `x` is the number of expansions already purchased (0 for the first expansion, incrementing by 1 for each subsequent expansion). The formula is evaluated at purchase time and its result is floored to an integer number of building blocks.
|
||||||
|
|
||||||
## UI
|
## UI
|
||||||
|
|
||||||
@@ -392,9 +392,10 @@ The screen is divided into two columns: a main column (75% width) containing the
|
|||||||
(75% width) (25% width)
|
(75% width) (25% width)
|
||||||
```
|
```
|
||||||
|
|
||||||
- REQ-UI-HEADER: The header bar spans the width of the game world column (75% of the screen width) and always shows the elapsed survival time, the current global building blocks stock, and the artifact count (REQ-WIN-ARTIFACT-COUNT) displayed as `Artifacts: x/y` (where `x` is the current artifact count and `y` is `world.toml [world].artifact_win_count`) 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-HEADER: The header bar spans the width of the game world column (75% of the screen width) and always shows the elapsed survival time, the current global building blocks stock, and the artifact count (REQ-WIN-ARTIFACT-COUNT) displayed as `Artifacts: x/y` (where `x` is the current artifact count and `y` is `world.toml [world].artifact_win_count`) on the left, the boss wave counter and boss countdown (REQ-UI-BOSS-STATUS) and an asteroid expansion button (REQ-UI-EXPAND-BUTTON) 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-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-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-EXPAND-BUTTON: The header bar shows an asteroid expansion button captioned `Expand: <x> Blocks`, where `<x>` is the current expansion cost computed from `world.toml [expansion].cost_building_blocks_formula` at the current number of purchased expansions (REQ-EXP-COST). Clicking the button unlocks the next asteroid expansion (REQ-EXP-UNLOCK, REQ-GW-ASTEROID-EXPAND), spending that many building blocks from the global stock. The button is disabled when the player cannot currently afford the cost (consistent with REQ-UI-BUILD-DISABLED). The caption updates as the cost changes with each purchased expansion.
|
||||||
- REQ-UI-WORLD-SIZE: The game world view occupies the full height below the header bar in the main column (75% of the screen width).
|
- REQ-UI-WORLD-SIZE: The game world view occupies the full height below the header bar in the main column (75% of the screen width).
|
||||||
- REQ-UI-PANEL-COLUMN: The side panel column occupies 25% of the screen width and the full screen height. It is divided into three equal-height panels stacked top to bottom: selected building panel (top), build button grid (middle), and blueprint panel (bottom).
|
- REQ-UI-PANEL-COLUMN: The side panel column occupies 25% of the screen width and the full screen height. It is divided into three equal-height panels stacked top to bottom: selected building panel (top), build button grid (middle), and blueprint panel (bottom).
|
||||||
|
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ WorldConfig ConfigLoader::loadWorld(const std::string& path)
|
|||||||
cfg.regions.enemyBufferWidth_tiles = static_cast<int>(requireInt(tbl["regions"]["enemy_buffer_width_tiles"], file, "regions.enemy_buffer_width_tiles"));
|
cfg.regions.enemyBufferWidth_tiles = static_cast<int>(requireInt(tbl["regions"]["enemy_buffer_width_tiles"], file, "regions.enemy_buffer_width_tiles"));
|
||||||
|
|
||||||
cfg.expansion.columnsPerExpansion_tiles = static_cast<int>(requireInt(tbl["expansion"]["columns_per_expansion_tiles"], file, "expansion.columns_per_expansion_tiles"));
|
cfg.expansion.columnsPerExpansion_tiles = static_cast<int>(requireInt(tbl["expansion"]["columns_per_expansion_tiles"], file, "expansion.columns_per_expansion_tiles"));
|
||||||
cfg.expansion.costBuildingBlocks = static_cast<int>(requireInt(tbl["expansion"]["cost_building_blocks"], file, "expansion.cost_building_blocks"));
|
cfg.expansion.costBuildingBlocksFormula = requireFormula(tbl["expansion"]["cost_building_blocks_formula"], file, "expansion.cost_building_blocks_formula");
|
||||||
|
|
||||||
cfg.push.pushExpandColumns_tiles = static_cast<int>(requireInt(tbl["push"]["push_expand_columns_tiles"], file, "push.push_expand_columns_tiles"));
|
cfg.push.pushExpandColumns_tiles = static_cast<int>(requireInt(tbl["push"]["push_expand_columns_tiles"], file, "push.push_expand_columns_tiles"));
|
||||||
cfg.push.bossAdvanceSeconds = requireDouble(tbl["push"]["boss_advance_seconds"], file, "push.boss_advance_seconds");
|
cfg.push.bossAdvanceSeconds = requireDouble(tbl["push"]["boss_advance_seconds"], file, "push.boss_advance_seconds");
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ struct WorldRegions
|
|||||||
struct WorldExpansion
|
struct WorldExpansion
|
||||||
{
|
{
|
||||||
int columnsPerExpansion_tiles;
|
int columnsPerExpansion_tiles;
|
||||||
int costBuildingBlocks;
|
Formula costBuildingBlocksFormula; // cost in building blocks; x = expansions already purchased
|
||||||
};
|
};
|
||||||
|
|
||||||
// Push effects (REQ-PSH-*, REQ-WAV-BOSS-ADVANCE).
|
// Push effects (REQ-PSH-*, REQ-WAV-BOSS-ADVANCE).
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ SET(HDRS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/TracePrintRequestedEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/TracePrintRequestedEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/TickAdvancedEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/TickAdvancedEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingBlocksChangedEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildingBlocksChangedEvent.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/ExpansionCostChangedEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/EntitySelectedEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/EntitySelectedEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/GameSpeedChangedEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/GameSpeedChangedEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BossWaveUpdatedEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BossWaveUpdatedEvent.h
|
||||||
|
|||||||
17
src/lib/eventsystem/event/ExpansionCostChangedEvent.h
Normal file
17
src/lib/eventsystem/event/ExpansionCostChangedEvent.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef EXPANSION_COST_CHANGED_EVENT_H
|
||||||
|
#define EXPANSION_COST_CHANGED_EVENT_H
|
||||||
|
|
||||||
|
#include "Event.h"
|
||||||
|
|
||||||
|
// Fired when the current asteroid-expansion cost changes (REQ-EXP-COST): once at
|
||||||
|
// startup and again after each expansion is purchased. Carries the cost in
|
||||||
|
// building blocks so the header Expand button can update its caption/enabled
|
||||||
|
// state (REQ-UI-EXPAND-BUTTON).
|
||||||
|
class ExpansionCostChangedEvent : public Event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit ExpansionCostChangedEvent(int cost) : cost(cost) {}
|
||||||
|
const int cost;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // EXPANSION_COST_CHANGED_EVENT_H
|
||||||
@@ -24,6 +24,7 @@ BuildingSystem::BuildingSystem(const GameConfig& config,
|
|||||||
, m_spawnShip(std::move(spawnShip))
|
, m_spawnShip(std::move(spawnShip))
|
||||||
, m_isItemUnlocked(std::move(isItemUnlocked))
|
, m_isItemUnlocked(std::move(isItemUnlocked))
|
||||||
, m_rng(rng)
|
, m_rng(rng)
|
||||||
|
, m_asteroidWidth_tiles(config.world.regions.asteroidWidth_tiles)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +282,7 @@ bool BuildingSystem::bodyCellsWithinWorldBounds(const std::vector<QPoint>& bodyC
|
|||||||
QPoint anchor) const
|
QPoint anchor) const
|
||||||
{
|
{
|
||||||
const int heightTiles = m_config.world.heightTiles;
|
const int heightTiles = m_config.world.heightTiles;
|
||||||
const int leftEdgeX = -m_config.world.regions.asteroidWidth_tiles;
|
const int leftEdgeX = -m_asteroidWidth_tiles;
|
||||||
for (const QPoint& cell : bodyCells)
|
for (const QPoint& cell : bodyCells)
|
||||||
{
|
{
|
||||||
const QPoint worldCell = anchor + cell;
|
const QPoint worldCell = anchor + cell;
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ public:
|
|||||||
bool isPlacementValid(BuildingType type, QPoint anchor,
|
bool isPlacementValid(BuildingType type, QPoint anchor,
|
||||||
Rotation rotation) const;
|
Rotation rotation) const;
|
||||||
|
|
||||||
|
// Sets the current buildable asteroid width in tiles. Grows the left
|
||||||
|
// placement bound as the player unlocks asteroid expansions (REQ-EXP-UNLOCK).
|
||||||
|
// Defaults to world.regions.asteroid_width_tiles at construction.
|
||||||
|
void setAsteroidWidth_tiles(int widthTiles) { m_asteroidWidth_tiles = widthTiles; }
|
||||||
|
|
||||||
// Remove a building or construction site by id. Returns the refund in
|
// Remove a building or construction site by id. Returns the refund in
|
||||||
// building blocks (floor(cost * refundPercentage / 100)). Returns 0 for
|
// building blocks (floor(cost * refundPercentage / 100)). Returns 0 for
|
||||||
// unknown ids.
|
// unknown ids.
|
||||||
@@ -179,6 +184,7 @@ private:
|
|||||||
const std::optional<ShipLayoutConfig>&)> m_spawnShip;
|
const std::optional<ShipLayoutConfig>&)> m_spawnShip;
|
||||||
std::function<bool(const std::string&)> m_isItemUnlocked;
|
std::function<bool(const std::string&)> m_isItemUnlocked;
|
||||||
std::mt19937& m_rng;
|
std::mt19937& m_rng;
|
||||||
|
int m_asteroidWidth_tiles;
|
||||||
|
|
||||||
std::vector<Building> m_buildings;
|
std::vector<Building> m_buildings;
|
||||||
std::deque<ConstructionSite> m_constructionQueue;
|
std::deque<ConstructionSite> m_constructionQueue;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ enum class CommandKind
|
|||||||
SetSplitterFilters,
|
SetSplitterFilters,
|
||||||
ClearBeltTiles,
|
ClearBeltTiles,
|
||||||
ApplySchematicChoice,
|
ApplySchematicChoice,
|
||||||
|
ExpandAsteroid,
|
||||||
Reset
|
Reset
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -129,6 +130,14 @@ struct ApplySchematicChoiceCommand : Command
|
|||||||
int choiceIndex = 0;
|
int choiceIndex = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Unlocks the next asteroid expansion (REQ-EXP-UNLOCK). Carries no payload: the
|
||||||
|
// simulation derives the cost and column count from its own expansion counter
|
||||||
|
// and config, so a recorded command replays identically.
|
||||||
|
struct ExpandAsteroidCommand : Command
|
||||||
|
{
|
||||||
|
ExpandAsteroidCommand() : Command(CommandKind::ExpandAsteroid) {}
|
||||||
|
};
|
||||||
|
|
||||||
// Restart boundary: reinitializes the simulation with a fresh seed and, if
|
// Restart boundary: reinitializes the simulation with a fresh seed and, if
|
||||||
// config is set, a reloaded config (GameConfig is move-only, so it is carried by
|
// config is set, a reloaded config (GameConfig is move-only, so it is carried by
|
||||||
// shared_ptr and moved into the sim on apply). A null config keeps the current
|
// shared_ptr and moved into the sim on apply). A null config keeps the current
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include "AiSystem.h"
|
#include "AiSystem.h"
|
||||||
#include "Command.h"
|
#include "Command.h"
|
||||||
@@ -132,6 +133,7 @@ void Simulation::reset(unsigned int seed)
|
|||||||
m_nextDepartureTick = secondsToTicks(m_config.world.departureIntervalSeconds);
|
m_nextDepartureTick = secondsToTicks(m_config.world.departureIntervalSeconds);
|
||||||
m_nextBuildingId = 1;
|
m_nextBuildingId = 1;
|
||||||
m_buildingBlocksStock = m_config.world.startingBuildingBlocks;
|
m_buildingBlocksStock = m_config.world.startingBuildingBlocks;
|
||||||
|
m_expansionsPurchased = 0;
|
||||||
m_gameOver = false;
|
m_gameOver = false;
|
||||||
m_isWon = false;
|
m_isWon = false;
|
||||||
m_artifactCount = 0;
|
m_artifactCount = 0;
|
||||||
@@ -275,6 +277,9 @@ void Simulation::apply(const Command& command)
|
|||||||
case CommandKind::ApplySchematicChoice:
|
case CommandKind::ApplySchematicChoice:
|
||||||
applySchematicChoice(static_cast<const ApplySchematicChoiceCommand&>(command).choiceIndex);
|
applySchematicChoice(static_cast<const ApplySchematicChoiceCommand&>(command).choiceIndex);
|
||||||
break;
|
break;
|
||||||
|
case CommandKind::ExpandAsteroid:
|
||||||
|
tryExpandAsteroid();
|
||||||
|
break;
|
||||||
case CommandKind::Reset:
|
case CommandKind::Reset:
|
||||||
{
|
{
|
||||||
const ResetCommand& c = static_cast<const ResetCommand&>(command);
|
const ResetCommand& c = static_cast<const ResetCommand&>(command);
|
||||||
@@ -993,6 +998,7 @@ unsigned long long Simulation::computeStateChecksum() const
|
|||||||
hasher.append(m_gameOver);
|
hasher.append(m_gameOver);
|
||||||
hasher.append(m_isWon);
|
hasher.append(m_isWon);
|
||||||
hasher.append(m_artifactCount);
|
hasher.append(m_artifactCount);
|
||||||
|
hasher.append(m_expansionsPurchased);
|
||||||
|
|
||||||
// WaveSystem scalar state, reached through existing accessors.
|
// WaveSystem scalar state, reached through existing accessors.
|
||||||
hasher.append(threatLevel());
|
hasher.append(threatLevel());
|
||||||
@@ -1100,6 +1106,31 @@ int Simulation::buildingBlocksStock() const
|
|||||||
return m_buildingBlocksStock;
|
return m_buildingBlocksStock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Simulation::currentAsteroidWidth_tiles() const
|
||||||
|
{
|
||||||
|
return m_config.world.regions.asteroidWidth_tiles
|
||||||
|
+ m_expansionsPurchased * m_config.world.expansion.columnsPerExpansion_tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Simulation::currentExpansionCost() const
|
||||||
|
{
|
||||||
|
const double cost = m_config.world.expansion.costBuildingBlocksFormula.evaluate(
|
||||||
|
static_cast<double>(m_expansionsPurchased));
|
||||||
|
return static_cast<int>(std::floor(cost));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Simulation::tryExpandAsteroid()
|
||||||
|
{
|
||||||
|
const int cost = currentExpansionCost();
|
||||||
|
if (m_buildingBlocksStock < cost)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_buildingBlocksStock -= cost;
|
||||||
|
++m_expansionsPurchased;
|
||||||
|
m_buildingSystem->setAsteroidWidth_tiles(currentAsteroidWidth_tiles());
|
||||||
|
}
|
||||||
|
|
||||||
bool Simulation::isGameOver() const
|
bool Simulation::isGameOver() const
|
||||||
{
|
{
|
||||||
return m_gameOver;
|
return m_gameOver;
|
||||||
|
|||||||
@@ -72,6 +72,12 @@ public:
|
|||||||
// The seed this run was (re)initialized with; written to the replay header.
|
// The seed this run was (re)initialized with; written to the replay header.
|
||||||
unsigned int getSeed() const;
|
unsigned int getSeed() const;
|
||||||
int buildingBlocksStock() const;
|
int buildingBlocksStock() const;
|
||||||
|
// Current asteroid width in tiles = base width + purchased expansions
|
||||||
|
// (REQ-EXP-UNLOCK, REQ-GW-ASTEROID-EXPAND).
|
||||||
|
int currentAsteroidWidth_tiles() const;
|
||||||
|
// Building block cost of the next expansion, floored to an integer
|
||||||
|
// (REQ-EXP-COST); x = number of expansions already purchased.
|
||||||
|
int currentExpansionCost() const;
|
||||||
bool isGameOver() const;
|
bool isGameOver() const;
|
||||||
bool isWon() const;
|
bool isWon() const;
|
||||||
int artifactCount() const;
|
int artifactCount() const;
|
||||||
@@ -136,6 +142,11 @@ private:
|
|||||||
// Clears the pending choices after application.
|
// Clears the pending choices after application.
|
||||||
void applySchematicChoice(int choiceIndex);
|
void applySchematicChoice(int choiceIndex);
|
||||||
|
|
||||||
|
// Unlocks one asteroid expansion if affordable (REQ-EXP-UNLOCK): checks the
|
||||||
|
// current cost against the stock, deducts it, increments the expansion
|
||||||
|
// counter, and widens the buildable asteroid. No-op if blocks are short.
|
||||||
|
void tryExpandAsteroid();
|
||||||
|
|
||||||
// Mutable subsystem accessors; same chokepoint rule as the mutators above.
|
// Mutable subsystem accessors; same chokepoint rule as the mutators above.
|
||||||
BuildingSystem& buildingsMutable();
|
BuildingSystem& buildingsMutable();
|
||||||
BeltSystem& beltsMutable();
|
BeltSystem& beltsMutable();
|
||||||
@@ -165,6 +176,7 @@ private:
|
|||||||
Tick m_nextDepartureTick;
|
Tick m_nextDepartureTick;
|
||||||
BuildingId m_nextBuildingId;
|
BuildingId m_nextBuildingId;
|
||||||
int m_buildingBlocksStock;
|
int m_buildingBlocksStock;
|
||||||
|
int m_expansionsPurchased = 0; // REQ-EXP-COST formula variable x
|
||||||
bool m_gameOver = false;
|
bool m_gameOver = false;
|
||||||
bool m_isWon = false;
|
bool m_isWon = false;
|
||||||
int m_artifactCount = 0;
|
int m_artifactCount = 0;
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ TEST_CASE("ConfigLoader loads the committed bin/config/ configs end-to-end", "[c
|
|||||||
REQUIRE(cfg.world.regions.playerBufferWidth_tiles == 10);
|
REQUIRE(cfg.world.regions.playerBufferWidth_tiles == 10);
|
||||||
REQUIRE(cfg.world.regions.enemyBufferWidth_tiles == 15);
|
REQUIRE(cfg.world.regions.enemyBufferWidth_tiles == 15);
|
||||||
REQUIRE(cfg.world.expansion.columnsPerExpansion_tiles == 10);
|
REQUIRE(cfg.world.expansion.columnsPerExpansion_tiles == 10);
|
||||||
|
REQUIRE(cfg.world.expansion.costBuildingBlocksFormula.evaluate(0) == Approx(400.0));
|
||||||
|
REQUIRE(cfg.world.expansion.costBuildingBlocksFormula.evaluate(1) == Approx(800.0));
|
||||||
REQUIRE(cfg.world.push.bossAdvanceSeconds == Approx(60.0));
|
REQUIRE(cfg.world.push.bossAdvanceSeconds == Approx(60.0));
|
||||||
REQUIRE(cfg.world.orbitFactor == Approx(0.8));
|
REQUIRE(cfg.world.orbitFactor == Approx(0.8));
|
||||||
REQUIRE(cfg.world.rallyOrbitRadius_tiles == Approx(5.0));
|
REQUIRE(cfg.world.rallyOrbitRadius_tiles == Approx(5.0));
|
||||||
@@ -186,7 +188,7 @@ contest_zone_width_tiles = 30
|
|||||||
|
|
||||||
[expansion]
|
[expansion]
|
||||||
columns_per_expansion_tiles = 10
|
columns_per_expansion_tiles = 10
|
||||||
cost_building_blocks = 200
|
cost_building_blocks_formula = "400 * 2^x"
|
||||||
|
|
||||||
[push]
|
[push]
|
||||||
push_expand_columns_tiles = 20
|
push_expand_columns_tiles = 20
|
||||||
@@ -237,7 +239,7 @@ enemy_buffer_width_tiles = 15
|
|||||||
|
|
||||||
[expansion]
|
[expansion]
|
||||||
columns_per_expansion_tiles = 10
|
columns_per_expansion_tiles = 10
|
||||||
cost_building_blocks = 200
|
cost_building_blocks_formula = "400 * 2^x"
|
||||||
|
|
||||||
[push]
|
[push]
|
||||||
push_expand_columns_tiles = 20
|
push_expand_columns_tiles = 20
|
||||||
@@ -284,7 +286,7 @@ enemy_buffer_width_tiles = 15
|
|||||||
|
|
||||||
[expansion]
|
[expansion]
|
||||||
columns_per_expansion_tiles = 10
|
columns_per_expansion_tiles = 10
|
||||||
cost_building_blocks = 200
|
cost_building_blocks_formula = "400 * 2^x"
|
||||||
|
|
||||||
[push]
|
[push]
|
||||||
push_expand_columns_tiles = 20
|
push_expand_columns_tiles = 20
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
#include "BuilderModeExitedEvent.h"
|
#include "BuilderModeExitedEvent.h"
|
||||||
#include "BlueprintModeExitedEvent.h"
|
#include "BlueprintModeExitedEvent.h"
|
||||||
#include "BuildingBlocksChangedEvent.h"
|
#include "BuildingBlocksChangedEvent.h"
|
||||||
|
#include "ExpansionCostChangedEvent.h"
|
||||||
#include "GameSpeedChangedEvent.h"
|
#include "GameSpeedChangedEvent.h"
|
||||||
#include "SchematicChoicesAvailableEvent.h"
|
#include "SchematicChoicesAvailableEvent.h"
|
||||||
#include "TickAdvancedEvent.h"
|
#include "TickAdvancedEvent.h"
|
||||||
@@ -276,6 +277,7 @@ void GameWorldView::onFrame()
|
|||||||
{
|
{
|
||||||
const Tick newTick = m_sim->currentTick();
|
const Tick newTick = m_sim->currentTick();
|
||||||
const int newBlocks = m_sim->buildingBlocksStock();
|
const int newBlocks = m_sim->buildingBlocksStock();
|
||||||
|
const int newExpCost = m_sim->currentExpansionCost();
|
||||||
const int newBoss = m_sim->bossWaveCounter();
|
const int newBoss = m_sim->bossWaveCounter();
|
||||||
const Tick newCountdown = m_sim->bossCountdownTicks();
|
const Tick newCountdown = m_sim->bossCountdownTicks();
|
||||||
|
|
||||||
@@ -291,6 +293,12 @@ void GameWorldView::onFrame()
|
|||||||
EventManager::getInstance()->sendEventImmediately(
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
std::make_shared<BuildingBlocksChangedEvent>(newBlocks));
|
std::make_shared<BuildingBlocksChangedEvent>(newBlocks));
|
||||||
}
|
}
|
||||||
|
if (newExpCost != m_lastExpansionCost)
|
||||||
|
{
|
||||||
|
m_lastExpansionCost = newExpCost;
|
||||||
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
|
std::make_shared<ExpansionCostChangedEvent>(newExpCost));
|
||||||
|
}
|
||||||
if (newBoss != m_lastBossCounter || newCountdown != m_lastBossCountdown)
|
if (newBoss != m_lastBossCounter || newCountdown != m_lastBossCountdown)
|
||||||
{
|
{
|
||||||
m_lastBossCounter = newBoss;
|
m_lastBossCounter = newBoss;
|
||||||
@@ -435,7 +443,7 @@ QRect GameWorldView::viewportRect() const
|
|||||||
|
|
||||||
float GameWorldView::asteroidLeftEdge() const
|
float GameWorldView::asteroidLeftEdge() const
|
||||||
{
|
{
|
||||||
float leftX = -static_cast<float>(m_config->world.regions.asteroidWidth_tiles);
|
float leftX = -static_cast<float>(m_sim->currentAsteroidWidth_tiles());
|
||||||
for (const Building& b : m_sim->buildings().allBuildings())
|
for (const Building& b : m_sim->buildings().allBuildings())
|
||||||
{
|
{
|
||||||
for (const QPoint& cell : b.bodyCells)
|
for (const QPoint& cell : b.bodyCells)
|
||||||
@@ -1827,6 +1835,7 @@ void GameWorldView::resetForNewGame()
|
|||||||
m_prevNonZeroSpeed = 1.0;
|
m_prevNonZeroSpeed = 1.0;
|
||||||
m_lastTick = Tick(-1);
|
m_lastTick = Tick(-1);
|
||||||
m_lastBlocks = -1;
|
m_lastBlocks = -1;
|
||||||
|
m_lastExpansionCost = -1;
|
||||||
m_lastBossCounter = -1;
|
m_lastBossCounter = -1;
|
||||||
m_lastBossCountdown = Tick(-1);
|
m_lastBossCountdown = Tick(-1);
|
||||||
m_lastArtifactCount = -1;
|
m_lastArtifactCount = -1;
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ private:
|
|||||||
|
|
||||||
Tick m_lastTick = Tick(-1);
|
Tick m_lastTick = Tick(-1);
|
||||||
int m_lastBlocks = -1;
|
int m_lastBlocks = -1;
|
||||||
|
int m_lastExpansionCost = -1;
|
||||||
int m_lastBossCounter = -1;
|
int m_lastBossCounter = -1;
|
||||||
Tick m_lastBossCountdown = Tick(-1);
|
Tick m_lastBossCountdown = Tick(-1);
|
||||||
int m_lastArtifactCount = -1;
|
int m_lastArtifactCount = -1;
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QSignalMapper>
|
#include <QSignalMapper>
|
||||||
|
|
||||||
|
#include "Command.h"
|
||||||
|
#include "CommandRequestedEvent.h"
|
||||||
#include "EventManager.h"
|
#include "EventManager.h"
|
||||||
#include "SpeedChangeRequestedEvent.h"
|
#include "SpeedChangeRequestedEvent.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
@@ -32,6 +34,17 @@ HeaderBar::HeaderBar(QWidget* parent)
|
|||||||
layout->addStretch();
|
layout->addStretch();
|
||||||
layout->addWidget(m_bossLabel);
|
layout->addWidget(m_bossLabel);
|
||||||
|
|
||||||
|
// Asteroid expansion button, to the left of the speed buttons (REQ-UI-HEADER,
|
||||||
|
// REQ-UI-EXPAND-BUTTON). Caption/enabled state are set on the first
|
||||||
|
// ExpansionCostChangedEvent; clicking requests an ExpandAsteroidCommand.
|
||||||
|
m_expandButton = new QPushButton(tr("Expand"), this);
|
||||||
|
layout->addWidget(m_expandButton);
|
||||||
|
connect(m_expandButton, &QPushButton::clicked, this, []() {
|
||||||
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
|
std::make_shared<CommandRequestedEvent>(
|
||||||
|
std::make_shared<ExpandAsteroidCommand>()));
|
||||||
|
});
|
||||||
|
|
||||||
const char* labels[] = { "0x", "0.5x", "1x", "2x", "10x" };
|
const char* labels[] = { "0x", "0.5x", "1x", "2x", "10x" };
|
||||||
QSignalMapper* mapper = new QSignalMapper(this);
|
QSignalMapper* mapper = new QSignalMapper(this);
|
||||||
for (int i = 0; i < kSpeedCount; ++i)
|
for (int i = 0; i < kSpeedCount; ++i)
|
||||||
@@ -67,7 +80,21 @@ void HeaderBar::handleEvent(std::shared_ptr<const TickAdvancedEvent> event)
|
|||||||
|
|
||||||
void HeaderBar::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event)
|
void HeaderBar::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event)
|
||||||
{
|
{
|
||||||
|
m_blocks = event->blocks;
|
||||||
m_blocksLabel->setText(tr("Blocks: %1").arg(event->blocks));
|
m_blocksLabel->setText(tr("Blocks: %1").arg(event->blocks));
|
||||||
|
updateExpandButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeaderBar::handleEvent(std::shared_ptr<const ExpansionCostChangedEvent> event)
|
||||||
|
{
|
||||||
|
m_expansionCost = event->cost;
|
||||||
|
updateExpandButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeaderBar::updateExpandButton()
|
||||||
|
{
|
||||||
|
m_expandButton->setText(tr("Expand: %1 Blocks").arg(m_expansionCost));
|
||||||
|
m_expandButton->setEnabled(m_blocks >= m_expansionCost);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeaderBar::handleEvent(std::shared_ptr<const GameSpeedChangedEvent> event)
|
void HeaderBar::handleEvent(std::shared_ptr<const GameSpeedChangedEvent> event)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "BossWaveUpdatedEvent.h"
|
#include "BossWaveUpdatedEvent.h"
|
||||||
#include "BuildingBlocksChangedEvent.h"
|
#include "BuildingBlocksChangedEvent.h"
|
||||||
#include "EventHandler.h"
|
#include "EventHandler.h"
|
||||||
|
#include "ExpansionCostChangedEvent.h"
|
||||||
#include "GameSpeedChangedEvent.h"
|
#include "GameSpeedChangedEvent.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
#include "TickAdvancedEvent.h"
|
#include "TickAdvancedEvent.h"
|
||||||
@@ -18,6 +19,7 @@ class QPushButton;
|
|||||||
class HeaderBar : public QWidget,
|
class HeaderBar : public QWidget,
|
||||||
public CombinedEventHandler<TickAdvancedEvent,
|
public CombinedEventHandler<TickAdvancedEvent,
|
||||||
BuildingBlocksChangedEvent,
|
BuildingBlocksChangedEvent,
|
||||||
|
ExpansionCostChangedEvent,
|
||||||
GameSpeedChangedEvent,
|
GameSpeedChangedEvent,
|
||||||
BossWaveUpdatedEvent,
|
BossWaveUpdatedEvent,
|
||||||
ArtifactCountChangedEvent>
|
ArtifactCountChangedEvent>
|
||||||
@@ -34,16 +36,25 @@ private slots:
|
|||||||
private:
|
private:
|
||||||
void handleEvent(std::shared_ptr<const TickAdvancedEvent> event) override;
|
void handleEvent(std::shared_ptr<const TickAdvancedEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
|
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
|
||||||
|
void handleEvent(std::shared_ptr<const ExpansionCostChangedEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const GameSpeedChangedEvent> event) override;
|
void handleEvent(std::shared_ptr<const GameSpeedChangedEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const BossWaveUpdatedEvent> event) override;
|
void handleEvent(std::shared_ptr<const BossWaveUpdatedEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const ArtifactCountChangedEvent> event) override;
|
void handleEvent(std::shared_ptr<const ArtifactCountChangedEvent> event) override;
|
||||||
|
|
||||||
|
// Refreshes the Expand button caption and enabled state from the current
|
||||||
|
// expansion cost and building block stock (REQ-UI-EXPAND-BUTTON).
|
||||||
|
void updateExpandButton();
|
||||||
|
|
||||||
QLabel* m_timeLabel;
|
QLabel* m_timeLabel;
|
||||||
QLabel* m_blocksLabel;
|
QLabel* m_blocksLabel;
|
||||||
QLabel* m_artifactsLabel;
|
QLabel* m_artifactsLabel;
|
||||||
QLabel* m_bossLabel;
|
QLabel* m_bossLabel;
|
||||||
|
QPushButton* m_expandButton;
|
||||||
std::vector<QPushButton*> m_speedButtons;
|
std::vector<QPushButton*> m_speedButtons;
|
||||||
|
|
||||||
|
int m_blocks = 0;
|
||||||
|
int m_expansionCost = 0;
|
||||||
|
|
||||||
static const double kSpeeds[];
|
static const double kSpeeds[];
|
||||||
static const int kSpeedCount;
|
static const int kSpeedCount;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user