diff --git a/src/balancing/ArenaSimulation.cpp b/src/balancing/ArenaSimulation.cpp index 7497692..c886e51 100644 --- a/src/balancing/ArenaSimulation.cpp +++ b/src/balancing/ArenaSimulation.cpp @@ -14,6 +14,7 @@ #include "EntityAdmin.h" #include "FactionComponent.h" #include "HealthComponent.h" +#include "HqProxyComponent.h" #include "ModuleOwnerComponent.h" #include "MovementIntentSystem.h" #include "PositionComponent.h" @@ -121,6 +122,8 @@ void ArenaSimulation::placeStructures() } m_team1HqEntity = m_admin.spawnStation(anchor, hqParsed.footprint, absCells, hp, hp, false); + // Tag as an HQ so it is excluded from repair targeting (REQ-SHP-REPAIR). + m_admin.addComponent(m_team1HqEntity); m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId()); } @@ -140,6 +143,8 @@ void ArenaSimulation::placeStructures() } m_team2HqEntity = m_admin.spawnStation(anchor, hqParsed.footprint, absCells, hp, hp, true); + // Tag as an HQ so it is excluded from repair targeting (REQ-SHP-REPAIR). + m_admin.addComponent(m_team2HqEntity); m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId()); } diff --git a/src/lib/ecs/system/ai/BehaviorTargeting.cpp b/src/lib/ecs/system/ai/BehaviorTargeting.cpp index 7f065db..47a482d 100644 --- a/src/lib/ecs/system/ai/BehaviorTargeting.cpp +++ b/src/lib/ecs/system/ai/BehaviorTargeting.cpp @@ -23,10 +23,14 @@ std::vector buildRepairables(EntityAdmin& admin) }); admin.forEach( - [&repairables](entt::entity e, const StationBodyComponent& /*sb*/, + [&repairables, &admin](entt::entity e, const StationBodyComponent& /*sb*/, const PositionComponent& pos, const FactionComponent& f, const HealthComponent& h) { + // The HQ is not a repair target — only ships and defence stations are + // (REQ-SHP-REPAIR). In the balancing arena the HQ is spawned as a station, + // so it is identified by its HqProxyComponent tag. + if (admin.hasAll(e)) { return; } repairables.push_back({e, pos.value, f.isEnemy, false, h.hp, h.maxHp}); }); @@ -52,9 +56,13 @@ std::vector buildCombatants(EntityAdmin& admin) }); admin.forEach( - [&combatants](entt::entity e, const PositionComponent& pos, + [&combatants, &admin](entt::entity e, const PositionComponent& pos, const FactionComponent& f, const HqProxyComponent& /*hq*/) { + // An arena HQ carries both StationBodyComponent and HqProxyComponent; it + // is already listed by the station pass above, so skip it here to avoid + // counting it twice. + if (admin.hasAll(e)) { return; } combatants.push_back({e, pos.value, f.isEnemy, true}); }); diff --git a/src/test/BehaviorSystemTest.cpp b/src/test/BehaviorSystemTest.cpp index 1f539f0..2c1e948 100644 --- a/src/test/BehaviorSystemTest.cpp +++ b/src/test/BehaviorSystemTest.cpp @@ -22,6 +22,7 @@ #include "EntityAdmin.h" #include "FactionComponent.h" #include "HealthComponent.h" +#include "HqProxyComponent.h" #include "ModuleOwnerComponent.h" #include "MovementIntentComponent.h" #include "MovementIntentSystem.h" @@ -862,6 +863,48 @@ TEST_CASE("RepairSystem: does not crash when a tool's owner is not a repair ship REQUIRE_FALSE(f.admin.get(moduleEntity).currentTarget.has_value()); } +TEST_CASE("RepairSystem: repair tool does not repair an HQ", "[behavior]") +{ + Fixture f; + const ShipLayoutConfig repairLayout = makeSingleModuleLayout("repair_tool"); + const entt::entity repairShip = f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f), + false, repairLayout); + + // A damaged, same-faction HQ in repair range — spawned as a station and tagged + // as an HQ (as the balancing arena does). The HQ is not a repair target. + const std::vector cells = {QPoint(2, 0), QPoint(3, 0), QPoint(2, 1), QPoint(3, 1)}; + const entt::entity hq = f.admin.spawnStation(QPoint(2, 0), QSize(2, 2), cells, + 100.0f, 200.0f, false); + f.admin.addComponent(hq); + + f.decide(); + f.runRepairHeal(); + + REQUIRE(f.admin.get(hq).hp == Approx(100.0f)); + const entt::entity rc = firstRepairChild(f.admin, repairShip); + REQUIRE_FALSE(f.admin.get(rc).currentTarget.has_value()); +} + +TEST_CASE("RepairSystem: repair tool still repairs a damaged defence station", "[behavior]") +{ + Fixture f; + const ShipLayoutConfig repairLayout = makeSingleModuleLayout("repair_tool"); + const entt::entity repairShip = f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f), + false, repairLayout); + + // A damaged, same-faction defence station (no HQ tag) in repair range. + const std::vector cells = {QPoint(2, 0), QPoint(3, 0), QPoint(2, 1), QPoint(3, 1)}; + const entt::entity station = f.admin.spawnStation(QPoint(2, 0), QSize(2, 2), cells, + 100.0f, 200.0f, false); + + f.decide(); + f.runRepairHeal(); + + REQUIRE(f.admin.get(station).hp > 100.0f); + const entt::entity rc = firstRepairChild(f.admin, repairShip); + REQUIRE(*f.admin.get(rc).currentTarget == station); +} + // --------------------------------------------------------------------------- // SalvageScrapBehavior / DeliverScrapBehavior // ---------------------------------------------------------------------------