Compare commits
3 Commits
26857e8414
...
69b35d2bfc
| Author | SHA1 | Date | |
|---|---|---|---|
| 69b35d2bfc | |||
| af96b95f61 | |||
| aad094f842 |
@@ -1,5 +1,6 @@
|
|||||||
[[module]]
|
[[module]]
|
||||||
id = "armor_plate"
|
id = "armor_plate"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["OO"]
|
surface_mask = ["OO"]
|
||||||
materials = [{item = "armor_plate_module", amount = 1}]
|
materials = [{item = "armor_plate_module", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -14,6 +15,7 @@ added_hp_formula = "40"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "sensor_booster"
|
id = "sensor_booster"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "sensor_booster_module", amount = 1}]
|
materials = [{item = "sensor_booster_module", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -28,6 +30,7 @@ added_sensor_range_m_formula = "50"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "manuvering_thrusters"
|
id = "manuvering_thrusters"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "manuvering_thrusters_module", amount = 1}]
|
materials = [{item = "manuvering_thrusters_module", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -43,6 +46,7 @@ added_maneuvering_acceleration_mpss_formula = "10"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "afterburner"
|
id = "afterburner"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "afterburner_module", amount = 1}]
|
materials = [{item = "afterburner_module", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -58,6 +62,7 @@ added_main_acceleration_mpss_formula = "60"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "weapon_upgrade"
|
id = "weapon_upgrade"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = [
|
surface_mask = [
|
||||||
"OO",
|
"OO",
|
||||||
"O ",
|
"O ",
|
||||||
@@ -75,6 +80,7 @@ multiplied_damage_formula = "1.2"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "weapon_primer"
|
id = "weapon_primer"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = [
|
surface_mask = [
|
||||||
"OO",
|
"OO",
|
||||||
"O ",
|
"O ",
|
||||||
@@ -92,6 +98,7 @@ multiplied_attack_rate_hz_formula = "1.2"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "weapon_stabilizer"
|
id = "weapon_stabilizer"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = [
|
surface_mask = [
|
||||||
"OO",
|
"OO",
|
||||||
"O ",
|
"O ",
|
||||||
@@ -110,6 +117,7 @@ multiplied_attack_rate_hz_formula = "0.8"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "laser_cannon_xs"
|
id = "laser_cannon_xs"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "laser_cannon_xs_module", amount = 1}]
|
materials = [{item = "laser_cannon_xs_module", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -126,6 +134,7 @@ attack_rate_hz_formula = "2.0"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "laser_cannon_s"
|
id = "laser_cannon_s"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = [
|
surface_mask = [
|
||||||
"OO",
|
"OO",
|
||||||
"OO"]
|
"OO"]
|
||||||
@@ -144,6 +153,7 @@ attack_rate_hz_formula = "1.5"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "salvager"
|
id = "salvager"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "salvager_module", amount = 1}]
|
materials = [{item = "salvager_module", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -160,6 +170,7 @@ collection_rate_hz_formula = "0.5"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "repair_tool"
|
id = "repair_tool"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "repair_tool_module", amount = 1}]
|
materials = [{item = "repair_tool_module", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
[[ship]]
|
[[ship]]
|
||||||
id = "drone"
|
id = "drone"
|
||||||
available_from_start = true
|
unlock_at_station_level = -1
|
||||||
layout = ["O"]
|
layout = ["O"]
|
||||||
default_modules = [{type = "laser_cannon", x = 0, y = 0, rotation = "east"}]
|
default_modules = [{type = "laser_cannon_xs", x = 0, y = 0, rotation = "east"}]
|
||||||
|
|
||||||
[ship.schematic]
|
[ship.schematic]
|
||||||
materials = [{item = "drone_hull", amount = 1}]
|
materials = [{item = "drone_hull", amount = 1}]
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ outline = "#201a14"
|
|||||||
fill = "#1b1b1b"
|
fill = "#1b1b1b"
|
||||||
outline = "#1402b3"
|
outline = "#1402b3"
|
||||||
|
|
||||||
[items.laser_cannon_module]
|
[items.laser_cannon_xs_module]
|
||||||
fill = "#691313"
|
fill = "#691313"
|
||||||
outline = "#f3ff4f"
|
outline = "#f3ff4f"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
[[module]]
|
[[module]]
|
||||||
id = "armor_plate"
|
id = "armor_plate"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["OO"]
|
surface_mask = ["OO"]
|
||||||
materials = [{item = "iron_ingot", amount = 2}]
|
materials = [{item = "iron_ingot", amount = 2}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -13,6 +14,7 @@ multiplied_hp_formula = "1.5"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "sensor_booster"
|
id = "sensor_booster"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "circuit_board", amount = 1}]
|
materials = [{item = "circuit_board", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -26,6 +28,7 @@ added_sensor_range_m_formula = "100"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "weapon_upgrade"
|
id = "weapon_upgrade"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "iron_ingot", amount = 1}, {item = "circuit_board", amount = 1}]
|
materials = [{item = "iron_ingot", amount = 1}, {item = "circuit_board", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -39,6 +42,7 @@ multiplied_damage_formula = "1.2"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "laser_cannon"
|
id = "laser_cannon"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "iron_ingot", amount = 1}]
|
materials = [{item = "iron_ingot", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -54,6 +58,7 @@ attack_rate_hz_formula = "2.0"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "salvager"
|
id = "salvager"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["OO"]
|
surface_mask = ["OO"]
|
||||||
materials = [{item = "iron_ingot", amount = 2}]
|
materials = [{item = "iron_ingot", amount = 2}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -69,6 +74,7 @@ collection_rate_hz_formula = "0.5"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "repair_tool"
|
id = "repair_tool"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "circuit_board", amount = 2}]
|
materials = [{item = "circuit_board", amount = 2}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -83,6 +89,7 @@ repair_range_m_formula = "800"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "weapon_primer"
|
id = "weapon_primer"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "iron_ingot", amount = 1}]
|
materials = [{item = "iron_ingot", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -96,6 +103,7 @@ multiplied_attack_rate_hz_formula = "1.2"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "weapon_stabilizer"
|
id = "weapon_stabilizer"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "iron_ingot", amount = 1}]
|
materials = [{item = "iron_ingot", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -110,6 +118,7 @@ multiplied_attack_rate_hz_formula = "0.8"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "afterburner"
|
id = "afterburner"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "iron_ingot", amount = 1}]
|
materials = [{item = "iron_ingot", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
@@ -124,6 +133,7 @@ added_main_acceleration_mpss_formula = "60"
|
|||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "maneuvering_thrusters"
|
id = "maneuvering_thrusters"
|
||||||
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "iron_ingot", amount = 1}]
|
materials = [{item = "iron_ingot", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[[ship]]
|
[[ship]]
|
||||||
id = "interceptor"
|
id = "interceptor"
|
||||||
available_from_start = true
|
unlock_at_station_level = -1
|
||||||
layout = ["XOX", "OOO", "XOX"]
|
layout = ["XOX", "OOO", "XOX"]
|
||||||
default_modules = [{type = "laser_cannon", x = 1, y = 1, rotation = "east"}]
|
default_modules = [{type = "laser_cannon", x = 1, y = 1, rotation = "east"}]
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ scrap_drop = 2
|
|||||||
|
|
||||||
[[ship]]
|
[[ship]]
|
||||||
id = "destroyer"
|
id = "destroyer"
|
||||||
available_from_start = true
|
unlock_at_station_level = -1
|
||||||
layout = ["XOOX", "OOOO", "XOOX"]
|
layout = ["XOOX", "OOOO", "XOOX"]
|
||||||
default_modules = [{type = "laser_cannon", x = 1, y = 1, rotation = "east"}]
|
default_modules = [{type = "laser_cannon", x = 1, y = 1, rotation = "east"}]
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ scrap_drop = 4
|
|||||||
|
|
||||||
[[ship]]
|
[[ship]]
|
||||||
id = "salvage_ship"
|
id = "salvage_ship"
|
||||||
available_from_start = true
|
unlock_at_station_level = -1
|
||||||
layout = ["OOO", "OOO"]
|
layout = ["OOO", "OOO"]
|
||||||
|
|
||||||
[ship.schematic]
|
[ship.schematic]
|
||||||
@@ -92,7 +92,7 @@ scrap_drop = 2
|
|||||||
|
|
||||||
[[ship]]
|
[[ship]]
|
||||||
id = "repair_ship"
|
id = "repair_ship"
|
||||||
available_from_start = false
|
unlock_at_station_level = 0
|
||||||
layout = ["XOX", "OOO", "XOX"]
|
layout = ["XOX", "OOO", "XOX"]
|
||||||
|
|
||||||
[ship.schematic]
|
[ship.schematic]
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ 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, boss wave timing, enemy ship level formula, belt speed, starting building blocks, departure interval.
|
- **world.toml** — world dimensions, region widths, expansion amounts, building refund percentage, wave timing, boss wave timing, enemy ship level formula, belt speed, starting building blocks, departure interval.
|
||||||
- **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 schematic: a human-readable display name (used in toasts and UI), hull stats (HP, max linear speed, sensor range, main acceleration, maneuvering acceleration, angular acceleration, max rotation speed) as formulas of ship level, required build materials, threat cost formula, player production level, whether the schematic is available from game start, a layout grid defining the ship's module slots, and a `default_modules` list used for enemy wave ships (see REQ-WAV-DEFAULT-MODULES).
|
- **ships.toml** — per schematic: a human-readable display name (used in toasts and UI), hull stats (HP, max linear speed, sensor range, main acceleration, maneuvering acceleration, angular acceleration, max rotation speed) as formulas of ship level, required build materials, threat cost formula, player production level, the station level at which the schematic becomes available for unlock (`unlock_at_station_level`; -1 means the player starts with the schematic already unlocked), a layout grid defining the ship's module slots, and a `default_modules` list used for enemy wave ships (see REQ-WAV-DEFAULT-MODULES).
|
||||||
- **modules.toml** — per module type: id, surface mask, materials list, player production level, production time, threat cost, fill color, glyph, and an optional capability section and/or stat modifier formulas. A module with a capability section (`[module.weapon]`, `[module.salvage]`, or `[module.repair]`) containing base stat formulas is a **capability module** that grants the ship a weapon, salvage bay, or repair tool per instance (see REQ-MOD-CONFIG for the full list of formulas per capability type). A module with only `added_*`/`multiplied_*` formulas is a **passive module** that modifies stats on the ship or on capability module instances (see REQ-MOD-STAT-CALC).
|
- **modules.toml** — per module type: id, surface mask, materials list, initial player production level, production time, threat cost, fill color, glyph, the station level at which the schematic becomes available for unlock (`unlock_at_station_level`; -1 means the player starts with the module schematic already unlocked), and an optional capability section and/or stat modifier formulas. A module with a capability section (`[module.weapon]`, `[module.salvage]`, or `[module.repair]`) containing base stat formulas is a **capability module** that grants the ship a weapon, salvage bay, or repair tool per instance (see REQ-MOD-CONFIG for the full list of formulas per capability type). A module with only `added_*`/`multiplied_*` formulas is a **passive module** that modifies stats on the ship or on capability module instances (see REQ-MOD-STAT-CALC).
|
||||||
- **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 schematic, 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 schematic, and station type; beam color and width; overlay and toast colors. Loaded by the UI at startup; the simulation does not read it.
|
||||||
- **ship_layouts.toml** — named layout blueprints per ship type; written and read by the application to persist the layout blueprint panel (REQ-MOD-UI-BLUEPRINT-PANEL through REQ-MOD-UI-BLUEPRINT-FILE-LOAD). Not a game parameter file; the simulation does not read it.
|
- **ship_layouts.toml** — named layout blueprints per ship type; written and read by the application to persist the layout blueprint panel (REQ-MOD-UI-BLUEPRINT-PANEL through REQ-MOD-UI-BLUEPRINT-FILE-LOAD). Not a game parameter file; the simulation does not read it.
|
||||||
@@ -149,7 +149,7 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
|
|||||||
## 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: Base hull stats are defined as formulas of ship level in `ships.toml`: HP (`[ship.health].hp_formula`), max linear speed (`[ship.movement].speed_formula`), sensor range (`[ship.sensors].range_formula`), main acceleration (`[ship.movement].main_acceleration_formula`, tiles/s²), maneuvering acceleration (`[ship.movement].maneuvering_acceleration_formula`, tiles/s²), angular acceleration (`[ship.movement].angular_acceleration_formula`, rad/s²), max rotation speed (`[ship.movement].max_rotation_speed_formula`, rad/s). Required build materials (`[ship.schematic].materials`) and availability from game start (`[[ship]].available_from_start`) are also defined there. Combat, salvage, and repair capabilities are provided by modules (see REQ-MOD-CONFIG). Final hull stats incorporate passive module modifiers per REQ-MOD-STAT-CALC.
|
- REQ-SHP-STATS: Base hull stats are defined as formulas of ship level in `ships.toml`: HP (`[ship.health].hp_formula`), max linear speed (`[ship.movement].speed_formula`), sensor range (`[ship.sensors].range_formula`), main acceleration (`[ship.movement].main_acceleration_formula`, tiles/s²), maneuvering acceleration (`[ship.movement].maneuvering_acceleration_formula`, tiles/s²), angular acceleration (`[ship.movement].angular_acceleration_formula`, rad/s²), max rotation speed (`[ship.movement].max_rotation_speed_formula`, rad/s). Required build materials (`[ship.schematic].materials`) and the station level at which the schematic becomes available for unlock (`[[ship]].unlock_at_station_level`; -1 = player starts with the schematic already unlocked) are also defined there. Combat, salvage, and repair capabilities are provided by modules (see REQ-MOD-CONFIG). Final hull stats incorporate passive module modifiers per REQ-MOD-STAT-CALC.
|
||||||
- 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 using a physics-based model. Each ship has a velocity and a facing direction, both updated each tick. The main acceleration (`main_acceleration_formula`) is applied along the ship's current facing direction only. The maneuvering acceleration (`maneuvering_acceleration_formula`) can be applied in any direction independently of the facing direction, enabling lateral or braking movement without rotating. The angular acceleration (`angular_acceleration_formula`) controls how quickly the ship rotates. Linear speed is capped at the ship's `speed_formula` value; rotation rate is capped at the ship's `max_rotation_speed_formula` value. Ship position refers to the ship's center for all range, sensor, and attack checks.
|
- REQ-SHP-MOVEMENT: Ships move using a physics-based model. Each ship has a velocity and a facing direction, both updated each tick. The main acceleration (`main_acceleration_formula`) is applied along the ship's current facing direction only. The maneuvering acceleration (`maneuvering_acceleration_formula`) can be applied in any direction independently of the facing direction, enabling lateral or braking movement without rotating. The angular acceleration (`angular_acceleration_formula`) controls how quickly the ship rotates. Linear speed is capped at the ship's `speed_formula` value; rotation rate is capped at the ship's `max_rotation_speed_formula` value. Ship position refers to the ship's center for all range, sensor, and attack checks.
|
||||||
@@ -179,7 +179,8 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
|
|||||||
- `id` — unique identifier, also used as the display name in the UI.
|
- `id` — unique identifier, also used as the display name in the UI.
|
||||||
- `surface_mask` — footprint within the ship layout grid (see Module Surface Mask Format).
|
- `surface_mask` — footprint within the ship layout grid (see Module Surface Mask Format).
|
||||||
- `materials` — list of materials required per instance (added to the ship's build cost).
|
- `materials` — list of materials required per instance (added to the ship's build cost).
|
||||||
- `player_production_level` — fixed level for this module type; used as `x` in its stat formulas.
|
- `player_production_level` — initial level for this module type; used as `x` in its stat formulas. Incremented by 1 on each duplicate schematic drop (REQ-DEF-SCHEMATIC-DROP).
|
||||||
|
- `unlock_at_station_level` — the enemy defence station level at which this module's schematic becomes available for unlock; -1 means the player starts with the module schematic already unlocked.
|
||||||
- `production_time_seconds` — time added to the ship's production cycle per instance.
|
- `production_time_seconds` — time added to the ship's production cycle per instance.
|
||||||
- `threat_cost` — threat cost added to the ship's threat cost per instance.
|
- `threat_cost` — threat cost added to the ship's threat cost per instance.
|
||||||
- `fill_color` — fill color used to render this module's cells in the layout grid.
|
- `fill_color` — fill color used to render this module's cells in the layout grid.
|
||||||
@@ -224,7 +225,7 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
|
|||||||
The dialog contains:
|
The dialog contains:
|
||||||
- **Top**: The ship's layout grid rendered at full scale. Buildable cells are white; non-buildable cells are black. Placed modules are rendered with their `fill_color` and `glyph`. The ghost of the currently selected module is shown at the cursor position when in placement mode.
|
- **Top**: The ship's layout grid rendered at full scale. Buildable cells are white; non-buildable cells are black. Placed modules are rendered with their `fill_color` and `glyph`. The ghost of the currently selected module is shown at the cursor position when in placement mode.
|
||||||
- **Left** (below the grid): The ship stats panel (see REQ-MOD-UI-STATS-PANEL).
|
- **Left** (below the grid): The ship stats panel (see REQ-MOD-UI-STATS-PANEL).
|
||||||
- **Center** (below the grid): A grid of module selection buttons (one per module type defined in `modules.toml`) plus a "Remove" button. Each module button shows the module id and its glyph.
|
- **Center** (below the grid): A grid of module selection buttons (one per **unlocked** module type; see REQ-DEF-SCHEMATIC-DROP) plus a "Remove" button. Each module button shows the module id and its glyph.
|
||||||
- **Right** (below the grid): The layout blueprint panel (see REQ-MOD-UI-BLUEPRINT-PANEL through REQ-MOD-UI-BLUEPRINT-FILE-LOAD).
|
- **Right** (below the grid): The layout blueprint panel (see REQ-MOD-UI-BLUEPRINT-PANEL through REQ-MOD-UI-BLUEPRINT-FILE-LOAD).
|
||||||
- **Bottom**: A "Confirm" button and a "Cancel" button. Cancel discards all changes made in this dialog session and closes the dialog. Confirm applies the changes: the shipyard's configured layout is updated, the required materials and cycle time displayed in the selected building panel are recalculated, and the ship layout preview is refreshed.
|
- **Bottom**: A "Confirm" button and a "Cancel" button. Cancel discards all changes made in this dialog session and closes the dialog. Confirm applies the changes: the shipyard's configured layout is updated, the required materials and cycle time displayed in the selected building panel are recalculated, and the ship layout preview is refreshed.
|
||||||
|
|
||||||
@@ -254,7 +255,7 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
|
|||||||
|
|
||||||
- REQ-MOD-UI-BLUEPRINT-CREATE: Clicking "Create Blueprint" opens a modal dialog prompting for a name. The dialog has Confirm and Cancel buttons. Clicking Cancel closes the dialog with no effect. Clicking Confirm with a non-empty name creates a blueprint from the module layout currently shown in the left-side layout grid (the in-progress state of the dialog, not the previously confirmed shipyard layout) and appends it to the blueprint list.
|
- REQ-MOD-UI-BLUEPRINT-CREATE: Clicking "Create Blueprint" opens a modal dialog prompting for a name. The dialog has Confirm and Cancel buttons. Clicking Cancel closes the dialog with no effect. Clicking Confirm with a non-empty name creates a blueprint from the module layout currently shown in the left-side layout grid (the in-progress state of the dialog, not the previously confirmed shipyard layout) and appends it to the blueprint list.
|
||||||
|
|
||||||
- REQ-MOD-UI-BLUEPRINT-ENTRY: Each blueprint entry shows the blueprint name and a delete icon ("×") to the right of the name. Clicking the entry (name area) loads that blueprint's module list into the left-side layout grid, replacing all currently placed modules. Module instances that are invalid for the current ship layout (unknown module type, position outside the grid, position on a non-buildable cell, or overlapping another module in the same blueprint) are silently skipped; the remaining valid instances are placed. Clicking the delete icon ("×") removes that blueprint entry from the list immediately.
|
- REQ-MOD-UI-BLUEPRINT-ENTRY: Each blueprint entry shows the blueprint name and a delete icon ("×") to the right of the name. Clicking the entry (name area) loads that blueprint's module list into the left-side layout grid, replacing all currently placed modules. Module instances that are invalid for the current ship layout (unknown module type, locked module type, position outside the grid, position on a non-buildable cell, or overlapping another module in the same blueprint) are silently skipped; the remaining valid instances are placed. Clicking the delete icon ("×") removes that blueprint entry from the list immediately.
|
||||||
|
|
||||||
- REQ-MOD-UI-BLUEPRINT-STARTUP: At application startup, layout blueprints are loaded from `ship_layouts.toml` in the same directory as the application executable. Blueprint entries missing required fields (`name` or `ship_type`) are silently skipped. If the file does not exist, the blueprint list starts empty with no error. If the file exists but cannot be parsed (malformed TOML), a modal error dialog describes the failure and the blueprint list starts empty.
|
- REQ-MOD-UI-BLUEPRINT-STARTUP: At application startup, layout blueprints are loaded from `ship_layouts.toml` in the same directory as the application executable. Blueprint entries missing required fields (`name` or `ship_type`) are silently skipped. If the file does not exist, the blueprint list starts empty with no error. If the file exists but cannot be parsed (malformed TOML), a modal error dialog describes the failure and the blueprint list starts empty.
|
||||||
|
|
||||||
@@ -269,7 +270,7 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
|
|||||||
- REQ-DEF-ENEMY-FIRE: Enemy defence stations automatically fire at player ships within range.
|
- REQ-DEF-ENEMY-FIRE: Enemy defence stations automatically fire at player ships within range.
|
||||||
- REQ-DEF-NO-CROSSFIRE: Enemy and player defence stations are never in each other's firing range.
|
- REQ-DEF-NO-CROSSFIRE: Enemy and player defence stations are never in each other's firing range.
|
||||||
- REQ-DEF-PUSH: When both enemy defence stations in a set are destroyed, the boss countdown is advanced (REQ-WAV-BOSS-ADVANCE), the scrollable area is extended (REQ-GW-PUSH-EXPAND), a new set of enemy defence stations is placed at the new boundary, and exactly one schematic drop is awarded for the destroyed set (REQ-DEF-SCHEMATIC-DROP).
|
- REQ-DEF-PUSH: When both enemy defence stations in a set are destroyed, the boss countdown is advanced (REQ-WAV-BOSS-ADVANCE), the scrollable area is extended (REQ-GW-PUSH-EXPAND), a new set of enemy defence stations is placed at the new boundary, and exactly one schematic drop is awarded for the destroyed set (REQ-DEF-SCHEMATIC-DROP).
|
||||||
- REQ-DEF-SCHEMATIC-DROP: Each destroyed set of enemy defence stations awards exactly one schematic drop (not one per station). The drop 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).
|
- 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 **ship and module schematics** whose `unlock_at_station_level` is -1 or is ≤ the level of the destroyed station set. If the player does not yet have that schematic, it is unlocked: ship schematics unlock the corresponding shipyard selection; module schematics unlock the module type for placement in the layout configuration dialog (REQ-MOD-UI-DIALOG). If the player already has it, the schematic's `player_production_level` is incremented by 1 — for ship schematics, subsequent ships of that type are produced at a higher level; for module schematics, all instances of that module type use the higher level in their stat formulas. The player is notified via a toast (REQ-UI-SCHEMATIC-TOAST).
|
||||||
|
|
||||||
## Threat Level & Enemy Waves
|
## Threat Level & Enemy Waves
|
||||||
|
|
||||||
@@ -327,9 +328,11 @@ The screen is divided into three vertical sections:
|
|||||||
- 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-HP-BARS: All entities with HP — the HQ, player and enemy defence stations, and player and enemy ships — render an HP bar below them. The bar is always visible regardless of current HP. The bar's filled portion represents the fraction of current HP to maximum HP.
|
- REQ-UI-HP-BARS: All entities with HP — the HQ, player and enemy defence stations, and player and enemy ships — render an HP bar below them. The bar is always visible regardless of current HP. The bar's filled portion represents the fraction of current HP to maximum HP.
|
||||||
- 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-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:
|
- 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. Toast text:
|
||||||
- **New unlock**: `Schematic unlocked: <Ship Name>`
|
- **Ship schematic — new unlock**: `Schematic unlocked: <Ship Name>` (where `<Ship Name>` is `ships.toml [ship.schematic].display_name`).
|
||||||
- **Level-up (duplicate drop)**: `<Ship Name> production level → N` (where N is the new level).
|
- **Ship schematic — level-up (duplicate drop)**: `<Ship Name> production level → N` (where N is the new level).
|
||||||
|
- **Module schematic — new unlock**: `Module unlocked: <Module Id>` (where `<Module Id>` is the module's `id` from `modules.toml`).
|
||||||
|
- **Module schematic — level-up (duplicate drop)**: `<Module Id> 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.
|
||||||
- REQ-UI-HOTKEYS: Global keyboard shortcuts:
|
- REQ-UI-HOTKEYS: Global keyboard shortcuts:
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ ShipsConfig ConfigLoader::loadShips(const std::string& path)
|
|||||||
|
|
||||||
ShipDef def;
|
ShipDef def;
|
||||||
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.unlockAtStationLevel = static_cast<int>(requireInt(mt["unlock_at_station_level"], file, elemPath + ".unlock_at_station_level"));
|
||||||
def.layout = requireStringArray(mt["layout"], file, elemPath + ".layout");
|
def.layout = requireStringArray(mt["layout"], file, elemPath + ".layout");
|
||||||
|
|
||||||
// Schematic
|
// Schematic
|
||||||
@@ -571,6 +571,8 @@ ModulesConfig ConfigLoader::loadModules(const std::string& path)
|
|||||||
|
|
||||||
ModuleDef def;
|
ModuleDef def;
|
||||||
def.id = requireString(mt["id"], file, elemPath + ".id");
|
def.id = requireString(mt["id"], file, elemPath + ".id");
|
||||||
|
def.unlockAtStationLevel = static_cast<int>(
|
||||||
|
mt["unlock_at_station_level"].value_or<int64_t>(-1));
|
||||||
def.surfaceMask = requireStringArray(mt["surface_mask"], file, elemPath + ".surface_mask");
|
def.surfaceMask = requireStringArray(mt["surface_mask"], file, elemPath + ".surface_mask");
|
||||||
def.playerProductionLevel = static_cast<int>(requireInt(
|
def.playerProductionLevel = static_cast<int>(requireInt(
|
||||||
mt["player_production_level"], file, elemPath + ".player_production_level"));
|
mt["player_production_level"], file, elemPath + ".player_production_level"));
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ struct ModuleRepairCapability
|
|||||||
struct ModuleDef
|
struct ModuleDef
|
||||||
{
|
{
|
||||||
std::string id;
|
std::string id;
|
||||||
|
int unlockAtStationLevel;
|
||||||
std::vector<std::string> surfaceMask;
|
std::vector<std::string> surfaceMask;
|
||||||
std::vector<RecipeIngredient> materials;
|
std::vector<RecipeIngredient> materials;
|
||||||
int playerProductionLevel;
|
int playerProductionLevel;
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ struct ShipLoot
|
|||||||
struct ShipDef
|
struct ShipDef
|
||||||
{
|
{
|
||||||
std::string id;
|
std::string id;
|
||||||
bool availableFromStart;
|
int unlockAtStationLevel;
|
||||||
std::vector<std::string> layout;
|
std::vector<std::string> layout;
|
||||||
|
|
||||||
ShipSchematic schematic;
|
ShipSchematic schematic;
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
// Emitted in tick step 9 (Deaths & loot) when a destroyed enemy-defence-station
|
// 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
|
// set awards a schematic (REQ-DEF-SCHEMATIC-DROP). The UI renders a toast
|
||||||
// (REQ-UI-SCHEMATIC-TOAST); wasNewUnlock chooses between the "unlocked" and
|
// (REQ-UI-SCHEMATIC-TOAST); wasNewUnlock chooses between the "unlocked" and
|
||||||
// "level -> N" wording.
|
// "level -> N" wording. isModuleSchematic selects ship vs. module toast text.
|
||||||
struct SchematicDropEvent
|
struct SchematicDropEvent
|
||||||
{
|
{
|
||||||
std::string schematicId; // matches ShipDef::id in the config.
|
std::string schematicId; // matches ShipDef::id or ModuleDef::id in the config.
|
||||||
int newLevel;
|
int newLevel;
|
||||||
bool wasNewUnlock;
|
bool wasNewUnlock;
|
||||||
|
bool isModuleSchematic;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ const ModuleDef* ShipSystem::findModuleDef(const std::string& id) const
|
|||||||
|
|
||||||
entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||||
QVector2D position, bool isEnemy,
|
QVector2D position, bool isEnemy,
|
||||||
const std::optional<ShipLayoutConfig>& layout)
|
const std::optional<ShipLayoutConfig>& layout,
|
||||||
|
const std::map<std::string, int>& moduleLevelOverrides)
|
||||||
{
|
{
|
||||||
const ShipDef* def = findShipDef(schematicId);
|
const ShipDef* def = findShipDef(schematicId);
|
||||||
assert(def != nullptr);
|
assert(def != nullptr);
|
||||||
@@ -105,7 +106,9 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
|||||||
const ModuleDef* modDef = findModuleDef(pm.moduleId);
|
const ModuleDef* modDef = findModuleDef(pm.moduleId);
|
||||||
if (!modDef) { throw std::runtime_error("unknown module id '" + pm.moduleId + "'"); }
|
if (!modDef) { throw std::runtime_error("unknown module id '" + pm.moduleId + "'"); }
|
||||||
|
|
||||||
const double mx = static_cast<double>(modDef->playerProductionLevel);
|
const auto overIt = moduleLevelOverrides.find(pm.moduleId);
|
||||||
|
const double mx = static_cast<double>(
|
||||||
|
overIt != moduleLevelOverrides.end() ? overIt->second : modDef->playerProductionLevel);
|
||||||
|
|
||||||
if (modDef->weaponCapability)
|
if (modDef->weaponCapability)
|
||||||
{
|
{
|
||||||
@@ -176,7 +179,9 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
|||||||
const ModuleDef* modDef = findModuleDef(pm.moduleId);
|
const ModuleDef* modDef = findModuleDef(pm.moduleId);
|
||||||
if (!modDef) { throw std::runtime_error("unknown module id '" + pm.moduleId + "'"); }
|
if (!modDef) { throw std::runtime_error("unknown module id '" + pm.moduleId + "'"); }
|
||||||
|
|
||||||
const double mx = static_cast<double>(modDef->playerProductionLevel);
|
const auto overIt2 = moduleLevelOverrides.find(pm.moduleId);
|
||||||
|
const double mx = static_cast<double>(
|
||||||
|
overIt2 != moduleLevelOverrides.end() ? overIt2->second : modDef->playerProductionLevel);
|
||||||
|
|
||||||
for (const ModuleStatModifier& sm : modDef->statModifiers)
|
for (const ModuleStatModifier& sm : modDef->statModifiers)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -19,7 +20,8 @@ public:
|
|||||||
|
|
||||||
entt::entity spawn(const std::string& schematicId, int level, QVector2D position,
|
entt::entity spawn(const std::string& schematicId, int level, QVector2D position,
|
||||||
bool isEnemy = false,
|
bool isEnemy = false,
|
||||||
const std::optional<ShipLayoutConfig>& layout = std::nullopt);
|
const std::optional<ShipLayoutConfig>& layout = std::nullopt,
|
||||||
|
const std::map<std::string, int>& moduleLevelOverrides = {});
|
||||||
void despawn(entt::entity entity);
|
void despawn(entt::entity entity);
|
||||||
|
|
||||||
// Reset all movement intents to priority 0 before behavior systems run.
|
// Reset all movement intents to priority 0 before behavior systems run.
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
ShipStats calculateShipStats(const GameConfig& config,
|
ShipStats calculateShipStats(const GameConfig& config,
|
||||||
const std::string& shipId,
|
const std::string& shipId,
|
||||||
int level,
|
int level,
|
||||||
const std::vector<PlacedModule>& modules)
|
const std::vector<PlacedModule>& modules,
|
||||||
|
const std::map<std::string, int>& moduleLevelOverrides)
|
||||||
{
|
{
|
||||||
ShipStats result{};
|
ShipStats result{};
|
||||||
|
|
||||||
@@ -70,7 +71,9 @@ ShipStats calculateShipStats(const GameConfig& config,
|
|||||||
const ModuleDef* def = findModuleDef(pm.moduleId);
|
const ModuleDef* def = findModuleDef(pm.moduleId);
|
||||||
if (!def) { throw std::runtime_error("unknown module id '" + pm.moduleId + "'"); }
|
if (!def) { throw std::runtime_error("unknown module id '" + pm.moduleId + "'"); }
|
||||||
|
|
||||||
const double mx = static_cast<double>(def->playerProductionLevel);
|
const auto overIt = moduleLevelOverrides.find(pm.moduleId);
|
||||||
|
const double mx = static_cast<double>(
|
||||||
|
overIt != moduleLevelOverrides.end() ? overIt->second : def->playerProductionLevel);
|
||||||
|
|
||||||
if (def->weaponCapability)
|
if (def->weaponCapability)
|
||||||
{
|
{
|
||||||
@@ -108,7 +111,9 @@ ShipStats calculateShipStats(const GameConfig& config,
|
|||||||
const ModuleDef* def = findModuleDef(pm.moduleId);
|
const ModuleDef* def = findModuleDef(pm.moduleId);
|
||||||
if (!def) { throw std::runtime_error("unknown module id '" + pm.moduleId + "'"); }
|
if (!def) { throw std::runtime_error("unknown module id '" + pm.moduleId + "'"); }
|
||||||
|
|
||||||
const double mx = static_cast<double>(def->playerProductionLevel);
|
const auto overIt = moduleLevelOverrides.find(pm.moduleId);
|
||||||
|
const double mx = static_cast<double>(
|
||||||
|
overIt != moduleLevelOverrides.end() ? overIt->second : def->playerProductionLevel);
|
||||||
|
|
||||||
for (const ModuleStatModifier& sm : def->statModifiers)
|
for (const ModuleStatModifier& sm : def->statModifiers)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -50,7 +51,8 @@ struct ShipStats
|
|||||||
ShipStats calculateShipStats(const GameConfig& config,
|
ShipStats calculateShipStats(const GameConfig& config,
|
||||||
const std::string& shipId,
|
const std::string& shipId,
|
||||||
int level,
|
int level,
|
||||||
const std::vector<PlacedModule>& modules);
|
const std::vector<PlacedModule>& modules,
|
||||||
|
const std::map<std::string, int>& moduleLevelOverrides = {});
|
||||||
|
|
||||||
ShipStats buildShipStatsFromEntity(const EntityAdmin& admin, entt::entity shipEntity);
|
ShipStats buildShipStatsFromEntity(const EntityAdmin& admin, entt::entity shipEntity);
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,13 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_shipSystem->spawn(id, it->second.level, pos, /*isEnemy=*/false, layout);
|
std::map<std::string, int> moduleLevels;
|
||||||
|
for (const auto& [mId, mState] : m_moduleSchematicLevels)
|
||||||
|
{
|
||||||
|
moduleLevels[mId] = mState.level;
|
||||||
|
}
|
||||||
|
m_shipSystem->spawn(id, it->second.level, pos, /*isEnemy=*/false, layout,
|
||||||
|
moduleLevels);
|
||||||
},
|
},
|
||||||
m_rng);
|
m_rng);
|
||||||
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
||||||
@@ -62,15 +68,24 @@ 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 schematic unlock state.
|
// Initialize ship schematic unlock state.
|
||||||
for (const ShipDef& def : m_config.ships.ships)
|
for (const ShipDef& def : m_config.ships.ships)
|
||||||
{
|
{
|
||||||
SchematicState state;
|
SchematicState state;
|
||||||
state.unlocked = def.availableFromStart;
|
state.unlocked = (def.unlockAtStationLevel == -1);
|
||||||
state.level = def.availableFromStart ? def.schematic.playerProductionLevel : 0;
|
state.level = (def.unlockAtStationLevel == -1) ? def.schematic.playerProductionLevel : 0;
|
||||||
m_schematicLevels[def.id] = state;
|
m_schematicLevels[def.id] = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize module schematic unlock state.
|
||||||
|
for (const ModuleDef& def : m_config.modules.modules)
|
||||||
|
{
|
||||||
|
SchematicState state;
|
||||||
|
state.unlocked = (def.unlockAtStationLevel == -1);
|
||||||
|
state.level = (def.unlockAtStationLevel == -1) ? def.playerProductionLevel : 0;
|
||||||
|
m_moduleSchematicLevels[def.id] = state;
|
||||||
|
}
|
||||||
|
|
||||||
placeInitialStructures();
|
placeInitialStructures();
|
||||||
registerForEvents();
|
registerForEvents();
|
||||||
}
|
}
|
||||||
@@ -124,7 +139,13 @@ void Simulation::reset(unsigned int seed)
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_shipSystem->spawn(id, it->second.level, pos, /*isEnemy=*/false, layout);
|
std::map<std::string, int> moduleLevels;
|
||||||
|
for (const auto& [mId, mState] : m_moduleSchematicLevels)
|
||||||
|
{
|
||||||
|
moduleLevels[mId] = mState.level;
|
||||||
|
}
|
||||||
|
m_shipSystem->spawn(id, it->second.level, pos, /*isEnemy=*/false, layout,
|
||||||
|
moduleLevels);
|
||||||
},
|
},
|
||||||
m_rng);
|
m_rng);
|
||||||
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
||||||
@@ -139,11 +160,20 @@ void Simulation::reset(unsigned int seed)
|
|||||||
for (const ShipDef& def : m_config.ships.ships)
|
for (const ShipDef& def : m_config.ships.ships)
|
||||||
{
|
{
|
||||||
SchematicState state;
|
SchematicState state;
|
||||||
state.unlocked = def.availableFromStart;
|
state.unlocked = (def.unlockAtStationLevel == -1);
|
||||||
state.level = def.availableFromStart ? def.schematic.playerProductionLevel : 0;
|
state.level = (def.unlockAtStationLevel == -1) ? def.schematic.playerProductionLevel : 0;
|
||||||
m_schematicLevels[def.id] = state;
|
m_schematicLevels[def.id] = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_moduleSchematicLevels.clear();
|
||||||
|
for (const ModuleDef& def : m_config.modules.modules)
|
||||||
|
{
|
||||||
|
SchematicState state;
|
||||||
|
state.unlocked = (def.unlockAtStationLevel == -1);
|
||||||
|
state.level = (def.unlockAtStationLevel == -1) ? def.playerProductionLevel : 0;
|
||||||
|
m_moduleSchematicLevels[def.id] = state;
|
||||||
|
}
|
||||||
|
|
||||||
placeInitialStructures();
|
placeInitialStructures();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,25 +503,37 @@ void Simulation::tickDeathsAndLoot()
|
|||||||
if (es0Gone && es1Gone &&
|
if (es0Gone && es1Gone &&
|
||||||
m_currentEnemyStationEntities[0] != entt::null)
|
m_currentEnemyStationEntities[0] != entt::null)
|
||||||
{
|
{
|
||||||
|
const int destroyedLevel = m_waveSystem->generation();
|
||||||
m_waveSystem->onEnemyStationsDestroyed();
|
m_waveSystem->onEnemyStationsDestroyed();
|
||||||
placeEnemyStationSet(m_waveSystem->generation());
|
placeEnemyStationSet(m_waveSystem->generation());
|
||||||
awardSchematicDrop();
|
awardSchematicDrop(destroyedLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Simulation::awardSchematicDrop()
|
void Simulation::awardSchematicDrop(int destroyedStationLevel)
|
||||||
{
|
{
|
||||||
std::vector<std::string> ids;
|
std::vector<std::pair<std::string, bool>> pool; // (id, isModule)
|
||||||
ids.reserve(m_config.ships.ships.size());
|
|
||||||
for (const ShipDef& def : m_config.ships.ships)
|
for (const ShipDef& def : m_config.ships.ships)
|
||||||
{
|
{
|
||||||
ids.push_back(def.id);
|
if (def.unlockAtStationLevel == -1 || def.unlockAtStationLevel <= destroyedStationLevel)
|
||||||
|
{
|
||||||
|
pool.push_back({def.id, false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const ModuleDef& def : m_config.modules.modules)
|
||||||
|
{
|
||||||
|
if (def.unlockAtStationLevel == -1 || def.unlockAtStationLevel <= destroyedStationLevel)
|
||||||
|
{
|
||||||
|
pool.push_back({def.id, true});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::uniform_int_distribution<int> dist(0, static_cast<int>(ids.size()) - 1);
|
std::uniform_int_distribution<int> dist(0, static_cast<int>(pool.size()) - 1);
|
||||||
const std::string chosen = ids[static_cast<std::size_t>(dist(m_rng))];
|
const auto& [chosen, isModule] = pool[static_cast<std::size_t>(dist(m_rng))];
|
||||||
|
|
||||||
SchematicState& state = m_schematicLevels.at(chosen);
|
SchematicState& state = isModule
|
||||||
|
? m_moduleSchematicLevels.at(chosen)
|
||||||
|
: 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;
|
||||||
@@ -500,6 +542,7 @@ void Simulation::awardSchematicDrop()
|
|||||||
evt.schematicId = chosen;
|
evt.schematicId = chosen;
|
||||||
evt.newLevel = state.level;
|
evt.newLevel = state.level;
|
||||||
evt.wasNewUnlock = wasNew;
|
evt.wasNewUnlock = wasNew;
|
||||||
|
evt.isModuleSchematic = isModule;
|
||||||
m_schematicDropEvents.push_back(evt);
|
m_schematicDropEvents.push_back(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,6 +625,28 @@ bool Simulation::isSchematicUnlocked(const std::string& shipId) const
|
|||||||
return it->second.unlocked;
|
return it->second.unlocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Simulation::moduleSchematicLevel(const std::string& moduleId) const
|
||||||
|
{
|
||||||
|
const std::map<std::string, SchematicState>::const_iterator it =
|
||||||
|
m_moduleSchematicLevels.find(moduleId);
|
||||||
|
if (it == m_moduleSchematicLevels.end())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return it->second.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Simulation::isModuleSchematicUnlocked(const std::string& moduleId) const
|
||||||
|
{
|
||||||
|
const std::map<std::string, SchematicState>::const_iterator it =
|
||||||
|
m_moduleSchematicLevels.find(moduleId);
|
||||||
|
if (it == m_moduleSchematicLevels.end())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return it->second.unlocked;
|
||||||
|
}
|
||||||
|
|
||||||
BuildingId Simulation::tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation)
|
BuildingId Simulation::tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation)
|
||||||
{
|
{
|
||||||
int cost = 0;
|
int cost = 0;
|
||||||
|
|||||||
@@ -62,10 +62,14 @@ public:
|
|||||||
Tick bossCountdownTicks() const;
|
Tick bossCountdownTicks() const;
|
||||||
Tick normalGapRemainingTicks() const;
|
Tick normalGapRemainingTicks() const;
|
||||||
|
|
||||||
// Schematic state queries.
|
// Ship schematic state queries.
|
||||||
int schematicLevel(const std::string& shipId) const;
|
int schematicLevel(const std::string& shipId) const;
|
||||||
bool isSchematicUnlocked(const std::string& shipId) const;
|
bool isSchematicUnlocked(const std::string& shipId) const;
|
||||||
|
|
||||||
|
// Module schematic state queries.
|
||||||
|
int moduleSchematicLevel(const std::string& moduleId) const;
|
||||||
|
bool isModuleSchematicUnlocked(const std::string& moduleId) 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 kInvalidBuildingId if blocks are insufficient.
|
// Returns the new entity id, or kInvalidBuildingId if blocks are insufficient.
|
||||||
BuildingId tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
|
BuildingId tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
|
||||||
@@ -100,7 +104,7 @@ private:
|
|||||||
void tickDeathsAndLoot();
|
void tickDeathsAndLoot();
|
||||||
|
|
||||||
// Award a random schematic drop (REQ-DEF-SCHEMATIC-DROP) and emit the event.
|
// Award a random schematic drop (REQ-DEF-SCHEMATIC-DROP) and emit the event.
|
||||||
void awardSchematicDrop();
|
void awardSchematicDrop(int destroyedStationLevel);
|
||||||
|
|
||||||
GameConfig m_config;
|
GameConfig m_config;
|
||||||
std::mt19937 m_rng;
|
std::mt19937 m_rng;
|
||||||
@@ -125,6 +129,7 @@ private:
|
|||||||
int level;
|
int level;
|
||||||
};
|
};
|
||||||
std::map<std::string, SchematicState> m_schematicLevels;
|
std::map<std::string, SchematicState> m_schematicLevels;
|
||||||
|
std::map<std::string, SchematicState> m_moduleSchematicLevels;
|
||||||
|
|
||||||
EntityAdmin m_admin;
|
EntityAdmin m_admin;
|
||||||
BeltSystem m_beltSystem;
|
BeltSystem m_beltSystem;
|
||||||
|
|||||||
@@ -654,7 +654,7 @@ TEST_CASE("Blueprint placement: recipe transfers to building after construction
|
|||||||
|
|
||||||
TEST_CASE("Blueprint placement: interceptor schematic is unlocked at game start", "[blueprint]")
|
TEST_CASE("Blueprint placement: interceptor schematic is unlocked at game start", "[blueprint]")
|
||||||
{
|
{
|
||||||
// "interceptor" has available_from_start = true in the test config.
|
// "interceptor" has unlock_at_station_level = -1 in the test config.
|
||||||
// This confirms the guard in placeBlueprintAtTile passes for start-unlocked schematics.
|
// This confirms the guard in placeBlueprintAtTile passes for start-unlocked schematics.
|
||||||
Simulation sim(loadConfig());
|
Simulation sim(loadConfig());
|
||||||
REQUIRE(sim.isSchematicUnlocked("interceptor"));
|
REQUIRE(sim.isSchematicUnlocked("interceptor"));
|
||||||
@@ -662,7 +662,7 @@ TEST_CASE("Blueprint placement: interceptor schematic is unlocked at game start"
|
|||||||
|
|
||||||
TEST_CASE("Blueprint placement: repair_ship schematic is locked at game start", "[blueprint]")
|
TEST_CASE("Blueprint placement: repair_ship schematic is locked at game start", "[blueprint]")
|
||||||
{
|
{
|
||||||
// "repair_ship" has available_from_start = false in the test config.
|
// "repair_ship" has unlock_at_station_level = 0 in the test config.
|
||||||
// This confirms the guard in placeBlueprintAtTile blocks locked schematics,
|
// This confirms the guard in placeBlueprintAtTile blocks locked schematics,
|
||||||
// leaving the shipyard's schematic unset.
|
// leaving the shipyard's schematic unset.
|
||||||
Simulation sim(loadConfig());
|
Simulation sim(loadConfig());
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ 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.schematic.materials.empty())
|
if (def.unlockAtStationLevel == -1 && !def.schematic.materials.empty())
|
||||||
{
|
{
|
||||||
return &def;
|
return &def;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
#include "ShipSystem.h"
|
#include "ShipSystem.h"
|
||||||
#include "StationBodyComponent.h"
|
#include "StationBodyComponent.h"
|
||||||
#include "WeaponComponent.h"
|
#include "WeaponComponent.h"
|
||||||
|
#include "ModulesConfig.h"
|
||||||
|
#include "ShipsConfig.h"
|
||||||
#include "Simulation.h"
|
#include "Simulation.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
#include "WaveSystem.h"
|
#include "WaveSystem.h"
|
||||||
@@ -278,10 +280,13 @@ TEST_CASE("WaveSystem: push schematic drop awards a known ship id", "[wave]")
|
|||||||
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].schematicId)
|
if (def.id == events[0].schematicId) { validId = true; break; }
|
||||||
|
}
|
||||||
|
if (!validId)
|
||||||
{
|
{
|
||||||
validId = true;
|
for (const ModuleDef& def : sim.config().modules.modules)
|
||||||
break;
|
{
|
||||||
|
if (def.id == events[0].schematicId) { validId = true; break; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
REQUIRE(validId);
|
REQUIRE(validId);
|
||||||
|
|||||||
@@ -194,16 +194,19 @@ void GameWorldView::onFrame()
|
|||||||
m_sim->drainSchematicDropEvents();
|
m_sim->drainSchematicDropEvents();
|
||||||
for (const SchematicDropEvent& ev : drops)
|
for (const SchematicDropEvent& ev : drops)
|
||||||
{
|
{
|
||||||
const QString shipName = toDisplayName(ev.schematicId);
|
const QString name = toDisplayName(ev.schematicId);
|
||||||
ToastEntry toast;
|
ToastEntry toast;
|
||||||
if (ev.wasNewUnlock)
|
if (ev.isModuleSchematic)
|
||||||
{
|
{
|
||||||
toast.text = "Schematic unlocked: " + shipName;
|
toast.text = ev.wasNewUnlock
|
||||||
|
? tr("Module unlocked: ") + name
|
||||||
|
: name + tr(" production level -> ") + QString::number(ev.newLevel);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
toast.text = shipName + " production level -> "
|
toast.text = ev.wasNewUnlock
|
||||||
+ QString::number(ev.newLevel);
|
? tr("Schematic unlocked: ") + name
|
||||||
|
: name + tr(" production level -> ") + QString::number(ev.newLevel);
|
||||||
}
|
}
|
||||||
toast.createdWallMs = m_wallMs;
|
toast.createdWallMs = m_wallMs;
|
||||||
m_toasts.push_back(toast);
|
m_toasts.push_back(toast);
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QCloseEvent>
|
#include <QCloseEvent>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
@@ -233,8 +236,22 @@ void MainWindow::onLayoutDialogRequested(BuildingId shipyardId)
|
|||||||
currentLayout = *b->shipLayout;
|
currentLayout = *b->shipLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::set<std::string> unlockedModuleIds;
|
||||||
|
std::map<std::string, int> moduleLevels;
|
||||||
|
for (const ModuleDef& def : m_sim->config().modules.modules)
|
||||||
|
{
|
||||||
|
if (m_sim->isModuleSchematicUnlocked(def.id))
|
||||||
|
{
|
||||||
|
unlockedModuleIds.insert(def.id);
|
||||||
|
}
|
||||||
|
moduleLevels[def.id] = m_sim->moduleSchematicLevel(def.id);
|
||||||
|
}
|
||||||
|
|
||||||
ShipLayoutDialog dialog(&m_sim->config(), b->recipeId, currentLayout,
|
ShipLayoutDialog dialog(&m_sim->config(), b->recipeId, currentLayout,
|
||||||
m_layoutBlueprints, this);
|
m_layoutBlueprints,
|
||||||
|
std::move(unlockedModuleIds),
|
||||||
|
std::move(moduleLevels),
|
||||||
|
this);
|
||||||
if (dialog.exec() == QDialog::Accepted && dialog.result().has_value())
|
if (dialog.exec() == QDialog::Accepted && dialog.result().has_value())
|
||||||
{
|
{
|
||||||
m_sim->buildings().setShipLayout(shipyardId, *dialog.result());
|
m_sim->buildings().setShipLayout(shipyardId, *dialog.result());
|
||||||
|
|||||||
@@ -387,10 +387,14 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
|
|||||||
const std::string& shipId,
|
const std::string& shipId,
|
||||||
const ShipLayoutConfig& currentLayout,
|
const ShipLayoutConfig& currentLayout,
|
||||||
std::vector<ShipLayoutBlueprint>& allBlueprints,
|
std::vector<ShipLayoutBlueprint>& allBlueprints,
|
||||||
|
std::set<std::string> unlockedModuleIds,
|
||||||
|
std::map<std::string, int> moduleLevels,
|
||||||
QWidget* parent)
|
QWidget* parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, m_config(config)
|
, m_config(config)
|
||||||
, m_shipId(shipId)
|
, m_shipId(shipId)
|
||||||
|
, m_unlockedModuleIds(std::move(unlockedModuleIds))
|
||||||
|
, m_moduleLevels(std::move(moduleLevels))
|
||||||
, m_rows(0)
|
, m_rows(0)
|
||||||
, m_cols(0)
|
, m_cols(0)
|
||||||
, m_placedModules(currentLayout.placedModules)
|
, m_placedModules(currentLayout.placedModules)
|
||||||
@@ -469,6 +473,11 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
|
|||||||
for (int i = 0; i < static_cast<int>(config->modules.modules.size()); ++i)
|
for (int i = 0; i < static_cast<int>(config->modules.modules.size()); ++i)
|
||||||
{
|
{
|
||||||
const ModuleDef& def = config->modules.modules[i];
|
const ModuleDef& def = config->modules.modules[i];
|
||||||
|
if (m_unlockedModuleIds.count(def.id) == 0)
|
||||||
|
{
|
||||||
|
m_moduleButtons.push_back(nullptr);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const QString label = displayName(def.id)
|
const QString label = displayName(def.id)
|
||||||
+ "\n" + QString::fromStdString(def.glyph);
|
+ "\n" + QString::fromStdString(def.glyph);
|
||||||
QPushButton* btn = new QPushButton(label, this);
|
QPushButton* btn = new QPushButton(label, this);
|
||||||
@@ -509,7 +518,7 @@ ShipLayoutDialog::ShipLayoutDialog(const GameConfig* config,
|
|||||||
{
|
{
|
||||||
for (QPushButton* btn : m_moduleButtons)
|
for (QPushButton* btn : m_moduleButtons)
|
||||||
{
|
{
|
||||||
btn->setChecked(false);
|
if (btn) { btn->setChecked(false); }
|
||||||
}
|
}
|
||||||
m_activeModuleIndex = -1;
|
m_activeModuleIndex = -1;
|
||||||
m_removeButton->setChecked(true);
|
m_removeButton->setChecked(true);
|
||||||
@@ -628,14 +637,14 @@ void ShipLayoutDialog::onModuleButtonClicked(int index)
|
|||||||
{
|
{
|
||||||
if (m_activeModuleIndex == index)
|
if (m_activeModuleIndex == index)
|
||||||
{
|
{
|
||||||
m_moduleButtons[index]->setChecked(false);
|
if (m_moduleButtons[index]) { m_moduleButtons[index]->setChecked(false); }
|
||||||
m_activeModuleIndex = -2;
|
m_activeModuleIndex = -2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (int i = 0; i < static_cast<int>(m_moduleButtons.size()); ++i)
|
for (int i = 0; i < static_cast<int>(m_moduleButtons.size()); ++i)
|
||||||
{
|
{
|
||||||
m_moduleButtons[i]->setChecked(i == index);
|
if (m_moduleButtons[i]) { m_moduleButtons[i]->setChecked(i == index); }
|
||||||
}
|
}
|
||||||
m_removeButton->setChecked(false);
|
m_removeButton->setChecked(false);
|
||||||
m_activeModuleIndex = index;
|
m_activeModuleIndex = index;
|
||||||
@@ -717,7 +726,7 @@ void ShipLayoutDialog::updateStats()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_statsPanel->refresh(m_shipId, level, m_placedModules);
|
m_statsPanel->refresh(m_shipId, level, m_placedModules, m_moduleLevels);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShipLayoutDialog::canPlaceModule(const ModuleDef& def, QPoint position,
|
bool ShipLayoutDialog::canPlaceModule(const ModuleDef& def, QPoint position,
|
||||||
@@ -779,13 +788,13 @@ void ShipLayoutDialog::loadLayoutBlueprint(const std::vector<PlacedModule>& modu
|
|||||||
|
|
||||||
for (const PlacedModule& pm : modules)
|
for (const PlacedModule& pm : modules)
|
||||||
{
|
{
|
||||||
// Validate module type exists.
|
// Validate module type exists and is unlocked.
|
||||||
const ModuleDef* def = nullptr;
|
const ModuleDef* def = nullptr;
|
||||||
for (const ModuleDef& d : m_config->modules.modules)
|
for (const ModuleDef& d : m_config->modules.modules)
|
||||||
{
|
{
|
||||||
if (d.id == pm.moduleId) { def = &d; break; }
|
if (d.id == pm.moduleId) { def = &d; break; }
|
||||||
}
|
}
|
||||||
if (!def) { continue; }
|
if (!def || m_unlockedModuleIds.count(def->id) == 0) { continue; }
|
||||||
|
|
||||||
const std::vector<std::string> mask = rotatedMask(*def, pm.rotation);
|
const std::vector<std::string> mask = rotatedMask(*def, pm.rotation);
|
||||||
bool valid = true;
|
bool valid = true;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -24,6 +26,8 @@ public:
|
|||||||
const std::string& shipId,
|
const std::string& shipId,
|
||||||
const ShipLayoutConfig& currentLayout,
|
const ShipLayoutConfig& currentLayout,
|
||||||
std::vector<ShipLayoutBlueprint>& allBlueprints,
|
std::vector<ShipLayoutBlueprint>& allBlueprints,
|
||||||
|
std::set<std::string> unlockedModuleIds,
|
||||||
|
std::map<std::string, int> moduleLevels,
|
||||||
QWidget* parent = nullptr);
|
QWidget* parent = nullptr);
|
||||||
|
|
||||||
std::optional<ShipLayoutConfig> result() const;
|
std::optional<ShipLayoutConfig> result() const;
|
||||||
@@ -57,6 +61,8 @@ private:
|
|||||||
|
|
||||||
const GameConfig* m_config;
|
const GameConfig* m_config;
|
||||||
std::string m_shipId;
|
std::string m_shipId;
|
||||||
|
std::set<std::string> m_unlockedModuleIds;
|
||||||
|
std::map<std::string, int> m_moduleLevels;
|
||||||
std::vector<std::string> m_shipLayout;
|
std::vector<std::string> m_shipLayout;
|
||||||
int m_rows;
|
int m_rows;
|
||||||
int m_cols;
|
int m_cols;
|
||||||
|
|||||||
@@ -108,9 +108,11 @@ ShipStatsPanel::ShipStatsPanel(const GameConfig* config, QWidget* parent)
|
|||||||
|
|
||||||
void ShipStatsPanel::refresh(const std::string& shipId,
|
void ShipStatsPanel::refresh(const std::string& shipId,
|
||||||
int level,
|
int level,
|
||||||
const std::vector<PlacedModule>& modules)
|
const std::vector<PlacedModule>& modules,
|
||||||
|
const std::map<std::string, int>& moduleLevelOverrides)
|
||||||
{
|
{
|
||||||
const ShipStats stats = calculateShipStats(*m_config, shipId, level, modules);
|
const ShipStats stats = calculateShipStats(*m_config, shipId, level, modules,
|
||||||
|
moduleLevelOverrides);
|
||||||
const QString hpText = tr("HP: %1").arg(static_cast<int>(stats.hp + 0.5f));
|
const QString hpText = tr("HP: %1").arg(static_cast<int>(stats.hp + 0.5f));
|
||||||
applyStats(stats, hpText);
|
applyStats(stats, hpText);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -20,7 +21,8 @@ public:
|
|||||||
|
|
||||||
void refresh(const std::string& shipId,
|
void refresh(const std::string& shipId,
|
||||||
int level,
|
int level,
|
||||||
const std::vector<PlacedModule>& modules);
|
const std::vector<PlacedModule>& modules,
|
||||||
|
const std::map<std::string, int>& moduleLevelOverrides = {});
|
||||||
|
|
||||||
void refreshFromLive(const ShipStats& stats, float currentHp);
|
void refreshFromLive(const ShipStats& stats, float currentHp);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user