rename blueprint to schematic

This commit is contained in:
2026-04-26 21:00:55 +02:00
parent 774f5dee28
commit fb83db98ab
27 changed files with 153 additions and 153 deletions

View File

@@ -2,7 +2,7 @@
id = "interceptor" id = "interceptor"
available_from_start = true available_from_start = true
[ship.blueprint] [ship.schematic]
materials = [{item = "iron_ingot", amount = 3}, {item = "circuit_board", amount = 1}] materials = [{item = "iron_ingot", amount = 3}, {item = "circuit_board", amount = 1}]
player_production_level = 3 player_production_level = 3
production_time_seconds = 10 production_time_seconds = 10
@@ -29,7 +29,7 @@ scrap_drop = 2
id = "destroyer" id = "destroyer"
available_from_start = true available_from_start = true
[ship.blueprint] [ship.schematic]
materials = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 2}] materials = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 2}]
player_production_level = 5 player_production_level = 5
production_time_seconds = 20 production_time_seconds = 20
@@ -56,7 +56,7 @@ scrap_drop = 4
id = "salvage_ship" id = "salvage_ship"
available_from_start = true available_from_start = true
[ship.blueprint] [ship.schematic]
materials = [{item = "iron_ingot", amount = 4}] materials = [{item = "iron_ingot", amount = 4}]
player_production_level = 3 player_production_level = 3
production_time_seconds = 10 production_time_seconds = 10
@@ -82,7 +82,7 @@ scrap_drop = 2
id = "repair_ship" id = "repair_ship"
available_from_start = false available_from_start = false
[ship.blueprint] [ship.schematic]
materials = [{item = "iron_ingot", amount = 4}, {item = "circuit_board", amount = 2}] materials = [{item = "iron_ingot", amount = 4}, {item = "circuit_board", amount = 2}]
player_production_level = 3 player_production_level = 3
production_time_seconds = 15 production_time_seconds = 15

View File

@@ -142,7 +142,7 @@ outline = "#201a14"
# Ships # Ships
# #
# Ships are drawn as oriented triangles/arrows. Color is keyed to role, not # Ships are drawn as oriented triangles/arrows. Color is keyed to role, not
# blueprint (architecture.md, "Layer Order"). # schematic (architecture.md, "Layer Order").
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
[ships.player_combat] [ships.player_combat]
@@ -185,7 +185,7 @@ tile_highlight = "#ffffff22" # tile under cursor
selected_outline = "#ffff00" # outline drawn around currently-selected building(s) selected_outline = "#ffff00" # outline drawn around currently-selected building(s)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Blueprint-drop toasts (REQ-UI-BLUEPRINT-TOAST) # Schematic-drop toasts (REQ-UI-SCHEMATIC-TOAST)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
[toast] [toast]

View File

@@ -60,11 +60,11 @@ Simulation types shared across subsystems:
- `Port``struct Port { QPoint tile; Rotation direction; }`. Identifies a belt-adjacent cell and the direction of flow across that cell. - `Port``struct Port { QPoint tile; Rotation direction; }`. Identifies a belt-adjacent cell and the direction of flow across that cell.
- `MovementIntent``struct MovementIntent { int priority; QVector2D target; }`. Priority follows the order declared under Movement Arbitration. Cleared at the start of each tick; the highest-priority write wins; `tickMovement` reads the winner. - `MovementIntent``struct MovementIntent { int priority; QVector2D target; }`. Priority follows the order declared under Movement Arbitration. Cleared at the start of each tick; the highest-priority write wins; `tickMovement` reads the winner.
- `FireEvent``struct FireEvent { EntityId shooter; EntityId target; Tick emittedAt; }`. Transient record emitted each time a weapon fires (REQ-SHP-FIRING, REQ-SHP-FIRING-BEAM). Buffered in a sim-owned queue and drained by the renderer; see Sim → UI Events. - `FireEvent``struct FireEvent { EntityId shooter; EntityId target; Tick emittedAt; }`. Transient record emitted each time a weapon fires (REQ-SHP-FIRING, REQ-SHP-FIRING-BEAM). Buffered in a sim-owned queue and drained by the renderer; see Sim → UI Events.
- `BlueprintDropEvent``struct BlueprintDropEvent { ShipBlueprintId blueprint; int newLevel; bool wasNewUnlock; }`. Emitted when a destroyed enemy-defence-station set awards a blueprint (REQ-DEF-BLUEPRINT-DROP). The UI renders a toast (REQ-UI-BLUEPRINT-TOAST); `wasNewUnlock` chooses between the "unlocked" and "level → N" wording. - `SchematicDropEvent``struct SchematicDropEvent { ShipSchematicId schematic; int newLevel; bool wasNewUnlock; }`. Emitted when a destroyed enemy-defence-station set awards a schematic (REQ-DEF-SCHEMATIC-DROP). The UI renders a toast (REQ-UI-SCHEMATIC-TOAST); `wasNewUnlock` chooses between the "unlocked" and "level → N" wording.
## Sim → UI Events ## Sim → UI Events
The sim owns a small set of per-frame event queues that the UI drains on each render. These carry one-shot signals that are not derivable from persistent state — currently weapon fires (REQ-SHP-FIRING-BEAM) and blueprint drops (REQ-UI-BLUEPRINT-TOAST). Additional event types can be added here later (e.g., building-complete, unit-death flashes) without changing the pattern. The sim owns a small set of per-frame event queues that the UI drains on each render. These carry one-shot signals that are not derivable from persistent state — currently weapon fires (REQ-SHP-FIRING-BEAM) and schematic drops (REQ-UI-SCHEMATIC-TOAST). Additional event types can be added here later (e.g., building-complete, unit-death flashes) without changing the pattern.
Implementation: a plain `std::vector<FireEvent>` owned by `Simulation`, one vector per event type. Combat resolution (tick-order step 8) appends to it. The UI calls `simulation.drainFireEvents()` once per rendered frame, which returns the accumulated vector by move and clears the internal one. Beams are tracked by the renderer for 0.3 s of wall time (9 ticks at 30 Hz) using the events' `emittedAt` tick, then discarded. If either the shooter or target entity is gone when the renderer looks them up, the beam is dropped early. Implementation: a plain `std::vector<FireEvent>` owned by `Simulation`, one vector per event type. Combat resolution (tick-order step 8) appends to it. The UI calls `simulation.drainFireEvents()` once per rendered frame, which returns the accumulated vector by move and clears the internal one. Beams are tracked by the renderer for 0.3 s of wall time (9 ticks at 30 Hz) using the events' `emittedAt` tick, then discarded. If either the shooter or target entity is gone when the renderer looks them up, the beam is dropped early.
@@ -89,7 +89,7 @@ Within a single simulation tick, subsystems run in this fixed order. The order i
6. **Belt tick** — advance items along belt tiles; apply splitter routing (REQ-BLD-SPLITTER). 6. **Belt tick** — advance items along belt tiles; apply splitter routing (REQ-BLD-SPLITTER).
7. **Ship behavior systems** — clear `MovementIntent` on each ship, then run `tickThreatResponse`, `tickScrapCollector`, `tickRepairBehavior`, `tickHomeReturn` in any order (arbitration is via intent priority). 7. **Ship behavior systems** — clear `MovementIntent` on each ship, then run `tickThreatResponse`, `tickScrapCollector`, `tickRepairBehavior`, `tickHomeReturn` in any order (arbitration is via intent priority).
8. **Combat resolution** — ships and defence stations acquire targets, fire, apply damage; queue deaths. Each fire appends a `FireEvent` to the sim's fire-event queue (REQ-SHP-FIRING-BEAM). 8. **Combat resolution** — ships and defence stations acquire targets, fire, apply damage; queue deaths. Each fire appends a `FireEvent` to the sim's fire-event queue (REQ-SHP-FIRING-BEAM).
9. **Deaths & loot** — process queued deaths: drop scrap (REQ-RES-SCRAP-DROP); if a full enemy-defence-station set was destroyed this tick, award one blueprint (REQ-DEF-BLUEPRINT-DROP) and append a `BlueprintDropEvent`; remove entities. 9. **Deaths & loot** — process queued deaths: drop scrap (REQ-RES-SCRAP-DROP); if a full enemy-defence-station set was destroyed this tick, award one schematic (REQ-DEF-SCHEMATIC-DROP) and append a `SchematicDropEvent`; remove entities.
10. **`tickMovement`** — advance ship positions based on final `MovementIntent`. 10. **`tickMovement`** — advance ship positions based on final `MovementIntent`.
11. **Scrap despawn** — decrement scrap timers; remove expired scrap (REQ-RES-SCRAP-DROP). 11. **Scrap despawn** — decrement scrap timers; remove expired scrap (REQ-RES-SCRAP-DROP).
@@ -219,7 +219,7 @@ struct Ship {
float hp; float hp;
float maxHp; float maxHp;
int level; int level;
ShipBlueprintId blueprint; ShipSchematicId schematic;
// Capabilities // Capabilities
std::optional<Weapon> weapon; std::optional<Weapon> weapon;
@@ -280,7 +280,7 @@ The game world is rendered by a single `GameWorldView` widget that inherits `QOp
### Threading ### Threading
Sim and UI run on the same thread for v1. `paintEvent` reads sim state directly without locks. If profiling later justifies moving the sim to a worker thread, the pull-style `drainFireEvents()` / `drainBlueprintDropEvents()` / `forEachVisualItem()` APIs already support a clean snapshot-and-render split; a single mutex at the sim boundary would suffice. Sim and UI run on the same thread for v1. `paintEvent` reads sim state directly without locks. If profiling later justifies moving the sim to a worker thread, the pull-style `drainFireEvents()` / `drainSchematicDropEvents()` / `forEachVisualItem()` APIs already support a clean snapshot-and-render split; a single mutex at the sim boundary would suffice.
### Layer Order (back to front) ### Layer Order (back to front)
@@ -291,7 +291,7 @@ Sim and UI run on the same thread for v1. `paintEvent` reads sim state directly
5. **Ships** — colored arrows oriented by velocity; color keyed to role (player combat / salvage / repair / enemy). 5. **Ships** — colored arrows oriented by velocity; color keyed to role (player combat / salvage / repair / enemy).
6. **Laser beams** — lines derived from live `FireEvent`s kept by the renderer for 0.3 s (REQ-SHP-FIRING-BEAM). 6. **Laser beams** — lines derived from live `FireEvent`s kept by the renderer for 0.3 s (REQ-SHP-FIRING-BEAM).
7. **Build overlays** — ghost in builder mode (REQ-BLD-GHOST), demolish-mode tint, tile highlight under cursor, box-drag selection rectangle. 7. **Build overlays** — ghost in builder mode (REQ-BLD-GHOST), demolish-mode tint, tile highlight under cursor, box-drag selection rectangle.
8. **Screen-space UI**blueprint toasts (REQ-UI-BLUEPRINT-TOAST) and any other screen-anchored elements, drawn after resetting the world-space transform. 8. **Screen-space UI**schematic toasts (REQ-UI-SCHEMATIC-TOAST) and any other screen-anchored elements, drawn after resetting the world-space transform.
### Coordinates and Scrolling ### Coordinates and Scrolling

View File

@@ -44,15 +44,15 @@ Two sources feed the same production tree:
| **Smelter** | 2×2 | Converts ore or scrap into basic materials. No recipe selection needed. | | **Smelter** | 2×2 | Converts ore or scrap into basic materials. No recipe selection needed. |
| **Assembler** | 3×3 | Player selects a recipe. Produces building blocks or intermediate/advanced products. | | **Assembler** | 3×3 | Player selects a recipe. Produces building blocks or intermediate/advanced products. |
| **Reprocessing Plant** | 3×3 | Converts scrap into higher-level products with fixed probabilities per product type. | | **Reprocessing Plant** | 3×3 | Converts scrap into higher-level products with fixed probabilities per product type. |
| **Shipyard** | 4×2 | Player selects a blueprint. Produces that ship type when required materials are available. | | **Shipyard** | 4×2 | Player selects a schematic. Produces that ship type when required materials are available. |
| **Belt** | 1×1 | Transports materials. Comes in straight and curved variants. | | **Belt** | 1×1 | Transports materials. Comes in straight and curved variants. |
| **Splitter** | 1×1 | Splits a belt's flow into two outputs. | | **Splitter** | 1×1 | Splits a belt's flow into two outputs. |
## Ships & Shipyards ## Ships & Shipyards
- Shipyards are built at the asteroid's right edge. - Shipyards are built at the asteroid's right edge.
- The player clicks a shipyard to assign a blueprint; the shipyard then automatically produces that ship type whenever the required materials are available. - The player clicks a shipyard to assign a schematic; the shipyard then automatically produces that ship type whenever the required materials are available.
- New blueprints are unlocked as loot from destroyed enemy defence stations. - New schematics are unlocked as loot from destroyed enemy defence stations.
- Ships are fully autonomous. Known roles: - Ships are fully autonomous. Known roles:
- **Combat ships** — travel right and engage enemies. - **Combat ships** — travel right and engage enemies.
- **Salvage ships** — fly out, collect scrap from destroyed enemies, and return; vulnerable while operating. - **Salvage ships** — fly out, collect scrap from destroyed enemies, and return; vulnerable while operating.
@@ -70,7 +70,7 @@ Two sources feed the same production tree:
- The player is not forced to push; purely defensive play is valid. - The player is not forced to push; purely defensive play is valid.
- Destroying enemy defence stations applies the push scaling multiplier to all future waves, extends the scrollable area, and places a new (stronger) set of stations at the new boundary. - Destroying enemy defence stations applies the push scaling multiplier to all future waves, extends the scrollable area, and places a new (stronger) set of stations at the new boundary.
- Destroyed enemy defence stations drop ship blueprints. - Destroyed enemy defence stations drop ship schematics.
## Starting Conditions & Game Over ## Starting Conditions & Game Over

View File

@@ -7,7 +7,7 @@ Config files use the TOML format. The following config files drive game paramete
- **world.toml** — world dimensions, region widths, expansion amounts, building refund percentage, wave timing, enemy ship level formula, belt speed, starting building blocks. - **world.toml** — world dimensions, region widths, expansion amounts, building refund percentage, wave timing, enemy ship level formula, belt speed, starting building blocks.
- **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. - **recipes.toml** — crafting recipes: inputs, outputs, quantities, durations, and reprocessing plant probabilities.
- **ships.toml** — per blueprint: a human-readable display name (used in toasts and UI), ship stats (HP, speed, damage, attack range, attack rate, sensor range) as formulas of ship level, required build materials, threat cost formula, player production level, and whether the blueprint is available from game start. - **ships.toml** — per schematic: a human-readable display name (used in toasts and UI), ship stats (HP, speed, damage, attack range, attack rate, sensor range) as formulas of ship level, required build materials, threat cost formula, player production level, and whether the schematic is available from game start.
- **stations.toml** — HP, damage, range, fire rate, and scrap drop for player and enemy defence stations, defined as formulas of station level. - **stations.toml** — HP, damage, range, fire rate, and scrap drop for player and enemy defence stations, defined as formulas of station level.
- **visuals.toml** — rendering-only config (not game parameters): fill and outline colors and glyphs for every building type, item type, ship role, and station type; beam color and width; overlay and toast colors. Loaded by the UI at startup; the simulation does not read it. - **visuals.toml** — rendering-only config (not game parameters): fill and outline colors and glyphs for every building type, item type, ship role, and station type; beam color and width; overlay and toast colors. Loaded by the UI at startup; the simulation does not read it.
@@ -70,7 +70,7 @@ Output port indicators are not building tiles themselves. A building may have mo
- REQ-BLD-SMELTER: **Smelter** (2×2): Converts ore or scrap into basic materials. No recipe selection required. Inputs, outputs, and rates are defined in `recipes.toml [[recipe]]` entries with `building = "smelter"`. - REQ-BLD-SMELTER: **Smelter** (2×2): Converts ore or scrap into basic materials. No recipe selection required. Inputs, outputs, and rates are defined in `recipes.toml [[recipe]]` entries with `building = "smelter"`.
- REQ-BLD-ASSEMBLER: **Assembler** (3×3): The player selects a recipe from the config-defined crafting tree. Produces the selected output item at the rate defined in the corresponding `recipes.toml [[recipe]]` entry with `building = "assembler"`. - REQ-BLD-ASSEMBLER: **Assembler** (3×3): The player selects a recipe from the config-defined crafting tree. Produces the selected output item at the rate defined in the corresponding `recipes.toml [[recipe]]` entry with `building = "assembler"`.
- REQ-BLD-REPROCESSING: **Reprocessing Plant** (3×3): Consumes scrap per cycle and produces exactly one higher-level intermediate product per cycle via weighted random pick. The input quantity, possible output items, per-output weights, and amounts are defined in `recipes.toml [[recipe]]` entries with `building = "reprocessing_plant"` (`inputs`, `outputs[].item`, `outputs[].amount`, `outputs[].weight`). Weights are normalized at load time; their sum does not need to equal 1. The output is rolled at cycle start (see REQ-MAT-CYCLE). The output buffer holds at most one cycle's output — see REQ-MAT-OUTPUT-BUFFER-REPROCESSING. - REQ-BLD-REPROCESSING: **Reprocessing Plant** (3×3): Consumes scrap per cycle and produces exactly one higher-level intermediate product per cycle via weighted random pick. The input quantity, possible output items, per-output weights, and amounts are defined in `recipes.toml [[recipe]]` entries with `building = "reprocessing_plant"` (`inputs`, `outputs[].item`, `outputs[].amount`, `outputs[].weight`). Weights are normalized at load time; their sum does not need to equal 1. The output is rolled at cycle start (see REQ-MAT-CYCLE). The output buffer holds at most one cycle's output — see REQ-MAT-OUTPUT-BUFFER-REPROCESSING.
- REQ-BLD-SHIPYARD: **Shipyard** (4×2): The player selects a blueprint. When all required materials (`[ship.blueprint].materials`) are present in its input buffer, the shipyard consumes them and begins a production cycle lasting `[ship.blueprint].production_time_seconds` seconds (read from `ships.toml`). One ship of that type is spawned at `ships.toml [ship.blueprint].player_production_level` (initial value 5, incremented by duplicate blueprint drops per REQ-DEF-BLUEPRINT-DROP) when the cycle completes. The shipyard cannot start a new cycle while one is in progress. - REQ-BLD-SHIPYARD: **Shipyard** (4×2): The player selects a schematic. When all required materials (`[ship.schematic].materials`) are present in its input buffer, the shipyard consumes them and begins a production cycle lasting `[ship.schematic].production_time_seconds` seconds (read from `ships.toml`). One ship of that type is spawned at `ships.toml [ship.schematic].player_production_level` (initial value 5, incremented by duplicate schematic drops per REQ-DEF-SCHEMATIC-DROP) when the cycle completes. The shipyard cannot start a new cycle while one is in progress.
- REQ-BLD-SALVAGE-BAY: **Salvage Bay** (3×2): A dedicated drop-off point for salvage ships. Scrap delivered here is placed onto connected output belts. - REQ-BLD-SALVAGE-BAY: **Salvage Bay** (3×2): A dedicated drop-off point for salvage ships. Scrap delivered here is placed onto connected output belts.
- REQ-BLD-BELT: **Belt** (1×1): Transports items. A belt tile has one direction (N, S, E, W) set at placement (modified by rotation). Curved belts are auto-derived: when a belt tile's outgoing direction leads into another belt whose direction is orthogonal, the downstream belt is rendered and behaves as a curve. Belt speed is defined in `world.toml [world].belt_speed_tiles_per_second` (REQ-GW-BELT-SPEED). - REQ-BLD-BELT: **Belt** (1×1): Transports items. A belt tile has one direction (N, S, E, W) set at placement (modified by rotation). Curved belts are auto-derived: when a belt tile's outgoing direction leads into another belt whose direction is orthogonal, the downstream belt is rendered and behaves as a curve. Belt speed is defined in `world.toml [world].belt_speed_tiles_per_second` (REQ-GW-BELT-SPEED).
- REQ-BLD-SPLITTER: **Splitter** (1×1): Distributes incoming items between two output directions. Each output can optionally have a filter (a list of item types), configurable via the selected building panel. Routing rules: - REQ-BLD-SPLITTER: **Splitter** (1×1): Distributes incoming items between two output directions. Each output can optionally have a filter (a list of item types), configurable via the selected building panel. Routing rules:
@@ -94,8 +94,8 @@ Output port indicators are not building tiles themselves. A building may have mo
- REQ-MAT-BELT-ONLY: Materials are transported exclusively via belts, splitters, and tunnels. - REQ-MAT-BELT-ONLY: Materials are transported exclusively via belts, splitters, and tunnels.
- REQ-MAT-INPUT-PORTS: A building accepts items from any adjacent belt tile on any edge of its footprint (excluding cells occupied by output port(s)) whose direction points toward the building, provided the item is an input required by the currently selected recipe and the matching per-material input buffer has free space. - REQ-MAT-INPUT-PORTS: A building accepts items from any adjacent belt tile on any edge of its footprint (excluding cells occupied by output port(s)) whose direction points toward the building, provided the item is an input required by the currently selected recipe and the matching per-material input buffer has free space.
- REQ-MAT-OUTPUT-PORT: Each building has one or more fixed output port(s) defined by its surface_mask (direction determined by rotation). Produced items are placed onto the belt at the output port tile regardless of that belt's direction. - REQ-MAT-OUTPUT-PORT: Each building has one or more fixed output port(s) defined by its surface_mask (direction determined by rotation). Produced items are placed onto the belt at the output port tile regardless of that belt's direction.
- REQ-MAT-INPUT-BUFFER: Each building has one input buffer per required input material. Each per-material buffer holds up to twice that material's per-cycle requirement. When the player selects a new recipe or blueprint, all items in all input buffers are cleared. - REQ-MAT-INPUT-BUFFER: Each building has one input buffer per required input material. Each per-material buffer holds up to twice that material's per-cycle requirement. When the player selects a new recipe or schematic, all items in all input buffers are cleared.
- REQ-MAT-OUTPUT-BUFFER: Each building has an output buffer that holds up to twice the quantity produced by one production cycle. If the output buffer is full, production stops until space is available. When the player selects a new recipe or blueprint, all items in the output buffer are cleared (relevant when the adjacent belt is jammed and items have accumulated). - REQ-MAT-OUTPUT-BUFFER: Each building has an output buffer that holds up to twice the quantity produced by one production cycle. If the output buffer is full, production stops until space is available. When the player selects a new recipe or schematic, all items in the output buffer are cleared (relevant when the adjacent belt is jammed and items have accumulated).
- REQ-MAT-OUTPUT-BUFFER-REPROCESSING: Exception to REQ-MAT-OUTPUT-BUFFER — the Reprocessing Plant's output buffer holds at most one cycle's output. This prevents exploits where the player stalls the output belt to force the plant to reroll. - REQ-MAT-OUTPUT-BUFFER-REPROCESSING: Exception to REQ-MAT-OUTPUT-BUFFER — the Reprocessing Plant's output buffer holds at most one cycle's output. This prevents exploits where the player stalls the output belt to force the plant to reroll.
- REQ-MAT-CYCLE: Production cycle lifecycle. When a building is idle, it attempts to start a new cycle: (a) all required inputs must be present in the per-material input buffers, and (b) the cycle's output must fit in the output buffer. For the Reprocessing Plant, the output is picked at cycle start (weighted pick); the cycle only starts if that chosen output fits. On cycle start, inputs are consumed immediately and the production timer begins. On cycle completion, the (already-decided) output is deposited into the output buffer and the building returns to idle. - REQ-MAT-CYCLE: Production cycle lifecycle. When a building is idle, it attempts to start a new cycle: (a) all required inputs must be present in the per-material input buffers, and (b) the cycle's output must fit in the output buffer. For the Reprocessing Plant, the output is picked at cycle start (weighted pick); the cycle only starts if that chosen output fits. On cycle start, inputs are consumed immediately and the production timer begins. On cycle completion, the (already-decided) output is deposited into the output buffer and the building returns to idle.
- REQ-MAT-GLOBAL-STOCK: The building blocks stock is the only global inventory. All other materials exist only in building buffers or on belt tiles. - REQ-MAT-GLOBAL-STOCK: The building blocks stock is the only global inventory. All other materials exist only in building buffers or on belt tiles.
@@ -108,7 +108,7 @@ Output port indicators are not building tiles themselves. A building may have mo
## Ships ## Ships
- REQ-SHP-AUTONOMOUS: Ships are produced by shipyards and are fully autonomous once produced. - REQ-SHP-AUTONOMOUS: Ships are produced by shipyards and are fully autonomous once produced.
- REQ-SHP-STATS: All ship stats are defined as formulas of ship level in `ships.toml`: HP (`[ship.health].hp_formula`), speed (`[ship.movement].speed_formula`), damage (`[ship.combat].damage_formula`), attack range (`[ship.combat].attack_range_formula`), attack rate (`[ship.combat].attack_rate_formula`), sensor range (`[ship.sensors].range_formula`). Required build materials (`[ship.blueprint].materials`) and availability from game start (`[[ship]].available_from_start`) are also defined there. - REQ-SHP-STATS: All ship stats are defined as formulas of ship level in `ships.toml`: HP (`[ship.health].hp_formula`), speed (`[ship.movement].speed_formula`), damage (`[ship.combat].damage_formula`), attack range (`[ship.combat].attack_range_formula`), attack rate (`[ship.combat].attack_rate_formula`), sensor range (`[ship.sensors].range_formula`). Required build materials (`[ship.schematic].materials`) and availability from game start (`[[ship]].available_from_start`) are also defined there.
- REQ-SHP-SPAWN-PLAYER: A ship produced by a shipyard spawns centered on the shipyard's output port tile. - REQ-SHP-SPAWN-PLAYER: A ship produced by a shipyard spawns centered on the shipyard's output port tile.
- REQ-SHP-SPAWN-ENEMY: Enemy ships spawn at a uniformly random position within the current enemy buffer zone — random X across the buffer's width and random Y across the world height. - REQ-SHP-SPAWN-ENEMY: Enemy ships spawn at a uniformly random position within the current enemy buffer zone — random X across the buffer's width and random Y across the world height.
- REQ-SHP-MOVEMENT: Ships move in straight lines toward their current destination at the speed defined by their speed formula. Ship position refers to the ship's center for all range, sensor, and attack checks. - REQ-SHP-MOVEMENT: Ships move in straight lines toward their current destination at the speed defined by their speed formula. Ship position refers to the ship's center for all range, sensor, and attack checks.
@@ -123,7 +123,7 @@ Output port indicators are not building tiles themselves. A building may have mo
- REQ-SHP-REPAIR: **Repair ships** (player) — patrol by moving forward (rightward, away from the asteroid) while searching sensor range. If a damaged player defence station or player ship enters sensor range, move to it and repair. If an enemy ship enters sensor range while not currently repairing, turn back (move toward the asteroid) until the enemy is no longer in sensor range, then resume patrol. The player can configure the target priority per shipyard: - REQ-SHP-REPAIR: **Repair ships** (player) — patrol by moving forward (rightward, away from the asteroid) while searching sensor range. If a damaged player defence station or player ship enters sensor range, move to it and repair. If an enemy ship enters sensor range while not currently repairing, turn back (move toward the asteroid) until the enemy is no longer in sensor range, then resume patrol. The player can configure the target priority per shipyard:
- Defence stations first / ships first / nearest target. - Defence stations first / ships first / nearest target.
- REQ-SHP-ENEMY-AI: **Enemy ships** — engage the closest valid target (player defence station, HQ, or player ship) within their sensor range. If no target is in sensor range, they move toward the asteroid (leftward in world coordinates). - REQ-SHP-ENEMY-AI: **Enemy ships** — engage the closest valid target (player defence station, HQ, or player ship) within their sensor range. If no target is in sensor range, they move toward the asteroid (leftward in world coordinates).
- REQ-SHP-BLUEPRINTS: The player selects a blueprint per shipyard by clicking it. New blueprints are unlocked automatically when an enemy defence station set is destroyed (REQ-DEF-BLUEPRINT-DROP) — there is no physical loot to collect. - REQ-SHP-SCHEMATICS: The player selects a schematic per shipyard by clicking it. New schematics are unlocked automatically when an enemy defence station set is destroyed (REQ-DEF-SCHEMATIC-DROP) — there is no physical loot to collect.
## Defence Stations ## Defence Stations
@@ -133,14 +133,14 @@ Output port indicators are not building tiles themselves. A building may have mo
- REQ-DEF-ENEMY-PLACEMENT: 2 enemy defence stations are placed at the right boundary of the scrollable area at game start, and again each time a new set is spawned after a push. Stats scale with the station level (REQ-PSH-STATION-STATS). - REQ-DEF-ENEMY-PLACEMENT: 2 enemy defence stations are placed at the right boundary of the scrollable area at game start, and again each time a new set is spawned after a push. Stats scale with the station level (REQ-PSH-STATION-STATS).
- 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 push scaling multiplier is applied (REQ-PSH-ACCUMULATION), 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 blueprint drop is awarded for the destroyed set (REQ-DEF-BLUEPRINT-DROP). - REQ-DEF-PUSH: When both enemy defence stations in a set are destroyed, the push scaling multiplier is applied (REQ-PSH-ACCUMULATION), 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-BLUEPRINT-DROP: Each destroyed set of enemy defence stations awards exactly one blueprint drop (not one per station). The drop is automatic — no physical item to collect. A blueprint is chosen uniformly at random from all blueprints defined in `ships.toml`. If the player does not yet have that blueprint, it is unlocked. If the player already has it, the blueprint's `[ship.blueprint].player_production_level` is incremented by 1 — so subsequent ships of that type are produced at a higher level. The player is notified via a toast (REQ-UI-BLUEPRINT-TOAST). - REQ-DEF-SCHEMATIC-DROP: Each destroyed set of enemy defence stations awards exactly one schematic drop (not one per station). The drop is automatic — no physical item to collect. A schematic is chosen uniformly at random from all schematics defined in `ships.toml`. If the player does not yet have that schematic, it is unlocked. If the player already has it, the schematic's `[ship.schematic].player_production_level` is incremented by 1 — so subsequent ships of that type are produced at a higher level. The player is notified via a toast (REQ-UI-SCHEMATIC-TOAST).
## Threat Level & Enemy Waves ## Threat Level & Enemy Waves
- REQ-WAV-THREAT-RATE: A global **threat level** accumulates continuously over time. The rate of increase per second is determined by `world.toml [waves].threat_rate_formula` where x is elapsed game time in seconds, clamped to a minimum of 0 (negative formula values are treated as 0). Example: `1*x - 30` yields 0 threat/s for x ≤ 30s and increases linearly after that. - REQ-WAV-THREAT-RATE: A global **threat level** accumulates continuously over time. The rate of increase per second is determined by `world.toml [waves].threat_rate_formula` where x is elapsed game time in seconds, clamped to a minimum of 0 (negative formula values are treated as 0). Example: `1*x - 30` yields 0 threat/s for x ≤ 30s and increases linearly after that.
- REQ-WAV-GAP: At game start and immediately after each wave is triggered, a random inter-wave gap is drawn uniformly from [`world.toml [waves].gap_min_seconds`, `gap_max_seconds`]. - REQ-WAV-GAP: At game start and immediately after each wave is triggered, a random inter-wave gap is drawn uniformly from [`world.toml [waves].gap_min_seconds`, `gap_max_seconds`].
- REQ-WAV-TRIGGER: When the gap expires, a wave is triggered. Ships are selected one at a time: from all blueprints whose `threat.cost_formula` evaluates to > 0 at the current enemy ship level, uniformly randomly pick one whose cost fits the remaining threat budget. Repeat until no eligible blueprint fits. Any remaining threat carries over to the next wave. A longer gap results in a larger wave. Because enemy ship level increases with time (REQ-WAV-SHIP-LEVEL), threat cost per ship rises naturally over the course of the game. - REQ-WAV-TRIGGER: When the gap expires, a wave is triggered. Ships are selected one at a time: from all schematics whose `threat.cost_formula` evaluates to > 0 at the current enemy ship level, uniformly randomly pick one whose cost fits the remaining threat budget. Repeat until no eligible schematic fits. Any remaining threat carries over to the next wave. A longer gap results in a larger wave. Because enemy ship level increases with time (REQ-WAV-SHIP-LEVEL), threat cost per ship rises naturally over the course of the game.
- REQ-WAV-SHIP-LEVEL: Each wave's enemy ships are assigned a level determined by `world.toml [waves].ship_level_formula` where x is elapsed game time in seconds. This is the sole mechanism by which individual enemy ships become stronger over time. Wave *size* grows separately via threat accumulation (REQ-WAV-THREAT-RATE) and push scaling (REQ-PSH-ACCUMULATION). Per-ship stats and threat cost are computed from the ship level via the formulas in `ships.toml` (see REQ-SHP-STATS). - REQ-WAV-SHIP-LEVEL: Each wave's enemy ships are assigned a level determined by `world.toml [waves].ship_level_formula` where x is elapsed game time in seconds. This is the sole mechanism by which individual enemy ships become stronger over time. Wave *size* grows separately via threat accumulation (REQ-WAV-THREAT-RATE) and push scaling (REQ-PSH-ACCUMULATION). Per-ship stats and threat cost are computed from the ship level via the formulas in `ships.toml` (see REQ-SHP-STATS).
- REQ-WAV-SPAWN-DURATION: Ships in a wave are spawned one at a time over `world.toml [waves].spawn_duration_seconds`. - REQ-WAV-SPAWN-DURATION: Ships in a wave are spawned one at a time over `world.toml [waves].spawn_duration_seconds`.
@@ -184,8 +184,8 @@ The screen is divided into three vertical sections:
- REQ-UI-CONSTRUCTION-PROGRESS: Construction sites display the building's glyph centered on the footprint (same as an operational building). Below the glyph — or centered on the footprint if the building has no glyph — a construction progress percentage is shown (integer, e.g. `42%`), increasing from 0% to 100% as construction completes. - REQ-UI-CONSTRUCTION-PROGRESS: Construction sites display the building's glyph centered on the footprint (same as an operational building). Below the glyph — or centered on the footprint if the building has no glyph — a construction progress percentage is shown (integer, e.g. `42%`), increasing from 0% to 100% as construction completes.
- REQ-UI-PORT-GLYPH: Every output port of every building is indicated by a directional glyph drawn on the port's tile. The glyph is a `>` rotated to face the port's exit direction (`>` for East, `^` for North, `<` for West, `v` for South). It is drawn at the midpoint between the tile center and the tile edge that the port exits through (i.e. halfway from center toward the exit edge). The indicator is rendered for all building states: operational buildings, construction sites, and the builder-mode ghost. Buildings with multiple output ports (e.g. splitters) show one indicator per port. - REQ-UI-PORT-GLYPH: Every output port of every building is indicated by a directional glyph drawn on the port's tile. The glyph is a `>` rotated to face the port's exit direction (`>` for East, `^` for North, `<` for West, `v` for South). It is drawn at the midpoint between the tile center and the tile edge that the port exits through (i.e. halfway from center toward the exit edge). The indicator is rendered for all building states: operational buildings, construction sites, and the builder-mode ghost. Buildings with multiple output ports (e.g. splitters) show one indicator per port.
- REQ-UI-NO-ZOOM: The view has a fixed zoom level; the player cannot zoom in or out. - REQ-UI-NO-ZOOM: The view has a fixed zoom level; the player cannot zoom in or out.
- REQ-UI-BLUEPRINT-TOAST: When a blueprint is unlocked or leveled up (REQ-DEF-BLUEPRINT-DROP), a transient notification toast appears in the top-right corner of the game world view for 4 seconds and then fades out. `<Ship Name>` in the text below is the blueprint's `ships.toml [ship.blueprint].display_name`. Toast text: - REQ-UI-SCHEMATIC-TOAST: When a schematic is unlocked or leveled up (REQ-DEF-SCHEMATIC-DROP), a transient notification toast appears in the top-right corner of the game world view for 4 seconds and then fades out. `<Ship Name>` in the text below is the schematic's `ships.toml [ship.schematic].display_name`. Toast text:
- **New unlock**: `Blueprint unlocked: <Ship Name>` - **New unlock**: `Schematic unlocked: <Ship Name>`
- **Level-up (duplicate drop)**: `<Ship Name> production level → N` (where N is the new level). - **Level-up (duplicate drop)**: `<Ship Name> production level → N` (where N is the new level).
If multiple toasts arrive in close succession, they stack vertically in a queue (most recent at the top) and each fades out independently after its own 4-second lifetime. If multiple toasts arrive in close succession, they stack vertically in a queue (most recent at the top) and each fades out independently after its own 4-second lifetime.
@@ -208,11 +208,11 @@ The screen is divided into three vertical sections:
### Selected Building Panel ### Selected Building Panel
- REQ-UI-EMPTY-SELECTION: When no building is selected, the panel is empty. - REQ-UI-EMPTY-SELECTION: When no building is selected, the panel is empty.
- REQ-UI-SINGLE-SELECTION: When one building is selected, the panel shows: building name, current recipe or blueprint selection, input buffer contents, and output buffer contents. Buffer counts are displayed as `a/b` where `a` is the current item count and `b` is the per-cycle amount (items consumed per run for inputs; items produced per run for outputs). - REQ-UI-SINGLE-SELECTION: When one building is selected, the panel shows: building name, current recipe or schematic selection, input buffer contents, and output buffer contents. Buffer counts are displayed as `a/b` where `a` is the current item count and `b` is the per-cycle amount (items consumed per run for inputs; items produced per run for outputs).
- REQ-UI-PRODUCTION-PROGRESS: For buildings that produce items or ships (miner, smelter, assembler, reprocessing plant, shipyard), the selected building panel also shows: (a) the cycle time of the currently selected recipe or blueprint in seconds, and (b) the completion percentage of the active production cycle as an integer (e.g. `42%`), or the text `idle` when no production cycle is active. When no recipe or blueprint is selected, neither the cycle time nor the progress indicator is shown. - REQ-UI-PRODUCTION-PROGRESS: For buildings that produce items or ships (miner, smelter, assembler, reprocessing plant, shipyard), the selected building panel also shows: (a) the cycle time of the currently selected recipe or schematic in seconds, and (b) the completion percentage of the active production cycle as an integer (e.g. `42%`), or the text `idle` when no production cycle is active. When no recipe or schematic is selected, neither the cycle time nor the progress indicator is shown.
- REQ-UI-MULTI-SELECT: The player selects multiple buildings by box-drag or by Ctrl+clicking individual buildings to add or remove them from the selection. - REQ-UI-MULTI-SELECT: The player selects multiple buildings by box-drag or by Ctrl+clicking individual buildings to add or remove them from the selection.
- REQ-UI-MULTI-SELECTION: When multiple buildings are selected, the panel shows how many of each building type are selected. No per-building detail is shown. - REQ-UI-MULTI-SELECTION: When multiple buildings are selected, the panel shows how many of each building type are selected. No per-building detail is shown.
- REQ-UI-CONFIG-INLINE: Recipe, blueprint, ship stance, and target priority configuration for a selected building is shown and changed inline within this panel. - REQ-UI-CONFIG-INLINE: Recipe, schematic, ship stance, and target priority configuration for a selected building is shown and changed inline within this panel.
- REQ-UI-BELT-CLEAR: When one or more belt, splitter, tunnel entry, or tunnel exit tiles are selected, the panel shows a "Clear" button that removes all items from the selected tiles. Clearing a tunnel entry or exit also discards all items currently in transit through that tunnel (REQ-BLD-TUNNEL-TRANSIT). This can be used to resolve stalled belts, splitters, and tunnels. - REQ-UI-BELT-CLEAR: When one or more belt, splitter, tunnel entry, or tunnel exit tiles are selected, the panel shows a "Clear" button that removes all items from the selected tiles. Clearing a tunnel entry or exit also discards all items currently in transit through that tunnel (REQ-BLD-TUNNEL-TRANSIT). This can be used to resolve stalled belts, splitters, and tunnels.
### Build Button Grid ### Build Button Grid

View File

@@ -358,17 +358,17 @@ ShipsConfig ConfigLoader::loadShips(const std::string& path)
def.id = requireString(mt["id"], file, elemPath + ".id"); def.id = requireString(mt["id"], file, elemPath + ".id");
def.availableFromStart = requireBool(mt["available_from_start"], file, elemPath + ".available_from_start"); def.availableFromStart = requireBool(mt["available_from_start"], file, elemPath + ".available_from_start");
// Blueprint // Schematic
{ {
const std::string bpPath = elemPath + ".blueprint"; const std::string bpPath = elemPath + ".schematic";
const toml::table& bpTable = requireTable(mt["blueprint"], file, bpPath); const toml::table& bpTable = requireTable(mt["schematic"], file, bpPath);
toml::table& bpMt = const_cast<toml::table&>(bpTable); toml::table& bpMt = const_cast<toml::table&>(bpTable);
const toml::array& materials = requireArray(bpMt["materials"], file, bpPath + ".materials"); const toml::array& materials = requireArray(bpMt["materials"], file, bpPath + ".materials");
def.blueprint.materials = parseIngredients(materials, file, bpPath + ".materials"); def.schematic.materials = parseIngredients(materials, file, bpPath + ".materials");
def.blueprint.playerProductionLevel = static_cast<int>(requireInt( def.schematic.playerProductionLevel = static_cast<int>(requireInt(
bpMt["player_production_level"], file, bpPath + ".player_production_level")); bpMt["player_production_level"], file, bpPath + ".player_production_level"));
def.blueprint.productionTimeSeconds = requireDouble( def.schematic.productionTimeSeconds = requireDouble(
bpMt["production_time_seconds"], file, bpPath + ".production_time_seconds"); bpMt["production_time_seconds"], file, bpPath + ".production_time_seconds");
} }

View File

@@ -7,9 +7,9 @@
#include "Formula.h" #include "Formula.h"
#include "RecipesConfig.h" // for RecipeIngredient #include "RecipesConfig.h" // for RecipeIngredient
// Build materials and initial per-blueprint production level // Build materials and initial per-schematic production level
// (REQ-BLD-SHIPYARD, REQ-DEF-BLUEPRINT-DROP). // (REQ-BLD-SHIPYARD, REQ-DEF-SCHEMATIC-DROP).
struct ShipBlueprint struct ShipSchematic
{ {
std::vector<RecipeIngredient> materials; std::vector<RecipeIngredient> materials;
int playerProductionLevel; int playerProductionLevel;
@@ -65,7 +65,7 @@ struct ShipDef
std::string id; std::string id;
bool availableFromStart; bool availableFromStart;
ShipBlueprint blueprint; ShipSchematic schematic;
ShipThreat threat; ShipThreat threat;
ShipHealth health; ShipHealth health;
ShipMovement movement; ShipMovement movement;

View File

@@ -1,14 +0,0 @@
#pragma once
#include <string>
// Emitted in tick step 9 (Deaths & loot) when a destroyed enemy-defence-station
// set awards a blueprint (REQ-DEF-BLUEPRINT-DROP). The UI renders a toast
// (REQ-UI-BLUEPRINT-TOAST); wasNewUnlock chooses between the "unlocked" and
// "level -> N" wording.
struct BlueprintDropEvent
{
std::string blueprintId; // matches ShipDef::id in the config.
int newLevel;
bool wasNewUnlock;
};

View File

@@ -9,7 +9,7 @@ SET(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/Port.h ${CMAKE_CURRENT_SOURCE_DIR}/Port.h
${CMAKE_CURRENT_SOURCE_DIR}/MovementIntent.h ${CMAKE_CURRENT_SOURCE_DIR}/MovementIntent.h
${CMAKE_CURRENT_SOURCE_DIR}/FireEvent.h ${CMAKE_CURRENT_SOURCE_DIR}/FireEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintDropEvent.h ${CMAKE_CURRENT_SOURCE_DIR}/SchematicDropEvent.h
PARENT_SCOPE PARENT_SCOPE
) )

View File

@@ -0,0 +1,14 @@
#pragma once
#include <string>
// Emitted in tick step 9 (Deaths & loot) when a destroyed enemy-defence-station
// set awards a schematic (REQ-DEF-SCHEMATIC-DROP). The UI renders a toast
// (REQ-UI-SCHEMATIC-TOAST); wasNewUnlock chooses between the "unlocked" and
// "level -> N" wording.
struct SchematicDropEvent
{
std::string schematicId; // matches ShipDef::id in the config.
int newLevel;
bool wasNewUnlock;
};

View File

@@ -111,7 +111,7 @@ void BuildingSystem::initShipyardBuffers(Building& b) const
{ {
return; return;
} }
for (const RecipeIngredient& ing : def->blueprint.materials) for (const RecipeIngredient& ing : def->schematic.materials)
{ {
const ItemType type{ing.item}; const ItemType type{ing.item};
b.inputBuffer.counts[type] = 0; b.inputBuffer.counts[type] = 0;
@@ -666,7 +666,7 @@ void BuildingSystem::tickShipyardProduction(Tick currentTick)
// Idle: check if all materials are available to start a new cycle. // Idle: check if all materials are available to start a new cycle.
bool inputsOk = true; bool inputsOk = true;
for (const RecipeIngredient& ing : shipDef->blueprint.materials) for (const RecipeIngredient& ing : shipDef->schematic.materials)
{ {
const ItemType type{ing.item}; const ItemType type{ing.item};
const std::map<ItemType, int>::const_iterator it = const std::map<ItemType, int>::const_iterator it =
@@ -684,7 +684,7 @@ void BuildingSystem::tickShipyardProduction(Tick currentTick)
} }
// Consume materials and start the production cycle. // Consume materials and start the production cycle.
for (const RecipeIngredient& ing : shipDef->blueprint.materials) for (const RecipeIngredient& ing : shipDef->schematic.materials)
{ {
building.inputBuffer.counts[ItemType{ing.item}] -= ing.amount; building.inputBuffer.counts[ItemType{ing.item}] -= ing.amount;
} }
@@ -692,7 +692,7 @@ void BuildingSystem::tickShipyardProduction(Tick currentTick)
Production prod; Production prod;
prod.recipeId = building.recipeId; prod.recipeId = building.recipeId;
prod.completesAt = currentTick prod.completesAt = currentTick
+ secondsToTicks(shipDef->blueprint.productionTimeSeconds); + secondsToTicks(shipDef->schematic.productionTimeSeconds);
building.production = std::move(prod); building.production = std::move(prod);
} }
} }

View File

@@ -46,7 +46,7 @@ public:
// unknown ids. // unknown ids.
int demolish(EntityId id); int demolish(EntityId id);
// Set the recipe (or blueprint id for shipyard) on a building or queued // Set the recipe (or schematic id for shipyard) on a building or queued
// construction site. Clears both buffers on an operational building. // construction site. Clears both buffers on an operational building.
void setRecipe(EntityId id, const std::string& recipeId); void setRecipe(EntityId id, const std::string& recipeId);

View File

@@ -75,7 +75,7 @@ struct Ship
float maxHp; float maxHp;
float speedPerTick; // pre-evaluated from speedFormula / kTickRateHz float speedPerTick; // pre-evaluated from speedFormula / kTickRateHz
int level; int level;
std::string blueprintId; std::string schematicId;
bool isEnemy = false; // true for enemy-faction ships (used by behavior systems) bool isEnemy = false; // true for enemy-faction ships (used by behavior systems)

View File

@@ -18,11 +18,11 @@ ShipSystem::ShipSystem(const GameConfig& config,
{ {
} }
const ShipDef* ShipSystem::findShipDef(const std::string& blueprintId) const const ShipDef* ShipSystem::findShipDef(const std::string& schematicId) const
{ {
for (const ShipDef& def : m_config.ships.ships) for (const ShipDef& def : m_config.ships.ships)
{ {
if (def.id == blueprintId) if (def.id == schematicId)
{ {
return &def; return &def;
} }
@@ -30,10 +30,10 @@ const ShipDef* ShipSystem::findShipDef(const std::string& blueprintId) const
return nullptr; return nullptr;
} }
EntityId ShipSystem::spawn(const std::string& blueprintId, int level, QVector2D position, EntityId ShipSystem::spawn(const std::string& schematicId, int level, QVector2D position,
bool isEnemy) bool isEnemy)
{ {
const ShipDef* def = findShipDef(blueprintId); const ShipDef* def = findShipDef(schematicId);
assert(def != nullptr); assert(def != nullptr);
const double x = static_cast<double>(level); const double x = static_cast<double>(level);
@@ -48,7 +48,7 @@ EntityId ShipSystem::spawn(const std::string& blueprintId, int level, QVector2D
def->movement.speedFormula.evaluate(x)) def->movement.speedFormula.evaluate(x))
/ static_cast<float>(kTickRateHz); / static_cast<float>(kTickRateHz);
ship.level = level; ship.level = level;
ship.blueprintId = blueprintId; ship.schematicId = schematicId;
ship.isEnemy = isEnemy; ship.isEnemy = isEnemy;
ship.intent = MovementIntent{0, QVector2D(0.0f, 0.0f)}; ship.intent = MovementIntent{0, QVector2D(0.0f, 0.0f)};

View File

@@ -19,7 +19,7 @@ public:
std::function<EntityId()> allocateId); std::function<EntityId()> allocateId);
// isEnemy defaults to false; set true for enemy-faction ships (step 7 wave spawning). // isEnemy defaults to false; set true for enemy-faction ships (step 7 wave spawning).
EntityId spawn(const std::string& blueprintId, int level, QVector2D position, EntityId spawn(const std::string& schematicId, int level, QVector2D position,
bool isEnemy = false); bool isEnemy = false);
void despawn(EntityId id); void despawn(EntityId id);
@@ -52,7 +52,7 @@ public:
bool damageShip(EntityId id, float amount); bool damageShip(EntityId id, float amount);
private: private:
const ShipDef* findShipDef(const std::string& blueprintId) const; const ShipDef* findShipDef(const std::string& schematicId) const;
// True if the entity identified by id is alive and within range of ship. // True if the entity identified by id is alive and within range of ship.
// Searches both the ship list and (for buildings) the supplied BuildingSystem. // Searches both the ship list and (for buildings) the supplied BuildingSystem.

View File

@@ -30,9 +30,9 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
[this]() { return allocateId(); }, [this]() { return allocateId(); },
[this](int amount) { m_buildingBlocksStock += amount; }, [this](int amount) { m_buildingBlocksStock += amount; },
[this](const std::string& id, QVector2D pos) { [this](const std::string& id, QVector2D pos) {
const std::map<std::string, BlueprintState>::const_iterator it = const std::map<std::string, SchematicState>::const_iterator it =
m_blueprintLevels.find(id); m_schematicLevels.find(id);
if (it == m_blueprintLevels.end() || !it->second.unlocked) if (it == m_schematicLevels.end() || !it->second.unlocked)
{ {
return; return;
} }
@@ -44,13 +44,13 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
m_waveSystem = std::make_unique<WaveSystem>(m_config, m_rng); m_waveSystem = std::make_unique<WaveSystem>(m_config, m_rng);
m_combatSystem = std::make_unique<CombatSystem>(m_config); m_combatSystem = std::make_unique<CombatSystem>(m_config);
// Initialize blueprint unlock state. // Initialize schematic unlock state.
for (const ShipDef& def : m_config.ships.ships) for (const ShipDef& def : m_config.ships.ships)
{ {
BlueprintState state; SchematicState state;
state.unlocked = def.availableFromStart; state.unlocked = def.availableFromStart;
state.level = def.availableFromStart ? def.blueprint.playerProductionLevel : 0; state.level = def.availableFromStart ? def.schematic.playerProductionLevel : 0;
m_blueprintLevels[def.id] = state; m_schematicLevels[def.id] = state;
} }
placeInitialStructures(); placeInitialStructures();
@@ -82,7 +82,7 @@ void Simulation::reset(unsigned int seed)
m_currentEnemyStationIds[0] = kInvalidEntityId; m_currentEnemyStationIds[0] = kInvalidEntityId;
m_currentEnemyStationIds[1] = kInvalidEntityId; m_currentEnemyStationIds[1] = kInvalidEntityId;
m_fireEvents.clear(); m_fireEvents.clear();
m_blueprintDropEvents.clear(); m_schematicDropEvents.clear();
m_beltSystem = BeltSystem(m_config.world.beltSpeedTilesPerSecond); m_beltSystem = BeltSystem(m_config.world.beltSpeedTilesPerSecond);
m_buildingSystem = std::make_unique<BuildingSystem>( m_buildingSystem = std::make_unique<BuildingSystem>(
@@ -91,9 +91,9 @@ void Simulation::reset(unsigned int seed)
[this]() { return allocateId(); }, [this]() { return allocateId(); },
[this](int amount) { m_buildingBlocksStock += amount; }, [this](int amount) { m_buildingBlocksStock += amount; },
[this](const std::string& id, QVector2D pos) { [this](const std::string& id, QVector2D pos) {
const std::map<std::string, BlueprintState>::const_iterator it = const std::map<std::string, SchematicState>::const_iterator it =
m_blueprintLevels.find(id); m_schematicLevels.find(id);
if (it == m_blueprintLevels.end() || !it->second.unlocked) if (it == m_schematicLevels.end() || !it->second.unlocked)
{ {
return; return;
} }
@@ -105,13 +105,13 @@ void Simulation::reset(unsigned int seed)
m_waveSystem = std::make_unique<WaveSystem>(m_config, m_rng); m_waveSystem = std::make_unique<WaveSystem>(m_config, m_rng);
m_combatSystem = std::make_unique<CombatSystem>(m_config); m_combatSystem = std::make_unique<CombatSystem>(m_config);
m_blueprintLevels.clear(); m_schematicLevels.clear();
for (const ShipDef& def : m_config.ships.ships) for (const ShipDef& def : m_config.ships.ships)
{ {
BlueprintState state; SchematicState state;
state.unlocked = def.availableFromStart; state.unlocked = def.availableFromStart;
state.level = def.availableFromStart ? def.blueprint.playerProductionLevel : 0; state.level = def.availableFromStart ? def.schematic.playerProductionLevel : 0;
m_blueprintLevels[def.id] = state; m_schematicLevels[def.id] = state;
} }
placeInitialStructures(); placeInitialStructures();
@@ -290,7 +290,7 @@ void Simulation::tickDeathsAndLoot()
// Look up scrap drop amount from config. // Look up scrap drop amount from config.
for (const ShipDef& def : m_config.ships.ships) for (const ShipDef& def : m_config.ships.ships)
{ {
if (def.id == s->blueprintId && def.loot.scrapDrop > 0) if (def.id == s->schematicId && def.loot.scrapDrop > 0)
{ {
const Tick despawnAt = m_currentTick const Tick despawnAt = m_currentTick
+ secondsToTicks(m_config.world.scrapDespawnSeconds); + secondsToTicks(m_config.world.scrapDespawnSeconds);
@@ -366,11 +366,11 @@ void Simulation::tickDeathsAndLoot()
{ {
m_waveSystem->applyPush(); m_waveSystem->applyPush();
placeEnemyStationSet(m_waveSystem->generation()); placeEnemyStationSet(m_waveSystem->generation());
awardBlueprintDrop(); awardSchematicDrop();
} }
} }
void Simulation::awardBlueprintDrop() void Simulation::awardSchematicDrop()
{ {
std::vector<std::string> ids; std::vector<std::string> ids;
ids.reserve(m_config.ships.ships.size()); ids.reserve(m_config.ships.ships.size());
@@ -382,16 +382,16 @@ void Simulation::awardBlueprintDrop()
std::uniform_int_distribution<int> dist(0, static_cast<int>(ids.size()) - 1); std::uniform_int_distribution<int> dist(0, static_cast<int>(ids.size()) - 1);
const std::string chosen = ids[static_cast<std::size_t>(dist(m_rng))]; const std::string chosen = ids[static_cast<std::size_t>(dist(m_rng))];
BlueprintState& state = m_blueprintLevels.at(chosen); SchematicState& state = m_schematicLevels.at(chosen);
const bool wasNew = !state.unlocked; const bool wasNew = !state.unlocked;
state.unlocked = true; state.unlocked = true;
state.level += 1; state.level += 1;
BlueprintDropEvent evt; SchematicDropEvent evt;
evt.blueprintId = chosen; evt.schematicId = chosen;
evt.newLevel = state.level; evt.newLevel = state.level;
evt.wasNewUnlock = wasNew; evt.wasNewUnlock = wasNew;
m_blueprintDropEvents.push_back(evt); m_schematicDropEvents.push_back(evt);
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -405,10 +405,10 @@ std::vector<FireEvent> Simulation::drainFireEvents()
return result; return result;
} }
std::vector<BlueprintDropEvent> Simulation::drainBlueprintDropEvents() std::vector<SchematicDropEvent> Simulation::drainSchematicDropEvents()
{ {
std::vector<BlueprintDropEvent> result; std::vector<SchematicDropEvent> result;
result.swap(m_blueprintDropEvents); result.swap(m_schematicDropEvents);
return result; return result;
} }
@@ -436,22 +436,22 @@ double Simulation::threatLevel() const
return m_waveSystem->threatLevel(); return m_waveSystem->threatLevel();
} }
int Simulation::blueprintLevel(const std::string& shipId) const int Simulation::schematicLevel(const std::string& shipId) const
{ {
const std::map<std::string, BlueprintState>::const_iterator it = const std::map<std::string, SchematicState>::const_iterator it =
m_blueprintLevels.find(shipId); m_schematicLevels.find(shipId);
if (it == m_blueprintLevels.end()) if (it == m_schematicLevels.end())
{ {
return 0; return 0;
} }
return it->second.level; return it->second.level;
} }
bool Simulation::isBlueprintUnlocked(const std::string& shipId) const bool Simulation::isSchematicUnlocked(const std::string& shipId) const
{ {
const std::map<std::string, BlueprintState>::const_iterator it = const std::map<std::string, SchematicState>::const_iterator it =
m_blueprintLevels.find(shipId); m_schematicLevels.find(shipId);
if (it == m_blueprintLevels.end()) if (it == m_schematicLevels.end())
{ {
return false; return false;
} }

View File

@@ -9,7 +9,7 @@
#include <QPoint> #include <QPoint>
#include "BeltSystem.h" #include "BeltSystem.h"
#include "BlueprintDropEvent.h" #include "SchematicDropEvent.h"
#include "BuildingType.h" #include "BuildingType.h"
#include "EntityId.h" #include "EntityId.h"
#include "FireEvent.h" #include "FireEvent.h"
@@ -44,17 +44,17 @@ public:
// internal queue. Call once per rendered frame (REQ-SHP-FIRING-BEAM). // internal queue. Call once per rendered frame (REQ-SHP-FIRING-BEAM).
std::vector<FireEvent> drainFireEvents(); std::vector<FireEvent> drainFireEvents();
// Returns all blueprint drop events since the last drain. // Returns all schematic drop events since the last drain.
std::vector<BlueprintDropEvent> drainBlueprintDropEvents(); std::vector<SchematicDropEvent> drainSchematicDropEvents();
Tick currentTick() const; Tick currentTick() const;
int buildingBlocksStock() const; int buildingBlocksStock() const;
bool isGameOver() const; bool isGameOver() const;
double threatLevel() const; double threatLevel() const;
// Blueprint state queries. // Schematic state queries.
int blueprintLevel(const std::string& shipId) const; int schematicLevel(const std::string& shipId) const;
bool isBlueprintUnlocked(const std::string& shipId) const; bool isSchematicUnlocked(const std::string& shipId) const;
// Checks affordability, deducts building blocks, and places the building. // Checks affordability, deducts building blocks, and places the building.
// Returns the new entity id, or kInvalidEntityId if blocks are insufficient. // Returns the new entity id, or kInvalidEntityId if blocks are insufficient.
@@ -85,8 +85,8 @@ private:
// Tick step 9: remove dead ships and buildings, drop scrap, handle push. // Tick step 9: remove dead ships and buildings, drop scrap, handle push.
void tickDeathsAndLoot(); void tickDeathsAndLoot();
// Award a random blueprint drop (REQ-DEF-BLUEPRINT-DROP) and emit the event. // Award a random schematic drop (REQ-DEF-SCHEMATIC-DROP) and emit the event.
void awardBlueprintDrop(); void awardSchematicDrop();
GameConfig m_config; GameConfig m_config;
std::mt19937 m_rng; std::mt19937 m_rng;
@@ -102,13 +102,13 @@ private:
EntityId m_playerStation2Id; EntityId m_playerStation2Id;
EntityId m_currentEnemyStationIds[2]; EntityId m_currentEnemyStationIds[2];
// Blueprint unlock state (REQ-DEF-BLUEPRINT-DROP). // Schematic unlock state (REQ-DEF-SCHEMATIC-DROP).
struct BlueprintState struct SchematicState
{ {
bool unlocked; bool unlocked;
int level; int level;
}; };
std::map<std::string, BlueprintState> m_blueprintLevels; std::map<std::string, SchematicState> m_schematicLevels;
BeltSystem m_beltSystem; BeltSystem m_beltSystem;
std::unique_ptr<BuildingSystem> m_buildingSystem; std::unique_ptr<BuildingSystem> m_buildingSystem;
@@ -118,5 +118,5 @@ private:
std::unique_ptr<CombatSystem> m_combatSystem; std::unique_ptr<CombatSystem> m_combatSystem;
std::vector<FireEvent> m_fireEvents; std::vector<FireEvent> m_fireEvents;
std::vector<BlueprintDropEvent> m_blueprintDropEvents; std::vector<SchematicDropEvent> m_schematicDropEvents;
}; };

View File

@@ -33,7 +33,7 @@ void WaveSystem::tickWaveScheduler(Tick currentTick, ShipSystem& ships,
{ {
if (currentTick >= entry.spawnAt) if (currentTick >= entry.spawnAt)
{ {
ships.spawn(entry.blueprintId, entry.level, entry.position, ships.spawn(entry.schematicId, entry.level, entry.position,
/*isEnemy=*/true); /*isEnemy=*/true);
} }
else else
@@ -90,7 +90,7 @@ std::vector<WaveSystem::SpawnEntry> WaveSystem::composeWave(Tick currentTick,
// Build eligible ship list with their costs at the current level. // Build eligible ship list with their costs at the current level.
struct EligibleShip struct EligibleShip
{ {
std::string blueprintId; std::string schematicId;
double cost; double cost;
}; };
std::vector<EligibleShip> eligible; std::vector<EligibleShip> eligible;
@@ -100,7 +100,7 @@ std::vector<WaveSystem::SpawnEntry> WaveSystem::composeWave(Tick currentTick,
if (cost > 0.0) if (cost > 0.0)
{ {
EligibleShip es; EligibleShip es;
es.blueprintId = def.id; es.schematicId = def.id;
es.cost = cost; es.cost = cost;
eligible.push_back(es); eligible.push_back(es);
} }
@@ -151,7 +151,7 @@ std::vector<WaveSystem::SpawnEntry> WaveSystem::composeWave(Tick currentTick,
budget -= chosen.cost; budget -= chosen.cost;
SpawnEntry entry; SpawnEntry entry;
entry.blueprintId = chosen.blueprintId; entry.schematicId = chosen.schematicId;
entry.level = shipLevel; entry.level = shipLevel;
entry.spawnAt = 0; // set below after all picks are done entry.spawnAt = 0; // set below after all picks are done
entry.position = QVector2D(xDist(m_rng), entry.position = QVector2D(xDist(m_rng),

View File

@@ -40,7 +40,7 @@ public:
private: private:
struct SpawnEntry struct SpawnEntry
{ {
std::string blueprintId; std::string schematicId;
int level; int level;
Tick spawnAt; Tick spawnAt;
QVector2D position; QVector2D position;

View File

@@ -221,7 +221,7 @@ TEST_CASE("CombatSystem: player station fires at enemy ship in range", "[combat]
} }
} }
// Find a combat ship blueprint for the enemy. // Find a combat ship schematic for the enemy.
const ShipDef* combatDef = findCombatShip(sim.config()); const ShipDef* combatDef = findCombatShip(sim.config());
REQUIRE(combatDef != nullptr); REQUIRE(combatDef != nullptr);

View File

@@ -17,11 +17,11 @@ static GameConfig loadConfig()
return ConfigLoader::loadFromDirectory(DOTA_FACTORY_CONFIG_DIR); return ConfigLoader::loadFromDirectory(DOTA_FACTORY_CONFIG_DIR);
} }
static const ShipDef* findAvailableBlueprint(const GameConfig& cfg) static const ShipDef* findAvailableSchematic(const GameConfig& cfg)
{ {
for (const ShipDef& def : cfg.ships.ships) for (const ShipDef& def : cfg.ships.ships)
{ {
if (def.availableFromStart && !def.blueprint.materials.empty()) if (def.availableFromStart && !def.schematic.materials.empty())
{ {
return &def; return &def;
} }
@@ -59,7 +59,7 @@ static void fillMaterials(Simulation& sim, EntityId yardId, const ShipDef& def)
{ {
return; return;
} }
for (const RecipeIngredient& ing : def.blueprint.materials) for (const RecipeIngredient& ing : def.schematic.materials)
{ {
b.inputBuffer.counts[ItemType{ing.item}] = ing.amount; b.inputBuffer.counts[ItemType{ing.item}] = ing.amount;
} }
@@ -75,7 +75,7 @@ TEST_CASE("Shipyard: spawns a player ship after production cycle completes",
{ {
Simulation sim(loadConfig(), 42); Simulation sim(loadConfig(), 42);
const ShipDef* def = findAvailableBlueprint(sim.config()); const ShipDef* def = findAvailableSchematic(sim.config());
REQUIRE(def != nullptr); REQUIRE(def != nullptr);
const BuildingDef* yardDef = findShipyardDef(sim.config()); const BuildingDef* yardDef = findShipyardDef(sim.config());
REQUIRE(yardDef != nullptr); REQUIRE(yardDef != nullptr);
@@ -93,7 +93,7 @@ TEST_CASE("Shipyard: spawns a player ship after production cycle completes",
REQUIRE(static_cast<int>(sim.ships().allShips().size()) == shipsBefore); REQUIRE(static_cast<int>(sim.ships().allShips().size()) == shipsBefore);
// Tick until the cycle completes. // Tick until the cycle completes.
const Tick cycleTicks = secondsToTicks(def->blueprint.productionTimeSeconds); const Tick cycleTicks = secondsToTicks(def->schematic.productionTimeSeconds);
for (Tick i = 1; i < cycleTicks; ++i) for (Tick i = 1; i < cycleTicks; ++i)
{ {
sim.tick(); sim.tick();
@@ -107,7 +107,7 @@ TEST_CASE("Shipyard: spawns a player ship after production cycle completes",
bool foundPlayerShip = false; bool foundPlayerShip = false;
for (const Ship& ship : sim.ships().allShips()) for (const Ship& ship : sim.ships().allShips())
{ {
if (!ship.isEnemy && ship.blueprintId == def->id) if (!ship.isEnemy && ship.schematicId == def->id)
{ {
foundPlayerShip = true; foundPlayerShip = true;
break; break;
@@ -116,7 +116,7 @@ TEST_CASE("Shipyard: spawns a player ship after production cycle completes",
REQUIRE(foundPlayerShip); REQUIRE(foundPlayerShip);
} }
TEST_CASE("Shipyard: does not spawn without a blueprint set", "[shipyard]") TEST_CASE("Shipyard: does not spawn without a schematic set", "[shipyard]")
{ {
Simulation sim(loadConfig(), 42); Simulation sim(loadConfig(), 42);
@@ -136,7 +136,7 @@ TEST_CASE("Shipyard: does not spawn with insufficient materials", "[shipyard]")
{ {
Simulation sim(loadConfig(), 42); Simulation sim(loadConfig(), 42);
const ShipDef* def = findAvailableBlueprint(sim.config()); const ShipDef* def = findAvailableSchematic(sim.config());
REQUIRE(def != nullptr); REQUIRE(def != nullptr);
const BuildingDef* yardDef = findShipyardDef(sim.config()); const BuildingDef* yardDef = findShipyardDef(sim.config());
REQUIRE(yardDef != nullptr); REQUIRE(yardDef != nullptr);
@@ -147,7 +147,7 @@ TEST_CASE("Shipyard: does not spawn with insufficient materials", "[shipyard]")
sim.buildings().setRecipe(yardId, def->id); sim.buildings().setRecipe(yardId, def->id);
// Materials remain at zero (default after setRecipe); no cycle starts. // Materials remain at zero (default after setRecipe); no cycle starts.
const Tick cycleTicks = secondsToTicks(def->blueprint.productionTimeSeconds); const Tick cycleTicks = secondsToTicks(def->schematic.productionTimeSeconds);
for (Tick i = 0; i <= cycleTicks; ++i) for (Tick i = 0; i <= cycleTicks; ++i)
{ {
sim.tick(); sim.tick();
@@ -160,7 +160,7 @@ TEST_CASE("Shipyard: spawns a second ship after materials replenished", "[shipya
{ {
Simulation sim(loadConfig(), 42); Simulation sim(loadConfig(), 42);
const ShipDef* def = findAvailableBlueprint(sim.config()); const ShipDef* def = findAvailableSchematic(sim.config());
REQUIRE(def != nullptr); REQUIRE(def != nullptr);
const BuildingDef* yardDef = findShipyardDef(sim.config()); const BuildingDef* yardDef = findShipyardDef(sim.config());
REQUIRE(yardDef != nullptr); REQUIRE(yardDef != nullptr);
@@ -168,7 +168,7 @@ TEST_CASE("Shipyard: spawns a second ship after materials replenished", "[shipya
const EntityId yardId = placeShipyard(sim, *yardDef); const EntityId yardId = placeShipyard(sim, *yardDef);
sim.buildings().setRecipe(yardId, def->id); sim.buildings().setRecipe(yardId, def->id);
const Tick cycleTicks = secondsToTicks(def->blueprint.productionTimeSeconds); const Tick cycleTicks = secondsToTicks(def->schematic.productionTimeSeconds);
// First cycle: capture count immediately after the spawn tick. // First cycle: capture count immediately after the spawn tick.
fillMaterials(sim, yardId, *def); fillMaterials(sim, yardId, *def);

View File

@@ -61,11 +61,11 @@ TEST_CASE("Simulation::drainFireEvents clears queue on drain", "[simulation]")
REQUIRE(sim.drainFireEvents().empty()); REQUIRE(sim.drainFireEvents().empty());
} }
TEST_CASE("Simulation::drainBlueprintDropEvents returns empty initially", "[simulation]") TEST_CASE("Simulation::drainSchematicDropEvents returns empty initially", "[simulation]")
{ {
Simulation sim(loadConfig()); Simulation sim(loadConfig());
REQUIRE(sim.drainBlueprintDropEvents().empty()); REQUIRE(sim.drainSchematicDropEvents().empty());
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@@ -233,8 +233,8 @@ TEST_CASE("WaveSystem: only eligible ships (cost > 0) appear in waves", "[wave]"
{ {
if (!s.isEnemy) { continue; } if (!s.isEnemy) { continue; }
// salvage_ship and repair_ship have cost_formula = "0" and must not spawn. // salvage_ship and repair_ship have cost_formula = "0" and must not spawn.
REQUIRE(s.blueprintId != "salvage_ship"); REQUIRE(s.schematicId != "salvage_ship");
REQUIRE(s.blueprintId != "repair_ship"); REQUIRE(s.schematicId != "repair_ship");
} }
} }
@@ -266,7 +266,7 @@ TEST_CASE("WaveSystem: destroying both enemy stations triggers a push", "[wave]"
REQUIRE(enemyCount == 2); REQUIRE(enemyCount == 2);
} }
TEST_CASE("WaveSystem: push emits exactly one BlueprintDropEvent", "[wave]") TEST_CASE("WaveSystem: push emits exactly one SchematicDropEvent", "[wave]")
{ {
Simulation sim(loadConfig(), 42); Simulation sim(loadConfig(), 42);
@@ -280,11 +280,11 @@ TEST_CASE("WaveSystem: push emits exactly one BlueprintDropEvent", "[wave]")
sim.tick(); sim.tick();
const std::vector<BlueprintDropEvent> events = sim.drainBlueprintDropEvents(); const std::vector<SchematicDropEvent> events = sim.drainSchematicDropEvents();
REQUIRE(events.size() == 1); REQUIRE(events.size() == 1);
} }
TEST_CASE("WaveSystem: push blueprint drop awards a known ship id", "[wave]") TEST_CASE("WaveSystem: push schematic drop awards a known ship id", "[wave]")
{ {
Simulation sim(loadConfig(), 42); Simulation sim(loadConfig(), 42);
@@ -297,13 +297,13 @@ TEST_CASE("WaveSystem: push blueprint drop awards a known ship id", "[wave]")
}); });
sim.tick(); sim.tick();
const std::vector<BlueprintDropEvent> events = sim.drainBlueprintDropEvents(); const std::vector<SchematicDropEvent> events = sim.drainSchematicDropEvents();
REQUIRE(events.size() == 1); REQUIRE(events.size() == 1);
bool validId = false; bool validId = false;
for (const ShipDef& def : sim.config().ships.ships) for (const ShipDef& def : sim.config().ships.ships)
{ {
if (def.id == events[0].blueprintId) if (def.id == events[0].schematicId)
{ {
validId = true; validId = true;
break; break;

View File

@@ -2,7 +2,7 @@
id = "interceptor" id = "interceptor"
available_from_start = true available_from_start = true
[ship.blueprint] [ship.schematic]
materials = [{item = "iron_ingot", amount = 3}, {item = "circuit_board", amount = 1}] materials = [{item = "iron_ingot", amount = 3}, {item = "circuit_board", amount = 1}]
player_production_level = 3 player_production_level = 3
production_time_seconds = 10 production_time_seconds = 10
@@ -29,7 +29,7 @@ scrap_drop = 2
id = "destroyer" id = "destroyer"
available_from_start = true available_from_start = true
[ship.blueprint] [ship.schematic]
materials = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 2}] materials = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 2}]
player_production_level = 5 player_production_level = 5
production_time_seconds = 20 production_time_seconds = 20
@@ -56,7 +56,7 @@ scrap_drop = 4
id = "salvage_ship" id = "salvage_ship"
available_from_start = true available_from_start = true
[ship.blueprint] [ship.schematic]
materials = [{item = "iron_ingot", amount = 4}] materials = [{item = "iron_ingot", amount = 4}]
player_production_level = 3 player_production_level = 3
production_time_seconds = 10 production_time_seconds = 10
@@ -82,7 +82,7 @@ scrap_drop = 2
id = "repair_ship" id = "repair_ship"
available_from_start = false available_from_start = false
[ship.blueprint] [ship.schematic]
materials = [{item = "iron_ingot", amount = 4}, {item = "circuit_board", amount = 2}] materials = [{item = "iron_ingot", amount = 4}, {item = "circuit_board", amount = 2}]
player_production_level = 3 player_production_level = 3
production_time_seconds = 15 production_time_seconds = 15

View File

@@ -163,17 +163,17 @@ void GameWorldView::onFrame()
} }
} }
// Drain blueprint drop events → toasts // Drain schematic drop events → toasts
{ {
const std::vector<BlueprintDropEvent> drops = const std::vector<SchematicDropEvent> drops =
m_sim->drainBlueprintDropEvents(); m_sim->drainSchematicDropEvents();
for (const BlueprintDropEvent& ev : drops) for (const SchematicDropEvent& ev : drops)
{ {
const QString shipName = toDisplayName(ev.blueprintId); const QString shipName = toDisplayName(ev.schematicId);
ToastEntry toast; ToastEntry toast;
if (ev.wasNewUnlock) if (ev.wasNewUnlock)
{ {
toast.text = "Blueprint unlocked: " + shipName; toast.text = "Schematic unlocked: " + shipName;
} }
else else
{ {

View File

@@ -11,7 +11,7 @@
#include <QTimer> #include <QTimer>
#include <QVector2D> #include <QVector2D>
#include "BlueprintDropEvent.h" #include "SchematicDropEvent.h"
#include "BuildingType.h" #include "BuildingType.h"
#include "EntityId.h" #include "EntityId.h"
#include "FireEvent.h" #include "FireEvent.h"

View File

@@ -187,7 +187,7 @@ void SelectedBuildingPanel::buildSingle(EntityId id)
{ {
for (const ShipDef& def : m_config->ships.ships) for (const ShipDef& def : m_config->ships.ships)
{ {
if (m_sim->isBlueprintUnlocked(def.id)) if (m_sim->isSchematicUnlocked(def.id))
{ {
m_recipeCombo->addItem( m_recipeCombo->addItem(
QString::fromStdString(def.id), QString::fromStdString(def.id),
@@ -267,7 +267,7 @@ void SelectedBuildingPanel::refreshBuffers(const Building* b)
} }
else if (shipDef) else if (shipDef)
{ {
for (const RecipeIngredient& mat : shipDef->blueprint.materials) for (const RecipeIngredient& mat : shipDef->schematic.materials)
{ {
if (mat.item == entry.first.id) { perCycle = mat.amount; break; } if (mat.item == entry.first.id) { perCycle = mat.amount; break; }
} }
@@ -320,7 +320,7 @@ void SelectedBuildingPanel::refreshBuffers(const Building* b)
{ {
const double durationSeconds = recipe const double durationSeconds = recipe
? recipe->durationSeconds ? recipe->durationSeconds
: shipDef->blueprint.productionTimeSeconds; : shipDef->schematic.productionTimeSeconds;
bufText += QString("Cycle: %1 s\n").arg(durationSeconds, 0, 'f', 1); bufText += QString("Cycle: %1 s\n").arg(durationSeconds, 0, 'f', 1);