Compare commits
3 Commits
ui_changes
...
09313c2d8a
| Author | SHA1 | Date | |
|---|---|---|---|
| 09313c2d8a | |||
| 2b6dafcf50 | |||
| e7d70a3640 |
@@ -29,6 +29,10 @@ target_score_formula = "1 / (1 + x)" # x = distance / max weapon r
|
|||||||
overclaim_penalty_formula = "max(0.5, 1 - 0.1*x)" # x = competing claim count; multiplies score, clamped to [0,1]
|
overclaim_penalty_formula = "max(0.5, 1 - 0.1*x)" # x = competing claim count; multiplies score, clamped to [0,1]
|
||||||
target_hysteresis = 0.40 # keep current target unless a challenger beats it by >10%
|
target_hysteresis = 0.40 # keep current target unless a challenger beats it by >10%
|
||||||
|
|
||||||
|
[artifacts]
|
||||||
|
artifact_chance_formula = "0.05 * x" # 5% chance per station level
|
||||||
|
artifact_win_count = 3
|
||||||
|
|
||||||
[waves]
|
[waves]
|
||||||
threat_rate_formula = "x"
|
threat_rate_formula = "x"
|
||||||
ship_level_formula = "1"
|
ship_level_formula = "1"
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ target_score_formula = "1 / (1 + x)" # x = distance / max weapon r
|
|||||||
overclaim_penalty_formula = "max(0.5, 1 - 0.1*x)" # x = competing claim count; multiplies score, clamped to [0,1]
|
overclaim_penalty_formula = "max(0.5, 1 - 0.1*x)" # x = competing claim count; multiplies score, clamped to [0,1]
|
||||||
target_hysteresis = 0.10 # keep current target unless a challenger beats it by >10%
|
target_hysteresis = 0.10 # keep current target unless a challenger beats it by >10%
|
||||||
|
|
||||||
|
[artifacts]
|
||||||
|
artifact_chance_formula = "0.05 * x" # 5% chance per station level
|
||||||
|
artifact_win_count = 3
|
||||||
|
|
||||||
[waves]
|
[waves]
|
||||||
threat_rate_formula = "x"
|
threat_rate_formula = "x"
|
||||||
ship_level_formula = "1 + x / 10"
|
ship_level_formula = "1 + x / 10"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Config files use the TOML format. The following config files drive game parameters:
|
Config files use the TOML format. The following config files drive game parameters:
|
||||||
|
|
||||||
- **world.toml** — world dimensions, region widths, expansion amounts, building refund percentage, wave timing, boss wave timing, enemy ship level formula, belt speed, starting building blocks, departure interval, ship orbit factor, rally orbit radius, and combat target-selection parameters (target score formula, overclaim penalty formula, target hysteresis).
|
- **world.toml** — world dimensions, region widths, expansion amounts, building refund percentage, wave timing, boss wave timing, enemy ship level formula, belt speed, starting building blocks, departure interval, ship orbit factor, rally orbit radius, combat target-selection parameters (target score formula, overclaim penalty formula, target hysteresis), artifact chance formula, and artifact win count.
|
||||||
- **buildings.toml** — building block cost and construction time per building type.
|
- **buildings.toml** — building block cost and construction time per building type.
|
||||||
- **recipes.toml** — crafting recipes: inputs, outputs, quantities, durations, and reprocessing plant probabilities. Assembler recipe entries may optionally define `unlock_at_station_level` (integer): -1 means the recipe is explicitly unlocked at game start; a value ≥ 0 means the recipe starts locked and a schematic for it can be awarded via defence station destruction (see REQ-LOCK-EXPLICIT, REQ-DEF-SCHEMATIC-DROP).
|
- **recipes.toml** — crafting recipes: inputs, outputs, quantities, durations, and reprocessing plant probabilities. Assembler recipe entries may optionally define `unlock_at_station_level` (integer): -1 means the recipe is explicitly unlocked at game start; a value ≥ 0 means the recipe starts locked and a schematic for it can be awarded via defence station destruction (see REQ-LOCK-EXPLICIT, REQ-DEF-SCHEMATIC-DROP).
|
||||||
- **ships.toml** — per schematic: a human-readable display name (used in the UI), hull stats (HP, max linear speed, sensor range, main acceleration, maneuvering acceleration, angular acceleration, max rotation speed) as formulas of ship level, required build materials, player production level, the station level at which the schematic becomes available for unlock (`unlock_at_station_level`; -1 means the player starts with the schematic already unlocked), a layout grid defining the ship's module slots, a `scrap_drop` loot value, and a `default_modules` list used for enemy wave ships (see REQ-WAV-DEFAULT-MODULES).
|
- **ships.toml** — per schematic: a human-readable display name (used in the UI), hull stats (HP, max linear speed, sensor range, main acceleration, maneuvering acceleration, angular acceleration, max rotation speed) as formulas of ship level, required build materials, player production level, the station level at which the schematic becomes available for unlock (`unlock_at_station_level`; -1 means the player starts with the schematic already unlocked), a layout grid defining the ship's module slots, a `scrap_drop` loot value, and a `default_modules` list used for enemy wave ships (see REQ-WAV-DEFAULT-MODULES).
|
||||||
@@ -90,6 +90,11 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
|
|||||||
- REQ-HQ-GAME-OVER: If the HQ is destroyed, the game ends. A game-over screen shows the final survival time and offers "Restart" and "Quit" buttons.
|
- REQ-HQ-GAME-OVER: If the HQ is destroyed, the game ends. A game-over screen shows the final survival time and offers "Restart" and "Quit" buttons.
|
||||||
- REQ-HQ-INVULNERABLE: Factory buildings (other than the HQ) are never targeted or destroyed by enemies.
|
- REQ-HQ-INVULNERABLE: Factory buildings (other than the HQ) are never targeted or destroyed by enemies.
|
||||||
|
|
||||||
|
## Win Condition
|
||||||
|
|
||||||
|
- REQ-WIN-ARTIFACT-COUNT: The player has an artifact count, starting at 0 at game start and resetting to 0 on Restart (REQ-CFG-RELOAD). When the player selects an artifact option in the schematic choice dialog (REQ-DEF-SCHEMATIC-DROP), the artifact count increments by 1. When the artifact count reaches `world.toml [world].artifact_win_count`, the player wins and the win screen is shown (REQ-WIN-SCREEN).
|
||||||
|
- REQ-WIN-SCREEN: When the player wins (REQ-WIN-ARTIFACT-COUNT), the simulation stops and a win screen is shown. The win screen functions identically to the game-over screen (REQ-HQ-GAME-OVER) — it displays the final survival time and offers "Restart" and "Quit" buttons — but with the caption "Won!" instead of the game-over caption.
|
||||||
|
|
||||||
## Building Placement & Management
|
## Building Placement & Management
|
||||||
|
|
||||||
- REQ-BLD-COST: The player places buildings from a build menu. Placement costs building blocks from the global stock. The cost per building type is read from `buildings.toml [[building]].cost`.
|
- REQ-BLD-COST: The player places buildings from a build menu. Placement costs building blocks from the global stock. The cost per building type is read from `buildings.toml [[building]].cost`.
|
||||||
@@ -299,11 +304,11 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
|
|||||||
- REQ-DEF-ENEMY-FIRE: Enemy defence stations automatically fire at player ships within range.
|
- REQ-DEF-ENEMY-FIRE: Enemy defence stations automatically fire at player ships within range.
|
||||||
- REQ-DEF-NO-CROSSFIRE: Enemy and player defence stations are never in each other's firing range.
|
- REQ-DEF-NO-CROSSFIRE: Enemy and player defence stations are never in each other's firing range.
|
||||||
- REQ-DEF-PUSH: When both enemy defence stations in a set are destroyed, the boss countdown is advanced (REQ-WAV-BOSS-ADVANCE), the scrollable area is extended (REQ-GW-PUSH-EXPAND), a new set of enemy defence stations is placed at the new boundary, and exactly one schematic drop is awarded for the destroyed set (REQ-DEF-SCHEMATIC-DROP).
|
- REQ-DEF-PUSH: When both enemy defence stations in a set are destroyed, the boss countdown is advanced (REQ-WAV-BOSS-ADVANCE), the scrollable area is extended (REQ-GW-PUSH-EXPAND), a new set of enemy defence stations is placed at the new boundary, and exactly one schematic drop is awarded for the destroyed set (REQ-DEF-SCHEMATIC-DROP).
|
||||||
- REQ-DEF-SCHEMATIC-DROP: Each destroyed set of enemy defence stations awards exactly one schematic drop (not one per station). The drop opens a **schematic choice dialog** — a modal dialog that pauses the game (speed set to 0×; on close, speed is restored to what it was before the dialog opened). Up to three schematic options are drawn uniformly at random **without replacement** from the eligible drop pool. If the pool contains fewer than three entries, only that many options are shown. The eligible drop pool contains:
|
- REQ-DEF-SCHEMATIC-DROP: Each destroyed set of enemy defence stations awards exactly one schematic drop (not one per station). The drop opens a **schematic choice dialog** — a modal dialog that pauses the game (speed set to 0×; on close, speed is restored to what it was before the dialog opened). Before drawing schematic picks, an artifact roll is made: evaluate `world.toml [world].artifact_chance_formula` with `x` set to the level of the destroyed station set, clamp the result to [0, 1], then compare against a uniform random value in [0, 1). If the roll succeeds, the dialog presents one **artifact option** plus two schematic picks drawn from the eligible pool; otherwise it presents three schematic picks. Up to three (or two, if an artifact option is present) schematic options are drawn uniformly at random **without replacement** from the eligible drop pool. If the pool contains fewer than the required number of entries, only that many schematic options are shown (the artifact option is always shown if the roll succeeded). The eligible drop pool contains:
|
||||||
- All **ship schematics** and **module schematics** whose `unlock_at_station_level` is -1 or is ≤ the level of the destroyed station set.
|
- All **ship schematics** and **module schematics** whose `unlock_at_station_level` is -1 or is ≤ the level of the destroyed station set.
|
||||||
- All **assembler recipe schematics** whose `unlock_at_station_level` is ≥ 0 and ≤ the level of the destroyed station set, whose output item is currently implicitly unlocked (REQ-LOCK-IMPLICIT), and which have not yet been awarded.
|
- All **assembler recipe schematics** whose `unlock_at_station_level` is ≥ 0 and ≤ the level of the destroyed station set, whose output item is currently implicitly unlocked (REQ-LOCK-IMPLICIT), and which have not yet been awarded.
|
||||||
|
|
||||||
Each option in the dialog displays: the schematic name (ship `display_name` from `ships.toml`, module `id` from `modules.toml`, or the output item type for assembler recipes), the schematic type (ship, module, or assembler recipe), and whether selecting it would be a **new unlock** or a **level-up** (showing the target level for level-ups). Assembler recipe schematics are always new unlocks since they are removed from the pool once awarded.
|
Each option in the dialog displays: the schematic name (ship `display_name` from `ships.toml`, module `id` from `modules.toml`, or the output item type for assembler recipes), the schematic type (ship, module, or assembler recipe), and whether selecting it would be a **new unlock** or a **level-up** (showing the target level for level-ups). Assembler recipe schematics are always new unlocks since they are removed from the pool once awarded. The artifact option (if present) is displayed as a distinct entry with the name "Artifact" and no unlock/level-up annotation.
|
||||||
|
|
||||||
Each option additionally displays a vertical list of item names labeled "Unlocks recipes for:", showing which recipes would newly become implicitly unlocked (REQ-LOCK-IMPLICIT) if this option were selected — specifically, the output items of miner recipes and assembler recipes (without `unlock_at_station_level`) that are not currently implicitly unlocked but would become so after applying this option's effect:
|
Each option additionally displays a vertical list of item names labeled "Unlocks recipes for:", showing which recipes would newly become implicitly unlocked (REQ-LOCK-IMPLICIT) if this option were selected — specifically, the output items of miner recipes and assembler recipes (without `unlock_at_station_level`) that are not currently implicitly unlocked but would become so after applying this option's effect:
|
||||||
- For a ship or module schematic that would be a **new unlock**, its `materials` are added to the base set per REQ-LOCK-IMPLICIT step 1a before recomputation.
|
- For a ship or module schematic that would be a **new unlock**, its `materials` are added to the base set per REQ-LOCK-IMPLICIT step 1a before recomputation.
|
||||||
@@ -312,7 +317,7 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
|
|||||||
|
|
||||||
Item names are deduplicated and sorted alphabetically. If no recipes would be newly unlocked, the list shows "None".
|
Item names are deduplicated and sorted alphabetically. If no recipes would be newly unlocked, the list shows "None".
|
||||||
|
|
||||||
The player selects one option by clicking it. The selected schematic is applied and the dialog closes:
|
The player selects one option by clicking it. If the player selects the artifact option, the player's artifact count is incremented by 1 (REQ-WIN-ARTIFACT-COUNT) and the dialog closes; no schematic is applied. Otherwise, the selected schematic is applied and the dialog closes:
|
||||||
|
|
||||||
For a **ship or module schematic**: if the player does not yet have the schematic, it is unlocked (ship schematics unlock the corresponding shipyard selection; module schematics unlock the module type for placement in the layout configuration dialog (REQ-MOD-UI-DIALOG)). If the player already has it, the schematic's `player_production_level` is incremented by 1 — for ship schematics, subsequent ships of that type are produced at a higher level; for module schematics, all instances of that module type use the higher level in their stat formulas.
|
For a **ship or module schematic**: if the player does not yet have the schematic, it is unlocked (ship schematics unlock the corresponding shipyard selection; module schematics unlock the module type for placement in the layout configuration dialog (REQ-MOD-UI-DIALOG)). If the player already has it, the schematic's `player_production_level` is incremented by 1 — for ship schematics, subsequent ships of that type are produced at a higher level; for module schematics, all instances of that module type use the higher level in their stat formulas.
|
||||||
|
|
||||||
@@ -384,7 +389,7 @@ 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 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-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-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-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).
|
||||||
|
|||||||
@@ -301,6 +301,9 @@ WorldConfig ConfigLoader::loadWorld(const std::string& path)
|
|||||||
cfg.targeting.overclaimPenaltyFormula = requireFormula(tbl["targeting"]["overclaim_penalty_formula"], file, "targeting.overclaim_penalty_formula");
|
cfg.targeting.overclaimPenaltyFormula = requireFormula(tbl["targeting"]["overclaim_penalty_formula"], file, "targeting.overclaim_penalty_formula");
|
||||||
cfg.targeting.hysteresis = requireDouble(tbl["targeting"]["target_hysteresis"], file, "targeting.target_hysteresis");
|
cfg.targeting.hysteresis = requireDouble(tbl["targeting"]["target_hysteresis"], file, "targeting.target_hysteresis");
|
||||||
|
|
||||||
|
cfg.artifacts.artifactChanceFormula = requireFormula(tbl["artifacts"]["artifact_chance_formula"], file, "artifacts.artifact_chance_formula");
|
||||||
|
cfg.artifacts.artifactWinCount = static_cast<int>(requireInt(tbl["artifacts"]["artifact_win_count"], file, "artifacts.artifact_win_count"));
|
||||||
|
|
||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,13 @@ struct WorldTargeting
|
|||||||
double hysteresis; // fractional margin a challenger must beat the current target by
|
double hysteresis; // fractional margin a challenger must beat the current target by
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Artifact win condition (REQ-WIN-ARTIFACT-COUNT, REQ-WIN-SCREEN).
|
||||||
|
struct WorldArtifacts
|
||||||
|
{
|
||||||
|
Formula artifactChanceFormula; // x = station level, result clamped to [0,1]
|
||||||
|
int artifactWinCount;
|
||||||
|
};
|
||||||
|
|
||||||
struct WorldConfig
|
struct WorldConfig
|
||||||
{
|
{
|
||||||
int heightTiles; // REQ-GW-HEIGHT
|
int heightTiles; // REQ-GW-HEIGHT
|
||||||
@@ -65,4 +72,5 @@ struct WorldConfig
|
|||||||
WorldPush push;
|
WorldPush push;
|
||||||
WorldWaves waves;
|
WorldWaves waves;
|
||||||
WorldTargeting targeting;
|
WorldTargeting targeting;
|
||||||
|
WorldArtifacts artifacts;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ enum class SchematicType
|
|||||||
{
|
{
|
||||||
Ship,
|
Ship,
|
||||||
Module,
|
Module,
|
||||||
Recipe
|
Recipe,
|
||||||
|
Artifact
|
||||||
};
|
};
|
||||||
|
|
||||||
// One option presented to the player in the schematic choice dialog
|
// One option presented to the player in the schematic choice dialog
|
||||||
|
|||||||
11
src/lib/eventsystem/event/ArtifactCountChangedEvent.h
Normal file
11
src/lib/eventsystem/event/ArtifactCountChangedEvent.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Event.h"
|
||||||
|
|
||||||
|
class ArtifactCountChangedEvent : public Event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ArtifactCountChangedEvent(int count, int winCount) : count(count), winCount(winCount) {}
|
||||||
|
const int count;
|
||||||
|
const int winCount;
|
||||||
|
};
|
||||||
@@ -9,6 +9,8 @@ SET(HDRS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/SchematicChoicesAvailableEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/SchematicChoicesAvailableEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/SelectionChangedEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/SelectionChangedEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/GameOverEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/GameOverEvent.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/WinEvent.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/ArtifactCountChangedEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuilderModeExitedEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BuilderModeExitedEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintModeExitedEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintModeExitedEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/EscapeMenuRequestedEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/EscapeMenuRequestedEvent.h
|
||||||
|
|||||||
5
src/lib/eventsystem/event/WinEvent.h
Normal file
5
src/lib/eventsystem/event/WinEvent.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Event.h"
|
||||||
|
|
||||||
|
class WinEvent : public Event {};
|
||||||
@@ -134,6 +134,8 @@ void Simulation::reset(unsigned int seed)
|
|||||||
m_nextBuildingId = 1;
|
m_nextBuildingId = 1;
|
||||||
m_buildingBlocksStock = m_config.world.startingBuildingBlocks;
|
m_buildingBlocksStock = m_config.world.startingBuildingBlocks;
|
||||||
m_gameOver = false;
|
m_gameOver = false;
|
||||||
|
m_isWon = false;
|
||||||
|
m_artifactCount = 0;
|
||||||
m_hqBuildingId = kInvalidBuildingId;
|
m_hqBuildingId = kInvalidBuildingId;
|
||||||
m_hqProxyEntity = entt::null;
|
m_hqProxyEntity = entt::null;
|
||||||
m_playerStation1Entity = entt::null;
|
m_playerStation1Entity = entt::null;
|
||||||
@@ -584,7 +586,14 @@ void Simulation::generateSchematicChoices(int destroyedStationLevel)
|
|||||||
|
|
||||||
if (pool.empty()) { return; }
|
if (pool.empty()) { return; }
|
||||||
|
|
||||||
const int numChoices = std::min(static_cast<int>(pool.size()), 3);
|
const double artifactChance = std::clamp(
|
||||||
|
m_config.world.artifacts.artifactChanceFormula.evaluate(
|
||||||
|
static_cast<double>(destroyedStationLevel)),
|
||||||
|
0.0, 1.0);
|
||||||
|
std::uniform_real_distribution<double> realDist(0.0, 1.0);
|
||||||
|
const bool artifactRolled = realDist(m_rng) < artifactChance;
|
||||||
|
|
||||||
|
const int numChoices = std::min(static_cast<int>(pool.size()), artifactRolled ? 2 : 3);
|
||||||
m_pendingSchematicChoices.clear();
|
m_pendingSchematicChoices.clear();
|
||||||
|
|
||||||
const std::set<std::string> currentShipIds = getUnlockedShipSchematicIds();
|
const std::set<std::string> currentShipIds = getUnlockedShipSchematicIds();
|
||||||
@@ -657,6 +666,17 @@ void Simulation::generateSchematicChoices(int destroyedStationLevel)
|
|||||||
|
|
||||||
m_pendingSchematicChoices.push_back(option);
|
m_pendingSchematicChoices.push_back(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (artifactRolled)
|
||||||
|
{
|
||||||
|
SchematicChoiceOption artifactOption;
|
||||||
|
artifactOption.schematicId = "";
|
||||||
|
artifactOption.type = SchematicType::Artifact;
|
||||||
|
artifactOption.displayName = "Artifact";
|
||||||
|
artifactOption.isNewUnlock = false;
|
||||||
|
artifactOption.targetLevel = 0;
|
||||||
|
m_pendingSchematicChoices.push_back(std::move(artifactOption));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Simulation::applySchematicChoice(int choiceIndex)
|
void Simulation::applySchematicChoice(int choiceIndex)
|
||||||
@@ -664,6 +684,17 @@ void Simulation::applySchematicChoice(int choiceIndex)
|
|||||||
assert(choiceIndex >= 0 && choiceIndex < static_cast<int>(m_pendingSchematicChoices.size()));
|
assert(choiceIndex >= 0 && choiceIndex < static_cast<int>(m_pendingSchematicChoices.size()));
|
||||||
const SchematicChoiceOption& chosen = m_pendingSchematicChoices[static_cast<std::size_t>(choiceIndex)];
|
const SchematicChoiceOption& chosen = m_pendingSchematicChoices[static_cast<std::size_t>(choiceIndex)];
|
||||||
|
|
||||||
|
if (chosen.type == SchematicType::Artifact)
|
||||||
|
{
|
||||||
|
m_artifactCount += 1;
|
||||||
|
if (m_artifactCount >= m_config.world.artifacts.artifactWinCount)
|
||||||
|
{
|
||||||
|
m_isWon = true;
|
||||||
|
}
|
||||||
|
m_pendingSchematicChoices.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (chosen.type == SchematicType::Recipe)
|
if (chosen.type == SchematicType::Recipe)
|
||||||
{
|
{
|
||||||
m_unlockedRecipeSchematicIds.insert(chosen.schematicId);
|
m_unlockedRecipeSchematicIds.insert(chosen.schematicId);
|
||||||
@@ -866,6 +897,16 @@ bool Simulation::isGameOver() const
|
|||||||
return m_gameOver;
|
return m_gameOver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Simulation::isWon() const
|
||||||
|
{
|
||||||
|
return m_isWon;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Simulation::artifactCount() const
|
||||||
|
{
|
||||||
|
return m_artifactCount;
|
||||||
|
}
|
||||||
|
|
||||||
double Simulation::threatLevel() const
|
double Simulation::threatLevel() const
|
||||||
{
|
{
|
||||||
return m_waveSystem->threatLevel();
|
return m_waveSystem->threatLevel();
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ public:
|
|||||||
Tick currentTick() const;
|
Tick currentTick() const;
|
||||||
int buildingBlocksStock() const;
|
int buildingBlocksStock() const;
|
||||||
bool isGameOver() const;
|
bool isGameOver() const;
|
||||||
|
bool isWon() const;
|
||||||
|
int artifactCount() const;
|
||||||
double threatLevel() const;
|
double threatLevel() const;
|
||||||
double threatAccumulationRate() const;
|
double threatAccumulationRate() const;
|
||||||
double maxFactoryProductionThreatRate() const;
|
double maxFactoryProductionThreatRate() const;
|
||||||
@@ -132,6 +134,8 @@ private:
|
|||||||
BuildingId m_nextBuildingId;
|
BuildingId m_nextBuildingId;
|
||||||
int m_buildingBlocksStock;
|
int m_buildingBlocksStock;
|
||||||
bool m_gameOver = false;
|
bool m_gameOver = false;
|
||||||
|
bool m_isWon = false;
|
||||||
|
int m_artifactCount = 0;
|
||||||
|
|
||||||
// Pre-placed structure IDs.
|
// Pre-placed structure IDs.
|
||||||
BuildingId m_hqBuildingId; // Building id (for belt integration)
|
BuildingId m_hqBuildingId; // Building id (for belt integration)
|
||||||
|
|||||||
253
src/test/ArtifactWinConditionTest.cpp
Normal file
253
src/test/ArtifactWinConditionTest.cpp
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "catch.hpp"
|
||||||
|
|
||||||
|
#include "ConfigLoader.h"
|
||||||
|
#include "FactionComponent.h"
|
||||||
|
#include "Formula.h"
|
||||||
|
#include "GameConfig.h"
|
||||||
|
#include "HealthComponent.h"
|
||||||
|
#include "SchematicChoiceOption.h"
|
||||||
|
#include "Simulation.h"
|
||||||
|
#include "StationBodyComponent.h"
|
||||||
|
|
||||||
|
static GameConfig loadConfig()
|
||||||
|
{
|
||||||
|
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void killEnemyStations(Simulation& sim)
|
||||||
|
{
|
||||||
|
sim.admin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||||
|
[](entt::entity, StationBodyComponent&, FactionComponent& faction, HealthComponent& health)
|
||||||
|
{
|
||||||
|
if (faction.isEnemy)
|
||||||
|
{
|
||||||
|
health.hp = 0.0f;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sim.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the index of the Artifact option in the pending choices, or -1 if absent.
|
||||||
|
static int findArtifactChoiceIndex(const Simulation& sim)
|
||||||
|
{
|
||||||
|
const auto& choices = sim.getPendingSchematicChoices();
|
||||||
|
for (int i = 0; i < static_cast<int>(choices.size()); ++i)
|
||||||
|
{
|
||||||
|
if (choices[static_cast<std::size_t>(i)].type == SchematicType::Artifact)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Config: new artifact fields are parsed correctly (REQ-WIN-ARTIFACT-COUNT)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE("ArtifactWinCondition: artifact_chance_formula and artifact_win_count are loaded",
|
||||||
|
"[artifact_win]")
|
||||||
|
{
|
||||||
|
const GameConfig cfg = loadConfig();
|
||||||
|
CHECK(cfg.world.artifacts.artifactWinCount == 3);
|
||||||
|
// 0.05 * x at x=2 should be 0.1
|
||||||
|
CHECK(cfg.world.artifacts.artifactChanceFormula.evaluate(2.0) == Approx(0.1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Simulation initial state
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE("ArtifactWinCondition: artifact count is 0 and isWon is false at game start",
|
||||||
|
"[artifact_win]")
|
||||||
|
{
|
||||||
|
const Simulation sim(loadConfig());
|
||||||
|
CHECK(sim.artifactCount() == 0);
|
||||||
|
CHECK_FALSE(sim.isWon());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Artifact option presence in schematic choices
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE("ArtifactWinCondition: artifact option appears when chance formula returns 1",
|
||||||
|
"[artifact_win]")
|
||||||
|
{
|
||||||
|
GameConfig cfg = loadConfig();
|
||||||
|
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||||
|
Simulation sim(std::move(cfg));
|
||||||
|
|
||||||
|
killEnemyStations(sim);
|
||||||
|
|
||||||
|
REQUIRE(sim.hasSchematicChoicesPending());
|
||||||
|
const auto& choices = sim.getPendingSchematicChoices();
|
||||||
|
|
||||||
|
const long artifactCount = std::count_if(choices.begin(), choices.end(),
|
||||||
|
[](const SchematicChoiceOption& opt) { return opt.type == SchematicType::Artifact; });
|
||||||
|
CHECK(artifactCount == 1);
|
||||||
|
// Exactly 2 schematic picks + 1 artifact (or fewer if pool was small)
|
||||||
|
CHECK(choices.size() <= 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("ArtifactWinCondition: at most 2 schematic options accompany the artifact",
|
||||||
|
"[artifact_win]")
|
||||||
|
{
|
||||||
|
GameConfig cfg = loadConfig();
|
||||||
|
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||||
|
Simulation sim(std::move(cfg));
|
||||||
|
|
||||||
|
killEnemyStations(sim);
|
||||||
|
|
||||||
|
REQUIRE(sim.hasSchematicChoicesPending());
|
||||||
|
const auto& choices = sim.getPendingSchematicChoices();
|
||||||
|
|
||||||
|
const long schematicCount = std::count_if(choices.begin(), choices.end(),
|
||||||
|
[](const SchematicChoiceOption& opt) { return opt.type != SchematicType::Artifact; });
|
||||||
|
CHECK(schematicCount <= 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("ArtifactWinCondition: no artifact option when chance formula returns 0",
|
||||||
|
"[artifact_win]")
|
||||||
|
{
|
||||||
|
GameConfig cfg = loadConfig();
|
||||||
|
cfg.world.artifacts.artifactChanceFormula = Formula::compile("0");
|
||||||
|
Simulation sim(std::move(cfg));
|
||||||
|
|
||||||
|
for (int i = 0; i < 20; ++i)
|
||||||
|
{
|
||||||
|
killEnemyStations(sim);
|
||||||
|
if (!sim.hasSchematicChoicesPending()) { continue; }
|
||||||
|
|
||||||
|
CHECK(findArtifactChoiceIndex(sim) == -1);
|
||||||
|
sim.applySchematicChoice(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Selecting the artifact option
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE("ArtifactWinCondition: selecting artifact increments artifact count",
|
||||||
|
"[artifact_win]")
|
||||||
|
{
|
||||||
|
GameConfig cfg = loadConfig();
|
||||||
|
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||||
|
Simulation sim(std::move(cfg));
|
||||||
|
|
||||||
|
killEnemyStations(sim);
|
||||||
|
REQUIRE(sim.hasSchematicChoicesPending());
|
||||||
|
|
||||||
|
const int index = findArtifactChoiceIndex(sim);
|
||||||
|
REQUIRE(index >= 0);
|
||||||
|
|
||||||
|
sim.applySchematicChoice(index);
|
||||||
|
|
||||||
|
CHECK(sim.artifactCount() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("ArtifactWinCondition: selecting a non-artifact option does not increment artifact count",
|
||||||
|
"[artifact_win]")
|
||||||
|
{
|
||||||
|
GameConfig cfg = loadConfig();
|
||||||
|
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||||
|
Simulation sim(std::move(cfg));
|
||||||
|
|
||||||
|
killEnemyStations(sim);
|
||||||
|
REQUIRE(sim.hasSchematicChoicesPending());
|
||||||
|
|
||||||
|
// Pick the first non-artifact choice.
|
||||||
|
const auto& choices = sim.getPendingSchematicChoices();
|
||||||
|
const auto it = std::find_if(choices.begin(), choices.end(),
|
||||||
|
[](const SchematicChoiceOption& opt) { return opt.type != SchematicType::Artifact; });
|
||||||
|
REQUIRE(it != choices.end());
|
||||||
|
|
||||||
|
sim.applySchematicChoice(static_cast<int>(it - choices.begin()));
|
||||||
|
|
||||||
|
CHECK(sim.artifactCount() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Win condition (REQ-WIN-ARTIFACT-COUNT)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE("ArtifactWinCondition: isWon becomes true when artifact count reaches win count",
|
||||||
|
"[artifact_win]")
|
||||||
|
{
|
||||||
|
GameConfig cfg = loadConfig();
|
||||||
|
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||||
|
cfg.world.artifacts.artifactWinCount = 1;
|
||||||
|
Simulation sim(std::move(cfg));
|
||||||
|
|
||||||
|
CHECK_FALSE(sim.isWon());
|
||||||
|
|
||||||
|
killEnemyStations(sim);
|
||||||
|
REQUIRE(sim.hasSchematicChoicesPending());
|
||||||
|
|
||||||
|
const int index = findArtifactChoiceIndex(sim);
|
||||||
|
REQUIRE(index >= 0);
|
||||||
|
sim.applySchematicChoice(index);
|
||||||
|
|
||||||
|
CHECK(sim.isWon());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("ArtifactWinCondition: isWon stays false when artifact count is below win count",
|
||||||
|
"[artifact_win]")
|
||||||
|
{
|
||||||
|
GameConfig cfg = loadConfig();
|
||||||
|
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||||
|
cfg.world.artifacts.artifactWinCount = 2;
|
||||||
|
Simulation sim(std::move(cfg));
|
||||||
|
|
||||||
|
killEnemyStations(sim);
|
||||||
|
REQUIRE(sim.hasSchematicChoicesPending());
|
||||||
|
sim.applySchematicChoice(findArtifactChoiceIndex(sim));
|
||||||
|
|
||||||
|
CHECK(sim.artifactCount() == 1);
|
||||||
|
CHECK_FALSE(sim.isWon());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("ArtifactWinCondition: isWon becomes true after collecting required number of artifacts",
|
||||||
|
"[artifact_win]")
|
||||||
|
{
|
||||||
|
GameConfig cfg = loadConfig();
|
||||||
|
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||||
|
cfg.world.artifacts.artifactWinCount = 2;
|
||||||
|
Simulation sim(std::move(cfg));
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
killEnemyStations(sim);
|
||||||
|
REQUIRE(sim.hasSchematicChoicesPending());
|
||||||
|
const int index = findArtifactChoiceIndex(sim);
|
||||||
|
REQUIRE(index >= 0);
|
||||||
|
sim.applySchematicChoice(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(sim.artifactCount() == 2);
|
||||||
|
CHECK(sim.isWon());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// reset() restores initial state (REQ-CFG-RELOAD)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE("ArtifactWinCondition: reset clears artifact count and win state",
|
||||||
|
"[artifact_win]")
|
||||||
|
{
|
||||||
|
GameConfig cfg = loadConfig();
|
||||||
|
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||||
|
cfg.world.artifacts.artifactWinCount = 1;
|
||||||
|
Simulation sim(std::move(cfg));
|
||||||
|
|
||||||
|
killEnemyStations(sim);
|
||||||
|
REQUIRE(sim.hasSchematicChoicesPending());
|
||||||
|
sim.applySchematicChoice(findArtifactChoiceIndex(sim));
|
||||||
|
REQUIRE(sim.isWon());
|
||||||
|
|
||||||
|
sim.reset();
|
||||||
|
|
||||||
|
CHECK(sim.artifactCount() == 0);
|
||||||
|
CHECK_FALSE(sim.isWon());
|
||||||
|
}
|
||||||
@@ -21,4 +21,5 @@ add_files(
|
|||||||
ShipModuleTest.cpp
|
ShipModuleTest.cpp
|
||||||
ThreatCostCalculatorTest.cpp
|
ThreatCostCalculatorTest.cpp
|
||||||
RecipeSchematicTest.cpp
|
RecipeSchematicTest.cpp
|
||||||
|
ArtifactWinConditionTest.cpp
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -251,6 +251,24 @@ void GameWorldView::onFrame()
|
|||||||
m_schematicChoiceShown = false;
|
m_schematicChoiceShown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Artifact count update
|
||||||
|
const int currentArtifactCount = m_sim->artifactCount();
|
||||||
|
if (currentArtifactCount != m_lastArtifactCount)
|
||||||
|
{
|
||||||
|
m_lastArtifactCount = currentArtifactCount;
|
||||||
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
|
std::make_shared<ArtifactCountChangedEvent>(
|
||||||
|
currentArtifactCount, m_sim->config().world.artifacts.artifactWinCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Win check
|
||||||
|
if (m_sim->isWon() && !m_winShown)
|
||||||
|
{
|
||||||
|
m_winShown = true;
|
||||||
|
m_gameSpeedMultiplier = 0.0;
|
||||||
|
EventManager::getInstance()->sendEventImmediately(std::make_shared<WinEvent>());
|
||||||
|
}
|
||||||
|
|
||||||
// Game over check
|
// Game over check
|
||||||
if (m_sim->isGameOver() && !m_gameOverShown)
|
if (m_sim->isGameOver() && !m_gameOverShown)
|
||||||
{
|
{
|
||||||
@@ -1668,11 +1686,13 @@ void GameWorldView::resetForNewGame()
|
|||||||
m_scrollLeft = false;
|
m_scrollLeft = false;
|
||||||
m_scrollRight = false;
|
m_scrollRight = false;
|
||||||
m_gameOverShown = false;
|
m_gameOverShown = false;
|
||||||
|
m_winShown = false;
|
||||||
m_prevNonZeroSpeed = 1.0;
|
m_prevNonZeroSpeed = 1.0;
|
||||||
m_lastTick = Tick(-1);
|
m_lastTick = Tick(-1);
|
||||||
m_lastBlocks = -1;
|
m_lastBlocks = -1;
|
||||||
m_lastBossCounter = -1;
|
m_lastBossCounter = -1;
|
||||||
m_lastBossCountdown = Tick(-1);
|
m_lastBossCountdown = Tick(-1);
|
||||||
|
m_lastArtifactCount = -1;
|
||||||
EventManager::getInstance()->sendEventImmediately(
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
std::make_shared<SelectionChangedEvent>(std::vector<BuildingId>{}));
|
std::make_shared<SelectionChangedEvent>(std::vector<BuildingId>{}));
|
||||||
setGameSpeed(1.0);
|
setGameSpeed(1.0);
|
||||||
|
|||||||
@@ -25,8 +25,10 @@
|
|||||||
#include "ExitBlueprintModeRequestedEvent.h"
|
#include "ExitBlueprintModeRequestedEvent.h"
|
||||||
#include "ExitBuilderModeRequestedEvent.h"
|
#include "ExitBuilderModeRequestedEvent.h"
|
||||||
#include "DebugDrawToggledEvent.h"
|
#include "DebugDrawToggledEvent.h"
|
||||||
|
#include "ArtifactCountChangedEvent.h"
|
||||||
#include "BeamFiredEvent.h"
|
#include "BeamFiredEvent.h"
|
||||||
#include "SchematicChoiceOption.h"
|
#include "SchematicChoiceOption.h"
|
||||||
|
#include "WinEvent.h"
|
||||||
#include "SpeedChangeRequestedEvent.h"
|
#include "SpeedChangeRequestedEvent.h"
|
||||||
|
|
||||||
#include "entt/entity/entity.hpp"
|
#include "entt/entity/entity.hpp"
|
||||||
@@ -191,10 +193,12 @@ private:
|
|||||||
bool m_scrollLeft;
|
bool m_scrollLeft;
|
||||||
bool m_scrollRight;
|
bool m_scrollRight;
|
||||||
bool m_gameOverShown;
|
bool m_gameOverShown;
|
||||||
|
bool m_winShown;
|
||||||
bool m_schematicChoiceShown;
|
bool m_schematicChoiceShown;
|
||||||
|
|
||||||
Tick m_lastTick = Tick(-1);
|
Tick m_lastTick = Tick(-1);
|
||||||
int m_lastBlocks = -1;
|
int m_lastBlocks = -1;
|
||||||
int m_lastBossCounter = -1;
|
int m_lastBossCounter = -1;
|
||||||
Tick m_lastBossCountdown = Tick(-1);
|
Tick m_lastBossCountdown = Tick(-1);
|
||||||
|
int m_lastArtifactCount = -1;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,9 +24,11 @@ HeaderBar::HeaderBar(QWidget* parent)
|
|||||||
|
|
||||||
m_timeLabel = new QLabel("00:00", this);
|
m_timeLabel = new QLabel("00:00", this);
|
||||||
m_blocksLabel = new QLabel(tr("Blocks: 0"), this);
|
m_blocksLabel = new QLabel(tr("Blocks: 0"), this);
|
||||||
|
m_artifactsLabel = new QLabel(tr("Artifacts: 0/?"), this);
|
||||||
m_bossLabel = new QLabel(tr("Boss Wave #1 Next boss: 5:00"), this);
|
m_bossLabel = new QLabel(tr("Boss Wave #1 Next boss: 5:00"), this);
|
||||||
layout->addWidget(m_timeLabel);
|
layout->addWidget(m_timeLabel);
|
||||||
layout->addWidget(m_blocksLabel);
|
layout->addWidget(m_blocksLabel);
|
||||||
|
layout->addWidget(m_artifactsLabel);
|
||||||
layout->addStretch();
|
layout->addStretch();
|
||||||
layout->addWidget(m_bossLabel);
|
layout->addWidget(m_bossLabel);
|
||||||
|
|
||||||
@@ -88,6 +90,11 @@ void HeaderBar::handleEvent(std::shared_ptr<const BossWaveUpdatedEvent> event)
|
|||||||
.arg(bossSeconds % 60, 2, 10, QChar('0')));
|
.arg(bossSeconds % 60, 2, 10, QChar('0')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HeaderBar::handleEvent(std::shared_ptr<const ArtifactCountChangedEvent> event)
|
||||||
|
{
|
||||||
|
m_artifactsLabel->setText(tr("Artifacts: %1/%2").arg(event->count).arg(event->winCount));
|
||||||
|
}
|
||||||
|
|
||||||
void HeaderBar::onSpeedButton(int index)
|
void HeaderBar::onSpeedButton(int index)
|
||||||
{
|
{
|
||||||
if (index >= 0 && index < kSpeedCount)
|
if (index >= 0 && index < kSpeedCount)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "ArtifactCountChangedEvent.h"
|
||||||
#include "BossWaveUpdatedEvent.h"
|
#include "BossWaveUpdatedEvent.h"
|
||||||
#include "BuildingBlocksChangedEvent.h"
|
#include "BuildingBlocksChangedEvent.h"
|
||||||
#include "EventHandler.h"
|
#include "EventHandler.h"
|
||||||
@@ -18,7 +19,8 @@ class HeaderBar : public QWidget,
|
|||||||
public CombinedEventHandler<TickAdvancedEvent,
|
public CombinedEventHandler<TickAdvancedEvent,
|
||||||
BuildingBlocksChangedEvent,
|
BuildingBlocksChangedEvent,
|
||||||
GameSpeedChangedEvent,
|
GameSpeedChangedEvent,
|
||||||
BossWaveUpdatedEvent>
|
BossWaveUpdatedEvent,
|
||||||
|
ArtifactCountChangedEvent>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@@ -34,9 +36,11 @@ private:
|
|||||||
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
|
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> 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;
|
||||||
|
|
||||||
QLabel* m_timeLabel;
|
QLabel* m_timeLabel;
|
||||||
QLabel* m_blocksLabel;
|
QLabel* m_blocksLabel;
|
||||||
|
QLabel* m_artifactsLabel;
|
||||||
QLabel* m_bossLabel;
|
QLabel* m_bossLabel;
|
||||||
std::vector<QPushButton*> m_speedButtons;
|
std::vector<QPushButton*> m_speedButtons;
|
||||||
|
|
||||||
|
|||||||
@@ -313,3 +313,42 @@ void MainWindow::handleEvent(std::shared_ptr<const GameOverEvent> /*event*/)
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::handleEvent(std::shared_ptr<const WinEvent> /*event*/)
|
||||||
|
{
|
||||||
|
const Tick tick = m_sim->currentTick();
|
||||||
|
const int totalSeconds = static_cast<int>(ticksToSeconds(tick));
|
||||||
|
const int minutes = totalSeconds / 60;
|
||||||
|
const int seconds = totalSeconds % 60;
|
||||||
|
|
||||||
|
QMessageBox box(this);
|
||||||
|
box.setWindowTitle(tr("Won!"));
|
||||||
|
box.setText(tr("You collected all artifacts!\nSurvival time: %1:%2")
|
||||||
|
.arg(minutes, 2, 10, QChar('0'))
|
||||||
|
.arg(seconds, 2, 10, QChar('0')));
|
||||||
|
QPushButton* restartBtn = box.addButton(tr("Restart"), QMessageBox::AcceptRole);
|
||||||
|
box.addButton(tr("Quit"), QMessageBox::RejectRole);
|
||||||
|
box.exec();
|
||||||
|
|
||||||
|
if (box.clickedButton() == restartBtn)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GameConfig newConfig = ConfigLoader::loadFromDirectory(m_configDir);
|
||||||
|
VisualsConfig newVisuals = VisualsLoader::load(m_configDir + "/visuals.toml");
|
||||||
|
m_visuals = std::move(newVisuals);
|
||||||
|
m_sim->reset(std::move(newConfig));
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this, tr("Config Error"),
|
||||||
|
tr("Failed to reload config:\n%1").arg(e.what()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_gameWorldView->resetForNewGame();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "EventHandler.h"
|
#include "EventHandler.h"
|
||||||
#include "GameOverEvent.h"
|
#include "GameOverEvent.h"
|
||||||
#include "LayoutDialogRequestedEvent.h"
|
#include "LayoutDialogRequestedEvent.h"
|
||||||
|
#include "WinEvent.h"
|
||||||
#include "RecipeSelectionRequestedEvent.h"
|
#include "RecipeSelectionRequestedEvent.h"
|
||||||
#include "SchematicChoicesAvailableEvent.h"
|
#include "SchematicChoicesAvailableEvent.h"
|
||||||
#include "ShipLayoutBlueprint.h"
|
#include "ShipLayoutBlueprint.h"
|
||||||
@@ -30,6 +31,7 @@ class MainWindow : public QWidget,
|
|||||||
public CombinedEventHandler<BuildingBlocksChangedEvent,
|
public CombinedEventHandler<BuildingBlocksChangedEvent,
|
||||||
SchematicChoicesAvailableEvent,
|
SchematicChoicesAvailableEvent,
|
||||||
GameOverEvent,
|
GameOverEvent,
|
||||||
|
WinEvent,
|
||||||
EscapeMenuRequestedEvent,
|
EscapeMenuRequestedEvent,
|
||||||
LayoutDialogRequestedEvent,
|
LayoutDialogRequestedEvent,
|
||||||
RecipeSelectionRequestedEvent>
|
RecipeSelectionRequestedEvent>
|
||||||
@@ -48,6 +50,7 @@ private:
|
|||||||
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
|
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> 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 GameOverEvent> event) override;
|
||||||
|
void handleEvent(std::shared_ptr<const WinEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const EscapeMenuRequestedEvent> event) override;
|
void handleEvent(std::shared_ptr<const EscapeMenuRequestedEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const LayoutDialogRequestedEvent> event) override;
|
void handleEvent(std::shared_ptr<const LayoutDialogRequestedEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const RecipeSelectionRequestedEvent> event) override;
|
void handleEvent(std::shared_ptr<const RecipeSelectionRequestedEvent> event) override;
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ SchematicChoiceDialog::SchematicChoiceDialog(
|
|||||||
nameLabel->setAlignment(Qt::AlignCenter);
|
nameLabel->setAlignment(Qt::AlignCenter);
|
||||||
cardLayout->addWidget(nameLabel);
|
cardLayout->addWidget(nameLabel);
|
||||||
|
|
||||||
|
if (option.type == SchematicType::Artifact)
|
||||||
|
{
|
||||||
|
QLabel* artifactTypeLabel = new QLabel(tr("Artifact"), card);
|
||||||
|
artifactTypeLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
cardLayout->addWidget(artifactTypeLabel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
QString typeText;
|
QString typeText;
|
||||||
if (option.type == SchematicType::Ship)
|
if (option.type == SchematicType::Ship)
|
||||||
{
|
{
|
||||||
@@ -99,6 +107,7 @@ SchematicChoiceDialog::SchematicChoiceDialog(
|
|||||||
QLabel* unlocksLabel = new QLabel(unlocksText, card);
|
QLabel* unlocksLabel = new QLabel(unlocksText, card);
|
||||||
unlocksLabel->setAlignment(Qt::AlignCenter);
|
unlocksLabel->setAlignment(Qt::AlignCenter);
|
||||||
cardLayout->addWidget(unlocksLabel);
|
cardLayout->addWidget(unlocksLabel);
|
||||||
|
}
|
||||||
|
|
||||||
QPushButton* selectButton = new QPushButton(tr("Select"), card);
|
QPushButton* selectButton = new QPushButton(tr("Select"), card);
|
||||||
cardLayout->addWidget(selectButton);
|
cardLayout->addWidget(selectButton);
|
||||||
|
|||||||
Reference in New Issue
Block a user