From f921f00a0d66666bae7c5217e75f5f892e879bff Mon Sep 17 00:00:00 2001 From: mlangkabel Date: Tue, 2 Jun 2026 21:20:44 +0200 Subject: [PATCH] add collection rate for salvager modules and respect collection range of each of these modules --- bin/app/data/config/modules.toml | 1 + bin/test/data/config/modules.toml | 1 + docs/requirements.md | 11 +++-- src/lib/config/ConfigLoader.cpp | 6 ++- src/lib/config/ModulesConfig.h | 1 + src/lib/ecs/component/SalvageCargoComponent.h | 2 + src/lib/ecs/system/AiSystem.cpp | 49 ++++++++++++------- src/lib/ecs/system/ShipSystem.cpp | 17 ++++++- 8 files changed, 64 insertions(+), 24 deletions(-) diff --git a/bin/app/data/config/modules.toml b/bin/app/data/config/modules.toml index 985be91..11e293c 100644 --- a/bin/app/data/config/modules.toml +++ b/bin/app/data/config/modules.toml @@ -78,6 +78,7 @@ glyph = "Sv" [module.salvage] collection_range_formula = "50" cargo_capacity_formula = "10" +collection_rate_formula = "0.5" [[module]] id = "repair_tool_module" diff --git a/bin/test/data/config/modules.toml b/bin/test/data/config/modules.toml index c39863d..a2f1513 100644 --- a/bin/test/data/config/modules.toml +++ b/bin/test/data/config/modules.toml @@ -65,6 +65,7 @@ glyph = "Sv" [module.salvage] collection_range_formula = "50" cargo_capacity_formula = "10" +collection_rate_formula = "0.5" [[module]] id = "repair_tool_module" diff --git a/docs/requirements.md b/docs/requirements.md index 0f1ee3d..c915dc6 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -8,7 +8,7 @@ Config files use the TOML format. The following config files drive game paramete - **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 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). -- **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. 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, 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). - **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. - **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. @@ -161,7 +161,9 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des - Stance: aggressive (advance toward enemies) / defensive (hold position near asteroid). - Target priority: closest / highest HP / structures first. - REQ-SHP-RALLY: After spawning, aggressive-stance ships with weapon modules move to and loiter at the **rally point** — the midpoint between the two player defence stations (center of their Y-span, at the player defence stations' X position). While at the rally point, ships still engage any enemy that enters sensor range. Every `world.toml [world].departure_interval_seconds` seconds (default 20), all ships with weapon modules currently at the rally point depart simultaneously and begin their normal aggressive advance toward the enemy. The departure timer is global and shared across all shipyards; it is not reset by individual ship arrivals at the rally point. -- REQ-SHP-SALVAGE: Ships with at least one **salvage module** (player) — patrol by moving forward (rightward, away from the asteroid) while searching sensor range. If scrap enters sensor range, move to it, collect, and deliver it to a Salvage Bay on the asteroid; after delivery, resume patrol. If an enemy ship enters sensor range while not currently targeting or carrying scrap, turn back (move toward the asteroid) until the enemy is no longer in sensor range, then resume patrol. Ships with salvage modules are vulnerable to enemy ships while operating. +- REQ-SHP-SALVAGE: Ships with at least one **salvage module** (player) — patrol by moving forward (rightward, away from the asteroid) while searching sensor range. If scrap enters sensor range, move to it; when it is within a module's `collection_range`, that module collects it (consuming the scrap entity). Once all cargo is full, fly to a Salvage Bay and deliver; after delivery, resume patrol. If an enemy ship enters sensor range while not currently targeting or carrying scrap, turn back (move toward the asteroid) until the enemy is no longer in sensor range, then resume patrol. Ships with salvage modules are vulnerable to enemy ships while operating. + + Each salvage module instance operates independently: it has its own cargo hold (`cargo_capacity`), collection range (`collection_range`), and collection rate (`collection_rate`, in collections per second). After collecting a piece of scrap, the module cannot collect again until `1 / collection_rate` seconds have elapsed. A ship with multiple salvage modules can therefore collect multiple pieces of scrap per tick (one per ready module), and installs of different module types may have different ranges and rates. The ship navigates based on the maximum collection range across all installed salvage modules. - REQ-SHP-REPAIR: Ships with at least one **repair module** (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). @@ -180,7 +182,10 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des - `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. - `glyph` — single character rendered on this module's cells in the layout grid and preview widget. - - An optional **capability section** (`[module.weapon]`, `[module.salvage]`, or `[module.repair]`) containing base stat formulas. A module with base stat formulas (e.g. `damage_formula`, `attack_range_formula`, `attack_rate_formula` for weapons) is a capability module — each placed instance grants the ship an independent weapon, salvage bay, or repair tool with its own state (cooldown, target, cargo). A ship may have multiple capability module instances of the same or different types. + - An optional **capability section** (`[module.weapon]`, `[module.salvage]`, or `[module.repair]`) containing base stat formulas. A module with base stat formulas is a capability module — each placed instance grants the ship an independent weapon, salvage bay, or repair tool with its own state (cooldown, target, cargo). A ship may have multiple capability module instances of the same or different types. Base stat formulas per capability type: + - **Weapon** (`[module.weapon]`): `damage_formula`, `attack_range_formula`, `attack_rate_formula`. + - **Salvage** (`[module.salvage]`): `collection_range_formula` (tiles), `cargo_capacity_formula` (integer scrap units), `collection_rate_formula` (collections per second). + - **Repair** (`[module.repair]`): `repair_rate_formula` (HP/s), `repair_range_formula` (tiles). - Zero or more **passive stat modifier formulas** (`added_*`/`multiplied_*`) that boost stats on the ship hull or on capability module instances (see REQ-MOD-STAT-CALC). A single module may be both a capability module and provide passive modifiers. - REQ-MOD-LAYOUT: Each ship in `ships.toml` defines a `layout` — a list of strings representing the ship's module grid (see Ship Layout Format). All ships define a layout. diff --git a/src/lib/config/ConfigLoader.cpp b/src/lib/config/ConfigLoader.cpp index fc3837c..7ca144c 100644 --- a/src/lib/config/ConfigLoader.cpp +++ b/src/lib/config/ConfigLoader.cpp @@ -530,6 +530,7 @@ static const StatEntry kKnownStats[] = { {"weapon", "attack_rate"}, {"salvage", "collection_range"}, {"salvage", "cargo_capacity"}, + {"salvage", "collection_rate"}, {"repair", "repair_rate"}, {"repair", "repair_range"}, }; @@ -636,13 +637,16 @@ ModulesConfig ConfigLoader::loadModules(const std::string& path) const std::string sPath = elemPath + ".salvage"; const toml::table& sTable = requireTable(mt["salvage"], file, sPath); toml::table& sMt = const_cast(sTable); - if (sMt.contains("collection_range_formula") || sMt.contains("cargo_capacity_formula")) + if (sMt.contains("collection_range_formula") || sMt.contains("cargo_capacity_formula") + || sMt.contains("collection_rate_formula")) { ModuleSalvageCapability cap; cap.collectionRangeFormula = requireFormula(sMt["collection_range_formula"], file, sPath + ".collection_range_formula"); cap.cargoCapacityFormula = requireFormula(sMt["cargo_capacity_formula"], file, sPath + ".cargo_capacity_formula"); + cap.collectionRateFormula = requireFormula(sMt["collection_rate_formula"], + file, sPath + ".collection_rate_formula"); def.salvageCapability = std::move(cap); } } diff --git a/src/lib/config/ModulesConfig.h b/src/lib/config/ModulesConfig.h index 8181a6c..ece28d4 100644 --- a/src/lib/config/ModulesConfig.h +++ b/src/lib/config/ModulesConfig.h @@ -28,6 +28,7 @@ struct ModuleSalvageCapability { Formula collectionRangeFormula; Formula cargoCapacityFormula; + Formula collectionRateFormula; }; struct ModuleRepairCapability diff --git a/src/lib/ecs/component/SalvageCargoComponent.h b/src/lib/ecs/component/SalvageCargoComponent.h index 95004b3..443a04f 100644 --- a/src/lib/ecs/component/SalvageCargoComponent.h +++ b/src/lib/ecs/component/SalvageCargoComponent.h @@ -5,4 +5,6 @@ struct SalvageCargoComponent int capacity; int current; float collectionRange; + int collectionIntervalTicks; + int cooldownTicksRemaining; }; diff --git a/src/lib/ecs/system/AiSystem.cpp b/src/lib/ecs/system/AiSystem.cpp index 25383b3..930777d 100644 --- a/src/lib/ecs/system/AiSystem.cpp +++ b/src/lib/ecs/system/AiSystem.cpp @@ -379,6 +379,13 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps, const std::vector allScrap = scraps.allScrapInfo(); + // Tick down per-module collection cooldowns. + admin.forEach( + [](entt::entity /*e*/, SalvageCargoComponent& c) + { + if (c.cooldownTicksRemaining > 0) { --c.cooldownTicksRemaining; } + }); + admin.forEach( [&](entt::entity e, SalvageBehaviorComponent& salvageBehavior, @@ -461,26 +468,32 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps, } if (retreating) { return; } - // Collect nearby scrap — increment first non-full salvage child. - for (const ScrapInfo& si : allScrap) - { - if ((si.position - pos.value).length() <= collectRange) + // Per-module independent collection: each ready module collects one scrap. + bool anythingCollected = false; + admin.forEach( + [&](entt::entity /*ce*/, SalvageCargoComponent& c, + const ModuleOwnerComponent& o) { - bool collected = false; - admin.forEach( - [&](entt::entity /*ce*/, SalvageCargoComponent& c, - const ModuleOwnerComponent& o) + if (o.owner != e || c.current >= c.capacity + || c.cooldownTicksRemaining > 0) + { + return; + } + for (const ScrapInfo& si : allScrap) + { + if ((si.position - pos.value).length() > c.collectionRange) { continue; } + if (scraps.consume(si.entity)) { - if (collected || o.owner != e || c.current >= c.capacity) { return; } - if (scraps.consume(si.entity)) - { - ++c.current; - salvageBehavior.scrapTarget = std::nullopt; - collected = true; - } - }); - break; - } + ++c.current; + c.cooldownTicksRemaining = c.collectionIntervalTicks; + anythingCollected = true; + break; + } + } + }); + if (anythingCollected) + { + salvageBehavior.scrapTarget = std::nullopt; } // Move toward scrap target or find a new one. diff --git a/src/lib/ecs/system/ShipSystem.cpp b/src/lib/ecs/system/ShipSystem.cpp index 6749e2d..cc12634 100644 --- a/src/lib/ecs/system/ShipSystem.cpp +++ b/src/lib/ecs/system/ShipSystem.cpp @@ -129,6 +129,11 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, cargo.current = 0; cargo.collectionRange = static_cast( modDef->salvageCapability->collectionRangeFormula.evaluate(mx)); + const double rate = modDef->salvageCapability->collectionRateFormula.evaluate(mx); + cargo.collectionIntervalTicks = (rate > 0.0) + ? static_cast(kTickRateHz / rate + 0.5) + : 0; + cargo.cooldownTicksRemaining = 0; entt::entity child = m_admin.createModuleEntity(); m_admin.addComponent(child, cargo); @@ -245,10 +250,18 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, SalvageCargoComponent& c = m_admin.get(child); float fRange = c.collectionRange; float fCapacity = static_cast(c.capacity); + // Apply rate modifier: compute rate from interval, apply multiplier, convert back. + float fRate = (c.collectionIntervalTicks > 0) + ? static_cast(kTickRateHz) / static_cast(c.collectionIntervalTicks) + : 0.0f; applyMod(fRange, "collection_range", salvageMods); applyMod(fCapacity, "cargo_capacity", salvageMods); - c.collectionRange = fRange; - c.capacity = static_cast(fCapacity); + applyMod(fRate, "collection_rate", salvageMods); + c.collectionRange = fRange; + c.capacity = static_cast(fCapacity + 0.5f); + c.collectionIntervalTicks = (fRate > 0.0f) + ? static_cast(static_cast(kTickRateHz) / fRate + 0.5f) + : 0; } // Apply repair modifiers to each repair child.