From 51e45e10a115a3c48c44b6018ff1386c57d6672f Mon Sep 17 00:00:00 2001 From: Malte Langkabel Date: Thu, 23 Jul 2026 13:10:04 +0200 Subject: [PATCH] Rename scrap-drop entity to "debris" in code and config Implements the requirements rename in the codebase. The salvageable object dropped by destroyed ships and defence stations is now the "debris" entity; the scrap resource it yields (cargo, delivery, smelting, threat, scrap_drop_formula, scrap_per_threat) is unchanged. Entity renames: ScrapDataComponent->DebrisComponent, ScrapSystem->DebrisSystem, ScrapInfo->DebrisInfo, ScrapSelectionChangedEvent->DebrisSelectionChangedEvent (member scrap->debris), spawnScrap->spawnDebris, getScraps->getDebrisSystem, getAllScrapInfo->getAllDebrisInfo, scrapAtWorldPos/scrapInBox->debris*, SalvageScrapBehavior::scrapTarget->debrisTarget, PendingCollection::scrap->debris, ScrapTest.cpp->DebrisTest.cpp. Config key scrap_despawn_seconds-> debris_despawn_seconds (WorldConfig.scrapDespawnSeconds->debrisDespawnSeconds). The salvage/deliver behavior classes keep their names (they act on the scrap resource). Also implements the new REQ-UI-DEBRIS-PANEL behavior in SelectedBuildingPanel: a single selected piece of debris shows a "Debris" heading plus a "Scrap" stat row; a multi/mixed field selection appends "Debris x N" and "Scrap x N" lines. Updates the four renamed REQ-ID references in comments/docs, plus architecture.md and derived.md. All 449 test cases pass; app, tests, and balancing targets build. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Y7N59FsLA5e2kuVdqe4Uhc --- bin/app/data/config/stations.toml | 2 +- bin/app/data/config/world.toml | 2 +- bin/test/data/config/world.toml | 2 +- docs/architecture.md | 25 ++-- docs/balancing/derived.md | 2 +- src/balancing/ArenaSimulation.cpp | 18 +-- src/balancing/ArenaSimulation.h | 6 +- src/balancing/ArenaView.cpp | 18 +-- src/balancing/ArenaView.h | 2 +- src/lib/config/ConfigLoader.cpp | 2 +- src/lib/config/WorldConfig.h | 4 +- src/lib/core/EntityAdmin.cpp | 6 +- src/lib/core/EntityAdmin.h | 2 +- src/lib/ecs/component/CMakeLists.txt | 2 +- src/lib/ecs/component/DebrisComponent.h | 8 ++ src/lib/ecs/component/SalvageScrapBehavior.h | 4 +- src/lib/ecs/component/ScrapDataComponent.h | 6 - src/lib/ecs/component/ShipIdentityComponent.h | 2 +- src/lib/ecs/system/AiSystem.cpp | 4 +- src/lib/ecs/system/AiSystem.h | 4 +- src/lib/ecs/system/CMakeLists.txt | 4 +- src/lib/ecs/system/DebrisSystem.cpp | 78 +++++++++++ .../system/{ScrapSystem.h => DebrisSystem.h} | 18 ++- src/lib/ecs/system/SalvagerSystem.cpp | 26 ++-- src/lib/ecs/system/SalvagerSystem.h | 10 +- src/lib/ecs/system/ScrapSystem.cpp | 78 ----------- src/lib/ecs/system/ShipSystem.cpp | 4 +- .../ecs/system/ai/SalvageScrapEvaluator.cpp | 14 +- src/lib/ecs/system/ai/SalvageScrapEvaluator.h | 8 +- .../ecs/system/ai/SalvageScrapExecutor.cpp | 4 +- .../event/DebrisSelectionChangedEvent.h | 18 +++ .../event/ScrapSelectionChangedEvent.h | 18 --- src/lib/sim/EntityHitTest.cpp | 26 ++-- src/lib/sim/EntityHitTest.h | 14 +- src/lib/sim/Simulation.cpp | 36 ++--- src/lib/sim/Simulation.h | 10 +- src/lib/sim/ThreatCostCalculator.cpp | 2 +- src/test/BehaviorSystemTest.cpp | 6 +- src/test/CMakeLists.txt | 2 +- src/test/CombatSystemTest.cpp | 10 +- src/test/ConfigLoaderTest.cpp | 6 +- src/test/{ScrapTest.cpp => DebrisTest.cpp} | 96 ++++++------- src/test/ShipTest.cpp | 2 +- src/ui/GameWorldView.cpp | 130 +++++++++--------- src/ui/GameWorldView.h | 16 +-- src/ui/SelectedBuildingPanel.cpp | 94 +++++++------ src/ui/SelectedBuildingPanel.h | 24 ++-- 47 files changed, 450 insertions(+), 425 deletions(-) create mode 100644 src/lib/ecs/component/DebrisComponent.h delete mode 100644 src/lib/ecs/component/ScrapDataComponent.h create mode 100644 src/lib/ecs/system/DebrisSystem.cpp rename src/lib/ecs/system/{ScrapSystem.h => DebrisSystem.h} (52%) delete mode 100644 src/lib/ecs/system/ScrapSystem.cpp create mode 100644 src/lib/eventsystem/event/DebrisSelectionChangedEvent.h delete mode 100644 src/lib/eventsystem/event/ScrapSelectionChangedEvent.h rename src/test/{ScrapTest.cpp => DebrisTest.cpp} (69%) diff --git a/bin/app/data/config/stations.toml b/bin/app/data/config/stations.toml index 6a13ec9..d0d3f78 100644 --- a/bin/app/data/config/stations.toml +++ b/bin/app/data/config/stations.toml @@ -4,7 +4,7 @@ # a fresh player defence station holds one early parity wave unaided; the # enemy station at level 0 matches the player station exactly and scales # with the push level x. Station scrap drops stay authored (pushing rewards -# are tuned independently of ship production costs, REQ-RES-SCRAP-DROP). +# are tuned independently of ship production costs, REQ-RES-DEBRIS-DROP). [hq] surface_mask = [ diff --git a/bin/app/data/config/world.toml b/bin/app/data/config/world.toml index a8eb712..0203cd3 100644 --- a/bin/app/data/config/world.toml +++ b/bin/app/data/config/world.toml @@ -3,7 +3,7 @@ height_tiles = 40 refund_percentage = 100 deconstruction_time_seconds = 0.1 starting_building_blocks = 200 -scrap_despawn_seconds = 120 +debris_despawn_seconds = 120 scrap_per_threat = 0.25 tile_size_m = 10 belt_speed_mps = 20 diff --git a/bin/test/data/config/world.toml b/bin/test/data/config/world.toml index 74da494..c817c92 100644 --- a/bin/test/data/config/world.toml +++ b/bin/test/data/config/world.toml @@ -3,7 +3,7 @@ height_tiles = 60 refund_percentage = 75 deconstruction_time_seconds = 0.1 starting_building_blocks = 100 -scrap_despawn_seconds = 30 +debris_despawn_seconds = 30 scrap_per_threat = 1.0 tile_size_m = 10 belt_speed_mps = 20 diff --git a/docs/architecture.md b/docs/architecture.md index 465b8a3..2765397 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -25,7 +25,7 @@ The simulation advances in discrete ticks. All game quantities — production ti - Tick rate: fixed at 30 Hz; `tickDurationMs = 1000 / 30 ≈ 33.33`. - Ticks are driven by an accumulator that is independent of the render rate. Each render frame, the driver adds `elapsedWallMs × gameSpeedMultiplier` to an accumulator and flushes one `tick()` per `tickDurationMs` of accumulated time (so multiple sim ticks may run between frames at high speeds, or a frame may run no ticks at low speeds). `gameSpeedMultiplier` ∈ {0, 0.5, 1, 2, 4} per REQ-UI-SPEED; 0× freezes the accumulator (pause). The concrete driver lives in the Rendering section. -- Config-level durations given in seconds (recipe durations, wave gap ranges, scrap despawn, etc.) are converted to ticks at config-load time. +- Config-level durations given in seconds (recipe durations, wave gap ranges, debris despawn, etc.) are converted to ticks at config-load time. Consequences: determinism, replayability, and the time-scale feature fall out for free. The simulation advances the same number of ticks over the same amount of game-time regardless of whether the game renders at 60 FPS, 30 FPS, or a stuttery mix. @@ -44,7 +44,7 @@ See REQ-GW-COORDS for the authoritative tile-coordinate convention. This section - Tile coordinates are `QPoint(x, y)`. Origin `(0, 0)` is the first space tile (just right of the asteroid's right edge at game start). X grows right; Y grows down. - Asteroid tiles have `x < 0`. Asteroid left-expansions add tiles at increasingly negative X; the origin never shifts, so existing tile coordinates remain stable across expansions. -- Continuous world positions (ship centers, scrap drops, projectiles) use `QVector2D` in tile units — one tile = 1.0 world unit. A ship center at `QVector2D(-3.5, 4.0)` sits at the center of the tile 3.5 tiles left of the asteroid's right edge and 4 tiles down from the top. +- Continuous world positions (ship centers, debris, projectiles) use `QVector2D` in tile units — one tile = 1.0 world unit. A ship center at `QVector2D(-3.5, 4.0)` sits at the center of the tile 3.5 tiles left of the asteroid's right edge and 4 tiles down from the top. - Rendering multiplies world units by the tile size in pixels (20) at draw time. - Ship position always refers to the ship's center — this is the point used for sensor, attack-range, and hit-detection checks. @@ -52,7 +52,7 @@ See REQ-GW-COORDS for the authoritative tile-coordinate convention. This section Simulation types shared across subsystems: -- `EntityId` — strictly increasing integer handle, allocated centrally by the simulation. Assigned to every targetable entity: ships, scrap drops, **and** buildings (including HQ and defence stations). Buildings additionally retain their anchor tile for spatial lookups and placement; the `EntityId` is the canonical reference used by ship-component target fields (`Weapon.currentTarget`, `RepairTool.currentTarget`, `AttackBehavior.currentTarget`, etc.), so a combat ship can target either another ship or a defence station uniformly. +- `EntityId` — strictly increasing integer handle, allocated centrally by the simulation. Assigned to every targetable entity: ships, debris, **and** buildings (including HQ and defence stations). Buildings additionally retain their anchor tile for spatial lookups and placement; the `EntityId` is the canonical reference used by ship-component target fields (`Weapon.currentTarget`, `RepairTool.currentTarget`, `AttackBehavior.currentTarget`, etc.), so a combat ship can target either another ship or a defence station uniformly. - `Rotation` — enum `{ North, East, South, West }`. The rotation applied to a building's surface_mask when placed. - `BuildingType` — enum covering every building type in requirements.md (Miner, Smelter, Assembler, ReprocessingPlant, Shipyard, SalvageBay, Belt, Splitter, Hq, PlayerDefenceStation, EnemyDefenceStation). `Belt` and `Splitter` share the enum for cost, construction, placement, and `visuals.toml` lookup, but their runtime data lives inside the belt subsystem rather than in `Building` instances (see Belt Subsystem). - `ItemType` — tagged id of every transportable material (ores, ingots, intermediates, building_blocks, scrap). @@ -115,9 +115,9 @@ 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 the `AiSystem` runs three batched phases: every behavior **evaluator** scores its behavior and sets its target data; a **selection** pass records the highest-scoring behavior per ship in `SelectedBehaviorComponent`; each behavior **executor** runs for the winner, writing `MovementIntent` and preferred module targets. The module systems then perform world mutation: `SalvagerSystem` (scrap collection/delivery) and `RepairSystem` (healing). See Movement Arbitration. 8. **Combat resolution** — ships and defence stations validate/acquire targets, fire, apply damage; queue deaths. Each fire appends a `BeamFiredEvent` to the sim's beam-fired-event queue (REQ-SHP-FIRING-BEAM). The repair and salvage module systems (tick step 7d) append their own `BeamFiredEvent`s to the same queue when they start a cycle. -9. **Deaths & loot** — process queued deaths: drop scrap (REQ-RES-SCRAP-DROP); if a full enemy-defence-station set was destroyed this tick, generate up to 3 schematic choice options (REQ-DEF-SCHEMATIC-DROP) stored as pending state for the UI to present; remove entities. +9. **Deaths & loot** — process queued deaths: drop debris (REQ-RES-DEBRIS-DROP); if a full enemy-defence-station set was destroyed this tick, generate up to 3 schematic choice options (REQ-DEF-SCHEMATIC-DROP) stored as pending state for the UI to present; remove entities. 10. **`tickMovement`** — advance ship positions based on final `MovementIntent`. -11. **Scrap despawn** — decrement scrap timers; remove expired scrap (REQ-RES-SCRAP-DROP). +11. **Debris despawn** — decrement debris timers; remove expired debris (REQ-RES-DEBRIS-DROP). ## CMake Target Layout @@ -193,20 +193,21 @@ struct Building { - Belts and splitters are separate types owned by the belt subsystem, not general `Building` instances. - No ECS for buildings. A miner is never also an assembler; there is no composition benefit to decomposing buildings into components. -## Scrap +## Debris -Scrap is the only non-ship, non-building entity in the simulation: +Debris — the salvageable object dropped by destroyed ships and defence stations — is the +only non-ship, non-building entity in the simulation. Each piece carries a scrap amount: ```cpp -struct Scrap { +struct Debris { EntityId id; QVector2D position; // world units, tile-fractional; ship-center convention - int amount; - Tick despawnAt; // absolute tick at which the scrap is removed + int amount; // scrap the piece still holds + Tick despawnAt; // absolute tick at which the debris is removed }; ``` -Created in tick step 9 (Deaths & loot) per REQ-RES-SCRAP-DROP, consumed by salvage ships in tick step 7 (ScrapCollector), and removed in tick step 11 when the current tick reaches `despawnAt`. +Created in tick step 9 (Deaths & loot) per REQ-RES-DEBRIS-DROP, drained one scrap per cycle by salvage ships in tick step 7 (SalvagerSystem), and removed in tick step 11 when the current tick reaches `despawnAt`. ## Ships @@ -234,7 +235,7 @@ struct RetreatBehavior { float retreatHpFraction; QVector2D retreatPoint; struct AttackBehavior { std::optional currentTarget; float score; }; struct RepairBehavior { std::optional currentTarget; float maxRepairRange_tiles; float score; }; -struct SalvageScrapBehavior { std::optional scrapTarget; +struct SalvageScrapBehavior { std::optional debrisTarget; float maxCollectionRange_tiles; float score; }; struct DeliverScrapBehavior { BuildingId deliveryBay; float score; }; struct SelectedBehaviorComponent { BehaviorKind winner; float bestScore; }; // selection result diff --git a/docs/balancing/derived.md b/docs/balancing/derived.md index 4ccefad..81e9048 100644 --- a/docs/balancing/derived.md +++ b/docs/balancing/derived.md @@ -17,7 +17,7 @@ move. Combat stats were tuned empirically against the arena suite in - Reprocessing: 4 scrap per cycle, 4 s; full-pool weights iron_ingot 30 / copper_ingot 30 / silicon 20 / voidsteel 20 → threat(voidsteel) = (4·4 + 4)/0.2 = 100. -- `scrap_despawn_seconds = 120` (a capital kill drops hundreds of scrap, +- `debris_despawn_seconds = 120` (a capital kill drops hundreds of scrap, collected one per salvage cycle). ## Recipes and item threats diff --git a/src/balancing/ArenaSimulation.cpp b/src/balancing/ArenaSimulation.cpp index 7e63772..7419bad 100644 --- a/src/balancing/ArenaSimulation.cpp +++ b/src/balancing/ArenaSimulation.cpp @@ -22,7 +22,7 @@ #include "PositionComponent.h" #include "RepairSystem.h" #include "SalvagerSystem.h" -#include "ScrapSystem.h" +#include "DebrisSystem.h" #include "ShipIdentityComponent.h" #include "ShipSystem.h" #include "ShipsConfig.h" @@ -63,7 +63,7 @@ ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig, m_movementIntentSystem = std::make_unique(); m_dynamicBodySystem = std::make_unique(); m_combatSystem = std::make_unique(m_gameConfig); - m_scrapSystem = std::make_unique(m_admin); + m_debrisSystem = std::make_unique(m_admin); m_salvagerSystem = std::make_unique(m_admin); m_repairSystem = std::make_unique(m_admin); @@ -322,9 +322,9 @@ void ArenaSimulation::tick() // Ship behavior systems (tick step 7): evaluate, select winner, execute. // Module + combat systems emit their tool beams into a shared buffer. m_shipSystem->clearMovementIntents(); - m_aiSystem->tick(m_admin, *m_buildingSystem, *m_scrapSystem); + m_aiSystem->tick(m_admin, *m_buildingSystem, *m_debrisSystem); std::vector beamFiredEvents; - m_salvagerSystem->tick(m_currentTick, *m_scrapSystem, *m_buildingSystem, beamFiredEvents); + m_salvagerSystem->tick(m_currentTick, *m_debrisSystem, *m_buildingSystem, beamFiredEvents); m_repairSystem->tick(m_currentTick, beamFiredEvents); // Combat resolution (tick step 8). @@ -340,7 +340,7 @@ void ArenaSimulation::tick() m_dynamicBodySystem->tick(m_admin); // Scrap despawn (tick step 11). - m_scrapSystem->tickDespawn(m_currentTick); + m_debrisSystem->tickDespawn(m_currentTick); ++m_currentTick; @@ -371,8 +371,8 @@ void ArenaSimulation::tickDeaths() if (si.scrapDrop > 0) { const Tick despawnAt = m_currentTick - + secondsToTicks(m_gameConfig.world.scrapDespawnSeconds); - m_scrapSystem->spawn(pos.value, si.scrapDrop, despawnAt); + + secondsToTicks(m_gameConfig.world.debrisDespawnSeconds); + m_debrisSystem->spawn(pos.value, si.scrapDrop, despawnAt); } m_shipSystem->despawn(deadEntity); } @@ -497,9 +497,9 @@ const ShipSystem& ArenaSimulation::getShips() const return *m_shipSystem; } -const ScrapSystem& ArenaSimulation::getScraps() const +const DebrisSystem& ArenaSimulation::getDebrisSystem() const { - return *m_scrapSystem; + return *m_debrisSystem; } EntityAdmin& ArenaSimulation::getAdmin() diff --git a/src/balancing/ArenaSimulation.h b/src/balancing/ArenaSimulation.h index 7936279..0b95ba7 100644 --- a/src/balancing/ArenaSimulation.h +++ b/src/balancing/ArenaSimulation.h @@ -26,7 +26,7 @@ class MovementIntentSystem; class RepairSystem; class SalvagerSystem; class ShipSystem; -class ScrapSystem; +class DebrisSystem; struct ArenaStatus { @@ -86,7 +86,7 @@ public: const ArenaConfig& getArenaConfig() const; const BuildingSystem& getBuildings() const; const ShipSystem& getShips() const; - const ScrapSystem& getScraps() const; + const DebrisSystem& getDebrisSystem() const; EntityAdmin& getAdmin(); const EntityAdmin& getAdmin() const; @@ -114,7 +114,7 @@ private: std::unique_ptr m_movementIntentSystem; std::unique_ptr m_dynamicBodySystem; std::unique_ptr m_combatSystem; - std::unique_ptr m_scrapSystem; + std::unique_ptr m_debrisSystem; std::unique_ptr m_salvagerSystem; std::unique_ptr m_repairSystem; diff --git a/src/balancing/ArenaView.cpp b/src/balancing/ArenaView.cpp index f781e19..aa3b838 100644 --- a/src/balancing/ArenaView.cpp +++ b/src/balancing/ArenaView.cpp @@ -24,11 +24,11 @@ #include "PositionComponent.h" #include "RepairBehavior.h" #include "SalvageScrapBehavior.h" -#include "ScrapSystem.h" +#include "DebrisSystem.h" #include "SensorRangeComponent.h" #include "ShipIdentityComponent.h" #include "StationBodyComponent.h" -#include "ScrapDataComponent.h" +#include "DebrisComponent.h" namespace { @@ -153,7 +153,7 @@ void ArenaView::handleEvent(std::shared_ptr event) maxRadius = shorter / 2.0f; } else if (m_sim->getAdmin().isValid(event->target) - && m_sim->getAdmin().hasAll(event->target)) + && m_sim->getAdmin().hasAll(event->target)) { maxRadius = 0.1f; } @@ -178,7 +178,7 @@ void ArenaView::paintGL() drawTiles(painter); drawBuildings(painter); drawStations(painter); - drawScrap(painter); + drawDebris(painter); if (m_debugDraw) { drawDebugSensorRanges(painter); @@ -336,12 +336,12 @@ void ArenaView::drawBuildings(QPainter& painter) } } -void ArenaView::drawScrap(QPainter& painter) +void ArenaView::drawDebris(QPainter& painter) { const float r = getTilePx() * 0.2f; - for (const ScrapInfo& scrap : m_sim->getScraps().getAllScrapInfo()) + for (const DebrisInfo& debris : m_sim->getDebrisSystem().getAllDebrisInfo()) { - const QPointF center = worldToWidget(scrap.position); + const QPointF center = worldToWidget(debris.position); painter.setBrush(QColor(128, 110, 90)); painter.setPen(QPen(QColor(50, 40, 30), 1)); painter.drawEllipse(center, @@ -529,9 +529,9 @@ void ArenaView::drawDebugTargetLines(QPainter& painter) const PositionComponent& pos, const FactionComponent& fac, const SalvageScrapBehavior& salvage) { - if (!salvage.scrapTarget.has_value()) { return; } + if (!salvage.debrisTarget.has_value()) { return; } - drawTargetLine(fac.isEnemy, pos.value, *salvage.scrapTarget); + drawTargetLine(fac.isEnemy, pos.value, *salvage.debrisTarget); }); } diff --git a/src/balancing/ArenaView.h b/src/balancing/ArenaView.h index 4bfc9f7..cc94374 100644 --- a/src/balancing/ArenaView.h +++ b/src/balancing/ArenaView.h @@ -50,7 +50,7 @@ private: void drawTiles(QPainter& painter); void drawBuildings(QPainter& painter); void drawStations(QPainter& painter); - void drawScrap(QPainter& painter); + void drawDebris(QPainter& painter); void drawShips(QPainter& painter); void drawDebugSensorRanges(QPainter& painter); void drawDebugTargetLines(QPainter& painter); diff --git a/src/lib/config/ConfigLoader.cpp b/src/lib/config/ConfigLoader.cpp index 901170a..2b9ea80 100644 --- a/src/lib/config/ConfigLoader.cpp +++ b/src/lib/config/ConfigLoader.cpp @@ -265,7 +265,7 @@ WorldConfig ConfigLoader::loadWorld(const std::string& path) cfg.refundPercentage = static_cast(requireInt(tbl["world"]["refund_percentage"], file, "world.refund_percentage")); cfg.deconstructionTimeSeconds = requireDouble(tbl["world"]["deconstruction_time_seconds"], file, "world.deconstruction_time_seconds"); cfg.startingBuildingBlocks = static_cast(requireInt(tbl["world"]["starting_building_blocks"], file, "world.starting_building_blocks")); - cfg.scrapDespawnSeconds = requireDouble(tbl["world"]["scrap_despawn_seconds"], file, "world.scrap_despawn_seconds"); + cfg.debrisDespawnSeconds = requireDouble(tbl["world"]["debris_despawn_seconds"], file, "world.debris_despawn_seconds"); cfg.scrapPerThreat = requireDouble(tbl["world"]["scrap_per_threat"], file, "world.scrap_per_threat"); cfg.tileSize_m = requireDouble(tbl["world"]["tile_size_m"], file, "world.tile_size_m"); cfg.beltSpeed_tps = requireDouble(tbl["world"]["belt_speed_mps"], file, "world.belt_speed_mps") / cfg.tileSize_m; diff --git a/src/lib/config/WorldConfig.h b/src/lib/config/WorldConfig.h index 55659bd..1e022e5 100644 --- a/src/lib/config/WorldConfig.h +++ b/src/lib/config/WorldConfig.h @@ -70,8 +70,8 @@ struct WorldConfig int refundPercentage; // REQ-BLD-DECONSTRUCT double deconstructionTimeSeconds; // REQ-BLD-DECON-QUEUE int startingBuildingBlocks; // REQ-HQ-STARTING-BLOCKS - double scrapDespawnSeconds; // REQ-RES-SCRAP-DROP - double scrapPerThreat; // REQ-RES-SCRAP-DROP, REQ-THREAT-SCRAP (scrap dropped per unit threat) + double debrisDespawnSeconds; // REQ-RES-DEBRIS-DROP + double scrapPerThreat; // REQ-RES-DEBRIS-DROP, REQ-THREAT-SCRAP (scrap dropped per unit threat) double tileSize_m; // metres per tile (REQ-GW-TILE-SIZE) double beltSpeed_tps; // REQ-GW-BELT-SPEED (tiles/s, converted from m/s in config) int tunnelMaxDistance_tiles; // REQ-BLD-TUNNEL-PAIR diff --git a/src/lib/core/EntityAdmin.cpp b/src/lib/core/EntityAdmin.cpp index 96191cf..e8062d0 100644 --- a/src/lib/core/EntityAdmin.cpp +++ b/src/lib/core/EntityAdmin.cpp @@ -8,7 +8,7 @@ #include "HqProxyComponent.h" #include "MovementIntentComponent.h" #include "PositionComponent.h" -#include "ScrapDataComponent.h" +#include "DebrisComponent.h" #include "SensorRangeComponent.h" #include "ShipIdentityComponent.h" #include "StationBodyComponent.h" @@ -80,11 +80,11 @@ entt::entity EntityAdmin::spawnStation(QPoint anchor, QSize footprint, return entity; } -entt::entity EntityAdmin::spawnScrap(QVector2D position, int amount, Tick despawnAt) +entt::entity EntityAdmin::spawnDebris(QVector2D position, int amount, Tick despawnAt) { entt::entity entity = createEntity(); add(entity, PositionComponent{position}); - add(entity, ScrapDataComponent{amount}); + add(entity, DebrisComponent{amount}); add(entity, DespawnAtComponent{despawnAt}); return entity; } diff --git a/src/lib/core/EntityAdmin.h b/src/lib/core/EntityAdmin.h index 72bbd7f..a0d783f 100644 --- a/src/lib/core/EntityAdmin.h +++ b/src/lib/core/EntityAdmin.h @@ -62,7 +62,7 @@ public: const std::vector& bodyCells, float hp, float maxHp, bool isEnemy); - entt::entity spawnScrap(QVector2D position, int amount, Tick despawnAt); + entt::entity spawnDebris(QVector2D position, int amount, Tick despawnAt); entt::entity spawnHqProxy(QVector2D position, float hp, float maxHp); diff --git a/src/lib/ecs/component/CMakeLists.txt b/src/lib/ecs/component/CMakeLists.txt index 68acc23..311058c 100644 --- a/src/lib/ecs/component/CMakeLists.txt +++ b/src/lib/ecs/component/CMakeLists.txt @@ -20,7 +20,7 @@ SET(HDRS ${CMAKE_CURRENT_SOURCE_DIR}/RetreatBehavior.h ${CMAKE_CURRENT_SOURCE_DIR}/SalvagerComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/SalvageScrapBehavior.h - ${CMAKE_CURRENT_SOURCE_DIR}/ScrapDataComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/DebrisComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/SelectedBehaviorComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/SensorRangeComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/ShipIdentityComponent.h diff --git a/src/lib/ecs/component/DebrisComponent.h b/src/lib/ecs/component/DebrisComponent.h new file mode 100644 index 0000000..2793b43 --- /dev/null +++ b/src/lib/ecs/component/DebrisComponent.h @@ -0,0 +1,8 @@ +#pragma once + +// Marks a piece of debris and holds the amount of scrap it still contains +// (REQ-RES-DEBRIS-DROP). Salvage modules collect one scrap per cycle (REQ-SHP-SALVAGE). +struct DebrisComponent +{ + int amount; +}; diff --git a/src/lib/ecs/component/SalvageScrapBehavior.h b/src/lib/ecs/component/SalvageScrapBehavior.h index 24fad19..60af3f2 100644 --- a/src/lib/ecs/component/SalvageScrapBehavior.h +++ b/src/lib/ecs/component/SalvageScrapBehavior.h @@ -5,10 +5,10 @@ #include // Collect-scrap behavior (one half of the old SalvageBehaviorComponent). The -// evaluator finds the nearest scrap and sets scrapTarget when cargo is not full. +// evaluator finds the nearest debris and sets debrisTarget when cargo is not full. struct SalvageScrapBehavior { - std::optional scrapTarget; + std::optional debrisTarget; float maxCollectionRange_tiles = 0.0f; float orbitRadius_tiles = 0.0f; // REQ-SHP-ORBIT float score = 0.0f; diff --git a/src/lib/ecs/component/ScrapDataComponent.h b/src/lib/ecs/component/ScrapDataComponent.h deleted file mode 100644 index 5e3e7a6..0000000 --- a/src/lib/ecs/component/ScrapDataComponent.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -struct ScrapDataComponent -{ - int amount; -}; diff --git a/src/lib/ecs/component/ShipIdentityComponent.h b/src/lib/ecs/component/ShipIdentityComponent.h index 1d71dd1..43559cc 100644 --- a/src/lib/ecs/component/ShipIdentityComponent.h +++ b/src/lib/ecs/component/ShipIdentityComponent.h @@ -6,6 +6,6 @@ struct ShipIdentityComponent { std::string schematicId; // Scrap dropped on destruction, derived from the ship's as-built threat cost - // at spawn time (REQ-RES-SCRAP-DROP). + // at spawn time (REQ-RES-DEBRIS-DROP). int scrapDrop = 0; }; diff --git a/src/lib/ecs/system/AiSystem.cpp b/src/lib/ecs/system/AiSystem.cpp index c47f07b..4fc8f91 100644 --- a/src/lib/ecs/system/AiSystem.cpp +++ b/src/lib/ecs/system/AiSystem.cpp @@ -43,7 +43,7 @@ AiSystem::AiSystem(const GameConfig& config) } void AiSystem::tick(EntityAdmin& admin, const BuildingSystem& buildings, - const ScrapSystem& scraps) + const DebrisSystem& debris) { TRACE(); @@ -54,7 +54,7 @@ void AiSystem::tick(EntityAdmin& admin, const BuildingSystem& buildings, m_retreatEvaluator.evaluate(admin); m_attackEvaluator.evaluate(admin); m_repairEvaluator.evaluate(admin); - m_salvageScrapEvaluator.evaluate(admin, scraps); + m_salvageScrapEvaluator.evaluate(admin, debris); m_deliverScrapEvaluator.evaluate(admin, buildings); // Phase 2: pick the highest-scoring behavior per ship. diff --git a/src/lib/ecs/system/AiSystem.h b/src/lib/ecs/system/AiSystem.h index 925f291..044c0c3 100644 --- a/src/lib/ecs/system/AiSystem.h +++ b/src/lib/ecs/system/AiSystem.h @@ -19,7 +19,7 @@ class BuildingSystem; class EntityAdmin; -class ScrapSystem; +class DebrisSystem; struct GameConfig; // Orchestrates ship-behavior decision-making in three batched phases: @@ -34,7 +34,7 @@ class AiSystem public: explicit AiSystem(const GameConfig& config); - void tick(EntityAdmin& admin, const BuildingSystem& buildings, const ScrapSystem& scraps); + void tick(EntityAdmin& admin, const BuildingSystem& buildings, const DebrisSystem& debris); private: void selectWinningBehaviors(EntityAdmin& admin); diff --git a/src/lib/ecs/system/CMakeLists.txt b/src/lib/ecs/system/CMakeLists.txt index 59286b2..f127656 100644 --- a/src/lib/ecs/system/CMakeLists.txt +++ b/src/lib/ecs/system/CMakeLists.txt @@ -23,7 +23,7 @@ SET(HDRS ${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.h ${CMAKE_CURRENT_SOURCE_DIR}/RepairSystem.h ${CMAKE_CURRENT_SOURCE_DIR}/SalvagerSystem.h - ${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.h + ${CMAKE_CURRENT_SOURCE_DIR}/DebrisSystem.h ${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.h PARENT_SCOPE ) @@ -53,7 +53,7 @@ SET(SRCS ${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RepairSystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/SalvagerSystem.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/DebrisSystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.cpp PARENT_SCOPE ) diff --git a/src/lib/ecs/system/DebrisSystem.cpp b/src/lib/ecs/system/DebrisSystem.cpp new file mode 100644 index 0000000..0a6936b --- /dev/null +++ b/src/lib/ecs/system/DebrisSystem.cpp @@ -0,0 +1,78 @@ +#include "DebrisSystem.h" + +#include "DespawnAtComponent.h" +#include "EntityAdmin.h" +#include "PositionComponent.h" +#include "DebrisComponent.h" +#include "tracing.h" + +DebrisSystem::DebrisSystem(EntityAdmin& admin) + : m_admin(admin) +{ +} + +entt::entity DebrisSystem::spawn(QVector2D position, int amount, Tick despawnAt) +{ + return m_admin.spawnDebris(position, amount, despawnAt); +} + +void DebrisSystem::tickDespawn(Tick currentTick) +{ + TRACE(); + std::vector expired; + m_admin.forEach( + [&expired, currentTick](entt::entity e, DespawnAtComponent& d) + { + if (d.tick <= currentTick) + { + expired.push_back(e); + } + }); + + for (entt::entity e : expired) + { + m_admin.destroy(e); + } +} + +std::optional DebrisSystem::consume(entt::entity entity) +{ + if (!m_admin.isValid(entity) || !m_admin.hasAll(entity)) + { + return std::nullopt; + } + int amount = m_admin.get(entity).amount; + m_admin.destroy(entity); + return amount; +} + +bool DebrisSystem::collectOne(entt::entity entity) +{ + if (!m_admin.isValid(entity) || !m_admin.hasAll(entity)) + { + return false; + } + DebrisComponent& data = m_admin.get(entity); + if (data.amount <= 0) + { + return false; + } + --data.amount; + if (data.amount <= 0) + { + m_admin.destroy(entity); + } + return true; +} + +std::vector DebrisSystem::getAllDebrisInfo() const +{ + std::vector result; + m_admin.forEach( + [&result, this](entt::entity e, const DebrisComponent& sd) + { + result.push_back(DebrisInfo{e, m_admin.get(e).value, sd.amount}); + }); + return result; +} + diff --git a/src/lib/ecs/system/ScrapSystem.h b/src/lib/ecs/system/DebrisSystem.h similarity index 52% rename from src/lib/ecs/system/ScrapSystem.h rename to src/lib/ecs/system/DebrisSystem.h index a38d183..2b41ff1 100644 --- a/src/lib/ecs/system/ScrapSystem.h +++ b/src/lib/ecs/system/DebrisSystem.h @@ -11,31 +11,35 @@ class EntityAdmin; -struct ScrapInfo +// A piece of debris and the scrap amount it still holds (REQ-RES-DEBRIS-DROP). +struct DebrisInfo { entt::entity entity; QVector2D position; int amount; }; -class ScrapSystem +// Manages debris entities: the salvageable objects dropped by destroyed ships and +// defence stations (REQ-RES-DEBRIS-DROP). Each piece carries a scrap amount that +// salvage modules collect one unit at a time (REQ-SHP-SALVAGE). +class DebrisSystem { public: - explicit ScrapSystem(EntityAdmin& admin); + explicit DebrisSystem(EntityAdmin& admin); entt::entity spawn(QVector2D position, int amount, Tick despawnAt); void tickDespawn(Tick currentTick); - // Removes the scrap and returns its amount, or nullopt if not found. + // Removes the debris and returns its remaining scrap amount, or nullopt if not found. std::optional consume(entt::entity entity); - // Collects a single scrap unit from the pile: decrements its amount by one, + // Collects a single scrap unit from the debris: decrements its amount by one, // destroying the entity once depleted. Returns true if a scrap was collected, // false if the entity is invalid or already empty (REQ-SHP-SALVAGE). bool collectOne(entt::entity entity); - // Lightweight snapshot for callers that need to iterate all scrap. - std::vector getAllScrapInfo() const; + // Lightweight snapshot for callers that need to iterate all debris. + std::vector getAllDebrisInfo() const; private: EntityAdmin& m_admin; diff --git a/src/lib/ecs/system/SalvagerSystem.cpp b/src/lib/ecs/system/SalvagerSystem.cpp index 5652713..33e65e8 100644 --- a/src/lib/ecs/system/SalvagerSystem.cpp +++ b/src/lib/ecs/system/SalvagerSystem.cpp @@ -14,8 +14,8 @@ #include "ModuleOwnerComponent.h" #include "PositionComponent.h" #include "SalvagerComponent.h" -#include "ScrapDataComponent.h" -#include "ScrapSystem.h" +#include "DebrisComponent.h" +#include "DebrisSystem.h" #include "tracing.h" SalvagerSystem::SalvagerSystem(EntityAdmin& admin) @@ -23,14 +23,14 @@ SalvagerSystem::SalvagerSystem(EntityAdmin& admin) { } -void SalvagerSystem::tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem& buildings, +void SalvagerSystem::tick(Tick currentTick, DebrisSystem& debris, BuildingSystem& buildings, std::vector& outBeamFiredEvents) { TRACE(); // Apply collections whose mid-beam delay has elapsed (cycles started earlier). - applyPendingCollections(currentTick, scraps); + applyPendingCollections(currentTick, debris); - const std::vector allScrap = scraps.getAllScrapInfo(); + const std::vector allDebris = debris.getAllDebrisInfo(); // Tick down per-module collection cooldowns. m_admin.forEach( @@ -40,8 +40,8 @@ void SalvagerSystem::tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem& }); // Scrap units already claimed by not-yet-applied collection cycles, so two - // modules don't both target the last unit of the same pile (the claim would be - // dropped at apply time). A pile is available while its amount exceeds its claims. + // modules don't both target the last unit of the same debris (the claim would be + // dropped at apply time). Debris is available while its amount exceeds its claims. std::map claimedUnits; // Collection cycles already in flight toward each ship's shared cargo pool, so // concurrent modules on the same ship never start more cycles than the remaining @@ -49,7 +49,7 @@ void SalvagerSystem::tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem& std::map pendingByShip; for (const PendingCollection& pc : m_pendingCollections) { - ++claimedUnits[pc.scrap]; + ++claimedUnits[pc.debris]; ++pendingByShip[pc.ship]; } @@ -66,12 +66,12 @@ void SalvagerSystem::tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem& if (cargo.current + pendingByShip[o.owner] >= cargo.maxCapacity) { return; } const QVector2D ownerPos = m_admin.get(o.owner).value; - for (const ScrapInfo& si : allScrap) + for (const DebrisInfo& si : allDebris) { if ((si.position - ownerPos).length() > s.collectionRange_tiles) { continue; } - if (claimedUnits[si.entity] >= m_admin.get(si.entity).amount) + if (claimedUnits[si.entity] >= m_admin.get(si.entity).amount) { - continue; // every remaining unit of this pile is already spoken for + continue; // every remaining unit of this debris is already spoken for } outBeamFiredEvents.push_back( BeamFiredEvent{BeamKind::Salvage, o.owner, si.entity, currentTick}); @@ -107,7 +107,7 @@ void SalvagerSystem::tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem& }); } -void SalvagerSystem::applyPendingCollections(Tick currentTick, ScrapSystem& scraps) +void SalvagerSystem::applyPendingCollections(Tick currentTick, DebrisSystem& debris) { std::vector::iterator it = m_pendingCollections.begin(); while (it != m_pendingCollections.end()) @@ -117,7 +117,7 @@ void SalvagerSystem::applyPendingCollections(Tick currentTick, ScrapSystem& scra if (m_admin.isValid(it->ship) && m_admin.hasAll(it->ship)) { CargoComponent& cargo = m_admin.get(it->ship); - if (cargo.current < cargo.maxCapacity && scraps.collectOne(it->scrap)) + if (cargo.current < cargo.maxCapacity && debris.collectOne(it->debris)) { ++cargo.current; } diff --git a/src/lib/ecs/system/SalvagerSystem.h b/src/lib/ecs/system/SalvagerSystem.h index b7b469f..a977652 100644 --- a/src/lib/ecs/system/SalvagerSystem.h +++ b/src/lib/ecs/system/SalvagerSystem.h @@ -9,11 +9,11 @@ class BuildingSystem; class EntityAdmin; -class ScrapSystem; +class DebrisSystem; // World-mutation system for salvage modules: each module runs a collection cycle // on its own cooldown. When a cycle starts it emits a salvage beam toward an -// in-range scrap pile and schedules the collection of one scrap for mid-beam +// in-range piece of debris and schedules the collection of one scrap for mid-beam // (kBeamImpactDelayTicks later) — mirroring weapon firing. Also delivers full // cargo at a SalvageBay. Runs every tick, independent of behavior selection. class SalvagerSystem @@ -21,18 +21,18 @@ class SalvagerSystem public: explicit SalvagerSystem(EntityAdmin& admin); - void tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem& buildings, + void tick(Tick currentTick, DebrisSystem& debris, BuildingSystem& buildings, std::vector& outBeamFiredEvents); private: struct PendingCollection { entt::entity ship; - entt::entity scrap; + entt::entity debris; Tick appliesAt; }; - void applyPendingCollections(Tick currentTick, ScrapSystem& scraps); + void applyPendingCollections(Tick currentTick, DebrisSystem& debris); EntityAdmin& m_admin; std::vector m_pendingCollections; diff --git a/src/lib/ecs/system/ScrapSystem.cpp b/src/lib/ecs/system/ScrapSystem.cpp deleted file mode 100644 index 0f012b9..0000000 --- a/src/lib/ecs/system/ScrapSystem.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "ScrapSystem.h" - -#include "DespawnAtComponent.h" -#include "EntityAdmin.h" -#include "PositionComponent.h" -#include "ScrapDataComponent.h" -#include "tracing.h" - -ScrapSystem::ScrapSystem(EntityAdmin& admin) - : m_admin(admin) -{ -} - -entt::entity ScrapSystem::spawn(QVector2D position, int amount, Tick despawnAt) -{ - return m_admin.spawnScrap(position, amount, despawnAt); -} - -void ScrapSystem::tickDespawn(Tick currentTick) -{ - TRACE(); - std::vector expired; - m_admin.forEach( - [&expired, currentTick](entt::entity e, DespawnAtComponent& d) - { - if (d.tick <= currentTick) - { - expired.push_back(e); - } - }); - - for (entt::entity e : expired) - { - m_admin.destroy(e); - } -} - -std::optional ScrapSystem::consume(entt::entity entity) -{ - if (!m_admin.isValid(entity) || !m_admin.hasAll(entity)) - { - return std::nullopt; - } - int amount = m_admin.get(entity).amount; - m_admin.destroy(entity); - return amount; -} - -bool ScrapSystem::collectOne(entt::entity entity) -{ - if (!m_admin.isValid(entity) || !m_admin.hasAll(entity)) - { - return false; - } - ScrapDataComponent& data = m_admin.get(entity); - if (data.amount <= 0) - { - return false; - } - --data.amount; - if (data.amount <= 0) - { - m_admin.destroy(entity); - } - return true; -} - -std::vector ScrapSystem::getAllScrapInfo() const -{ - std::vector result; - m_admin.forEach( - [&result, this](entt::entity e, const ScrapDataComponent& sd) - { - result.push_back(ScrapInfo{e, m_admin.get(e).value, sd.amount}); - }); - return result; -} - diff --git a/src/lib/ecs/system/ShipSystem.cpp b/src/lib/ecs/system/ShipSystem.cpp index d2f6417..6b7182f 100644 --- a/src/lib/ecs/system/ShipSystem.cpp +++ b/src/lib/ecs/system/ShipSystem.cpp @@ -96,7 +96,7 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, layout.has_value() ? layout->placedModules : def->defaultModules; // Derive the scrap dropped on destruction from the ship's as-built threat cost - // (REQ-RES-SCRAP-DROP): round(threat * scrap_per_threat), floored at 1 for any + // (REQ-RES-DEBRIS-DROP): round(threat * scrap_per_threat), floored at 1 for any // ship with threat > 0. Computed once here since threat is level-independent. const double threatCost = calculateShipThreatCost(m_config.threatCosts, m_config, schematicId, modules); @@ -392,7 +392,7 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, } SalvageScrapBehavior salvage; - salvage.scrapTarget = std::nullopt; + salvage.debrisTarget = std::nullopt; salvage.maxCollectionRange_tiles = maxCollRange; salvage.orbitRadius_tiles = maxCollRange * static_cast(m_config.world.orbitFactor); diff --git a/src/lib/ecs/system/ai/SalvageScrapEvaluator.cpp b/src/lib/ecs/system/ai/SalvageScrapEvaluator.cpp index 83310cc..4b78060 100644 --- a/src/lib/ecs/system/ai/SalvageScrapEvaluator.cpp +++ b/src/lib/ecs/system/ai/SalvageScrapEvaluator.cpp @@ -11,15 +11,15 @@ #include "EntityAdmin.h" #include "PositionComponent.h" #include "SalvageScrapBehavior.h" -#include "ScrapSystem.h" +#include "DebrisSystem.h" #include "SensorRangeComponent.h" #include "tracing.h" -void SalvageScrapEvaluator::evaluate(EntityAdmin& admin, const ScrapSystem& scraps) +void SalvageScrapEvaluator::evaluate(EntityAdmin& admin, const DebrisSystem& debris) { TRACE(); const std::unordered_map cargoByShip = buildCargoByShip(admin); - const std::vector allScrap = scraps.getAllScrapInfo(); + const std::vector allDebris = debris.getAllDebrisInfo(); admin.forEach( [&](entt::entity e, SalvageScrapBehavior& salvage, const PositionComponent& pos, @@ -31,15 +31,15 @@ void SalvageScrapEvaluator::evaluate(EntityAdmin& admin, const ScrapSystem& scra if (cargoFull) { - salvage.scrapTarget = std::nullopt; + salvage.debrisTarget = std::nullopt; salvage.score = BehaviorScores::kInactive; return; } - // Find nearest scrap within sensor range. + // Find nearest debris within sensor range. float bestDist = sensor.value_tiles; std::optional bestPos; - for (const ScrapInfo& si : allScrap) + for (const DebrisInfo& si : allDebris) { const float dist = (si.position - pos.value).length(); if (dist < bestDist) @@ -49,7 +49,7 @@ void SalvageScrapEvaluator::evaluate(EntityAdmin& admin, const ScrapSystem& scra } } - salvage.scrapTarget = bestPos; + salvage.debrisTarget = bestPos; salvage.score = bestPos ? BehaviorScores::kSalvage : BehaviorScores::kInactive; }); } diff --git a/src/lib/ecs/system/ai/SalvageScrapEvaluator.h b/src/lib/ecs/system/ai/SalvageScrapEvaluator.h index f6d6a07..ef33f47 100644 --- a/src/lib/ecs/system/ai/SalvageScrapEvaluator.h +++ b/src/lib/ecs/system/ai/SalvageScrapEvaluator.h @@ -1,13 +1,13 @@ #pragma once class EntityAdmin; -class ScrapSystem; +class DebrisSystem; -// When cargo is not full, finds the nearest scrap within sensor range and sets -// it as the target, scoring high. Scores inactive when cargo is full or no scrap +// When cargo is not full, finds the nearest debris within sensor range and sets +// it as the target, scoring high. Scores inactive when cargo is full or no debris // is in range (Advance then handles roaming). class SalvageScrapEvaluator { public: - void evaluate(EntityAdmin& admin, const ScrapSystem& scraps); + void evaluate(EntityAdmin& admin, const DebrisSystem& debris); }; diff --git a/src/lib/ecs/system/ai/SalvageScrapExecutor.cpp b/src/lib/ecs/system/ai/SalvageScrapExecutor.cpp index b56a16c..66e4a35 100644 --- a/src/lib/ecs/system/ai/SalvageScrapExecutor.cpp +++ b/src/lib/ecs/system/ai/SalvageScrapExecutor.cpp @@ -17,8 +17,8 @@ void SalvageScrapExecutor::execute(EntityAdmin& admin) MovementIntentComponent& intent) { if (selected.winner != BehaviorKind::SalvageScrap) { return; } - if (!salvage.scrapTarget) { return; } - intent = MovementIntentComponent{true, *salvage.scrapTarget, + if (!salvage.debrisTarget) { return; } + intent = MovementIntentComponent{true, *salvage.debrisTarget, salvage.orbitRadius_tiles}; }); } diff --git a/src/lib/eventsystem/event/DebrisSelectionChangedEvent.h b/src/lib/eventsystem/event/DebrisSelectionChangedEvent.h new file mode 100644 index 0000000..26a49f7 --- /dev/null +++ b/src/lib/eventsystem/event/DebrisSelectionChangedEvent.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "entt/entity/entity.hpp" + +#include "Event.h" + +// The set of currently selected debris (REQ-UI-DEBRIS-CLICK-SELECT, +// REQ-UI-DEBRIS-MULTI-SELECT). An empty list means no debris is selected. Debris forms +// its own selection category, mutually exclusive with buildings and entities. +class DebrisSelectionChangedEvent : public Event +{ +public: + explicit DebrisSelectionChangedEvent(std::vector debris) + : debris(std::move(debris)) {} + const std::vector debris; +}; diff --git a/src/lib/eventsystem/event/ScrapSelectionChangedEvent.h b/src/lib/eventsystem/event/ScrapSelectionChangedEvent.h deleted file mode 100644 index 36bc7fd..0000000 --- a/src/lib/eventsystem/event/ScrapSelectionChangedEvent.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include - -#include "entt/entity/entity.hpp" - -#include "Event.h" - -// The set of currently selected scrap piles (REQ-UI-SCRAP-CLICK-SELECT, -// REQ-UI-SCRAP-MULTI-SELECT). An empty list means no scrap is selected. Scrap forms -// its own selection category, mutually exclusive with buildings and entities. -class ScrapSelectionChangedEvent : public Event -{ -public: - explicit ScrapSelectionChangedEvent(std::vector scrap) - : scrap(std::move(scrap)) {} - const std::vector scrap; -}; diff --git a/src/lib/sim/EntityHitTest.cpp b/src/lib/sim/EntityHitTest.cpp index 77c7445..29cee97 100644 --- a/src/lib/sim/EntityHitTest.cpp +++ b/src/lib/sim/EntityHitTest.cpp @@ -5,7 +5,7 @@ #include "EntityAdmin.h" #include "PositionComponent.h" -#include "ScrapDataComponent.h" +#include "DebrisComponent.h" #include "ShipIdentityComponent.h" #include "StationBodyComponent.h" #include "HealthComponent.h" @@ -58,16 +58,16 @@ entt::entity entityAtWorldPos(EntityAdmin& admin, QVector2D worldPos) return bestShip; } -entt::entity scrapAtWorldPos(EntityAdmin& admin, QVector2D worldPos) +entt::entity debrisAtWorldPos(EntityAdmin& admin, QVector2D worldPos) { - // Slightly larger than the scrap's rendered radius (0.2 tiles) so small piles + // Slightly larger than the debris's rendered radius (0.2 tiles) so small pieces // remain easy to click; tunable. - constexpr float kScrapHitRadiusSquared = 0.35f * 0.35f; - entt::entity bestScrap = entt::null; - float bestDistSquared = kScrapHitRadiusSquared; + constexpr float kDebrisHitRadiusSquared = 0.35f * 0.35f; + entt::entity bestDebris = entt::null; + float bestDistSquared = kDebrisHitRadiusSquared; - admin.forEach( - [&](entt::entity entity, const ScrapDataComponent& /*sd*/, const PositionComponent& pos) + admin.forEach( + [&](entt::entity entity, const DebrisComponent& /*sd*/, const PositionComponent& pos) { const float dx = pos.value.x() - worldPos.x(); const float dy = pos.value.y() - worldPos.y(); @@ -75,14 +75,14 @@ entt::entity scrapAtWorldPos(EntityAdmin& admin, QVector2D worldPos) if (distSquared < bestDistSquared) { bestDistSquared = distSquared; - bestScrap = entity; + bestDebris = entity; } }); - return bestScrap; + return bestDebris; } -std::vector scrapInBox(EntityAdmin& admin, QPoint tileA, QPoint tileB) +std::vector debrisInBox(EntityAdmin& admin, QPoint tileA, QPoint tileB) { const int minX = std::min(tileA.x(), tileB.x()); const int maxX = std::max(tileA.x(), tileB.x()); @@ -90,8 +90,8 @@ std::vector scrapInBox(EntityAdmin& admin, QPoint tileA, QPoint ti const int maxY = std::max(tileA.y(), tileB.y()); std::vector result; - admin.forEach( - [&](entt::entity entity, const ScrapDataComponent& /*sd*/, const PositionComponent& pos) + admin.forEach( + [&](entt::entity entity, const DebrisComponent& /*sd*/, const PositionComponent& pos) { const int tileX = static_cast(std::floor(pos.value.x())); const int tileY = static_cast(std::floor(pos.value.y())); diff --git a/src/lib/sim/EntityHitTest.h b/src/lib/sim/EntityHitTest.h index ca473a5..c44a7da 100644 --- a/src/lib/sim/EntityHitTest.h +++ b/src/lib/sim/EntityHitTest.h @@ -11,14 +11,14 @@ class EntityAdmin; entt::entity entityAtWorldPos(EntityAdmin& admin, QVector2D worldPos); -// Returns the nearest scrap pile whose center is within the scrap pick radius of -// worldPos, or entt::null if none (REQ-UI-SCRAP-CLICK-SELECT). Scrap is picked only -// after actors: entityAtWorldPos never returns scrap (scrap has no HealthComponent). -entt::entity scrapAtWorldPos(EntityAdmin& admin, QVector2D worldPos); +// Returns the nearest piece of debris whose center is within the debris pick radius of +// worldPos, or entt::null if none (REQ-UI-DEBRIS-CLICK-SELECT). Debris is picked only +// after actors: entityAtWorldPos never returns debris (debris has no HealthComponent). +entt::entity debrisAtWorldPos(EntityAdmin& admin, QVector2D worldPos); -// Returns every scrap pile whose position falls within the inclusive tile rectangle -// spanned by tileA and tileB, in any corner order (REQ-UI-SCRAP-MULTI-SELECT). -std::vector scrapInBox(EntityAdmin& admin, QPoint tileA, QPoint tileB); +// Returns every piece of debris whose position falls within the inclusive tile rectangle +// spanned by tileA and tileB, in any corner order (REQ-UI-DEBRIS-MULTI-SELECT). +std::vector debrisInBox(EntityAdmin& admin, QPoint tileA, QPoint tileB); // Returns every living actor (ship or defence station, player or enemy) that falls // within the inclusive tile rectangle spanned by tileA and tileB, in any corner order diff --git a/src/lib/sim/Simulation.cpp b/src/lib/sim/Simulation.cpp index eb6c433..17d14df 100644 --- a/src/lib/sim/Simulation.cpp +++ b/src/lib/sim/Simulation.cpp @@ -20,8 +20,8 @@ #include "PositionComponent.h" #include "RepairSystem.h" #include "SalvagerSystem.h" -#include "ScrapDataComponent.h" -#include "ScrapSystem.h" +#include "DebrisComponent.h" +#include "DebrisSystem.h" #include "ShipIdentityComponent.h" #include "ShipSystem.h" #include "StateChecksum.h" @@ -69,7 +69,7 @@ Simulation::Simulation(GameConfig config, unsigned int seed) m_aiSystem = std::make_unique(m_config); m_movementIntentSystem = std::make_unique(); m_dynamicBodySystem = std::make_unique(); - m_scrapSystem = std::make_unique(m_admin); + m_debrisSystem = std::make_unique(m_admin); m_salvagerSystem = std::make_unique(m_admin); m_repairSystem = std::make_unique(m_admin); m_waveSystem = std::make_unique(m_config, m_rng); @@ -141,7 +141,7 @@ void Simulation::reset(unsigned int seed) m_aiSystem = std::make_unique(m_config); m_movementIntentSystem = std::make_unique(); m_dynamicBodySystem = std::make_unique(); - m_scrapSystem = std::make_unique(m_admin); + m_debrisSystem = std::make_unique(m_admin); m_salvagerSystem = std::make_unique(m_admin); m_repairSystem = std::make_unique(m_admin); m_waveSystem = std::make_unique(m_config, m_rng); @@ -329,10 +329,10 @@ void Simulation::tick() m_shipSystem->clearMovementIntents(); // Score-based behavior selection: evaluate, select winner, execute (sets // movement intent + preferred module targets only — no world mutation). - m_aiSystem->tick(m_admin, *m_buildingSystem, *m_scrapSystem); + m_aiSystem->tick(m_admin, *m_buildingSystem, *m_debrisSystem); // Module systems perform the world mutation (collection/delivery, healing). // Each emits its tool beams and applies its own delayed (mid-beam) effects. - m_salvagerSystem->tick(m_currentTick, *m_scrapSystem, *m_buildingSystem, m_beamFiredEvents); + m_salvagerSystem->tick(m_currentTick, *m_debrisSystem, *m_buildingSystem, m_beamFiredEvents); m_repairSystem->tick(m_currentTick, m_beamFiredEvents); // Step 8: combat resolution @@ -352,8 +352,8 @@ void Simulation::tick() m_movementIntentSystem->tick(m_admin); m_dynamicBodySystem->tick(m_admin); - // Step 11: scrap despawn - m_scrapSystem->tickDespawn(m_currentTick); + // Step 11: debris despawn + m_debrisSystem->tickDespawn(m_currentTick); ++m_currentTick; } @@ -542,8 +542,8 @@ void Simulation::tickDeathsAndLoot() if (si.scrapDrop > 0) { const Tick despawnAt = m_currentTick - + secondsToTicks(m_config.world.scrapDespawnSeconds); - m_scrapSystem->spawn(pos.value, si.scrapDrop, despawnAt); + + secondsToTicks(m_config.world.debrisDespawnSeconds); + m_debrisSystem->spawn(pos.value, si.scrapDrop, despawnAt); } m_shipSystem->despawn(deadEntity); } @@ -567,7 +567,7 @@ void Simulation::tickDeathsAndLoot() const FactionComponent& fac = m_admin.get(deadEntity); const Tick despawnAt = m_currentTick - + secondsToTicks(m_config.world.scrapDespawnSeconds); + + secondsToTicks(m_config.world.debrisDespawnSeconds); int scrap = 0; if (!fac.isEnemy) { @@ -584,7 +584,7 @@ void Simulation::tickDeathsAndLoot() } if (scrap > 0) { - m_scrapSystem->spawn(pos.value, scrap, despawnAt); + m_debrisSystem->spawn(pos.value, scrap, despawnAt); } m_buildingSystem->unregisterTileOccupancy(sb.bodyCells); { @@ -1000,8 +1000,8 @@ unsigned long long Simulation::computeStateChecksum() const hasher.append(c.linearAcceleration_tptt); hasher.append(c.angularAcceleration_rptt); }); - m_admin.forEach( - [&hasher](entt::entity entity, const ScrapDataComponent& c) + m_admin.forEach( + [&hasher](entt::entity entity, const DebrisComponent& c) { hasher.append(static_cast(entity)); hasher.append(c.amount); @@ -1238,14 +1238,14 @@ const ShipSystem& Simulation::getShips() const return *m_shipSystem; } -ScrapSystem& Simulation::getScraps() +DebrisSystem& Simulation::getDebrisSystem() { - return *m_scrapSystem; + return *m_debrisSystem; } -const ScrapSystem& Simulation::getScraps() const +const DebrisSystem& Simulation::getDebrisSystem() const { - return *m_scrapSystem; + return *m_debrisSystem; } EntityAdmin& Simulation::getAdmin() diff --git a/src/lib/sim/Simulation.h b/src/lib/sim/Simulation.h index d73cbdf..851787c 100644 --- a/src/lib/sim/Simulation.h +++ b/src/lib/sim/Simulation.h @@ -33,7 +33,7 @@ class MovementIntentSystem; class RepairSystem; class SalvagerSystem; class ShipSystem; -class ScrapSystem; +class DebrisSystem; class WaveSystem; class Simulation: public CombinedEventHandler @@ -122,8 +122,8 @@ public: const BeltSystem& getBelts() const; ShipSystem& getShips(); const ShipSystem& getShips() const; - ScrapSystem& getScraps(); - const ScrapSystem& getScraps() const; + DebrisSystem& getDebrisSystem(); + const DebrisSystem& getDebrisSystem() const; EntityAdmin& getAdmin(); const EntityAdmin& getAdmin() const; @@ -172,7 +172,7 @@ private: // Stores their IDs in m_currentEnemyStationIds. void placeEnemyStationSet(int generation); - // Tick step 9: remove dead ships and buildings, drop scrap, handle push. + // Tick step 9: remove dead ships and buildings, drop debris, handle push. void tickDeathsAndLoot(); // Generate up to 3 schematic choices (REQ-DEF-SCHEMATIC-DROP) for the player. @@ -269,7 +269,7 @@ private: std::unique_ptr m_aiSystem; std::unique_ptr m_movementIntentSystem; std::unique_ptr m_dynamicBodySystem; - std::unique_ptr m_scrapSystem; + std::unique_ptr m_debrisSystem; std::unique_ptr m_salvagerSystem; std::unique_ptr m_repairSystem; std::unique_ptr m_waveSystem; diff --git a/src/lib/sim/ThreatCostCalculator.cpp b/src/lib/sim/ThreatCostCalculator.cpp index 30ecfc1..51c458f 100644 --- a/src/lib/sim/ThreatCostCalculator.cpp +++ b/src/lib/sim/ThreatCostCalculator.cpp @@ -66,7 +66,7 @@ ThreatCostTable computeThreatCostTable(const GameConfig& config) ThreatCostTable table; // Scrap threat (REQ-THREAT-SCRAP) is the constant inverse of the scrap-drop - // conversion (REQ-RES-SCRAP-DROP): one scrap is worth 1 / scrap_per_threat. + // conversion (REQ-RES-DEBRIS-DROP): one scrap is worth 1 / scrap_per_threat. // Set it up front so reprocessing-only item threats (below) can use it, and so // it no longer depends on any ship's threat cost. table.scrapThreat = config.world.scrapPerThreat > 0.0 diff --git a/src/test/BehaviorSystemTest.cpp b/src/test/BehaviorSystemTest.cpp index 28bee2b..2e3e84f 100644 --- a/src/test/BehaviorSystemTest.cpp +++ b/src/test/BehaviorSystemTest.cpp @@ -38,7 +38,7 @@ #include "SalvageScrapBehavior.h" #include "SalvagerComponent.h" #include "SalvagerSystem.h" -#include "ScrapSystem.h" +#include "DebrisSystem.h" #include "SelectedBehaviorComponent.h" #include "SensorRangeComponent.h" #include "ShipIdentityComponent.h" @@ -70,7 +70,7 @@ struct Fixture RepairSystem repair; MovementIntentSystem movementIntent; DynamicBodySystem dynamicBody; - ScrapSystem scraps; + DebrisSystem scraps; Tick tick; std::vector beamEvents; @@ -1288,7 +1288,7 @@ TEST_CASE("SensorRange: salvage ship ignores scrap beyond sensor range", "[senso f.decide(); - REQUIRE_FALSE(f.admin.get(ship).scrapTarget.has_value()); + REQUIRE_FALSE(f.admin.get(ship).debrisTarget.has_value()); REQUIRE(intent(f.admin, ship).target.x() > pos(f.admin, ship).value.x()); } diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 598845a..3f88e32 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -13,7 +13,7 @@ add_files( BuildingTest.cpp BuildingConfigTest.cpp ShipTest.cpp - ScrapTest.cpp + DebrisTest.cpp BehaviorSystemTest.cpp WaveSystemTest.cpp CombatSystemTest.cpp diff --git a/src/test/CombatSystemTest.cpp b/src/test/CombatSystemTest.cpp index 2ffdc32..be33464 100644 --- a/src/test/CombatSystemTest.cpp +++ b/src/test/CombatSystemTest.cpp @@ -14,8 +14,8 @@ #include "HealthComponent.h" #include "HqProxyComponent.h" #include "ModuleOwnerComponent.h" -#include "ScrapDataComponent.h" -#include "ScrapSystem.h" +#include "DebrisComponent.h" +#include "DebrisSystem.h" #include "ShipSystem.h" #include "Simulation.h" #include "AttackBehavior.h" @@ -408,7 +408,7 @@ TEST_CASE("CombatSystem: scrap is spawned on ship death", "[combat]") Simulation sim(loadConfig(), 42); // Scrap dropped on death is derived from the ship's as-built threat cost - // (REQ-RES-SCRAP-DROP): round(threat * scrap_per_threat). The interceptor's + // (REQ-RES-DEBRIS-DROP): round(threat * scrap_per_threat). The interceptor's // threat is 59.0 and the test config sets scrap_per_threat = 1.0, so it drops // round(59.0 * 1.0) = 59 scrap. const entt::entity ship = sim.getShips().spawn("interceptor", @@ -417,9 +417,9 @@ TEST_CASE("CombatSystem: scrap is spawned on ship death", "[combat]") sim.tick(); - const std::vector scraps = sim.getScraps().getAllScrapInfo(); + const std::vector scraps = sim.getDebrisSystem().getAllDebrisInfo(); REQUIRE(scraps.size() == 1); - CHECK(sim.getAdmin().get(scraps[0].entity).amount == 59); + CHECK(sim.getAdmin().get(scraps[0].entity).amount == 59); } TEST_CASE("CombatSystem: HQ death sets game over", "[combat]") diff --git a/src/test/ConfigLoaderTest.cpp b/src/test/ConfigLoaderTest.cpp index 42d5a39..bfdf4bf 100644 --- a/src/test/ConfigLoaderTest.cpp +++ b/src/test/ConfigLoaderTest.cpp @@ -212,7 +212,7 @@ TEST_CASE("Missing field in world.toml is rejected with the field path", "[confi height_tiles = 60 refund_percentage = 75 deconstruction_time_seconds = 0.1 -scrap_despawn_seconds = 30 +debris_despawn_seconds = 30 scrap_per_threat = 0.01 tile_size_m = 10 belt_speed_mps = 20 @@ -264,7 +264,7 @@ TEST_CASE("Malformed formula in world.toml is rejected with field identification height_tiles = 60 refund_percentage = 75 deconstruction_time_seconds = 0.1 -scrap_despawn_seconds = 30 +debris_despawn_seconds = 30 scrap_per_threat = 0.01 tile_size_m = 10 belt_speed_mps = 20 @@ -317,7 +317,7 @@ TEST_CASE("Inverted wave gap range is rejected", "[config]") height_tiles = 60 refund_percentage = 75 deconstruction_time_seconds = 0.1 -scrap_despawn_seconds = 30 +debris_despawn_seconds = 30 scrap_per_threat = 0.01 tile_size_m = 10 belt_speed_mps = 20 diff --git a/src/test/ScrapTest.cpp b/src/test/DebrisTest.cpp similarity index 69% rename from src/test/ScrapTest.cpp rename to src/test/DebrisTest.cpp index 35209bc..fd3acea 100644 --- a/src/test/ScrapTest.cpp +++ b/src/test/DebrisTest.cpp @@ -8,8 +8,8 @@ #include "DespawnAtComponent.h" #include "EntityAdmin.h" #include "EntityHitTest.h" -#include "ScrapDataComponent.h" -#include "ScrapSystem.h" +#include "DebrisComponent.h" +#include "DebrisSystem.h" namespace { @@ -23,15 +23,15 @@ bool contains(const std::vector& v, entt::entity e) // Spawn // --------------------------------------------------------------------------- -TEST_CASE("ScrapSystem: spawn returns a valid entity with correct scrap data", "[scrap]") +TEST_CASE("DebrisSystem: spawn returns a valid entity with correct debris data", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); const entt::entity e = ss.spawn(QVector2D(3.0f, 4.0f), 5, 100); REQUIRE(admin.isValid(e)); - REQUIRE(admin.get(e).amount == 5); + REQUIRE(admin.get(e).amount == 5); REQUIRE(admin.get(e).tick == 100); } @@ -39,10 +39,10 @@ TEST_CASE("ScrapSystem: spawn returns a valid entity with correct scrap data", " // Despawn timing // --------------------------------------------------------------------------- -TEST_CASE("ScrapSystem: scrap still present one tick before despawnAt", "[scrap]") +TEST_CASE("DebrisSystem: debris still present one tick before despawnAt", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); const entt::entity e = ss.spawn(QVector2D(0.0f, 0.0f), 1, 50); @@ -50,10 +50,10 @@ TEST_CASE("ScrapSystem: scrap still present one tick before despawnAt", "[scrap] REQUIRE(admin.isValid(e)); } -TEST_CASE("ScrapSystem: scrap removed at despawnAt tick", "[scrap]") +TEST_CASE("DebrisSystem: debris removed at despawnAt tick", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); const entt::entity e = ss.spawn(QVector2D(0.0f, 0.0f), 1, 50); @@ -65,10 +65,10 @@ TEST_CASE("ScrapSystem: scrap removed at despawnAt tick", "[scrap]") // Selective removal // --------------------------------------------------------------------------- -TEST_CASE("ScrapSystem: tickDespawn removes only expired scraps", "[scrap]") +TEST_CASE("DebrisSystem: tickDespawn removes only expired debris", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); const entt::entity earlyE = ss.spawn(QVector2D(0.0f, 0.0f), 1, 30); const entt::entity lateE = ss.spawn(QVector2D(1.0f, 0.0f), 2, 60); @@ -83,10 +83,10 @@ TEST_CASE("ScrapSystem: tickDespawn removes only expired scraps", "[scrap]") // Consume // --------------------------------------------------------------------------- -TEST_CASE("ScrapSystem: consume returns amount and destroys entity", "[scrap]") +TEST_CASE("DebrisSystem: consume returns amount and destroys entity", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); const entt::entity e = ss.spawn(QVector2D(0.0f, 0.0f), 7, 100); @@ -96,10 +96,10 @@ TEST_CASE("ScrapSystem: consume returns amount and destroys entity", "[scrap]") REQUIRE_FALSE(admin.isValid(e)); } -TEST_CASE("ScrapSystem: consume returns nullopt for invalid entity", "[scrap]") +TEST_CASE("DebrisSystem: consume returns nullopt for invalid entity", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); const std::optional amount = ss.consume(entt::null); REQUIRE_FALSE(amount.has_value()); @@ -109,118 +109,118 @@ TEST_CASE("ScrapSystem: consume returns nullopt for invalid entity", "[scrap]") // collectOne // --------------------------------------------------------------------------- -TEST_CASE("ScrapSystem: collectOne depletes one scrap and keeps the pile until empty", "[scrap]") +TEST_CASE("DebrisSystem: collectOne depletes one scrap and keeps the debris until empty", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); const entt::entity e = ss.spawn(QVector2D(0.0f, 0.0f), 3, 100); REQUIRE(ss.collectOne(e)); REQUIRE(admin.isValid(e)); - REQUIRE(admin.get(e).amount == 2); + REQUIRE(admin.get(e).amount == 2); REQUIRE(ss.collectOne(e)); REQUIRE(admin.isValid(e)); - REQUIRE(admin.get(e).amount == 1); + REQUIRE(admin.get(e).amount == 1); - // Final unit collected: the pile is removed once depleted. + // Final unit collected: the debris is removed once depleted. REQUIRE(ss.collectOne(e)); REQUIRE_FALSE(admin.isValid(e)); } -TEST_CASE("ScrapSystem: collectOne returns false for an invalid entity", "[scrap]") +TEST_CASE("DebrisSystem: collectOne returns false for an invalid entity", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); REQUIRE_FALSE(ss.collectOne(entt::null)); } // --------------------------------------------------------------------------- -// allScrapInfo +// getAllDebrisInfo // --------------------------------------------------------------------------- -TEST_CASE("ScrapSystem: allScrapInfo returns all spawned scrap", "[scrap]") +TEST_CASE("DebrisSystem: getAllDebrisInfo returns all spawned debris", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); ss.spawn(QVector2D(1.0f, 2.0f), 3, 100); ss.spawn(QVector2D(4.0f, 5.0f), 6, 200); - const std::vector info = ss.getAllScrapInfo(); + const std::vector info = ss.getAllDebrisInfo(); REQUIRE(info.size() == 2); } -TEST_CASE("ScrapSystem: allScrapInfo reports each pile's remaining amount", "[scrap]") +TEST_CASE("DebrisSystem: getAllDebrisInfo reports each debris entry.s remaining amount", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); const entt::entity a = ss.spawn(QVector2D(1.0f, 2.0f), 3, 100); const entt::entity b = ss.spawn(QVector2D(4.0f, 5.0f), 6, 200); - const std::vector info = ss.getAllScrapInfo(); + const std::vector info = ss.getAllDebrisInfo(); REQUIRE(info.size() == 2); - for (const ScrapInfo& i : info) + for (const DebrisInfo& i : info) { if (i.entity == a) { REQUIRE(i.amount == 3); } else if (i.entity == b) { REQUIRE(i.amount == 6); } - else { FAIL("unexpected scrap entity"); } + else { FAIL("unexpected debris entity"); } } } // --------------------------------------------------------------------------- -// Selection hit-testing (REQ-UI-SCRAP-CLICK-SELECT, REQ-UI-SCRAP-MULTI-SELECT) +// Selection hit-testing (REQ-UI-DEBRIS-CLICK-SELECT, REQ-UI-DEBRIS-MULTI-SELECT) // --------------------------------------------------------------------------- -TEST_CASE("scrapAtWorldPos returns the pile near a point and null when far", "[scrap]") +TEST_CASE("debrisAtWorldPos returns the debris near a point and null when far", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); const entt::entity e = ss.spawn(QVector2D(3.0f, 4.0f), 5, 100); // Extra parens keep Catch from decomposing the comparison, which is ambiguous // between Catch's expression templates and entt's entity operator==. - REQUIRE((scrapAtWorldPos(admin, QVector2D(3.1f, 4.0f)) == e)); - REQUIRE((scrapAtWorldPos(admin, QVector2D(10.0f, 10.0f)) == entt::null)); + REQUIRE((debrisAtWorldPos(admin, QVector2D(3.1f, 4.0f)) == e)); + REQUIRE((debrisAtWorldPos(admin, QVector2D(10.0f, 10.0f)) == entt::null)); } -TEST_CASE("scrapAtWorldPos returns the nearest of several piles", "[scrap]") +TEST_CASE("debrisAtWorldPos returns the nearest of several debris", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); const entt::entity near = ss.spawn(QVector2D(2.0f, 2.0f), 1, 100); ss.spawn(QVector2D(2.4f, 2.0f), 1, 100); - REQUIRE((scrapAtWorldPos(admin, QVector2D(2.05f, 2.0f)) == near)); + REQUIRE((debrisAtWorldPos(admin, QVector2D(2.05f, 2.0f)) == near)); } -TEST_CASE("entityAtWorldPos never returns a scrap pile", "[scrap]") +TEST_CASE("entityAtWorldPos never returns debris", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); ss.spawn(QVector2D(3.0f, 4.0f), 5, 100); - // Scrap has no HealthComponent, so the actor hit-test ignores it entirely. + // Debris has no HealthComponent, so the actor hit-test ignores it entirely. REQUIRE((entityAtWorldPos(admin, QVector2D(3.0f, 4.0f)) == entt::null)); } -TEST_CASE("scrapInBox returns exactly the piles inside the tile rectangle", "[scrap]") +TEST_CASE("debrisInBox returns exactly the debris inside the tile rectangle", "[debris]") { EntityAdmin admin; - ScrapSystem ss(admin); + DebrisSystem ss(admin); const entt::entity inA = ss.spawn(QVector2D(1.2f, 2.7f), 1, 100); // tile (1,2) const entt::entity inB = ss.spawn(QVector2D(4.9f, 5.1f), 1, 100); // tile (4,5) const entt::entity outX = ss.spawn(QVector2D(10.0f, 10.0f), 1, 100); // Box given in reversed corner order to confirm normalization. - const std::vector hit = scrapInBox(admin, QPoint(5, 5), QPoint(0, 0)); + const std::vector hit = debrisInBox(admin, QPoint(5, 5), QPoint(0, 0)); REQUIRE(hit.size() == 2); REQUIRE(contains(hit, inA)); @@ -228,7 +228,7 @@ TEST_CASE("scrapInBox returns exactly the piles inside the tile rectangle", "[sc REQUIRE_FALSE(contains(hit, outX)); } -TEST_CASE("actorsInBox returns living ships and stations, excluding scrap and dead actors", +TEST_CASE("actorsInBox returns living ships and stations, excluding debris and dead actors", "[actor]") { EntityAdmin admin; @@ -256,8 +256,8 @@ TEST_CASE("actorsInBox returns living ships and stations, excluding scrap and de const entt::entity station = admin.spawnStation( QPoint(2, 2), QSize(2, 1), stationCells, 200.0f, 200.0f, true); - // Scrap and the HQ proxy are never actors. - admin.spawnScrap(QVector2D(1.0f, 1.0f), 5, Tick(1000)); + // Debris and the HQ proxy are never actors. + admin.spawnDebris(QVector2D(1.0f, 1.0f), 5, Tick(1000)); admin.spawnHqProxy(QVector2D(0.5f, 0.5f), 500.0f, 500.0f); const std::vector hit = actorsInBox(admin, QPoint(5, 5), QPoint(0, 0)); diff --git a/src/test/ShipTest.cpp b/src/test/ShipTest.cpp index 0a91c17..f370a5f 100644 --- a/src/test/ShipTest.cpp +++ b/src/test/ShipTest.cpp @@ -225,7 +225,7 @@ TEST_CASE("ShipSystem: salvage_ship cargo capacity matches config", "[ship]") REQUIRE(admin.get(e).maxCapacity == 10); REQUIRE(admin.get(e).current == 0); REQUIRE_FALSE(admin.get(e).deliveryBay.has_value()); - REQUIRE_FALSE(admin.get(e).scrapTarget.has_value()); + REQUIRE_FALSE(admin.get(e).debrisTarget.has_value()); REQUIRE(admin.get(e).maxCollectionRange_tiles == Approx(50.0f)); } diff --git a/src/ui/GameWorldView.cpp b/src/ui/GameWorldView.cpp index b63fafe..83bde00 100644 --- a/src/ui/GameWorldView.cpp +++ b/src/ui/GameWorldView.cpp @@ -52,15 +52,15 @@ #include "PositionComponent.h" #include "RepairBehavior.h" #include "SalvageScrapBehavior.h" -#include "ScrapSelectionChangedEvent.h" -#include "ScrapSystem.h" +#include "DebrisSelectionChangedEvent.h" +#include "DebrisSystem.h" #include "SelectionChangedEvent.h" #include "SensorRangeComponent.h" #include "ShipIdentityComponent.h" #include "ShipSystem.h" #include "Simulation.h" #include "StationBodyComponent.h" -#include "ScrapDataComponent.h" +#include "DebrisComponent.h" #include "SurfaceMask.h" #include "Tick.h" #include "TunnelCompletion.h" @@ -350,9 +350,9 @@ void GameWorldView::onFrame() m_activeBeams = std::move(live); } - // Drop selected scrap piles that were collected or despawned this frame, so the - // panel stops counting them and the selection empties out (REQ-UI-SCRAP-CLICK-SELECT). - pruneDespawnedScrap(); + // Drop selected debris that were collected or despawned this frame, so the + // panel stops counting them and the selection empties out (REQ-UI-DEBRIS-CLICK-SELECT). + pruneDespawnedDebris(); pruneDespawnedActors(); // Expire copy/paste flashes. Lifetime is wall-clock (the frame delta), so the @@ -504,7 +504,7 @@ void GameWorldView::paintGL() drawCopyConfigFeedback(painter); drawStations(painter); drawBeltItems(painter); - drawScrap(painter); + drawDebris(painter); if (m_debugDraw) { drawDebugSensorRanges(painter); @@ -779,33 +779,33 @@ std::optional GameWorldView::entityPosition(entt::entity entity) cons return m_sim->getAdmin().get(entity).value; } -void GameWorldView::clearScrapSelection() +void GameWorldView::clearDebrisSelection() { - if (m_selectedScrap.empty()) { return; } - m_selectedScrap.clear(); + if (m_selectedDebris.empty()) { return; } + m_selectedDebris.clear(); EventManager::getInstance()->sendEventImmediately( - std::make_shared(m_selectedScrap)); + std::make_shared(m_selectedDebris)); } -void GameWorldView::pruneDespawnedScrap() +void GameWorldView::pruneDespawnedDebris() { - if (m_selectedScrap.empty()) { return; } + if (m_selectedDebris.empty()) { return; } std::vector live; - for (const ScrapInfo& info : m_sim->getScraps().getAllScrapInfo()) + for (const DebrisInfo& info : m_sim->getDebrisSystem().getAllDebrisInfo()) { - if (std::find(m_selectedScrap.begin(), m_selectedScrap.end(), info.entity) - != m_selectedScrap.end()) + if (std::find(m_selectedDebris.begin(), m_selectedDebris.end(), info.entity) + != m_selectedDebris.end()) { live.push_back(info.entity); } } - if (live.size() != m_selectedScrap.size()) + if (live.size() != m_selectedDebris.size()) { - m_selectedScrap = std::move(live); + m_selectedDebris = std::move(live); EventManager::getInstance()->sendEventImmediately( - std::make_shared(m_selectedScrap)); + std::make_shared(m_selectedDebris)); } } @@ -1510,16 +1510,16 @@ void GameWorldView::drawSelectionHighlights(QPainter& painter) painter.drawRect(rect->adjusted(-1, -1, 1, 1)); } - // A ring around each selected scrap pile, sitting just outside the pile's - // rendered circle (radius getTilePx()*0.2, matching drawScrap) (REQ-UI-SCRAP-CLICK-SELECT). - if (!m_selectedScrap.empty()) + // A ring around each selected piece of debris, sitting just outside the debris's + // rendered circle (radius getTilePx()*0.2, matching drawDebris) (REQ-UI-DEBRIS-CLICK-SELECT). + if (!m_selectedDebris.empty()) { const qreal outlineRadius = static_cast(getTilePx() * 0.2f) + 3.0; - for (const ScrapInfo& scrap : m_sim->getScraps().getAllScrapInfo()) + for (const DebrisInfo& debris : m_sim->getDebrisSystem().getAllDebrisInfo()) { - if (std::find(m_selectedScrap.begin(), m_selectedScrap.end(), scrap.entity) - == m_selectedScrap.end()) { continue; } - painter.drawEllipse(worldToWidget(scrap.position), outlineRadius, outlineRadius); + if (std::find(m_selectedDebris.begin(), m_selectedDebris.end(), debris.entity) + == m_selectedDebris.end()) { continue; } + painter.drawEllipse(worldToWidget(debris.position), outlineRadius, outlineRadius); } } } @@ -1646,12 +1646,12 @@ void GameWorldView::drawBeltItems(QPainter& painter) }); } -void GameWorldView::drawScrap(QPainter& painter) +void GameWorldView::drawDebris(QPainter& painter) { const float r = getTilePx() * 0.2f; - for (const ScrapInfo& scrap : m_sim->getScraps().getAllScrapInfo()) + for (const DebrisInfo& debris : m_sim->getDebrisSystem().getAllDebrisInfo()) { - const QPointF center = worldToWidget(scrap.position); + const QPointF center = worldToWidget(debris.position); painter.setBrush(QColor(128, 110, 90)); painter.setPen(QPen(QColor(50, 40, 30), 1)); painter.drawEllipse(center, @@ -1838,9 +1838,9 @@ void GameWorldView::drawDebugTargetLines(QPainter& painter) [&](entt::entity /*e*/, const ShipIdentityComponent& si, const PositionComponent& pos, const SalvageScrapBehavior& salvage) { - if (!salvage.scrapTarget.has_value()) { return; } + if (!salvage.debrisTarget.has_value()) { return; } - drawTargetLine(si.schematicId, pos.value, *salvage.scrapTarget); + drawTargetLine(si.schematicId, pos.value, *salvage.debrisTarget); }); } @@ -2567,7 +2567,7 @@ void GameWorldView::mousePressEvent(QMouseEvent* event) const bool ctrl = (event->modifiers() & Qt::ControlModifier) != 0; const QVector2D worldPos = widgetToWorld(event->pos()); - // Point hit-test precedence: buildings win over actors, which win over scrap + // Point hit-test precedence: buildings win over actors, which win over debris // (REQ-UI-SELECTION-CATEGORIES). std::optional buildingHit = buildingAtTile(tile); if (!buildingHit.has_value()) { buildingHit = siteAtTile(tile); } @@ -2576,9 +2576,9 @@ void GameWorldView::mousePressEvent(QMouseEvent* event) { const BuildingId id = *buildingHit; // A building selection is exclusive: it clears any field selection — - // actors and scrap — because buildings win (REQ-UI-SELECTION-CATEGORIES). + // actors and debris — because buildings win (REQ-UI-SELECTION-CATEGORIES). clearEntitySelection(); - clearScrapSelection(); + clearDebrisSelection(); if (ctrl) { bool found = false; @@ -2600,8 +2600,8 @@ void GameWorldView::mousePressEvent(QMouseEvent* event) return; } - // Selecting a field object (actor or scrap) clears any building selection but - // lets actors and scrap coexist (REQ-UI-SELECTION-CATEGORIES). + // Selecting a field object (actor or debris) clears any building selection but + // lets actors and debris coexist (REQ-UI-SELECTION-CATEGORIES). const entt::entity actorHit = entityAtWorldPos(m_sim->getAdmin(), worldPos); if (actorHit != entt::null) { @@ -2613,7 +2613,7 @@ void GameWorldView::mousePressEvent(QMouseEvent* event) } if (ctrl) { - // Toggle this actor within the field selection, leaving scrap intact + // Toggle this actor within the field selection, leaving debris intact // (REQ-UI-ENTITY-CLICK-SELECT). bool found = false; std::vector newSel; @@ -2629,15 +2629,15 @@ void GameWorldView::mousePressEvent(QMouseEvent* event) { // A plain click makes this actor the sole selection. m_selectedEntities = { actorHit }; - clearScrapSelection(); + clearDebrisSelection(); } EventManager::getInstance()->sendEventImmediately( std::make_shared(m_selectedEntities)); return; } - if (const entt::entity scrapHit = - scrapAtWorldPos(m_sim->getAdmin(), worldPos); scrapHit != entt::null) + if (const entt::entity debrisHit = + debrisAtWorldPos(m_sim->getAdmin(), worldPos); debrisHit != entt::null) { if (!m_selectedBuildingIds.empty()) { @@ -2647,26 +2647,26 @@ void GameWorldView::mousePressEvent(QMouseEvent* event) } if (ctrl) { - // Toggle this pile within the field selection, leaving actors intact - // (REQ-UI-SCRAP-MULTI-SELECT). + // Toggle this debris within the field selection, leaving actors intact + // (REQ-UI-DEBRIS-MULTI-SELECT). bool found = false; std::vector newSel; - for (entt::entity sel : m_selectedScrap) + for (entt::entity sel : m_selectedDebris) { - if (sel == scrapHit) { found = true; } + if (sel == debrisHit) { found = true; } else { newSel.push_back(sel); } } - if (!found) { newSel.push_back(scrapHit); } - m_selectedScrap = newSel; + if (!found) { newSel.push_back(debrisHit); } + m_selectedDebris = newSel; } else { - // A plain click makes this pile the sole selection. - m_selectedScrap = { scrapHit }; + // A plain click makes this debris the sole selection. + m_selectedDebris = { debrisHit }; clearEntitySelection(); } EventManager::getInstance()->sendEventImmediately( - std::make_shared(m_selectedScrap)); + std::make_shared(m_selectedDebris)); return; } @@ -2681,7 +2681,7 @@ void GameWorldView::mousePressEvent(QMouseEvent* event) std::make_shared(m_selectedBuildingIds)); } clearEntitySelection(); - clearScrapSelection(); + clearDebrisSelection(); } m_boxSelecting = true; m_boxStartTile = tile; @@ -2815,9 +2815,9 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event) if (!boxIds.empty()) { // A box covering any building selects buildings; field objects (actors and - // scrap) in the box are ignored — buildings win (REQ-UI-MULTI-SELECT). + // debris) in the box are ignored — buildings win (REQ-UI-MULTI-SELECT). clearEntitySelection(); - clearScrapSelection(); + clearDebrisSelection(); if (!ctrl) { m_selectedBuildingIds = boxIds; @@ -2840,12 +2840,12 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event) } // No buildings in the box: select the field objects it covers — ships, defence - // stations, and scrap together (REQ-UI-MULTI-SELECT, REQ-UI-SCRAP-MULTI-SELECT). + // stations, and debris together (REQ-UI-MULTI-SELECT, REQ-UI-DEBRIS-MULTI-SELECT). const std::vector boxActors = actorsInBox(m_sim->getAdmin(), m_boxStartTile, m_boxCurrentTile); - const std::vector boxScrap = - scrapInBox(m_sim->getAdmin(), m_boxStartTile, m_boxCurrentTile); - if (!boxActors.empty() || !boxScrap.empty()) + const std::vector boxDebris = + debrisInBox(m_sim->getAdmin(), m_boxStartTile, m_boxCurrentTile); + if (!boxActors.empty() || !boxDebris.empty()) { if (!m_selectedBuildingIds.empty()) { @@ -2856,7 +2856,7 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event) if (!ctrl) { m_selectedEntities = boxActors; - m_selectedScrap = boxScrap; + m_selectedDebris = boxDebris; } else { @@ -2869,20 +2869,20 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event) } if (!found) { m_selectedEntities.push_back(e); } } - for (entt::entity e : boxScrap) + for (entt::entity e : boxDebris) { bool found = false; - for (entt::entity sel : m_selectedScrap) + for (entt::entity sel : m_selectedDebris) { if (sel == e) { found = true; break; } } - if (!found) { m_selectedScrap.push_back(e); } + if (!found) { m_selectedDebris.push_back(e); } } } EventManager::getInstance()->sendEventImmediately( std::make_shared(m_selectedEntities)); EventManager::getInstance()->sendEventImmediately( - std::make_shared(m_selectedScrap)); + std::make_shared(m_selectedDebris)); return; } @@ -2893,7 +2893,7 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event) EventManager::getInstance()->sendEventImmediately( std::make_shared(m_selectedBuildingIds)); clearEntitySelection(); - clearScrapSelection(); + clearDebrisSelection(); } } } @@ -3115,7 +3115,7 @@ void GameWorldView::resetForNewGame() std::make_shared(false)); m_selectedBuildingIds.clear(); clearEntitySelection(); - clearScrapSelection(); + clearDebrisSelection(); m_copiedConfig = std::nullopt; m_copyConfigFlashes.clear(); m_boxSelecting = false; @@ -3151,7 +3151,7 @@ void GameWorldView::handleEvent(std::shared_ptr event) { // Endpoint offset is a fraction of the target's visual size (REQ-SHP-FIRING-BEAM): // half a ship's rendered radius, half a station's shorter footprint side, or - // half a scrap pile's rendered radius (scrap is drawn at getTilePx()*0.2). + // half a piece of debris's rendered radius (debris is drawn at getTilePx()*0.2). float maxRadius = 0.125f; if (m_sim->getAdmin().isValid(event->target) && m_sim->getAdmin().hasAll(event->target)) @@ -3161,7 +3161,7 @@ void GameWorldView::handleEvent(std::shared_ptr event) maxRadius = shorter / 2.0f; } else if (m_sim->getAdmin().isValid(event->target) - && m_sim->getAdmin().hasAll(event->target)) + && m_sim->getAdmin().hasAll(event->target)) { maxRadius = 0.1f; } diff --git a/src/ui/GameWorldView.h b/src/ui/GameWorldView.h index cf7d4a0..84d2765 100644 --- a/src/ui/GameWorldView.h +++ b/src/ui/GameWorldView.h @@ -128,7 +128,7 @@ private: void drawCopyConfigFeedback(QPainter& painter); void drawStations(QPainter& painter); void drawBeltItems(QPainter& painter); - void drawScrap(QPainter& painter); + void drawDebris(QPainter& painter); void drawShips(QPainter& painter); void drawHpBar(QPainter& painter, qreal left, qreal top, qreal width, float fraction, bool isEnemy); @@ -206,13 +206,13 @@ private: void placeBlueprintAtTile(QPoint center); std::optional entityPosition(entt::entity entity) const; - // Clears the scrap selection, emitting an empty ScrapSelectionChangedEvent when - // it was non-empty (REQ-UI-SCRAP-CLICK-SELECT). Used when another selection + // Clears the debris selection, emitting an empty DebrisSelectionChangedEvent when + // it was non-empty (REQ-UI-DEBRIS-CLICK-SELECT). Used when another selection // category takes over. - void clearScrapSelection(); - // Drops despawned or fully-collected piles from the scrap selection and re-emits - // when it changed (REQ-UI-SCRAP-CLICK-SELECT). Called each frame from onFrame(). - void pruneDespawnedScrap(); + void clearDebrisSelection(); + // Drops despawned or fully-collected debris from the selection and re-emits + // when it changed (REQ-UI-DEBRIS-CLICK-SELECT). Called each frame from onFrame(). + void pruneDespawnedDebris(); // Clears the actor selection, emitting an empty EntitySelectionChangedEvent when it was // non-empty (REQ-UI-ENTITY-CLICK-SELECT). Used when buildings take over. void clearEntitySelection(); @@ -361,7 +361,7 @@ private: std::vector m_selectedBuildingIds; std::vector m_selectedEntities; - std::vector m_selectedScrap; + std::vector m_selectedDebris; bool m_boxSelecting; QPoint m_boxStartTile; QPoint m_boxCurrentTile; diff --git a/src/ui/SelectedBuildingPanel.cpp b/src/ui/SelectedBuildingPanel.cpp index 9b035b7..aa6ce54 100644 --- a/src/ui/SelectedBuildingPanel.cpp +++ b/src/ui/SelectedBuildingPanel.cpp @@ -40,7 +40,7 @@ #include "RecipeSelectionDialog.h" #include "RecipeSelectionRequestedEvent.h" #include "Rotation.h" -#include "ScrapSystem.h" +#include "DebrisSystem.h" #include "ShipLayoutPreview.h" #include "Simulation.h" #include "WeaponComponent.h" @@ -225,7 +225,7 @@ void SelectedBuildingPanel::onSelectionChanged(const std::vector& id // A building selection is exclusive: it supersedes any field selection — // actors and scrap (REQ-UI-SELECTION-CATEGORIES). clearEntityDisplay(); - m_selectedScrap.clear(); + m_selectedDebris.clear(); m_scrapLabel->hide(); } rebuild(); @@ -667,19 +667,19 @@ void SelectedBuildingPanel::handleEvent( void SelectedBuildingPanel::refreshSelectionDisplay(RefreshReason reason) { - if (!m_selectedEntities.empty() || !m_selectedScrap.empty()) + if (!m_selectedEntities.empty() || !m_selectedDebris.empty()) { // Field selection. Keep the live values current: the single-actor stats panel, - // the standalone scrap total, or the count summary (whose scrap line shrinks as - // piles are collected) — matching the layout chosen by buildFieldSelection() - // (REQ-UI-SHIP-STATS-PANEL, REQ-UI-SCRAP-PANEL). - if (m_selectedEntities.size() == 1 && m_selectedScrap.empty()) + // the single-debris stats panel (whose Scrap row shrinks as it is collected), or + // the count summary (whose Scrap line shrinks likewise) — matching the layout + // chosen by buildFieldSelection() (REQ-UI-SHIP-STATS-PANEL, REQ-UI-DEBRIS-PANEL). + if (m_selectedEntities.size() == 1 && m_selectedDebris.empty()) { refreshEntityStats(); } - else if (m_selectedEntities.empty()) + else if (m_selectedEntities.empty() && m_selectedDebris.size() == 1) { - refreshScrapTotal(); + buildDebrisSingle(); } else { @@ -936,7 +936,7 @@ void SelectedBuildingPanel::handleEvent(std::shared_ptrgetAdmin(); - // Full single-actor stats are shown only for a lone actor with no scrap. As soon as - // the selection holds more than one object (multiple actors, or an actor plus scrap), - // the panel switches to the compact count summary (REQ-UI-FIELD-MULTI-SELECTION). - if (m_selectedEntities.size() == 1 && m_selectedScrap.empty()) + // A full single-object stats panel is shown only for a lone field object: one actor + // with no debris, or one piece of debris with no actors. As soon as the selection holds + // more than one object (multiple actors, multiple debris, or actors plus debris), the + // panel switches to the compact count summary (REQ-UI-FIELD-MULTI-SELECTION). + if (m_selectedEntities.size() == 1 && m_selectedDebris.empty()) { m_entitySummaryLabel->hide(); m_scrapLabel->hide(); @@ -978,25 +979,36 @@ void SelectedBuildingPanel::buildFieldSelection() return; } - m_entityTitleLabel->hide(); - m_entityStatsPanel->hide(); - m_stationStatsLabel->hide(); - - if (m_selectedEntities.empty()) + if (m_selectedEntities.empty() && m_selectedDebris.size() == 1) { - // Scrap only: a single "Scrap: N" line. + // Single piece of debris: a "Debris" heading plus a "Scrap" stat row, styled like + // the ship/station stats panels (REQ-UI-DEBRIS-PANEL). m_entitySummaryLabel->hide(); - refreshScrapTotal(); - m_scrapLabel->show(); + m_entityStatsPanel->hide(); + m_stationStatsLabel->hide(); + buildDebrisSingle(); return; } - // Actor counts, with the scrap total appended into the same label so every line - // shares the same spacing. + // More than one field object: a compact count summary. buildEntitySummary() appends the + // "Debris x N" and "Scrap x N" lines when debris is part of the selection. + m_entityTitleLabel->hide(); + m_entityStatsPanel->hide(); + m_stationStatsLabel->hide(); m_scrapLabel->hide(); buildEntitySummary(); } +void SelectedBuildingPanel::buildDebrisSingle() +{ + // "Debris" heading + a single "Scrap" stat row for the piece's remaining amount, + // mirroring the single-actor stats panels (REQ-UI-DEBRIS-PANEL). + m_entityTitleLabel->setText(tr("Debris")); + m_entityTitleLabel->show(); + m_scrapLabel->setText(tr("Scrap: %1").arg(selectedDebrisScrapTotal())); + m_scrapLabel->show(); +} + void SelectedBuildingPanel::buildEntitySummary() { EntityAdmin& admin = m_sim->getAdmin(); @@ -1042,16 +1054,18 @@ void SelectedBuildingPanel::buildEntitySummary() } // One " x " line per group (matching the recipe tooltip and the building - // multi-selection). No total-count header, consistent with the building panel. The - // scrap total, when present, is appended as another line in the same label so the - // line spacing is uniform (REQ-UI-FIELD-MULTI-SELECTION, REQ-UI-SCRAP-PANEL). + // multi-selection). No total-count header, consistent with the building panel. When + // debris is part of the selection, a "Debris x " line followed by a + // "Scrap x " line are appended into the same label so the line spacing is + // uniform (REQ-UI-FIELD-MULTI-SELECTION, REQ-UI-DEBRIS-PANEL). QStringList lines; for (const QString& key : keys) { lines << tr("%1 x %2").arg(labels[key]).arg(counts[key]); } - if (!m_selectedScrap.empty()) + if (!m_selectedDebris.empty()) { + lines << tr("Debris x %1").arg(static_cast(m_selectedDebris.size())); lines << scrapTotalText(); } m_entitySummaryLabel->setText(lines.join('\n')); @@ -1173,36 +1187,36 @@ void SelectedBuildingPanel::handleEvent(std::shared_ptr event) + std::shared_ptr event) { - m_selectedScrap = event->scrap; - if (!m_selectedScrap.empty()) + m_selectedDebris = event->debris; + if (!m_selectedDebris.empty()) { - // Scrap is a field object: it supersedes any building selection but coexists + // Debris is a field object: it supersedes any building selection but coexists // with actors (REQ-UI-SELECTION-CATEGORIES). m_selectedBuildingIds.clear(); } buildFieldSelection(); } -QString SelectedBuildingPanel::scrapTotalText() const +int SelectedBuildingPanel::selectedDebrisScrapTotal() const { - // Sum the remaining amounts of the still-living selected piles (REQ-UI-SCRAP-PANEL). + // Sum the remaining scrap across the still-living selected debris (REQ-UI-DEBRIS-PANEL). int total = 0; - for (const ScrapInfo& info : m_sim->getScraps().getAllScrapInfo()) + for (const DebrisInfo& info : m_sim->getDebrisSystem().getAllDebrisInfo()) { - if (std::find(m_selectedScrap.begin(), m_selectedScrap.end(), info.entity) - != m_selectedScrap.end()) + if (std::find(m_selectedDebris.begin(), m_selectedDebris.end(), info.entity) + != m_selectedDebris.end()) { total += info.amount; } } - return tr("Scrap x %1").arg(total); + return total; } -void SelectedBuildingPanel::refreshScrapTotal() +QString SelectedBuildingPanel::scrapTotalText() const { - m_scrapLabel->setText(scrapTotalText()); + return tr("Scrap x %1").arg(selectedDebrisScrapTotal()); } void SelectedBuildingPanel::handleEvent(std::shared_ptr event) diff --git a/src/ui/SelectedBuildingPanel.h b/src/ui/SelectedBuildingPanel.h index 71052db..f2b6c4f 100644 --- a/src/ui/SelectedBuildingPanel.h +++ b/src/ui/SelectedBuildingPanel.h @@ -18,7 +18,7 @@ #include "GameConfig.h" #include "PlayerCommandsAppliedEvent.h" #include "RecipesConfig.h" -#include "ScrapSelectionChangedEvent.h" +#include "DebrisSelectionChangedEvent.h" #include "SelectionChangedEvent.h" #include "ShipLayout.h" #include "ShipsConfig.h" @@ -38,7 +38,7 @@ class SelectedBuildingPanel : public QWidget, PlayerCommandsAppliedEvent, EntitySelectionChangedEvent, SelectionChangedEvent, - ScrapSelectionChangedEvent, + DebrisSelectionChangedEvent, DebugDrawToggledEvent> { Q_OBJECT @@ -53,7 +53,7 @@ private: void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; - void handleEvent(std::shared_ptr event) override; + void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; private slots: @@ -80,8 +80,9 @@ private: void buildEmpty(); void buildSingle(BuildingId id); void buildMulti(const std::vector& ids); - void refreshScrapTotal(); - // "Scrap: N" for the summed remaining amount of the selected piles (REQ-UI-SCRAP-PANEL). + // Summed remaining scrap across the selected debris (REQ-UI-DEBRIS-PANEL). + int selectedDebrisScrapTotal() const; + // "Scrap x N" line for the multi-object summary (REQ-UI-FIELD-MULTI-SELECTION). QString scrapTotalText() const; void refreshBuffers(const Building* b); void refreshSiteProgress(const ConstructionSite* s); @@ -117,23 +118,26 @@ private: bool m_debugDraw = false; // The selected ships/defence stations. Shares the "field" selection category with - // scrap (m_selectedScrap): both can be non-empty at once (REQ-UI-SELECTION-CATEGORIES). + // debris (m_selectedDebris): both can be non-empty at once (REQ-UI-SELECTION-CATEGORIES). std::vector m_selectedEntities; ShipStatsPanel* m_entityStatsPanel; QLabel* m_entityTitleLabel; QLabel* m_stationStatsLabel; QLabel* m_entitySummaryLabel; - std::vector m_selectedScrap; + std::vector m_selectedDebris; + // Shows the debris "Scrap" stat row (single selection) — the scrap total for the + // multi-object summary lives in m_entitySummaryLabel instead. QLabel* m_scrapLabel; - // Renders the combined field selection (actors + scrap): a single-actor stats panel - // or a multi-actor summary, plus the scrap total when scrap is also selected - // (REQ-UI-FIELD-MULTI-SELECTION). + // Renders the combined field selection (actors + debris): a single-object stats panel + // (ship, station, or debris) or a multi-object count summary that appends the debris + // count and scrap total when debris is also selected (REQ-UI-FIELD-MULTI-SELECTION). void buildFieldSelection(); void buildEntityShip(entt::entity entity); void buildEntityStation(entt::entity entity); void buildEntitySummary(); + void buildDebrisSingle(); void refreshEntityStats(); void clearEntityDisplay(); };