From fb83db98ab219ca4e9692fc725a75d8ba7fd03d8 Mon Sep 17 00:00:00 2001 From: Malte Langkabel Date: Sun, 26 Apr 2026 21:00:55 +0200 Subject: [PATCH] rename blueprint to schematic --- bin/config/ships.toml | 8 ++-- bin/config/visuals.toml | 4 +- docs/architecture.md | 12 +++--- docs/concept.md | 8 ++-- docs/requirements.md | 28 ++++++------- src/lib/config/ConfigLoader.cpp | 12 +++--- src/lib/config/ShipsConfig.h | 8 ++-- src/lib/core/BlueprintDropEvent.h | 14 ------- src/lib/core/CMakeLists.txt | 2 +- src/lib/core/SchematicDropEvent.h | 14 +++++++ src/lib/sim/BuildingSystem.cpp | 8 ++-- src/lib/sim/BuildingSystem.h | 2 +- src/lib/sim/Ship.h | 2 +- src/lib/sim/ShipSystem.cpp | 10 ++--- src/lib/sim/ShipSystem.h | 4 +- src/lib/sim/Simulation.cpp | 66 +++++++++++++++---------------- src/lib/sim/Simulation.h | 24 +++++------ src/lib/sim/WaveSystem.cpp | 8 ++-- src/lib/sim/WaveSystem.h | 2 +- src/test/CombatSystemTest.cpp | 2 +- src/test/ShipyardTest.cpp | 22 +++++------ src/test/SimulationTest.cpp | 4 +- src/test/WaveSystemTest.cpp | 14 +++---- src/test/config/ships.toml | 8 ++-- src/ui/GameWorldView.cpp | 12 +++--- src/ui/GameWorldView.h | 2 +- src/ui/SelectedBuildingPanel.cpp | 6 +-- 27 files changed, 153 insertions(+), 153 deletions(-) delete mode 100644 src/lib/core/BlueprintDropEvent.h create mode 100644 src/lib/core/SchematicDropEvent.h diff --git a/bin/config/ships.toml b/bin/config/ships.toml index 8299067..08d33db 100644 --- a/bin/config/ships.toml +++ b/bin/config/ships.toml @@ -2,7 +2,7 @@ id = "interceptor" available_from_start = true -[ship.blueprint] +[ship.schematic] materials = [{item = "iron_ingot", amount = 3}, {item = "circuit_board", amount = 1}] player_production_level = 3 production_time_seconds = 10 @@ -29,7 +29,7 @@ scrap_drop = 2 id = "destroyer" available_from_start = true -[ship.blueprint] +[ship.schematic] materials = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 2}] player_production_level = 5 production_time_seconds = 20 @@ -56,7 +56,7 @@ scrap_drop = 4 id = "salvage_ship" available_from_start = true -[ship.blueprint] +[ship.schematic] materials = [{item = "iron_ingot", amount = 4}] player_production_level = 3 production_time_seconds = 10 @@ -82,7 +82,7 @@ scrap_drop = 2 id = "repair_ship" available_from_start = false -[ship.blueprint] +[ship.schematic] materials = [{item = "iron_ingot", amount = 4}, {item = "circuit_board", amount = 2}] player_production_level = 3 production_time_seconds = 15 diff --git a/bin/config/visuals.toml b/bin/config/visuals.toml index 0b6282f..b1b6351 100644 --- a/bin/config/visuals.toml +++ b/bin/config/visuals.toml @@ -142,7 +142,7 @@ outline = "#201a14" # Ships # # 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] @@ -185,7 +185,7 @@ tile_highlight = "#ffffff22" # tile under cursor 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] diff --git a/docs/architecture.md b/docs/architecture.md index baf4a1d..c123a0d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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. - `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. -- `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 -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` 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). 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). -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`. 11. **Scrap despawn** — decrement scrap timers; remove expired scrap (REQ-RES-SCRAP-DROP). @@ -219,7 +219,7 @@ struct Ship { float hp; float maxHp; int level; - ShipBlueprintId blueprint; + ShipSchematicId schematic; // Capabilities std::optional weapon; @@ -280,7 +280,7 @@ The game world is rendered by a single `GameWorldView` widget that inherits `QOp ### 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) @@ -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). 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. -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 diff --git a/docs/concept.md b/docs/concept.md index 910efca..1fe776a 100644 --- a/docs/concept.md +++ b/docs/concept.md @@ -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. | | **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. | -| **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. | | **Splitter** | 1×1 | Splits a belt's flow into two outputs. | ## Ships & Shipyards - 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. -- New blueprints are unlocked as loot from destroyed enemy defence stations. +- The player clicks a shipyard to assign a schematic; the shipyard then automatically produces that ship type whenever the required materials are available. +- New schematics are unlocked as loot from destroyed enemy defence stations. - Ships are fully autonomous. Known roles: - **Combat ships** — travel right and engage enemies. - **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. - 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 diff --git a/docs/requirements.md b/docs/requirements.md index 32852c3..8a767bf 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -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. - **buildings.toml** — building block cost and construction time per building type. - **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. - **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-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-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-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: @@ -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-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-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-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-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 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-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. @@ -108,7 +108,7 @@ Output port indicators are not building tiles themselves. A building may have mo ## Ships - 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-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. @@ -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: - 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-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 @@ -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-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-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-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-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-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 - 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-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-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-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-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. `` in the text below is the blueprint's `ships.toml [ship.blueprint].display_name`. Toast text: - - **New unlock**: `Blueprint unlocked: ` +- 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. `` in the text below is the schematic's `ships.toml [ship.schematic].display_name`. Toast text: + - **New unlock**: `Schematic unlocked: ` - **Level-up (duplicate drop)**: ` 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. @@ -208,11 +208,11 @@ The screen is divided into three vertical sections: ### Selected Building Panel - 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-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-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 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-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. ### Build Button Grid diff --git a/src/lib/config/ConfigLoader.cpp b/src/lib/config/ConfigLoader.cpp index e771119..533e2b2 100644 --- a/src/lib/config/ConfigLoader.cpp +++ b/src/lib/config/ConfigLoader.cpp @@ -358,17 +358,17 @@ ShipsConfig ConfigLoader::loadShips(const std::string& path) def.id = requireString(mt["id"], file, elemPath + ".id"); def.availableFromStart = requireBool(mt["available_from_start"], file, elemPath + ".available_from_start"); - // Blueprint + // Schematic { - const std::string bpPath = elemPath + ".blueprint"; - const toml::table& bpTable = requireTable(mt["blueprint"], file, bpPath); + const std::string bpPath = elemPath + ".schematic"; + const toml::table& bpTable = requireTable(mt["schematic"], file, bpPath); toml::table& bpMt = const_cast(bpTable); const toml::array& materials = requireArray(bpMt["materials"], file, bpPath + ".materials"); - def.blueprint.materials = parseIngredients(materials, file, bpPath + ".materials"); - def.blueprint.playerProductionLevel = static_cast(requireInt( + def.schematic.materials = parseIngredients(materials, file, bpPath + ".materials"); + def.schematic.playerProductionLevel = static_cast(requireInt( 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"); } diff --git a/src/lib/config/ShipsConfig.h b/src/lib/config/ShipsConfig.h index 586225e..2271d97 100644 --- a/src/lib/config/ShipsConfig.h +++ b/src/lib/config/ShipsConfig.h @@ -7,9 +7,9 @@ #include "Formula.h" #include "RecipesConfig.h" // for RecipeIngredient -// Build materials and initial per-blueprint production level -// (REQ-BLD-SHIPYARD, REQ-DEF-BLUEPRINT-DROP). -struct ShipBlueprint +// Build materials and initial per-schematic production level +// (REQ-BLD-SHIPYARD, REQ-DEF-SCHEMATIC-DROP). +struct ShipSchematic { std::vector materials; int playerProductionLevel; @@ -65,7 +65,7 @@ struct ShipDef std::string id; bool availableFromStart; - ShipBlueprint blueprint; + ShipSchematic schematic; ShipThreat threat; ShipHealth health; ShipMovement movement; diff --git a/src/lib/core/BlueprintDropEvent.h b/src/lib/core/BlueprintDropEvent.h deleted file mode 100644 index d5dff06..0000000 --- a/src/lib/core/BlueprintDropEvent.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -// 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; -}; diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt index a675998..a05b251 100644 --- a/src/lib/core/CMakeLists.txt +++ b/src/lib/core/CMakeLists.txt @@ -9,7 +9,7 @@ SET(HDRS ${CMAKE_CURRENT_SOURCE_DIR}/Port.h ${CMAKE_CURRENT_SOURCE_DIR}/MovementIntent.h ${CMAKE_CURRENT_SOURCE_DIR}/FireEvent.h - ${CMAKE_CURRENT_SOURCE_DIR}/BlueprintDropEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/SchematicDropEvent.h PARENT_SCOPE ) diff --git a/src/lib/core/SchematicDropEvent.h b/src/lib/core/SchematicDropEvent.h new file mode 100644 index 0000000..053bdc2 --- /dev/null +++ b/src/lib/core/SchematicDropEvent.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +// 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; +}; diff --git a/src/lib/sim/BuildingSystem.cpp b/src/lib/sim/BuildingSystem.cpp index 3304261..b2ff9ff 100644 --- a/src/lib/sim/BuildingSystem.cpp +++ b/src/lib/sim/BuildingSystem.cpp @@ -111,7 +111,7 @@ void BuildingSystem::initShipyardBuffers(Building& b) const { return; } - for (const RecipeIngredient& ing : def->blueprint.materials) + for (const RecipeIngredient& ing : def->schematic.materials) { const ItemType type{ing.item}; 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. bool inputsOk = true; - for (const RecipeIngredient& ing : shipDef->blueprint.materials) + for (const RecipeIngredient& ing : shipDef->schematic.materials) { const ItemType type{ing.item}; const std::map::const_iterator it = @@ -684,7 +684,7 @@ void BuildingSystem::tickShipyardProduction(Tick currentTick) } // 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; } @@ -692,7 +692,7 @@ void BuildingSystem::tickShipyardProduction(Tick currentTick) Production prod; prod.recipeId = building.recipeId; prod.completesAt = currentTick - + secondsToTicks(shipDef->blueprint.productionTimeSeconds); + + secondsToTicks(shipDef->schematic.productionTimeSeconds); building.production = std::move(prod); } } diff --git a/src/lib/sim/BuildingSystem.h b/src/lib/sim/BuildingSystem.h index a06e4a2..1c899f7 100644 --- a/src/lib/sim/BuildingSystem.h +++ b/src/lib/sim/BuildingSystem.h @@ -46,7 +46,7 @@ public: // unknown ids. 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. void setRecipe(EntityId id, const std::string& recipeId); diff --git a/src/lib/sim/Ship.h b/src/lib/sim/Ship.h index a541f97..f0d90e6 100644 --- a/src/lib/sim/Ship.h +++ b/src/lib/sim/Ship.h @@ -75,7 +75,7 @@ struct Ship float maxHp; float speedPerTick; // pre-evaluated from speedFormula / kTickRateHz int level; - std::string blueprintId; + std::string schematicId; bool isEnemy = false; // true for enemy-faction ships (used by behavior systems) diff --git a/src/lib/sim/ShipSystem.cpp b/src/lib/sim/ShipSystem.cpp index b6c7f8a..1d286ed 100644 --- a/src/lib/sim/ShipSystem.cpp +++ b/src/lib/sim/ShipSystem.cpp @@ -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) { - if (def.id == blueprintId) + if (def.id == schematicId) { return &def; } @@ -30,10 +30,10 @@ const ShipDef* ShipSystem::findShipDef(const std::string& blueprintId) const 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) { - const ShipDef* def = findShipDef(blueprintId); + const ShipDef* def = findShipDef(schematicId); assert(def != nullptr); const double x = static_cast(level); @@ -48,7 +48,7 @@ EntityId ShipSystem::spawn(const std::string& blueprintId, int level, QVector2D def->movement.speedFormula.evaluate(x)) / static_cast(kTickRateHz); ship.level = level; - ship.blueprintId = blueprintId; + ship.schematicId = schematicId; ship.isEnemy = isEnemy; ship.intent = MovementIntent{0, QVector2D(0.0f, 0.0f)}; diff --git a/src/lib/sim/ShipSystem.h b/src/lib/sim/ShipSystem.h index 0a96f0b..761c995 100644 --- a/src/lib/sim/ShipSystem.h +++ b/src/lib/sim/ShipSystem.h @@ -19,7 +19,7 @@ public: std::function allocateId); // 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); void despawn(EntityId id); @@ -52,7 +52,7 @@ public: bool damageShip(EntityId id, float amount); 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. // Searches both the ship list and (for buildings) the supplied BuildingSystem. diff --git a/src/lib/sim/Simulation.cpp b/src/lib/sim/Simulation.cpp index f561ca2..1175033 100644 --- a/src/lib/sim/Simulation.cpp +++ b/src/lib/sim/Simulation.cpp @@ -30,9 +30,9 @@ Simulation::Simulation(GameConfig config, unsigned int seed) [this]() { return allocateId(); }, [this](int amount) { m_buildingBlocksStock += amount; }, [this](const std::string& id, QVector2D pos) { - const std::map::const_iterator it = - m_blueprintLevels.find(id); - if (it == m_blueprintLevels.end() || !it->second.unlocked) + const std::map::const_iterator it = + m_schematicLevels.find(id); + if (it == m_schematicLevels.end() || !it->second.unlocked) { return; } @@ -44,13 +44,13 @@ Simulation::Simulation(GameConfig config, unsigned int seed) m_waveSystem = std::make_unique(m_config, m_rng); m_combatSystem = std::make_unique(m_config); - // Initialize blueprint unlock state. + // Initialize schematic unlock state. for (const ShipDef& def : m_config.ships.ships) { - BlueprintState state; + SchematicState state; state.unlocked = def.availableFromStart; - state.level = def.availableFromStart ? def.blueprint.playerProductionLevel : 0; - m_blueprintLevels[def.id] = state; + state.level = def.availableFromStart ? def.schematic.playerProductionLevel : 0; + m_schematicLevels[def.id] = state; } placeInitialStructures(); @@ -82,7 +82,7 @@ void Simulation::reset(unsigned int seed) m_currentEnemyStationIds[0] = kInvalidEntityId; m_currentEnemyStationIds[1] = kInvalidEntityId; m_fireEvents.clear(); - m_blueprintDropEvents.clear(); + m_schematicDropEvents.clear(); m_beltSystem = BeltSystem(m_config.world.beltSpeedTilesPerSecond); m_buildingSystem = std::make_unique( @@ -91,9 +91,9 @@ void Simulation::reset(unsigned int seed) [this]() { return allocateId(); }, [this](int amount) { m_buildingBlocksStock += amount; }, [this](const std::string& id, QVector2D pos) { - const std::map::const_iterator it = - m_blueprintLevels.find(id); - if (it == m_blueprintLevels.end() || !it->second.unlocked) + const std::map::const_iterator it = + m_schematicLevels.find(id); + if (it == m_schematicLevels.end() || !it->second.unlocked) { return; } @@ -105,13 +105,13 @@ void Simulation::reset(unsigned int seed) m_waveSystem = std::make_unique(m_config, m_rng); m_combatSystem = std::make_unique(m_config); - m_blueprintLevels.clear(); + m_schematicLevels.clear(); for (const ShipDef& def : m_config.ships.ships) { - BlueprintState state; + SchematicState state; state.unlocked = def.availableFromStart; - state.level = def.availableFromStart ? def.blueprint.playerProductionLevel : 0; - m_blueprintLevels[def.id] = state; + state.level = def.availableFromStart ? def.schematic.playerProductionLevel : 0; + m_schematicLevels[def.id] = state; } placeInitialStructures(); @@ -290,7 +290,7 @@ void Simulation::tickDeathsAndLoot() // Look up scrap drop amount from config. 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 + secondsToTicks(m_config.world.scrapDespawnSeconds); @@ -366,11 +366,11 @@ void Simulation::tickDeathsAndLoot() { m_waveSystem->applyPush(); placeEnemyStationSet(m_waveSystem->generation()); - awardBlueprintDrop(); + awardSchematicDrop(); } } -void Simulation::awardBlueprintDrop() +void Simulation::awardSchematicDrop() { std::vector ids; ids.reserve(m_config.ships.ships.size()); @@ -382,16 +382,16 @@ void Simulation::awardBlueprintDrop() std::uniform_int_distribution dist(0, static_cast(ids.size()) - 1); const std::string chosen = ids[static_cast(dist(m_rng))]; - BlueprintState& state = m_blueprintLevels.at(chosen); + SchematicState& state = m_schematicLevels.at(chosen); const bool wasNew = !state.unlocked; state.unlocked = true; state.level += 1; - BlueprintDropEvent evt; - evt.blueprintId = chosen; + SchematicDropEvent evt; + evt.schematicId = chosen; evt.newLevel = state.level; evt.wasNewUnlock = wasNew; - m_blueprintDropEvents.push_back(evt); + m_schematicDropEvents.push_back(evt); } // --------------------------------------------------------------------------- @@ -405,10 +405,10 @@ std::vector Simulation::drainFireEvents() return result; } -std::vector Simulation::drainBlueprintDropEvents() +std::vector Simulation::drainSchematicDropEvents() { - std::vector result; - result.swap(m_blueprintDropEvents); + std::vector result; + result.swap(m_schematicDropEvents); return result; } @@ -436,22 +436,22 @@ double Simulation::threatLevel() const return m_waveSystem->threatLevel(); } -int Simulation::blueprintLevel(const std::string& shipId) const +int Simulation::schematicLevel(const std::string& shipId) const { - const std::map::const_iterator it = - m_blueprintLevels.find(shipId); - if (it == m_blueprintLevels.end()) + const std::map::const_iterator it = + m_schematicLevels.find(shipId); + if (it == m_schematicLevels.end()) { return 0; } return it->second.level; } -bool Simulation::isBlueprintUnlocked(const std::string& shipId) const +bool Simulation::isSchematicUnlocked(const std::string& shipId) const { - const std::map::const_iterator it = - m_blueprintLevels.find(shipId); - if (it == m_blueprintLevels.end()) + const std::map::const_iterator it = + m_schematicLevels.find(shipId); + if (it == m_schematicLevels.end()) { return false; } diff --git a/src/lib/sim/Simulation.h b/src/lib/sim/Simulation.h index bd21a27..063612c 100644 --- a/src/lib/sim/Simulation.h +++ b/src/lib/sim/Simulation.h @@ -9,7 +9,7 @@ #include #include "BeltSystem.h" -#include "BlueprintDropEvent.h" +#include "SchematicDropEvent.h" #include "BuildingType.h" #include "EntityId.h" #include "FireEvent.h" @@ -44,17 +44,17 @@ public: // internal queue. Call once per rendered frame (REQ-SHP-FIRING-BEAM). std::vector drainFireEvents(); - // Returns all blueprint drop events since the last drain. - std::vector drainBlueprintDropEvents(); + // Returns all schematic drop events since the last drain. + std::vector drainSchematicDropEvents(); Tick currentTick() const; int buildingBlocksStock() const; bool isGameOver() const; double threatLevel() const; - // Blueprint state queries. - int blueprintLevel(const std::string& shipId) const; - bool isBlueprintUnlocked(const std::string& shipId) const; + // Schematic state queries. + int schematicLevel(const std::string& shipId) const; + bool isSchematicUnlocked(const std::string& shipId) const; // Checks affordability, deducts building blocks, and places the building. // 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. void tickDeathsAndLoot(); - // Award a random blueprint drop (REQ-DEF-BLUEPRINT-DROP) and emit the event. - void awardBlueprintDrop(); + // Award a random schematic drop (REQ-DEF-SCHEMATIC-DROP) and emit the event. + void awardSchematicDrop(); GameConfig m_config; std::mt19937 m_rng; @@ -102,13 +102,13 @@ private: EntityId m_playerStation2Id; EntityId m_currentEnemyStationIds[2]; - // Blueprint unlock state (REQ-DEF-BLUEPRINT-DROP). - struct BlueprintState + // Schematic unlock state (REQ-DEF-SCHEMATIC-DROP). + struct SchematicState { bool unlocked; int level; }; - std::map m_blueprintLevels; + std::map m_schematicLevels; BeltSystem m_beltSystem; std::unique_ptr m_buildingSystem; @@ -118,5 +118,5 @@ private: std::unique_ptr m_combatSystem; std::vector m_fireEvents; - std::vector m_blueprintDropEvents; + std::vector m_schematicDropEvents; }; diff --git a/src/lib/sim/WaveSystem.cpp b/src/lib/sim/WaveSystem.cpp index 93413d9..15dec05 100644 --- a/src/lib/sim/WaveSystem.cpp +++ b/src/lib/sim/WaveSystem.cpp @@ -33,7 +33,7 @@ void WaveSystem::tickWaveScheduler(Tick currentTick, ShipSystem& ships, { if (currentTick >= entry.spawnAt) { - ships.spawn(entry.blueprintId, entry.level, entry.position, + ships.spawn(entry.schematicId, entry.level, entry.position, /*isEnemy=*/true); } else @@ -90,7 +90,7 @@ std::vector WaveSystem::composeWave(Tick currentTick, // Build eligible ship list with their costs at the current level. struct EligibleShip { - std::string blueprintId; + std::string schematicId; double cost; }; std::vector eligible; @@ -100,7 +100,7 @@ std::vector WaveSystem::composeWave(Tick currentTick, if (cost > 0.0) { EligibleShip es; - es.blueprintId = def.id; + es.schematicId = def.id; es.cost = cost; eligible.push_back(es); } @@ -151,7 +151,7 @@ std::vector WaveSystem::composeWave(Tick currentTick, budget -= chosen.cost; SpawnEntry entry; - entry.blueprintId = chosen.blueprintId; + entry.schematicId = chosen.schematicId; entry.level = shipLevel; entry.spawnAt = 0; // set below after all picks are done entry.position = QVector2D(xDist(m_rng), diff --git a/src/lib/sim/WaveSystem.h b/src/lib/sim/WaveSystem.h index 3304bfd..bf85891 100644 --- a/src/lib/sim/WaveSystem.h +++ b/src/lib/sim/WaveSystem.h @@ -40,7 +40,7 @@ public: private: struct SpawnEntry { - std::string blueprintId; + std::string schematicId; int level; Tick spawnAt; QVector2D position; diff --git a/src/test/CombatSystemTest.cpp b/src/test/CombatSystemTest.cpp index a1a82b6..bec09af 100644 --- a/src/test/CombatSystemTest.cpp +++ b/src/test/CombatSystemTest.cpp @@ -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()); REQUIRE(combatDef != nullptr); diff --git a/src/test/ShipyardTest.cpp b/src/test/ShipyardTest.cpp index d4e691a..fd8d676 100644 --- a/src/test/ShipyardTest.cpp +++ b/src/test/ShipyardTest.cpp @@ -17,11 +17,11 @@ static GameConfig loadConfig() 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) { - if (def.availableFromStart && !def.blueprint.materials.empty()) + if (def.availableFromStart && !def.schematic.materials.empty()) { return &def; } @@ -59,7 +59,7 @@ static void fillMaterials(Simulation& sim, EntityId yardId, const ShipDef& def) { return; } - for (const RecipeIngredient& ing : def.blueprint.materials) + for (const RecipeIngredient& ing : def.schematic.materials) { 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); - const ShipDef* def = findAvailableBlueprint(sim.config()); + const ShipDef* def = findAvailableSchematic(sim.config()); REQUIRE(def != nullptr); const BuildingDef* yardDef = findShipyardDef(sim.config()); REQUIRE(yardDef != nullptr); @@ -93,7 +93,7 @@ TEST_CASE("Shipyard: spawns a player ship after production cycle completes", REQUIRE(static_cast(sim.ships().allShips().size()) == shipsBefore); // 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) { sim.tick(); @@ -107,7 +107,7 @@ TEST_CASE("Shipyard: spawns a player ship after production cycle completes", bool foundPlayerShip = false; for (const Ship& ship : sim.ships().allShips()) { - if (!ship.isEnemy && ship.blueprintId == def->id) + if (!ship.isEnemy && ship.schematicId == def->id) { foundPlayerShip = true; break; @@ -116,7 +116,7 @@ TEST_CASE("Shipyard: spawns a player ship after production cycle completes", 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); @@ -136,7 +136,7 @@ TEST_CASE("Shipyard: does not spawn with insufficient materials", "[shipyard]") { Simulation sim(loadConfig(), 42); - const ShipDef* def = findAvailableBlueprint(sim.config()); + const ShipDef* def = findAvailableSchematic(sim.config()); REQUIRE(def != nullptr); const BuildingDef* yardDef = findShipyardDef(sim.config()); REQUIRE(yardDef != nullptr); @@ -147,7 +147,7 @@ TEST_CASE("Shipyard: does not spawn with insufficient materials", "[shipyard]") sim.buildings().setRecipe(yardId, def->id); // 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) { sim.tick(); @@ -160,7 +160,7 @@ TEST_CASE("Shipyard: spawns a second ship after materials replenished", "[shipya { Simulation sim(loadConfig(), 42); - const ShipDef* def = findAvailableBlueprint(sim.config()); + const ShipDef* def = findAvailableSchematic(sim.config()); REQUIRE(def != nullptr); const BuildingDef* yardDef = findShipyardDef(sim.config()); REQUIRE(yardDef != nullptr); @@ -168,7 +168,7 @@ TEST_CASE("Shipyard: spawns a second ship after materials replenished", "[shipya const EntityId yardId = placeShipyard(sim, *yardDef); 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. fillMaterials(sim, yardId, *def); diff --git a/src/test/SimulationTest.cpp b/src/test/SimulationTest.cpp index c3644c0..aab9528 100644 --- a/src/test/SimulationTest.cpp +++ b/src/test/SimulationTest.cpp @@ -61,11 +61,11 @@ TEST_CASE("Simulation::drainFireEvents clears queue on drain", "[simulation]") REQUIRE(sim.drainFireEvents().empty()); } -TEST_CASE("Simulation::drainBlueprintDropEvents returns empty initially", "[simulation]") +TEST_CASE("Simulation::drainSchematicDropEvents returns empty initially", "[simulation]") { Simulation sim(loadConfig()); - REQUIRE(sim.drainBlueprintDropEvents().empty()); + REQUIRE(sim.drainSchematicDropEvents().empty()); } // --------------------------------------------------------------------------- diff --git a/src/test/WaveSystemTest.cpp b/src/test/WaveSystemTest.cpp index ae3f046..79b18dc 100644 --- a/src/test/WaveSystemTest.cpp +++ b/src/test/WaveSystemTest.cpp @@ -233,8 +233,8 @@ TEST_CASE("WaveSystem: only eligible ships (cost > 0) appear in waves", "[wave]" { if (!s.isEnemy) { continue; } // salvage_ship and repair_ship have cost_formula = "0" and must not spawn. - REQUIRE(s.blueprintId != "salvage_ship"); - REQUIRE(s.blueprintId != "repair_ship"); + REQUIRE(s.schematicId != "salvage_ship"); + REQUIRE(s.schematicId != "repair_ship"); } } @@ -266,7 +266,7 @@ TEST_CASE("WaveSystem: destroying both enemy stations triggers a push", "[wave]" 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); @@ -280,11 +280,11 @@ TEST_CASE("WaveSystem: push emits exactly one BlueprintDropEvent", "[wave]") sim.tick(); - const std::vector events = sim.drainBlueprintDropEvents(); + const std::vector events = sim.drainSchematicDropEvents(); 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); @@ -297,13 +297,13 @@ TEST_CASE("WaveSystem: push blueprint drop awards a known ship id", "[wave]") }); sim.tick(); - const std::vector events = sim.drainBlueprintDropEvents(); + const std::vector events = sim.drainSchematicDropEvents(); REQUIRE(events.size() == 1); bool validId = false; for (const ShipDef& def : sim.config().ships.ships) { - if (def.id == events[0].blueprintId) + if (def.id == events[0].schematicId) { validId = true; break; diff --git a/src/test/config/ships.toml b/src/test/config/ships.toml index 8299067..08d33db 100644 --- a/src/test/config/ships.toml +++ b/src/test/config/ships.toml @@ -2,7 +2,7 @@ id = "interceptor" available_from_start = true -[ship.blueprint] +[ship.schematic] materials = [{item = "iron_ingot", amount = 3}, {item = "circuit_board", amount = 1}] player_production_level = 3 production_time_seconds = 10 @@ -29,7 +29,7 @@ scrap_drop = 2 id = "destroyer" available_from_start = true -[ship.blueprint] +[ship.schematic] materials = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 2}] player_production_level = 5 production_time_seconds = 20 @@ -56,7 +56,7 @@ scrap_drop = 4 id = "salvage_ship" available_from_start = true -[ship.blueprint] +[ship.schematic] materials = [{item = "iron_ingot", amount = 4}] player_production_level = 3 production_time_seconds = 10 @@ -82,7 +82,7 @@ scrap_drop = 2 id = "repair_ship" available_from_start = false -[ship.blueprint] +[ship.schematic] materials = [{item = "iron_ingot", amount = 4}, {item = "circuit_board", amount = 2}] player_production_level = 3 production_time_seconds = 15 diff --git a/src/ui/GameWorldView.cpp b/src/ui/GameWorldView.cpp index 55805e5..13e3988 100644 --- a/src/ui/GameWorldView.cpp +++ b/src/ui/GameWorldView.cpp @@ -163,17 +163,17 @@ void GameWorldView::onFrame() } } - // Drain blueprint drop events → toasts + // Drain schematic drop events → toasts { - const std::vector drops = - m_sim->drainBlueprintDropEvents(); - for (const BlueprintDropEvent& ev : drops) + const std::vector drops = + m_sim->drainSchematicDropEvents(); + for (const SchematicDropEvent& ev : drops) { - const QString shipName = toDisplayName(ev.blueprintId); + const QString shipName = toDisplayName(ev.schematicId); ToastEntry toast; if (ev.wasNewUnlock) { - toast.text = "Blueprint unlocked: " + shipName; + toast.text = "Schematic unlocked: " + shipName; } else { diff --git a/src/ui/GameWorldView.h b/src/ui/GameWorldView.h index 70b75b8..236c58a 100644 --- a/src/ui/GameWorldView.h +++ b/src/ui/GameWorldView.h @@ -11,7 +11,7 @@ #include #include -#include "BlueprintDropEvent.h" +#include "SchematicDropEvent.h" #include "BuildingType.h" #include "EntityId.h" #include "FireEvent.h" diff --git a/src/ui/SelectedBuildingPanel.cpp b/src/ui/SelectedBuildingPanel.cpp index 52d04bc..a522286 100644 --- a/src/ui/SelectedBuildingPanel.cpp +++ b/src/ui/SelectedBuildingPanel.cpp @@ -187,7 +187,7 @@ void SelectedBuildingPanel::buildSingle(EntityId id) { for (const ShipDef& def : m_config->ships.ships) { - if (m_sim->isBlueprintUnlocked(def.id)) + if (m_sim->isSchematicUnlocked(def.id)) { m_recipeCombo->addItem( QString::fromStdString(def.id), @@ -267,7 +267,7 @@ void SelectedBuildingPanel::refreshBuffers(const Building* b) } 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; } } @@ -320,7 +320,7 @@ void SelectedBuildingPanel::refreshBuffers(const Building* b) { const double durationSeconds = recipe ? recipe->durationSeconds - : shipDef->blueprint.productionTimeSeconds; + : shipDef->schematic.productionTimeSeconds; bufText += QString("Cycle: %1 s\n").arg(durationSeconds, 0, 'f', 1);