From 25ff3c56c5435624d3124e3c880aaad9a4fe3548 Mon Sep 17 00:00:00 2001 From: mlangkabel Date: Mon, 25 May 2026 08:46:58 +0200 Subject: [PATCH] move ecs related code to own folder --- src/balancing/ArenaSimulation.cpp | 61 +++---- src/balancing/ArenaSimulation.h | 1 - src/balancing/ArenaView.cpp | 35 ++-- src/lib/CMakeLists.txt | 1 + src/lib/core/CMakeLists.txt | 6 +- src/lib/core/EcsComponents.h | 91 ----------- src/lib/core/EntityAdmin.cpp | 49 +++--- src/lib/ecs/CMakeLists.txt | 17 ++ src/lib/ecs/component/CMakeLists.txt | 30 ++++ src/lib/ecs/component/DespawnAtComponent.h | 8 + .../component}/DynamicBodyComponent.h | 0 src/lib/ecs/component/FacingComponent.h | 6 + src/lib/ecs/component/FactionComponent.h | 6 + src/lib/ecs/component/HealthComponent.h | 7 + .../component/HomeReturnBehaviorComponent.h | 9 ++ src/lib/ecs/component/HqProxyComponent.h | 6 + .../component/MovementIntentComponent.h} | 2 +- src/lib/ecs/component/PositionComponent.h | 8 + .../ecs/component/RallyBehaviorComponent.h | 8 + .../ecs/component/RepairBehaviorComponent.h | 10 ++ src/lib/ecs/component/RepairToolComponent.h | 12 ++ .../ecs/component/SalvageBehaviorComponent.h | 13 ++ src/lib/ecs/component/SalvageCargoComponent.h | 8 + src/lib/ecs/component/ScrapDataComponent.h | 6 + src/lib/ecs/component/SensorRangeComponent.h | 6 + src/lib/ecs/component/ShipIdentityComponent.h | 9 ++ src/lib/ecs/component/StationBodyComponent.h | 13 ++ .../ThreatResponseBehaviorComponent.h | 10 ++ src/lib/ecs/component/WeaponComponent.h | 14 ++ src/lib/{sim => ecs/system}/AiSystem.cpp | 151 +++++++++++------- src/lib/{sim => ecs/system}/AiSystem.h | 0 src/lib/ecs/system/CMakeLists.txt | 27 ++++ src/lib/ecs/system/CombatSystem.cpp | 142 ++++++++++++++++ src/lib/{sim => ecs/system}/CombatSystem.h | 20 +-- .../{sim => ecs/system}/DynamicBodySystem.cpp | 8 +- .../{sim => ecs/system}/DynamicBodySystem.h | 0 .../system}/MovementIntentSystem.cpp | 24 +-- .../system}/MovementIntentSystem.h | 0 src/lib/{sim => ecs/system}/ScrapSystem.cpp | 17 +- src/lib/{sim => ecs/system}/ScrapSystem.h | 1 - src/lib/{sim => ecs/system}/ShipSystem.cpp | 95 +++++++---- src/lib/{sim => ecs/system}/ShipSystem.h | 1 - src/lib/sim/CMakeLists.txt | 13 -- src/lib/sim/CombatSystem.cpp | 131 --------------- src/lib/sim/Ship.h | 69 -------- src/lib/sim/Simulation.cpp | 45 +++--- src/test/BehaviorSystemTest.cpp | 89 ++++++----- src/test/CombatSystemTest.cpp | 56 ++++--- src/test/ScrapTest.cpp | 6 +- src/test/ShipModuleTest.cpp | 14 +- src/test/ShipTest.cpp | 66 ++++---- src/test/ShipyardTest.cpp | 12 +- src/test/WaveSystemTest.cpp | 64 ++++---- src/ui/GameWorldView.cpp | 54 ++++--- 54 files changed, 877 insertions(+), 680 deletions(-) delete mode 100644 src/lib/core/EcsComponents.h create mode 100644 src/lib/ecs/CMakeLists.txt create mode 100644 src/lib/ecs/component/CMakeLists.txt create mode 100644 src/lib/ecs/component/DespawnAtComponent.h rename src/lib/{core => ecs/component}/DynamicBodyComponent.h (100%) create mode 100644 src/lib/ecs/component/FacingComponent.h create mode 100644 src/lib/ecs/component/FactionComponent.h create mode 100644 src/lib/ecs/component/HealthComponent.h create mode 100644 src/lib/ecs/component/HomeReturnBehaviorComponent.h create mode 100644 src/lib/ecs/component/HqProxyComponent.h rename src/lib/{core/MovementIntent.h => ecs/component/MovementIntentComponent.h} (89%) create mode 100644 src/lib/ecs/component/PositionComponent.h create mode 100644 src/lib/ecs/component/RallyBehaviorComponent.h create mode 100644 src/lib/ecs/component/RepairBehaviorComponent.h create mode 100644 src/lib/ecs/component/RepairToolComponent.h create mode 100644 src/lib/ecs/component/SalvageBehaviorComponent.h create mode 100644 src/lib/ecs/component/SalvageCargoComponent.h create mode 100644 src/lib/ecs/component/ScrapDataComponent.h create mode 100644 src/lib/ecs/component/SensorRangeComponent.h create mode 100644 src/lib/ecs/component/ShipIdentityComponent.h create mode 100644 src/lib/ecs/component/StationBodyComponent.h create mode 100644 src/lib/ecs/component/ThreatResponseBehaviorComponent.h create mode 100644 src/lib/ecs/component/WeaponComponent.h rename src/lib/{sim => ecs/system}/AiSystem.cpp (66%) rename src/lib/{sim => ecs/system}/AiSystem.h (100%) create mode 100644 src/lib/ecs/system/CMakeLists.txt create mode 100644 src/lib/ecs/system/CombatSystem.cpp rename src/lib/{sim => ecs/system}/CombatSystem.h (70%) rename src/lib/{sim => ecs/system}/DynamicBodySystem.cpp (85%) rename src/lib/{sim => ecs/system}/DynamicBodySystem.h (100%) rename src/lib/{sim => ecs/system}/MovementIntentSystem.cpp (82%) rename src/lib/{sim => ecs/system}/MovementIntentSystem.h (100%) rename src/lib/{sim => ecs/system}/ScrapSystem.cpp (60%) rename src/lib/{sim => ecs/system}/ScrapSystem.h (96%) rename src/lib/{sim => ecs/system}/ShipSystem.cpp (60%) rename src/lib/{sim => ecs/system}/ShipSystem.h (98%) delete mode 100644 src/lib/sim/CombatSystem.cpp delete mode 100644 src/lib/sim/Ship.h diff --git a/src/balancing/ArenaSimulation.cpp b/src/balancing/ArenaSimulation.cpp index 10d9261..bf252c3 100644 --- a/src/balancing/ArenaSimulation.cpp +++ b/src/balancing/ArenaSimulation.cpp @@ -10,16 +10,20 @@ #include "BuildingSystem.h" #include "BuildingType.h" #include "CombatSystem.h" -#include "EcsComponents.h" -#include "EntityAdmin.h" #include "DynamicBodySystem.h" +#include "EntityAdmin.h" +#include "FactionComponent.h" +#include "HealthComponent.h" #include "MovementIntentSystem.h" +#include "PositionComponent.h" #include "ScrapSystem.h" -#include "Ship.h" +#include "ShipIdentityComponent.h" #include "ShipSystem.h" #include "ShipsConfig.h" +#include "StationBodyComponent.h" #include "StationsConfig.h" #include "SurfaceMask.h" +#include "WeaponComponent.h" ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig, ArenaConfig arenaConfig, @@ -87,8 +91,6 @@ void ArenaSimulation::placeStructures() { absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y())); } - const QVector2D center(anchorX + hqParsed.footprint.width() / 2.0f, - anchorY + hqParsed.footprint.height() / 2.0f); m_team1HqEntity = m_admin.spawnStation(anchor, hqParsed.footprint, absCells, hp, hp, false); m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId()); @@ -116,7 +118,7 @@ void ArenaSimulation::placeStructures() auto placeArenaStation = [&](const ArenaStationEntry& entry, bool isEnemy) { float hp = 0.0f; - Weapon weapon; + WeaponComponent weapon; weapon.cooldownTicks = 0.0f; weapon.currentTarget = std::nullopt; const double lv = static_cast(entry.level); @@ -157,7 +159,7 @@ void ArenaSimulation::placeStructures() } const entt::entity stationEntity = m_admin.spawnStation( anchor, parsed.footprint, absCells, hp, hp, isEnemy); - m_admin.addComponent(stationEntity, weapon); + m_admin.addComponent(stationEntity, weapon); m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId()); }; @@ -275,8 +277,9 @@ void ArenaSimulation::tickDeaths() { // Dead ships. std::vector deadShips; - m_admin.forEach( - [&deadShips](entt::entity e, const ShipIdentity& /*si*/, const Health& h) + m_admin.forEach( + [&deadShips](entt::entity e, const ShipIdentityComponent& /*si*/, + const HealthComponent& h) { if (h.hp <= 0.0f) { @@ -286,8 +289,8 @@ void ArenaSimulation::tickDeaths() for (entt::entity deadEntity : deadShips) { - const ShipIdentity& si = m_admin.get(deadEntity); - const Position& pos = m_admin.get(deadEntity); + const ShipIdentityComponent& si = m_admin.get(deadEntity); + const PositionComponent& pos = m_admin.get(deadEntity); for (const ShipDef& def : m_gameConfig.ships.ships) { if (def.id == si.schematicId && def.loot.scrapDrop > 0) @@ -303,8 +306,9 @@ void ArenaSimulation::tickDeaths() // Dead stations. std::vector deadStations; - m_admin.forEach( - [&deadStations](entt::entity e, const StationBody& /*sb*/, const Health& h) + m_admin.forEach( + [&deadStations](entt::entity e, const StationBodyComponent& /*sb*/, + const HealthComponent& h) { if (h.hp <= 0.0f) { @@ -314,16 +318,16 @@ void ArenaSimulation::tickDeaths() for (entt::entity deadEntity : deadStations) { - const StationBody& sb = m_admin.get(deadEntity); + const StationBodyComponent& sb = m_admin.get(deadEntity); m_buildingSystem->unregisterTileOccupancy(sb.bodyCells); m_admin.destroy(deadEntity); } // Check end conditions — HQ proxy entities. const bool team1HqGone = !m_admin.isValid(m_team1HqEntity) - || m_admin.get(m_team1HqEntity).hp <= 0.0f; + || m_admin.get(m_team1HqEntity).hp <= 0.0f; const bool team2HqGone = !m_admin.isValid(m_team2HqEntity) - || m_admin.get(m_team2HqEntity).hp <= 0.0f; + || m_admin.get(m_team2HqEntity).hp <= 0.0f; if (team1HqGone || team2HqGone) { @@ -336,19 +340,19 @@ void ArenaSimulation::tickDeaths() // Check if all ships and defence stations of one team are destroyed. bool team1HasUnits = false; bool team2HasUnits = false; - m_admin.forEach( + m_admin.forEach( [&team1HasUnits, &team2HasUnits](entt::entity /*e*/, - const ShipIdentity& /*si*/, - const Faction& f) + const ShipIdentityComponent& /*si*/, + const FactionComponent& f) { if (f.isEnemy) { team2HasUnits = true; } else { team1HasUnits = true; } }); - m_admin.forEach( + m_admin.forEach( [&team1HasUnits, &team2HasUnits](entt::entity /*e*/, - const StationBody& /*sb*/, - const Faction& f) + const StationBodyComponent& /*sb*/, + const FactionComponent& f) { if (f.isEnemy) { team2HasUnits = true; } else { team1HasUnits = true; } @@ -442,7 +446,7 @@ void ArenaSimulation::updateStatus() hqEntry.total = 1; const entt::entity hqEntity = (ti == 0) ? m_team1HqEntity : m_team2HqEntity; hqEntry.surviving = (m_admin.isValid(hqEntity) - && m_admin.get(hqEntity).hp > 0.0f) ? 1 : 0; + && m_admin.get(hqEntity).hp > 0.0f) ? 1 : 0; teamStatus.entries.push_back(hqEntry); } @@ -456,9 +460,10 @@ void ArenaSimulation::updateStatus() int surviving = 0; const bool isEnemyTeam = (ti == 1); - m_admin.forEach( + m_admin.forEach( [&surviving, &shipEntry, isEnemyTeam](entt::entity /*e*/, - const ShipIdentity& si, const Faction& f, const Health& h) + const ShipIdentityComponent& si, const FactionComponent& f, + const HealthComponent& h) { if (f.isEnemy == isEnemyTeam && si.schematicId == shipEntry.schematicId @@ -484,9 +489,10 @@ void ArenaSimulation::updateStatus() int surviving = 0; const bool isEnemyTeam = (ti == 1); - m_admin.forEach( + m_admin.forEach( [&surviving, &stationEntry, isEnemyTeam](entt::entity /*e*/, - const StationBody& sb, const Faction& f, const Health& h) + const StationBodyComponent& sb, const FactionComponent& f, + const HealthComponent& h) { if (f.isEnemy == isEnemyTeam && sb.anchor == stationEntry.position @@ -503,4 +509,3 @@ void ArenaSimulation::updateStatus() std::lock_guard lock(m_statusMutex); m_status = newStatus; } - diff --git a/src/balancing/ArenaSimulation.h b/src/balancing/ArenaSimulation.h index f0c7716..84cebb3 100644 --- a/src/balancing/ArenaSimulation.h +++ b/src/balancing/ArenaSimulation.h @@ -9,7 +9,6 @@ #include "BalancingConfig.h" #include "BeltSystem.h" -#include "EcsComponents.h" #include "EntityAdmin.h" #include "BuildingId.h" diff --git a/src/balancing/ArenaView.cpp b/src/balancing/ArenaView.cpp index 46745ac..3b7c161 100644 --- a/src/balancing/ArenaView.cpp +++ b/src/balancing/ArenaView.cpp @@ -10,10 +10,15 @@ #include "ArenaSimulation.h" #include "Building.h" #include "BuildingSystem.h" -#include "DynamicBodyComponent.h" -#include "EcsComponents.h" +#include "FacingComponent.h" +#include "FactionComponent.h" +#include "HealthComponent.h" +#include "PositionComponent.h" +#include "RepairToolComponent.h" +#include "SalvageCargoComponent.h" #include "ScrapSystem.h" -#include "Ship.h" +#include "ShipIdentityComponent.h" +#include "StationBodyComponent.h" namespace { @@ -101,9 +106,9 @@ void ArenaView::onFrame() { float maxRadius = 0.125f; if (m_sim->admin().isValid(fe.target) - && m_sim->admin().hasAll(fe.target)) + && m_sim->admin().hasAll(fe.target)) { - const StationBody& sb = m_sim->admin().get(fe.target); + const StationBodyComponent& sb = m_sim->admin().get(fe.target); const int shorter = std::min(sb.footprint.width(), sb.footprint.height()); maxRadius = shorter / 2.0f; @@ -197,11 +202,11 @@ QRectF ArenaView::tileRect(QPoint tile) const std::optional ArenaView::entityPosition(entt::entity entity) const { - if (!m_sim->admin().isValid(entity) || !m_sim->admin().hasAll(entity)) + if (!m_sim->admin().isValid(entity) || !m_sim->admin().hasAll(entity)) { return std::nullopt; } - return m_sim->admin().get(entity).value; + return m_sim->admin().get(entity).value; } // --------------------------------------------------------------------------- @@ -273,8 +278,8 @@ void ArenaView::drawScrap(QPainter& painter) void ArenaView::drawStations(QPainter& painter) { - m_sim->admin().forEach( - [&](entt::entity /*e*/, const StationBody& sb, const Faction& f, const Health& h) + m_sim->admin().forEach( + [&](entt::entity /*e*/, const StationBodyComponent& sb, const FactionComponent& f, const HealthComponent& h) { const BuildingType visType = f.isEnemy ? BuildingType::EnemyDefenceStation @@ -315,12 +320,14 @@ void ArenaView::drawStations(QPainter& painter) void ArenaView::drawShips(QPainter& painter) { - m_sim->admin().forEach( - [&](entt::entity e, const ShipIdentity& /*si*/, const Position& pos, - const Facing& facing, const Faction& fac) + m_sim->admin().forEach( + [&](entt::entity e, const ShipIdentityComponent& /*si*/, + const PositionComponent& pos, const FacingComponent& facing, + const FactionComponent& fac) { - const bool hasCargo = m_sim->admin().hasAll(e); - const bool hasRepair = m_sim->admin().hasAll(e); + const bool hasCargo = m_sim->admin().hasAll(e); + const bool hasRepair = m_sim->admin().hasAll(e); const ShipRole role = shipRoleFromComponents(fac.isEnemy, hasCargo, hasRepair); const std::map::const_iterator it = m_visuals->ships.find(role); diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index d094525..2bb00c8 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(core) add_subdirectory(config) add_subdirectory(utility) add_subdirectory(sim) +add_subdirectory(ecs) SET(HDRS ${HDRS} diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt index faf3587..f3e8f86 100644 --- a/src/lib/core/CMakeLists.txt +++ b/src/lib/core/CMakeLists.txt @@ -4,14 +4,12 @@ SET(HDRS ${CMAKE_CURRENT_SOURCE_DIR}/Rotation.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildingType.h ${CMAKE_CURRENT_SOURCE_DIR}/EntityAdmin.h - ${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodyComponent.h - ${CMAKE_CURRENT_SOURCE_DIR}/EcsComponents.h ${CMAKE_CURRENT_SOURCE_DIR}/Blueprint.h + ${CMAKE_CURRENT_SOURCE_DIR}/BuildingId.h + ${CMAKE_CURRENT_SOURCE_DIR}/FireEvent.h ${CMAKE_CURRENT_SOURCE_DIR}/ItemType.h ${CMAKE_CURRENT_SOURCE_DIR}/Item.h ${CMAKE_CURRENT_SOURCE_DIR}/Port.h - ${CMAKE_CURRENT_SOURCE_DIR}/MovementIntent.h - ${CMAKE_CURRENT_SOURCE_DIR}/FireEvent.h ${CMAKE_CURRENT_SOURCE_DIR}/SchematicDropEvent.h PARENT_SCOPE ) diff --git a/src/lib/core/EcsComponents.h b/src/lib/core/EcsComponents.h deleted file mode 100644 index 307fc74..0000000 --- a/src/lib/core/EcsComponents.h +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include - -#include "Tick.h" - -#include "entt/entity/entity.hpp" - -// --------------------------------------------------------------------------- -// Shared components (used by ships, stations, scrap, HQ proxy) -// --------------------------------------------------------------------------- - -struct Position -{ - QVector2D value; -}; - -struct Health -{ - float hp; - float maxHp; -}; - -struct Faction -{ - bool isEnemy; -}; - -// --------------------------------------------------------------------------- -// Ship components (always present on every ship) -// --------------------------------------------------------------------------- - -struct Facing -{ - float radians; -}; - -struct SensorRange -{ - float value; -}; - -struct ShipIdentity -{ - int level; - std::string schematicId; -}; - -// --------------------------------------------------------------------------- -// Ship optional components (hardware + behavior, in Ship.h) -// --------------------------------------------------------------------------- -// Weapon, SalvageCargo, RepairTool, ThreatResponseBehavior, SalvageBehavior, -// RepairBehavior, HomeReturnBehavior, RallyBehavior remain defined in Ship.h. - -// --------------------------------------------------------------------------- -// Station components -// --------------------------------------------------------------------------- - -struct StationBody -{ - QPoint anchor; - QSize footprint; - std::vector bodyCells; -}; - -// --------------------------------------------------------------------------- -// Scrap components -// --------------------------------------------------------------------------- - -struct ScrapData -{ - int amount; -}; - -struct DespawnAt -{ - Tick tick; -}; - -// --------------------------------------------------------------------------- -// HQ proxy (empty tag) -// --------------------------------------------------------------------------- - -struct HqProxy { char unused = 0; }; - - diff --git a/src/lib/core/EntityAdmin.cpp b/src/lib/core/EntityAdmin.cpp index 45817bf..90e6fc1 100644 --- a/src/lib/core/EntityAdmin.cpp +++ b/src/lib/core/EntityAdmin.cpp @@ -1,8 +1,17 @@ #include "EntityAdmin.h" +#include "DespawnAtComponent.h" #include "DynamicBodyComponent.h" -#include "EcsComponents.h" -#include "MovementIntent.h" +#include "FacingComponent.h" +#include "FactionComponent.h" +#include "HealthComponent.h" +#include "HqProxyComponent.h" +#include "MovementIntentComponent.h" +#include "PositionComponent.h" +#include "ScrapDataComponent.h" +#include "SensorRangeComponent.h" +#include "ShipIdentityComponent.h" +#include "StationBodyComponent.h" entt::entity EntityAdmin::createEntity() { @@ -31,10 +40,10 @@ entt::entity EntityAdmin::spawnShip(QVector2D position, float hp, float maxHp, int level, const std::string& schematicId, bool isEnemy) { entt::entity entity = createEntity(); - add(entity, Position{position}); - add(entity, Health{hp, maxHp}); - add(entity, Faction{isEnemy}); - add(entity, Facing{0.0f}); + add(entity, PositionComponent{position}); + add(entity, HealthComponent{hp, maxHp}); + add(entity, FactionComponent{isEnemy}); + add(entity, FacingComponent{0.0f}); add(entity, DynamicBodyComponent{ maxSpeedPerTick, mainAccelPerTick, @@ -46,9 +55,9 @@ entt::entity EntityAdmin::spawnShip(QVector2D position, float hp, float maxHp, QVector2D(0.0f, 0.0f), // linearAcceleration 0.0f // angularAcceleration }); - add(entity, SensorRange{sensorRange}); - add(entity, ShipIdentity{level, schematicId}); - add(entity, MovementIntent{0, QVector2D(0.0f, 0.0f)}); + add(entity, SensorRangeComponent{sensorRange}); + add(entity, ShipIdentityComponent{level, schematicId}); + add(entity, MovementIntentComponent{0, QVector2D(0.0f, 0.0f)}); return entity; } @@ -59,28 +68,28 @@ entt::entity EntityAdmin::spawnStation(QPoint anchor, QSize footprint, entt::entity entity = createEntity(); QVector2D center(anchor.x() + footprint.width() / 2.0f, anchor.y() + footprint.height() / 2.0f); - add(entity, Position{center}); - add(entity, Health{hp, maxHp}); - add(entity, Faction{isEnemy}); - add(entity, StationBody{anchor, footprint, bodyCells}); + add(entity, PositionComponent{center}); + add(entity, HealthComponent{hp, maxHp}); + add(entity, FactionComponent{isEnemy}); + add(entity, StationBodyComponent{anchor, footprint, bodyCells}); return entity; } entt::entity EntityAdmin::spawnScrap(QVector2D position, int amount, Tick despawnAt) { entt::entity entity = createEntity(); - add(entity, Position{position}); - add(entity, ScrapData{amount}); - add(entity, DespawnAt{despawnAt}); + add(entity, PositionComponent{position}); + add(entity, ScrapDataComponent{amount}); + add(entity, DespawnAtComponent{despawnAt}); return entity; } entt::entity EntityAdmin::spawnHqProxy(QVector2D position, float hp, float maxHp) { entt::entity entity = createEntity(); - add(entity, Position{position}); - add(entity, Health{hp, maxHp}); - add(entity, Faction{false}); - add(entity); + add(entity, PositionComponent{position}); + add(entity, HealthComponent{hp, maxHp}); + add(entity, FactionComponent{false}); + add(entity); return entity; } diff --git a/src/lib/ecs/CMakeLists.txt b/src/lib/ecs/CMakeLists.txt new file mode 100644 index 0000000..d61b84f --- /dev/null +++ b/src/lib/ecs/CMakeLists.txt @@ -0,0 +1,17 @@ +add_subdirectory(component) +add_subdirectory(system) + +SET(HDRS + ${HDRS} + PARENT_SCOPE +) + +SET(SRCS + ${SRCS} + PARENT_SCOPE +) + +set(LIB_INCLUDE_PATH + ${LIB_INCLUDE_PATH} + PARENT_SCOPE +) diff --git a/src/lib/ecs/component/CMakeLists.txt b/src/lib/ecs/component/CMakeLists.txt new file mode 100644 index 0000000..62a853e --- /dev/null +++ b/src/lib/ecs/component/CMakeLists.txt @@ -0,0 +1,30 @@ +SET(HDRS + ${HDRS} + ${CMAKE_CURRENT_SOURCE_DIR}/DespawnAtComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodyComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/FacingComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/FactionComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/HealthComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/HomeReturnBehaviorComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/HqProxyComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/PositionComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/RallyBehaviorComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/RepairBehaviorComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/RepairToolComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/SalvageBehaviorComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/SalvageCargoComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/ScrapDataComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/SensorRangeComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/ShipIdentityComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/StationBodyComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/ThreatResponseBehaviorComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/WeaponComponent.h + PARENT_SCOPE +) + +set(LIB_INCLUDE_PATH + ${LIB_INCLUDE_PATH} + ${CMAKE_CURRENT_SOURCE_DIR} + PARENT_SCOPE +) diff --git a/src/lib/ecs/component/DespawnAtComponent.h b/src/lib/ecs/component/DespawnAtComponent.h new file mode 100644 index 0000000..de7542d --- /dev/null +++ b/src/lib/ecs/component/DespawnAtComponent.h @@ -0,0 +1,8 @@ +#pragma once + +#include "Tick.h" + +struct DespawnAtComponent +{ + Tick tick; +}; diff --git a/src/lib/core/DynamicBodyComponent.h b/src/lib/ecs/component/DynamicBodyComponent.h similarity index 100% rename from src/lib/core/DynamicBodyComponent.h rename to src/lib/ecs/component/DynamicBodyComponent.h diff --git a/src/lib/ecs/component/FacingComponent.h b/src/lib/ecs/component/FacingComponent.h new file mode 100644 index 0000000..7c5c2d6 --- /dev/null +++ b/src/lib/ecs/component/FacingComponent.h @@ -0,0 +1,6 @@ +#pragma once + +struct FacingComponent +{ + float radians; +}; diff --git a/src/lib/ecs/component/FactionComponent.h b/src/lib/ecs/component/FactionComponent.h new file mode 100644 index 0000000..b548369 --- /dev/null +++ b/src/lib/ecs/component/FactionComponent.h @@ -0,0 +1,6 @@ +#pragma once + +struct FactionComponent +{ + bool isEnemy; +}; diff --git a/src/lib/ecs/component/HealthComponent.h b/src/lib/ecs/component/HealthComponent.h new file mode 100644 index 0000000..53b5aac --- /dev/null +++ b/src/lib/ecs/component/HealthComponent.h @@ -0,0 +1,7 @@ +#pragma once + +struct HealthComponent +{ + float hp; + float maxHp; +}; diff --git a/src/lib/ecs/component/HomeReturnBehaviorComponent.h b/src/lib/ecs/component/HomeReturnBehaviorComponent.h new file mode 100644 index 0000000..940e723 --- /dev/null +++ b/src/lib/ecs/component/HomeReturnBehaviorComponent.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +struct HomeReturnBehaviorComponent +{ + float retreatHpFraction; + QVector2D homePos; +}; diff --git a/src/lib/ecs/component/HqProxyComponent.h b/src/lib/ecs/component/HqProxyComponent.h new file mode 100644 index 0000000..352d01a --- /dev/null +++ b/src/lib/ecs/component/HqProxyComponent.h @@ -0,0 +1,6 @@ +#pragma once + +struct HqProxyComponent +{ + char unused = 0; +}; diff --git a/src/lib/core/MovementIntent.h b/src/lib/ecs/component/MovementIntentComponent.h similarity index 89% rename from src/lib/core/MovementIntent.h rename to src/lib/ecs/component/MovementIntentComponent.h index f319273..6e3602e 100644 --- a/src/lib/core/MovementIntent.h +++ b/src/lib/ecs/component/MovementIntentComponent.h @@ -5,7 +5,7 @@ // A ship-behavior system writes this each tick before movement runs; the // highest-priority write wins. Priority order is fixed globally — see // architecture.md "Movement Arbitration". -struct MovementIntent +struct MovementIntentComponent { int priority; QVector2D target; diff --git a/src/lib/ecs/component/PositionComponent.h b/src/lib/ecs/component/PositionComponent.h new file mode 100644 index 0000000..b75fd13 --- /dev/null +++ b/src/lib/ecs/component/PositionComponent.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +struct PositionComponent +{ + QVector2D value; +}; diff --git a/src/lib/ecs/component/RallyBehaviorComponent.h b/src/lib/ecs/component/RallyBehaviorComponent.h new file mode 100644 index 0000000..cbef327 --- /dev/null +++ b/src/lib/ecs/component/RallyBehaviorComponent.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +struct RallyBehaviorComponent +{ + QVector2D rallyPoint; +}; diff --git a/src/lib/ecs/component/RepairBehaviorComponent.h b/src/lib/ecs/component/RepairBehaviorComponent.h new file mode 100644 index 0000000..ac24de8 --- /dev/null +++ b/src/lib/ecs/component/RepairBehaviorComponent.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "entt/entity/entity.hpp" + +struct RepairBehaviorComponent +{ + std::optional currentTarget; +}; diff --git a/src/lib/ecs/component/RepairToolComponent.h b/src/lib/ecs/component/RepairToolComponent.h new file mode 100644 index 0000000..7a4f13d --- /dev/null +++ b/src/lib/ecs/component/RepairToolComponent.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include "entt/entity/entity.hpp" + +struct RepairToolComponent +{ + float ratePerTick; + float range; + std::optional currentTarget; +}; diff --git a/src/lib/ecs/component/SalvageBehaviorComponent.h b/src/lib/ecs/component/SalvageBehaviorComponent.h new file mode 100644 index 0000000..26639e8 --- /dev/null +++ b/src/lib/ecs/component/SalvageBehaviorComponent.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include + +#include "BuildingId.h" + +struct SalvageBehaviorComponent +{ + std::optional scrapTarget; + BuildingId deliveryBay; // kInvalidBuildingId until assigned at a salvage bay +}; diff --git a/src/lib/ecs/component/SalvageCargoComponent.h b/src/lib/ecs/component/SalvageCargoComponent.h new file mode 100644 index 0000000..95004b3 --- /dev/null +++ b/src/lib/ecs/component/SalvageCargoComponent.h @@ -0,0 +1,8 @@ +#pragma once + +struct SalvageCargoComponent +{ + int capacity; + int current; + float collectionRange; +}; diff --git a/src/lib/ecs/component/ScrapDataComponent.h b/src/lib/ecs/component/ScrapDataComponent.h new file mode 100644 index 0000000..5e3e7a6 --- /dev/null +++ b/src/lib/ecs/component/ScrapDataComponent.h @@ -0,0 +1,6 @@ +#pragma once + +struct ScrapDataComponent +{ + int amount; +}; diff --git a/src/lib/ecs/component/SensorRangeComponent.h b/src/lib/ecs/component/SensorRangeComponent.h new file mode 100644 index 0000000..4aa75b8 --- /dev/null +++ b/src/lib/ecs/component/SensorRangeComponent.h @@ -0,0 +1,6 @@ +#pragma once + +struct SensorRangeComponent +{ + float value; +}; diff --git a/src/lib/ecs/component/ShipIdentityComponent.h b/src/lib/ecs/component/ShipIdentityComponent.h new file mode 100644 index 0000000..5670b87 --- /dev/null +++ b/src/lib/ecs/component/ShipIdentityComponent.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +struct ShipIdentityComponent +{ + int level; + std::string schematicId; +}; diff --git a/src/lib/ecs/component/StationBodyComponent.h b/src/lib/ecs/component/StationBodyComponent.h new file mode 100644 index 0000000..663d7ec --- /dev/null +++ b/src/lib/ecs/component/StationBodyComponent.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include +#include + +struct StationBodyComponent +{ + QPoint anchor; + QSize footprint; + std::vector bodyCells; +}; diff --git a/src/lib/ecs/component/ThreatResponseBehaviorComponent.h b/src/lib/ecs/component/ThreatResponseBehaviorComponent.h new file mode 100644 index 0000000..79a8769 --- /dev/null +++ b/src/lib/ecs/component/ThreatResponseBehaviorComponent.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "entt/entity/entity.hpp" + +struct ThreatResponseBehaviorComponent +{ + std::optional currentTarget; +}; diff --git a/src/lib/ecs/component/WeaponComponent.h b/src/lib/ecs/component/WeaponComponent.h new file mode 100644 index 0000000..14b945e --- /dev/null +++ b/src/lib/ecs/component/WeaponComponent.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "entt/entity/entity.hpp" + +struct WeaponComponent +{ + float damage; + float range; + float fireRateHz; + float cooldownTicks; + std::optional currentTarget; +}; diff --git a/src/lib/sim/AiSystem.cpp b/src/lib/ecs/system/AiSystem.cpp similarity index 66% rename from src/lib/sim/AiSystem.cpp rename to src/lib/ecs/system/AiSystem.cpp index 355fc33..d71cfae 100644 --- a/src/lib/sim/AiSystem.cpp +++ b/src/lib/ecs/system/AiSystem.cpp @@ -8,12 +8,24 @@ #include "Building.h" #include "BuildingSystem.h" #include "BuildingType.h" -#include "EcsComponents.h" -#include "EntityAdmin.h" #include "BuildingId.h" -#include "MovementIntent.h" +#include "EntityAdmin.h" +#include "FactionComponent.h" +#include "HealthComponent.h" +#include "HomeReturnBehaviorComponent.h" +#include "HqProxyComponent.h" +#include "MovementIntentComponent.h" +#include "PositionComponent.h" +#include "RallyBehaviorComponent.h" +#include "RepairBehaviorComponent.h" +#include "RepairToolComponent.h" +#include "SalvageBehaviorComponent.h" +#include "SalvageCargoComponent.h" #include "ScrapSystem.h" -#include "Ship.h" +#include "SensorRangeComponent.h" +#include "ShipIdentityComponent.h" +#include "StationBodyComponent.h" +#include "ThreatResponseBehaviorComponent.h" // --------------------------------------------------------------------------- // tickHomeReturnBehavior (priority 4) @@ -21,14 +33,15 @@ void AiSystem::tickHomeReturnBehavior(EntityAdmin& admin) { - admin.forEach( - [](entt::entity /*e*/, const HomeReturnBehavior& homeReturnBehavior, const Health& h, MovementIntent& intent) + admin.forEach( + [](entt::entity /*e*/, const HomeReturnBehaviorComponent& homeReturnBehavior, + const HealthComponent& h, MovementIntentComponent& intent) { if (h.hp / h.maxHp < homeReturnBehavior.retreatHpFraction) { if (4 > intent.priority) { - intent = MovementIntent{4, homeReturnBehavior.homePos}; + intent = MovementIntentComponent{4, homeReturnBehavior.homePos}; } } }); @@ -50,27 +63,32 @@ void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSyst }; std::vector combatants; - admin.forEach( - [&combatants](entt::entity e, const Position& pos, const Faction& f, const ShipIdentity& /*si*/) + admin.forEach( + [&combatants](entt::entity e, const PositionComponent& pos, + const FactionComponent& f, const ShipIdentityComponent& /*si*/) { combatants.push_back({e, pos.value, f.isEnemy, false}); }); - admin.forEach( - [&combatants](entt::entity e, const Position& pos, const Faction& f, const StationBody& /*sb*/) + admin.forEach( + [&combatants](entt::entity e, const PositionComponent& pos, + const FactionComponent& f, const StationBodyComponent& /*sb*/) { combatants.push_back({e, pos.value, f.isEnemy, true}); }); - admin.forEach( - [&combatants](entt::entity e, const Position& pos, const Faction& f, const HqProxy& /*hq*/) + admin.forEach( + [&combatants](entt::entity e, const PositionComponent& pos, + const FactionComponent& f, const HqProxyComponent& /*hq*/) { combatants.push_back({e, pos.value, f.isEnemy, true}); }); - admin.forEach( - [&](entt::entity e, ThreatResponseBehavior& threatResponseBehavior, Position& pos, Faction& faction, - SensorRange& sensor, MovementIntent& intent) + admin.forEach( + [&](entt::entity e, ThreatResponseBehaviorComponent& threatResponseBehavior, + PositionComponent& pos, FactionComponent& faction, + SensorRangeComponent& sensor, MovementIntentComponent& intent) { const float range = sensor.value; @@ -79,9 +97,10 @@ void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSyst if (threatResponseBehavior.currentTarget) { const entt::entity t = *threatResponseBehavior.currentTarget; - if (admin.isValid(t) && admin.hasAll(t)) + if (admin.isValid(t) && admin.hasAll(t)) { - const float dist = (admin.get(t).value - pos.value).length(); + const float dist = + (admin.get(t).value - pos.value).length(); if (dist <= range) { targetValid = true; @@ -122,31 +141,33 @@ void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSyst { const entt::entity t = *threatResponseBehavior.currentTarget; QVector2D dest = pos.value; - if (admin.isValid(t) && admin.hasAll(t)) + if (admin.isValid(t) && admin.hasAll(t)) { - dest = admin.get(t).value; + dest = admin.get(t).value; } if (3 > intent.priority) { - intent = MovementIntent{3, dest}; + intent = MovementIntentComponent{3, dest}; } } else { if (3 > intent.priority) { - if (admin.hasAll(e)) + if (admin.hasAll(e)) { - intent = MovementIntent{3, admin.get(e).rallyPoint}; + intent = MovementIntentComponent{ + 3, admin.get(e).rallyPoint}; } else if (!faction.isEnemy) { - intent = MovementIntent{3, QVector2D(pos.value.x() + 1000.0f, - pos.value.y())}; + intent = MovementIntentComponent{ + 3, QVector2D(pos.value.x() + 1000.0f, pos.value.y())}; } else { - intent = MovementIntent{3, QVector2D(-10000.0f, pos.value.y())}; + intent = MovementIntentComponent{ + 3, QVector2D(-10000.0f, pos.value.y())}; } } } @@ -171,16 +192,18 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings) }; std::vector repairables; - admin.forEach( - [&repairables](entt::entity e, const ShipIdentity& /*si*/, - const Position& pos, const Faction& f, const Health& h) + admin.forEach( + [&repairables](entt::entity e, const ShipIdentityComponent& /*si*/, + const PositionComponent& pos, const FactionComponent& f, + const HealthComponent& h) { repairables.push_back({e, pos.value, f.isEnemy, true, h.hp, h.maxHp}); }); - admin.forEach( - [&repairables](entt::entity e, const StationBody& /*sb*/, - const Position& pos, const Faction& f, const Health& h) + admin.forEach( + [&repairables](entt::entity e, const StationBodyComponent& /*sb*/, + const PositionComponent& pos, const FactionComponent& f, + const HealthComponent& h) { repairables.push_back({e, pos.value, f.isEnemy, false, h.hp, h.maxHp}); }); @@ -191,9 +214,9 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings) QVector2D position; }; std::vector enemies; - admin.forEach( - [&enemies](entt::entity /*e*/, const ShipIdentity& /*si*/, - const Position& pos, const Faction& f) + admin.forEach( + [&enemies](entt::entity /*e*/, const ShipIdentityComponent& /*si*/, + const PositionComponent& pos, const FactionComponent& f) { if (f.isEnemy) { @@ -201,9 +224,11 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings) } }); - admin.forEach( - [&](entt::entity e, RepairBehavior& rb, RepairTool& rt, Position& pos, - Faction& /*faction*/, SensorRange& sensor, MovementIntent& intent) + admin.forEach( + [&](entt::entity e, RepairBehaviorComponent& rb, RepairToolComponent& rt, + PositionComponent& pos, FactionComponent& /*faction*/, + SensorRangeComponent& sensor, MovementIntentComponent& intent) { const float repairRange = rt.range; @@ -221,7 +246,8 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings) { if (2 > intent.priority) { - intent = MovementIntent{2, QVector2D(-10000.0f, pos.value.y())}; + intent = MovementIntentComponent{ + 2, QVector2D(-10000.0f, pos.value.y())}; } return; } @@ -231,9 +257,9 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings) if (rb.currentTarget) { const entt::entity t = *rb.currentTarget; - if (admin.isValid(t) && admin.hasAll(t)) + if (admin.isValid(t) && admin.hasAll(t)) { - const Health& th = admin.get(t); + const HealthComponent& th = admin.get(t); if (th.hp > 0.0f && th.hp < th.maxHp) { targetValid = true; @@ -264,27 +290,25 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings) { if (2 > intent.priority) { - intent = MovementIntent{2, QVector2D(pos.value.x() + 1000.0f, - pos.value.y())}; + intent = MovementIntentComponent{ + 2, QVector2D(pos.value.x() + 1000.0f, pos.value.y())}; } return; } const entt::entity target = *rb.currentTarget; QVector2D targetPos = pos.value; - bool isShipTarget = false; - if (admin.isValid(target) && admin.hasAll(target)) + if (admin.isValid(target) && admin.hasAll(target)) { - targetPos = admin.get(target).value; - isShipTarget = admin.hasAll(target); + targetPos = admin.get(target).value; } const float distToTarget = (targetPos - pos.value).length(); if (distToTarget <= repairRange) { - if (admin.isValid(target) && admin.hasAll(target)) + if (admin.isValid(target) && admin.hasAll(target)) { - Health& targetHealth = admin.get(target); + HealthComponent& targetHealth = admin.get(target); targetHealth.hp = std::min(targetHealth.hp + rt.ratePerTick, targetHealth.maxHp); } @@ -292,7 +316,7 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings) if (2 > intent.priority) { - intent = MovementIntent{2, targetPos}; + intent = MovementIntentComponent{2, targetPos}; } }); } @@ -310,9 +334,9 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps, QVector2D position; }; std::vector enemyShips; - admin.forEach( - [&enemyShips](entt::entity /*e*/, const ShipIdentity& /*si*/, - const Position& pos, const Faction& f) + admin.forEach( + [&enemyShips](entt::entity /*e*/, const ShipIdentityComponent& /*si*/, + const PositionComponent& pos, const FactionComponent& f) { if (f.isEnemy) { @@ -322,9 +346,11 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps, const std::vector allScrap = scraps.allScrapInfo(); - admin.forEach( - [&](entt::entity /*e*/, SalvageBehavior& salvageBehavior, SalvageCargo& cargo, - Position& pos, SensorRange& sensor, MovementIntent& intent) + admin.forEach( + [&](entt::entity /*e*/, SalvageBehaviorComponent& salvageBehavior, + SalvageCargoComponent& cargo, PositionComponent& pos, + SensorRangeComponent& sensor, MovementIntentComponent& intent) { const float collectRange = cargo.collectionRange; @@ -358,7 +384,7 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps, { if (1 > intent.priority) { - intent = MovementIntent{1, bayPos}; + intent = MovementIntentComponent{1, bayPos}; } if (bayId != kInvalidBuildingId && (pos.value - bayPos).length() <= 1.0f) @@ -381,7 +407,8 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps, { if (1 > intent.priority) { - intent = MovementIntent{1, QVector2D(-10000.0f, pos.value.y())}; + intent = MovementIntentComponent{ + 1, QVector2D(-10000.0f, pos.value.y())}; } retreating = true; break; @@ -409,7 +436,7 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps, { if (1 > intent.priority) { - intent = MovementIntent{1, *salvageBehavior.scrapTarget}; + intent = MovementIntentComponent{1, *salvageBehavior.scrapTarget}; } } else @@ -430,15 +457,15 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps, salvageBehavior.scrapTarget = bestPos; if (1 > intent.priority) { - intent = MovementIntent{1, *bestPos}; + intent = MovementIntentComponent{1, *bestPos}; } } else { if (1 > intent.priority) { - intent = MovementIntent{1, QVector2D(pos.value.x() + 1000.0f, - pos.value.y())}; + intent = MovementIntentComponent{ + 1, QVector2D(pos.value.x() + 1000.0f, pos.value.y())}; } } } diff --git a/src/lib/sim/AiSystem.h b/src/lib/ecs/system/AiSystem.h similarity index 100% rename from src/lib/sim/AiSystem.h rename to src/lib/ecs/system/AiSystem.h diff --git a/src/lib/ecs/system/CMakeLists.txt b/src/lib/ecs/system/CMakeLists.txt new file mode 100644 index 0000000..d1b1074 --- /dev/null +++ b/src/lib/ecs/system/CMakeLists.txt @@ -0,0 +1,27 @@ +SET(HDRS + ${HDRS} + ${CMAKE_CURRENT_SOURCE_DIR}/AiSystem.h + ${CMAKE_CURRENT_SOURCE_DIR}/CombatSystem.h + ${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodySystem.h + ${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.h + ${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.h + ${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.h + PARENT_SCOPE +) + +SET(SRCS + ${SRCS} + ${CMAKE_CURRENT_SOURCE_DIR}/AiSystem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/CombatSystem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodySystem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.cpp + PARENT_SCOPE +) + +set(LIB_INCLUDE_PATH + ${LIB_INCLUDE_PATH} + ${CMAKE_CURRENT_SOURCE_DIR} + PARENT_SCOPE +) diff --git a/src/lib/ecs/system/CombatSystem.cpp b/src/lib/ecs/system/CombatSystem.cpp new file mode 100644 index 0000000..0b3f9cb --- /dev/null +++ b/src/lib/ecs/system/CombatSystem.cpp @@ -0,0 +1,142 @@ +#include "CombatSystem.h" + +#include "EntityAdmin.h" +#include "FactionComponent.h" +#include "HealthComponent.h" +#include "PositionComponent.h" +#include "ShipIdentityComponent.h" +#include "ThreatResponseBehaviorComponent.h" +#include "WeaponComponent.h" + +static constexpr Tick kWeaponImpactDelayTicks = 5; + +CombatSystem::CombatSystem(const GameConfig& config) + : m_config(config) +{ +} + +void CombatSystem::tick(Tick currentTick, + EntityAdmin& admin, + BuildingSystem& /*buildings*/, + std::vector& outFireEvents) +{ + // Ship weapons. + admin.forEach( + [&](entt::entity e, WeaponComponent& weapon, + ThreatResponseBehaviorComponent& threatResponseBehavior, + PositionComponent& pos, FactionComponent& faction) + { + weapon.currentTarget = threatResponseBehavior.currentTarget; + resolveWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents); + }); + + // Station weapons. + admin.forEach( + [&](entt::entity e, WeaponComponent& weapon, PositionComponent& pos, + FactionComponent& faction) + { + resolveWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents); + }); +} + +void CombatSystem::resolveWeapon( + entt::entity shipEntity, + WeaponComponent& weapon, + const PositionComponent& ownPos, + const FactionComponent& ownFaction, + Tick currentTick, + EntityAdmin& admin, + std::vector& out) +{ + if (weapon.cooldownTicks > 0.0f) + { + weapon.cooldownTicks -= 1.0f; + } + if (weapon.cooldownTicks > 0.0f) + { + return; + } + + // Validate or clear existing target. + if (weapon.currentTarget) + { + const entt::entity t = *weapon.currentTarget; + if (!admin.isValid(t) || !admin.hasAll(t)) + { + weapon.currentTarget = std::nullopt; + } + else + { + const float distanceSquared = + (ownPos.value - admin.get(t).value).lengthSquared(); + if (distanceSquared > weapon.range * weapon.range) + { + weapon.currentTarget = std::nullopt; + } + } + } + + // Acquire a new target if needed (nearest opposing-faction ship). + if (!weapon.currentTarget) + { + float bestDistanceSquared = weapon.range * weapon.range; + admin.forEach( + [&](entt::entity candidate, const ShipIdentityComponent& /*si*/, + const PositionComponent& candidatePos, + const FactionComponent& candidateFaction) + { + const bool isValidTarget = ownFaction.isEnemy + ? !candidateFaction.isEnemy + : candidateFaction.isEnemy; + if (!isValidTarget) + { + return; + } + const float distanceSquared = + (candidatePos.value - ownPos.value).lengthSquared(); + if (distanceSquared < bestDistanceSquared) + { + bestDistanceSquared = distanceSquared; + weapon.currentTarget = candidate; + } + }); + } + + if (!weapon.currentTarget) + { + return; + } + + const entt::entity targetEntity = *weapon.currentTarget; + m_pendingDamage.push_back({targetEntity, weapon.damage, + currentTick + kWeaponImpactDelayTicks}); + + FireEvent evt; + evt.shooter = shipEntity; + evt.target = targetEntity; + evt.emittedAt = currentTick; + out.push_back(evt); + + weapon.cooldownTicks = static_cast(kTickRateHz) / weapon.fireRateHz; +} + +void CombatSystem::applyPendingDamage(Tick currentTick, EntityAdmin& admin) +{ + std::vector::iterator it = m_pendingDamage.begin(); + while (it != m_pendingDamage.end()) + { + if (it->appliesAt <= currentTick) + { + if (admin.isValid(it->target) && admin.hasAll(it->target)) + { + admin.get(it->target).hp -= it->amount; + } + it = m_pendingDamage.erase(it); + } + else + { + ++it; + } + } +} diff --git a/src/lib/sim/CombatSystem.h b/src/lib/ecs/system/CombatSystem.h similarity index 70% rename from src/lib/sim/CombatSystem.h rename to src/lib/ecs/system/CombatSystem.h index 3997957..771d9cd 100644 --- a/src/lib/sim/CombatSystem.h +++ b/src/lib/ecs/system/CombatSystem.h @@ -6,11 +6,12 @@ #include #include "Building.h" -#include "EcsComponents.h" +#include "FactionComponent.h" #include "FireEvent.h" #include "GameConfig.h" -#include "Ship.h" +#include "PositionComponent.h" #include "Tick.h" +#include "WeaponComponent.h" #include "entt/entity/entity.hpp" @@ -40,14 +41,13 @@ private: std::vector m_pendingDamage; void resolveWeapon( - entt::entity shipEntity, - Weapon& weapon, - const Position& ownPos, - const Faction& ownFaction, - Tick currentTick, - EntityAdmin& admin, - std::vector& out); + entt::entity shipEntity, + WeaponComponent& weapon, + const PositionComponent& ownPos, + const FactionComponent& ownFaction, + Tick currentTick, + EntityAdmin& admin, + std::vector& out); const GameConfig& m_config; }; - diff --git a/src/lib/sim/DynamicBodySystem.cpp b/src/lib/ecs/system/DynamicBodySystem.cpp similarity index 85% rename from src/lib/sim/DynamicBodySystem.cpp rename to src/lib/ecs/system/DynamicBodySystem.cpp index a95d99f..1a85c9a 100644 --- a/src/lib/sim/DynamicBodySystem.cpp +++ b/src/lib/ecs/system/DynamicBodySystem.cpp @@ -6,8 +6,9 @@ #include #include "DynamicBodyComponent.h" -#include "EcsComponents.h" #include "EntityAdmin.h" +#include "FacingComponent.h" +#include "PositionComponent.h" static float wrapAngle(float a) { @@ -20,8 +21,9 @@ static float wrapAngle(float a) void DynamicBodySystem::tick(EntityAdmin& admin) { - admin.forEach( - [](entt::entity /*e*/, Position& pos, Facing& facing, DynamicBodyComponent& body) + admin.forEach( + [](entt::entity /*e*/, PositionComponent& pos, FacingComponent& facing, + DynamicBodyComponent& body) { // Integrate angular velocity, clamp to max rotation speed, then advance facing. body.angularVelocity += body.angularAcceleration; diff --git a/src/lib/sim/DynamicBodySystem.h b/src/lib/ecs/system/DynamicBodySystem.h similarity index 100% rename from src/lib/sim/DynamicBodySystem.h rename to src/lib/ecs/system/DynamicBodySystem.h diff --git a/src/lib/sim/MovementIntentSystem.cpp b/src/lib/ecs/system/MovementIntentSystem.cpp similarity index 82% rename from src/lib/sim/MovementIntentSystem.cpp rename to src/lib/ecs/system/MovementIntentSystem.cpp index d943c4b..49a0406 100644 --- a/src/lib/sim/MovementIntentSystem.cpp +++ b/src/lib/ecs/system/MovementIntentSystem.cpp @@ -6,9 +6,10 @@ #include #include "DynamicBodyComponent.h" -#include "EcsComponents.h" #include "EntityAdmin.h" -#include "MovementIntent.h" +#include "FacingComponent.h" +#include "MovementIntentComponent.h" +#include "PositionComponent.h" static float wrapAngle(float a) { @@ -21,9 +22,10 @@ static float wrapAngle(float a) void MovementIntentSystem::tick(EntityAdmin& admin) { - admin.forEach( - [](entt::entity /*e*/, const Position& pos, const Facing& facing, - DynamicBodyComponent& body, const MovementIntent& intent) + admin.forEach( + [](entt::entity /*e*/, const PositionComponent& pos, const FacingComponent& facing, + DynamicBodyComponent& body, const MovementIntentComponent& intent) { if (intent.priority == 0) { @@ -36,7 +38,8 @@ void MovementIntentSystem::tick(EntityAdmin& admin) const float angBraking = std::min(std::abs(body.angularVelocity), body.angularAccelerationPerTick); - body.angularAcceleration = (body.angularVelocity >= 0.0f) ? -angBraking : angBraking; + body.angularAcceleration = + (body.angularVelocity >= 0.0f) ? -angBraking : angBraking; return; } @@ -58,7 +61,8 @@ void MovementIntentSystem::tick(EntityAdmin& admin) const float angleDiff = wrapAngle(desiredAngle - facing.radians); const float rotDelta = std::max(-body.angularAccelerationPerTick, - std::min(angleDiff, body.angularAccelerationPerTick)); + std::min(angleDiff, + body.angularAccelerationPerTick)); float newAngVel = body.angularVelocity + rotDelta; @@ -81,7 +85,8 @@ void MovementIntentSystem::tick(EntityAdmin& admin) // pointing when DynamicBodySystem applies the forces. const float projectedRadians = wrapAngle(facing.radians + newAngVel); - const QVector2D facingVec(std::cos(projectedRadians), std::sin(projectedRadians)); + const QVector2D facingVec(std::cos(projectedRadians), + std::sin(projectedRadians)); const float manAccel = body.maneuveringAccelerationPerTick; const float stoppingDist = (body.maxSpeedPerTick * body.maxSpeedPerTick) @@ -95,7 +100,8 @@ void MovementIntentSystem::tick(EntityAdmin& admin) const float mainAligned = std::max(0.0f, QVector2D::dotProduct(velError, facingVec)); - const float mainApplied = std::min(mainAligned, body.mainAccelerationPerTick); + const float mainApplied = std::min(mainAligned, + body.mainAccelerationPerTick); const QVector2D mainDelta = facingVec * mainApplied; const QVector2D remaining = velError - mainDelta; diff --git a/src/lib/sim/MovementIntentSystem.h b/src/lib/ecs/system/MovementIntentSystem.h similarity index 100% rename from src/lib/sim/MovementIntentSystem.h rename to src/lib/ecs/system/MovementIntentSystem.h diff --git a/src/lib/sim/ScrapSystem.cpp b/src/lib/ecs/system/ScrapSystem.cpp similarity index 60% rename from src/lib/sim/ScrapSystem.cpp rename to src/lib/ecs/system/ScrapSystem.cpp index aaf0f5d..6f5228c 100644 --- a/src/lib/sim/ScrapSystem.cpp +++ b/src/lib/ecs/system/ScrapSystem.cpp @@ -1,6 +1,9 @@ #include "ScrapSystem.h" +#include "DespawnAtComponent.h" #include "EntityAdmin.h" +#include "PositionComponent.h" +#include "ScrapDataComponent.h" ScrapSystem::ScrapSystem(EntityAdmin& admin) : m_admin(admin) @@ -15,8 +18,8 @@ entt::entity ScrapSystem::spawn(QVector2D position, int amount, Tick despawnAt) void ScrapSystem::tickDespawn(Tick currentTick) { std::vector expired; - m_admin.forEach( - [&expired, currentTick](entt::entity e, DespawnAt& d) + m_admin.forEach( + [&expired, currentTick](entt::entity e, DespawnAtComponent& d) { if (d.tick <= currentTick) { @@ -32,11 +35,11 @@ void ScrapSystem::tickDespawn(Tick currentTick) std::optional ScrapSystem::consume(entt::entity entity) { - if (!m_admin.isValid(entity) || !m_admin.hasAll(entity)) + if (!m_admin.isValid(entity) || !m_admin.hasAll(entity)) { return std::nullopt; } - int amount = m_admin.get(entity).amount; + int amount = m_admin.get(entity).amount; m_admin.destroy(entity); return amount; } @@ -44,10 +47,10 @@ std::optional ScrapSystem::consume(entt::entity entity) std::vector ScrapSystem::allScrapInfo() const { std::vector result; - m_admin.forEach( - [&result, this](entt::entity e, const ScrapData& /*sd*/) + m_admin.forEach( + [&result, this](entt::entity e, const ScrapDataComponent& /*sd*/) { - result.push_back(ScrapInfo{e, m_admin.get(e).value}); + result.push_back(ScrapInfo{e, m_admin.get(e).value}); }); return result; } diff --git a/src/lib/sim/ScrapSystem.h b/src/lib/ecs/system/ScrapSystem.h similarity index 96% rename from src/lib/sim/ScrapSystem.h rename to src/lib/ecs/system/ScrapSystem.h index 1c4b099..a1f58b1 100644 --- a/src/lib/sim/ScrapSystem.h +++ b/src/lib/ecs/system/ScrapSystem.h @@ -5,7 +5,6 @@ #include -#include "EcsComponents.h" #include "Tick.h" #include "entt/entity/entity.hpp" diff --git a/src/lib/sim/ShipSystem.cpp b/src/lib/ecs/system/ShipSystem.cpp similarity index 60% rename from src/lib/sim/ShipSystem.cpp rename to src/lib/ecs/system/ShipSystem.cpp index db0bcdb..2082f6a 100644 --- a/src/lib/sim/ShipSystem.cpp +++ b/src/lib/ecs/system/ShipSystem.cpp @@ -5,10 +5,20 @@ #include #include "DynamicBodyComponent.h" -#include "EcsComponents.h" #include "EntityAdmin.h" +#include "FactionComponent.h" +#include "HealthComponent.h" #include "ModulesConfig.h" +#include "MovementIntentComponent.h" +#include "RallyBehaviorComponent.h" +#include "RepairBehaviorComponent.h" +#include "RepairToolComponent.h" +#include "SalvageBehaviorComponent.h" +#include "SalvageCargoComponent.h" +#include "SensorRangeComponent.h" #include "Tick.h" +#include "ThreatResponseBehaviorComponent.h" +#include "WeaponComponent.h" ShipSystem::ShipSystem(const GameConfig& config, EntityAdmin& admin) : m_config(config) @@ -40,8 +50,8 @@ const ModuleDef* ShipSystem::findModuleDef(const std::string& id) const return nullptr; } -entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVector2D position, - bool isEnemy, +entt::entity ShipSystem::spawn(const std::string& schematicId, int level, + QVector2D position, bool isEnemy, const std::optional& layout) { const ShipDef* def = findShipDef(schematicId); @@ -52,12 +62,22 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto float hp = static_cast(def->health.hpFormula.evaluate(x)); float maxHp = hp; - float maxSpeedPerTick = static_cast(def->movement.speedFormula.evaluate(x)) / tickRate; - float mainAccelPerTick = static_cast(def->movement.mainAccelerationFormula.evaluate(x)) / tickRate; - float maneuveringAccelPerTick = static_cast(def->movement.maneuveringAccelerationFormula.evaluate(x)) / tickRate; - float angularAccelPerTick = static_cast(def->movement.angularAccelerationFormula.evaluate(x)) / tickRate; - float maxRotationSpeedPerTick = static_cast(def->movement.maxRotationSpeedFormula.evaluate(x)) / tickRate; - float sensorRange = static_cast(def->sensor.sensorRangeFormula.evaluate(x)); + float maxSpeedPerTick = static_cast(def->movement.speedFormula.evaluate(x)) + / tickRate; + float mainAccelPerTick = static_cast( + def->movement.mainAccelerationFormula.evaluate(x)) + / tickRate; + float maneuveringAccelPerTick = static_cast( + def->movement.maneuveringAccelerationFormula.evaluate(x)) + / tickRate; + float angularAccelPerTick = static_cast( + def->movement.angularAccelerationFormula.evaluate(x)) + / tickRate; + float maxRotationSpeedPerTick = static_cast( + def->movement.maxRotationSpeedFormula.evaluate(x)) + / tickRate; + float sensorRange = static_cast( + def->sensor.sensorRangeFormula.evaluate(x)); entt::entity entity = m_admin.spawnShip( position, hp, maxHp, @@ -68,45 +88,47 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto // Optional components based on ship role. if (def->combat) { - Weapon w; + WeaponComponent w; w.damage = static_cast(def->combat->damageFormula.evaluate(x)); w.range = static_cast(def->combat->attackRangeFormula.evaluate(x)); w.fireRateHz = static_cast(def->combat->attackRateFormula.evaluate(x)); w.cooldownTicks = 0.0f; w.currentTarget = std::nullopt; - m_admin.addComponent(entity, w); + m_admin.addComponent(entity, w); - m_admin.addComponent(entity, ThreatResponseBehavior{}); + m_admin.addComponent( + entity, ThreatResponseBehaviorComponent{}); if (!isEnemy) { - m_admin.addComponent(entity, RallyBehavior{m_rallyPoint}); + m_admin.addComponent( + entity, RallyBehaviorComponent{m_rallyPoint}); } } if (def->salvage) { - SalvageCargo cargo; + SalvageCargoComponent cargo; cargo.capacity = def->salvage->cargoCapacity; cargo.current = 0; cargo.collectionRange = static_cast(def->salvage->collectionRange); - m_admin.addComponent(entity, cargo); + m_admin.addComponent(entity, cargo); - SalvageBehavior salvageBehavior; + SalvageBehaviorComponent salvageBehavior; salvageBehavior.scrapTarget = std::nullopt; salvageBehavior.deliveryBay = kInvalidBuildingId; - m_admin.addComponent(entity, salvageBehavior); + m_admin.addComponent(entity, salvageBehavior); } if (def->repair) { - RepairTool rt; + RepairToolComponent rt; rt.ratePerTick = static_cast(def->repair->repairRateFormula.evaluate(x)); rt.range = static_cast(def->repair->repairRangeFormula.evaluate(x)); rt.currentTarget = std::nullopt; - m_admin.addComponent(entity, rt); + m_admin.addComponent(entity, rt); - m_admin.addComponent(entity, RepairBehavior{}); + m_admin.addComponent(entity, RepairBehaviorComponent{}); } // Apply module stat modifiers (REQ-MOD-STAT-CALC). @@ -142,13 +164,14 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto if (it != mods.end()) { stat = static_cast( - static_cast(stat) * (1.0 + it->second.first) + it->second.second); + static_cast(stat) * (1.0 + it->second.first) + + it->second.second); } }; - Health& health = m_admin.get(entity); - DynamicBodyComponent& dynamics = m_admin.get(entity); - SensorRange& sensor = m_admin.get(entity); + HealthComponent& health = m_admin.get(entity); + DynamicBodyComponent& dynamics = m_admin.get(entity); + SensorRangeComponent& sensor = m_admin.get(entity); applyMod(health.maxHp, "hp"); health.hp = health.maxHp; @@ -159,16 +182,16 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto applyMod(dynamics.maxRotationSpeedPerTick, "max_rotation_speed"); applyMod(sensor.value, "sensor_range"); - if (m_admin.hasAll(entity)) + if (m_admin.hasAll(entity)) { - Weapon& weapon = m_admin.get(entity); + WeaponComponent& weapon = m_admin.get(entity); applyMod(weapon.damage, "damage"); applyMod(weapon.range, "attack_range"); applyMod(weapon.fireRateHz, "attack_rate"); } - if (m_admin.hasAll(entity)) + if (m_admin.hasAll(entity)) { - RepairTool& repairTool = m_admin.get(entity); + RepairToolComponent& repairTool = m_admin.get(entity); applyMod(repairTool.ratePerTick, "repair_rate"); applyMod(repairTool.range, "repair_range"); } @@ -184,10 +207,11 @@ void ShipSystem::despawn(entt::entity entity) void ShipSystem::clearMovementIntents() { - m_admin.forEach([](entt::entity /*e*/, MovementIntent& i) - { - i = MovementIntent{0, QVector2D(0.0f, 0.0f)}; - }); + m_admin.forEach( + [](entt::entity /*e*/, MovementIntentComponent& i) + { + i = MovementIntentComponent{0, QVector2D(0.0f, 0.0f)}; + }); } void ShipSystem::setRallyPoint(QVector2D point) @@ -198,8 +222,9 @@ void ShipSystem::setRallyPoint(QVector2D point) void ShipSystem::triggerRallyDeparture() { std::vector toRemove; - m_admin.forEach( - [&toRemove](entt::entity e, const RallyBehavior& /*rb*/, const Faction& f) + m_admin.forEach( + [&toRemove](entt::entity e, const RallyBehaviorComponent& /*rb*/, + const FactionComponent& f) { if (!f.isEnemy) { @@ -208,6 +233,6 @@ void ShipSystem::triggerRallyDeparture() }); for (entt::entity e : toRemove) { - m_admin.removeComponent(e); + m_admin.removeComponent(e); } } diff --git a/src/lib/sim/ShipSystem.h b/src/lib/ecs/system/ShipSystem.h similarity index 98% rename from src/lib/sim/ShipSystem.h rename to src/lib/ecs/system/ShipSystem.h index a819533..b1125d8 100644 --- a/src/lib/sim/ShipSystem.h +++ b/src/lib/ecs/system/ShipSystem.h @@ -6,7 +6,6 @@ #include #include "GameConfig.h" -#include "Ship.h" #include "ShipLayout.h" #include "entt/entity/entity.hpp" diff --git a/src/lib/sim/CMakeLists.txt b/src/lib/sim/CMakeLists.txt index 65370bc..0f910e8 100644 --- a/src/lib/sim/CMakeLists.txt +++ b/src/lib/sim/CMakeLists.txt @@ -5,16 +5,9 @@ SET(HDRS ${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.h ${CMAKE_CURRENT_SOURCE_DIR}/Building.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h - ${CMAKE_CURRENT_SOURCE_DIR}/Ship.h ${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h ${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutBlueprint.h - ${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.h - ${CMAKE_CURRENT_SOURCE_DIR}/AiSystem.h - ${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.h - ${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodySystem.h - ${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.h ${CMAKE_CURRENT_SOURCE_DIR}/WaveSystem.h - ${CMAKE_CURRENT_SOURCE_DIR}/CombatSystem.h PARENT_SCOPE ) @@ -24,13 +17,7 @@ SET(SRCS ${CMAKE_CURRENT_SOURCE_DIR}/TickDriver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/AiSystem.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodySystem.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/WaveSystem.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/CombatSystem.cpp PARENT_SCOPE ) diff --git a/src/lib/sim/CombatSystem.cpp b/src/lib/sim/CombatSystem.cpp deleted file mode 100644 index 9072e93..0000000 --- a/src/lib/sim/CombatSystem.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "CombatSystem.h" - -#include "EcsComponents.h" -#include "EntityAdmin.h" - -static constexpr Tick kWeaponImpactDelayTicks = 5; - -CombatSystem::CombatSystem(const GameConfig& config) - : m_config(config) -{ -} - -void CombatSystem::tick(Tick currentTick, - EntityAdmin& admin, - BuildingSystem& /*buildings*/, - std::vector& outFireEvents) -{ - // Ship weapons. - admin.forEach( - [&](entt::entity e, Weapon& weapon, ThreatResponseBehavior& threatResponseBehavior, Position& pos, Faction& faction) - { - weapon.currentTarget = threatResponseBehavior.currentTarget; - resolveWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents); - }); - - // Station weapons. - admin.forEach( - [&](entt::entity e, Weapon& weapon, Position& pos, Faction& faction) - { - resolveWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents); - }); -} - -void CombatSystem::resolveWeapon( - entt::entity shipEntity, - Weapon& weapon, - const Position& ownPos, - const Faction& ownFaction, - Tick currentTick, - EntityAdmin& admin, - std::vector& out) -{ - if (weapon.cooldownTicks > 0.0f) - { - weapon.cooldownTicks -= 1.0f; - } - if (weapon.cooldownTicks > 0.0f) - { - return; - } - - // Validate or clear existing target. - if (weapon.currentTarget) - { - const entt::entity t = *weapon.currentTarget; - if (!admin.isValid(t) || !admin.hasAll(t)) - { - weapon.currentTarget = std::nullopt; - } - else - { - const float distanceSquared = (ownPos.value - admin.get(t).value).lengthSquared(); - if (distanceSquared > weapon.range * weapon.range) - { - weapon.currentTarget = std::nullopt; - } - } - } - - // Acquire a new target if needed (nearest opposing-faction ship). - if (!weapon.currentTarget) - { - float bestDistanceSquared = weapon.range * weapon.range; - admin.forEach( - [&](entt::entity candidate, const ShipIdentity& /*si*/, - const Position& candidatePos, const Faction& candidateFaction) - { - const bool isValidTarget = ownFaction.isEnemy - ? !candidateFaction.isEnemy - : candidateFaction.isEnemy; - if (!isValidTarget) - { - return; - } - const float distanceSquared = (candidatePos.value - ownPos.value).lengthSquared(); - if (distanceSquared < bestDistanceSquared) - { - bestDistanceSquared = distanceSquared; - weapon.currentTarget = candidate; - } - }); - } - - if (!weapon.currentTarget) - { - return; - } - - const entt::entity targetEntity = *weapon.currentTarget; - m_pendingDamage.push_back({targetEntity, weapon.damage, - currentTick + kWeaponImpactDelayTicks}); - - FireEvent evt; - evt.shooter = shipEntity; - evt.target = targetEntity; - evt.emittedAt = currentTick; - out.push_back(evt); - - weapon.cooldownTicks = static_cast(kTickRateHz) / weapon.fireRateHz; -} - -void CombatSystem::applyPendingDamage(Tick currentTick, EntityAdmin& admin) -{ - std::vector::iterator it = m_pendingDamage.begin(); - while (it != m_pendingDamage.end()) - { - if (it->appliesAt <= currentTick) - { - if (admin.isValid(it->target) && admin.hasAll(it->target)) - { - admin.get(it->target).hp -= it->amount; - } - it = m_pendingDamage.erase(it); - } - else - { - ++it; - } - } -} - diff --git a/src/lib/sim/Ship.h b/src/lib/sim/Ship.h deleted file mode 100644 index b052284..0000000 --- a/src/lib/sim/Ship.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include -#include - -#include - -#include "BuildingId.h" -#include "MovementIntent.h" - -#include "entt/entity/entity.hpp" - -// --------------------------------------------------------------------------- -// Hardware components — derived from config at spawn, stored on ship -// --------------------------------------------------------------------------- - -struct Weapon -{ - float damage; - float range; - float fireRateHz; - float cooldownTicks; - std::optional currentTarget; -}; - -struct SalvageCargo -{ - int capacity; - int current; - float collectionRange; -}; - -struct RepairTool -{ - float ratePerTick; - float range; - std::optional currentTarget; -}; - -// --------------------------------------------------------------------------- -// Behavior components — AI state consumed by step-6 behavior systems -// --------------------------------------------------------------------------- - -struct ThreatResponseBehavior -{ - std::optional currentTarget; -}; - -struct SalvageBehavior -{ - std::optional scrapTarget; - BuildingId deliveryBay; // kInvalidBuildingId until assigned at a salvage bay -}; - -struct RepairBehavior -{ - std::optional currentTarget; -}; - -struct HomeReturnBehavior -{ - float retreatHpFraction; - QVector2D homePos; -}; - -struct RallyBehavior -{ - QVector2D rallyPoint; -}; diff --git a/src/lib/sim/Simulation.cpp b/src/lib/sim/Simulation.cpp index b3ad5a9..98d0eb4 100644 --- a/src/lib/sim/Simulation.cpp +++ b/src/lib/sim/Simulation.cpp @@ -4,14 +4,19 @@ #include "AiSystem.h" #include "BuildingSystem.h" -#include "EcsComponents.h" #include "CombatSystem.h" #include "DynamicBodySystem.h" +#include "FactionComponent.h" +#include "HealthComponent.h" #include "MovementIntentSystem.h" +#include "PositionComponent.h" #include "ScrapSystem.h" +#include "ShipIdentityComponent.h" #include "ShipSystem.h" +#include "StationBodyComponent.h" #include "SurfaceMask.h" #include "WaveSystem.h" +#include "WeaponComponent.h" Simulation::Simulation(GameConfig config, unsigned int seed) : m_config(std::move(config)) @@ -228,7 +233,7 @@ void Simulation::placeInitialStructures() const float psHp = static_cast( m_config.stations.playerStation.hpFormula.evaluate(psLevel)); - Weapon psWeapon; + WeaponComponent psWeapon; psWeapon.damage = static_cast( m_config.stations.playerStation.damageFormula.evaluate(psLevel)); psWeapon.range = static_cast( @@ -250,7 +255,7 @@ void Simulation::placeInitialStructures() } m_playerStation1Entity = m_admin.spawnStation( anchor, psParsed.footprint, absCells, psHp, psHp, false); - m_admin.addComponent(m_playerStation1Entity, psWeapon); + m_admin.addComponent(m_playerStation1Entity, psWeapon); m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId()); } { @@ -262,7 +267,7 @@ void Simulation::placeInitialStructures() } m_playerStation2Entity = m_admin.spawnStation( anchor, psParsed.footprint, absCells, psHp, psHp, false); - m_admin.addComponent(m_playerStation2Entity, psWeapon); + m_admin.addComponent(m_playerStation2Entity, psWeapon); m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId()); } @@ -289,7 +294,7 @@ void Simulation::placeEnemyStationSet(int generation) const float esHp = static_cast( m_config.stations.enemyStation.hpFormula.evaluate(genD)); - Weapon esWeapon; + WeaponComponent esWeapon; esWeapon.damage = static_cast( m_config.stations.enemyStation.damageFormula.evaluate(genD)); esWeapon.range = static_cast( @@ -311,7 +316,7 @@ void Simulation::placeEnemyStationSet(int generation) } m_currentEnemyStationEntities[0] = m_admin.spawnStation( anchor, esParsed.footprint, absCells, esHp, esHp, true); - m_admin.addComponent(m_currentEnemyStationEntities[0], esWeapon); + m_admin.addComponent(m_currentEnemyStationEntities[0], esWeapon); m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId()); } { @@ -323,7 +328,7 @@ void Simulation::placeEnemyStationSet(int generation) } m_currentEnemyStationEntities[1] = m_admin.spawnStation( anchor, esParsed.footprint, absCells, esHp, esHp, true); - m_admin.addComponent(m_currentEnemyStationEntities[1], esWeapon); + m_admin.addComponent(m_currentEnemyStationEntities[1], esWeapon); m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId()); } } @@ -336,8 +341,9 @@ void Simulation::tickDeathsAndLoot() { // --- Dead ships --- std::vector deadShips; - m_admin.forEach( - [&deadShips](entt::entity e, const ShipIdentity& /*si*/, const Health& h) + m_admin.forEach( + [&deadShips](entt::entity e, const ShipIdentityComponent& /*si*/, + const HealthComponent& h) { if (h.hp <= 0.0f) { @@ -347,8 +353,8 @@ void Simulation::tickDeathsAndLoot() for (entt::entity deadEntity : deadShips) { - const ShipIdentity& si = m_admin.get(deadEntity); - const Position& pos = m_admin.get(deadEntity); + const ShipIdentityComponent& si = m_admin.get(deadEntity); + const PositionComponent& pos = m_admin.get(deadEntity); for (const ShipDef& def : m_config.ships.ships) { if (def.id == si.schematicId && def.loot.scrapDrop > 0) @@ -364,8 +370,9 @@ void Simulation::tickDeathsAndLoot() // --- Dead stations --- std::vector deadStations; - m_admin.forEach( - [&deadStations](entt::entity e, const StationBody& /*sb*/, const Health& h) + m_admin.forEach( + [&deadStations](entt::entity e, const StationBodyComponent& /*sb*/, + const HealthComponent& h) { if (h.hp <= 0.0f) { @@ -375,9 +382,9 @@ void Simulation::tickDeathsAndLoot() for (entt::entity deadEntity : deadStations) { - const StationBody& sb = m_admin.get(deadEntity); - const Position& pos = m_admin.get(deadEntity); - const Faction& fac = m_admin.get(deadEntity); + const StationBodyComponent& sb = m_admin.get(deadEntity); + const PositionComponent& pos = m_admin.get(deadEntity); + const FactionComponent& fac = m_admin.get(deadEntity); const Tick despawnAt = m_currentTick + secondsToTicks(m_config.world.scrapDespawnSeconds); @@ -406,7 +413,7 @@ void Simulation::tickDeathsAndLoot() // --- HQ death check --- if (m_admin.isValid(m_hqProxyEntity)) { - const Health& hqHealth = m_admin.get(m_hqProxyEntity); + const HealthComponent& hqHealth = m_admin.get(m_hqProxyEntity); if (hqHealth.hp <= 0.0f) { m_gameOver = true; @@ -415,9 +422,9 @@ void Simulation::tickDeathsAndLoot() // --- Push check: if both current enemy stations are gone, trigger push --- const bool es0Gone = !m_admin.isValid(m_currentEnemyStationEntities[0]) - || m_admin.get(m_currentEnemyStationEntities[0]).hp <= 0.0f; + || m_admin.get(m_currentEnemyStationEntities[0]).hp <= 0.0f; const bool es1Gone = !m_admin.isValid(m_currentEnemyStationEntities[1]) - || m_admin.get(m_currentEnemyStationEntities[1]).hp <= 0.0f; + || m_admin.get(m_currentEnemyStationEntities[1]).hp <= 0.0f; if (es0Gone && es1Gone && m_currentEnemyStationEntities[0] != entt::null) diff --git a/src/test/BehaviorSystemTest.cpp b/src/test/BehaviorSystemTest.cpp index e06705f..d05cd70 100644 --- a/src/test/BehaviorSystemTest.cpp +++ b/src/test/BehaviorSystemTest.cpp @@ -12,14 +12,25 @@ #include "ConfigLoader.h" #include "DynamicBodyComponent.h" #include "DynamicBodySystem.h" -#include "EcsComponents.h" #include "EntityAdmin.h" +#include "FactionComponent.h" +#include "HealthComponent.h" +#include "HomeReturnBehaviorComponent.h" +#include "MovementIntentComponent.h" #include "MovementIntentSystem.h" +#include "PositionComponent.h" +#include "RallyBehaviorComponent.h" +#include "RepairBehaviorComponent.h" +#include "RepairToolComponent.h" #include "Rotation.h" +#include "SalvageBehaviorComponent.h" +#include "SalvageCargoComponent.h" #include "ScrapSystem.h" -#include "Ship.h" +#include "SensorRangeComponent.h" +#include "ShipIdentityComponent.h" #include "ShipSystem.h" #include "Tick.h" +#include "ThreatResponseBehaviorComponent.h" // --------------------------------------------------------------------------- // Fixture @@ -78,19 +89,19 @@ struct Fixture }; // Helpers to read ECS data for a ship entity. -static const MovementIntent& intent(EntityAdmin& a, entt::entity e) +static const MovementIntentComponent& intent(EntityAdmin& a, entt::entity e) { - return a.get(e); + return a.get(e); } -static const Health& health(EntityAdmin& a, entt::entity e) +static const HealthComponent& health(EntityAdmin& a, entt::entity e) { - return a.get(e); + return a.get(e); } -static const Position& pos(EntityAdmin& a, entt::entity e) +static const PositionComponent& pos(EntityAdmin& a, entt::entity e) { - return a.get(e); + return a.get(e); } // --------------------------------------------------------------------------- @@ -103,7 +114,7 @@ TEST_CASE("BehaviorSystem: clearMovementIntents resets all ships to priority 0", Fixture f; const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f)); - f.admin.get(e) = MovementIntent{3, QVector2D(10.0f, 0.0f)}; + f.admin.get(e) = MovementIntentComponent{3, QVector2D(10.0f, 0.0f)}; f.ships.clearMovementIntents(); REQUIRE(intent(f.admin, e).priority == 0); @@ -120,7 +131,7 @@ TEST_CASE("BehaviorSystem: tickMovement advances ship by maxSpeedPerTick toward const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f)); const float speed = f.admin.get(e).maxSpeedPerTick; - f.admin.get(e) = MovementIntent{1, QVector2D(100.0f, 0.0f)}; + f.admin.get(e) = MovementIntentComponent{1, QVector2D(100.0f, 0.0f)}; f.movementIntent.tick(f.admin); f.dynamicBody.tick(f.admin); @@ -136,7 +147,7 @@ TEST_CASE("BehaviorSystem: tickMovement stops exactly at target without overshoo const float speed = f.admin.get(e).maxSpeedPerTick; const QVector2D target(speed * 0.5f, 0.0f); - f.admin.get(e) = MovementIntent{1, target}; + f.admin.get(e) = MovementIntentComponent{1, target}; f.movementIntent.tick(f.admin); f.dynamicBody.tick(f.admin); @@ -153,8 +164,8 @@ TEST_CASE("BehaviorSystem: tickHomeReturnBehavior does nothing when HP is above { Fixture f; const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f)); - f.admin.addComponent(e, HomeReturnBehavior{0.3f, QVector2D(-10.0f, 0.0f)}); - f.admin.get(e).hp = f.admin.get(e).maxHp; // full HP + f.admin.addComponent(e, HomeReturnBehaviorComponent{0.3f, QVector2D(-10.0f, 0.0f)}); + f.admin.get(e).hp = f.admin.get(e).maxHp; // full HP f.ships.clearMovementIntents(); f.ai.tickHomeReturnBehavior(f.admin); @@ -168,8 +179,8 @@ TEST_CASE("BehaviorSystem: tickHomeReturnBehavior writes priority-4 intent towar Fixture f; const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f)); const QVector2D homePos(-10.0f, 0.0f); - f.admin.addComponent(e, HomeReturnBehavior{0.5f, homePos}); - f.admin.get(e).hp = f.admin.get(e).maxHp * 0.2f; // below threshold + f.admin.addComponent(e, HomeReturnBehaviorComponent{0.5f, homePos}); + f.admin.get(e).hp = f.admin.get(e).maxHp * 0.2f; // below threshold f.ships.clearMovementIntents(); f.ai.tickHomeReturnBehavior(f.admin); @@ -186,8 +197,8 @@ TEST_CASE("BehaviorSystem: tickHomeReturnBehavior priority-4 beats tickThreatRes f.ships.spawn("interceptor", 1, QVector2D(5.0f, 0.0f), /*isEnemy=*/true); const QVector2D homePos(-50.0f, 0.0f); - f.admin.addComponent(player, HomeReturnBehavior{0.5f, homePos}); - f.admin.get(player).hp = f.admin.get(player).maxHp * 0.1f; + f.admin.addComponent(player, HomeReturnBehaviorComponent{0.5f, homePos}); + f.admin.get(player).hp = f.admin.get(player).maxHp * 0.1f; f.ships.clearMovementIntents(); f.ai.tickHomeReturnBehavior(f.admin); @@ -212,8 +223,8 @@ TEST_CASE("BehaviorSystem: player combat ship acquires nearest enemy ship in ran f.ships.clearMovementIntents(); f.ai.tickThreatResponseBehavior(f.admin, f.buildings); - REQUIRE(f.admin.hasAll(player)); - const ThreatResponseBehavior& threatResponseBehavior = f.admin.get(player); + REQUIRE(f.admin.hasAll(player)); + const ThreatResponseBehaviorComponent& threatResponseBehavior = f.admin.get(player); REQUIRE(threatResponseBehavior.currentTarget.has_value()); REQUIRE(*threatResponseBehavior.currentTarget == enemy); } @@ -228,8 +239,8 @@ TEST_CASE("BehaviorSystem: player combat ship does not target friendly ships", f.ships.clearMovementIntents(); f.ai.tickThreatResponseBehavior(f.admin, f.buildings); - REQUIRE(f.admin.hasAll(e1)); - REQUIRE_FALSE(f.admin.get(e1).currentTarget.has_value()); + REQUIRE(f.admin.hasAll(e1)); + REQUIRE_FALSE(f.admin.get(e1).currentTarget.has_value()); } TEST_CASE("BehaviorSystem: player combat ship ignores enemy beyond engagement range", @@ -242,7 +253,7 @@ TEST_CASE("BehaviorSystem: player combat ship ignores enemy beyond engagement ra f.ships.clearMovementIntents(); f.ai.tickThreatResponseBehavior(f.admin, f.buildings); - REQUIRE_FALSE(f.admin.get(player).currentTarget.has_value()); + REQUIRE_FALSE(f.admin.get(player).currentTarget.has_value()); } // --------------------------------------------------------------------------- @@ -260,8 +271,8 @@ TEST_CASE("BehaviorSystem: enemy ship acquires nearest player ship in range", f.ships.clearMovementIntents(); f.ai.tickThreatResponseBehavior(f.admin, f.buildings); - REQUIRE(f.admin.hasAll(enemy)); - const ThreatResponseBehavior& threatResponseBehavior = f.admin.get(enemy); + REQUIRE(f.admin.hasAll(enemy)); + const ThreatResponseBehaviorComponent& threatResponseBehavior = f.admin.get(enemy); REQUIRE(threatResponseBehavior.currentTarget.has_value()); REQUIRE(*threatResponseBehavior.currentTarget == player); } @@ -291,7 +302,7 @@ TEST_CASE("BehaviorSystem: repair ship writes intent toward damaged friendly shi const entt::entity repairShip = f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f)); const entt::entity friendly = f.ships.spawn("interceptor", 1, QVector2D(5.0f, 0.0f)); - f.admin.get(friendly).hp = f.admin.get(friendly).maxHp * 0.5f; + f.admin.get(friendly).hp = f.admin.get(friendly).maxHp * 0.5f; f.ships.clearMovementIntents(); f.ai.tickRepairBehavior(f.admin, f.buildings); @@ -307,8 +318,8 @@ TEST_CASE("BehaviorSystem: repair ship heals damaged ally within repair range", const entt::entity repairShip = f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f)); const entt::entity friendly = f.ships.spawn("interceptor", 1, QVector2D(1.0f, 0.0f)); - const float initialHp = f.admin.get(friendly).maxHp * 0.5f; - f.admin.get(friendly).hp = initialHp; + const float initialHp = f.admin.get(friendly).maxHp * 0.5f; + f.admin.get(friendly).hp = initialHp; f.ships.clearMovementIntents(); f.ai.tickRepairBehavior(f.admin, f.buildings); @@ -322,7 +333,7 @@ TEST_CASE("BehaviorSystem: repair ship does not heal above maxHp", "[behavior]") f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f)); const entt::entity friendly = f.ships.spawn("interceptor", 1, QVector2D(1.0f, 0.0f)); - f.admin.get(friendly).hp = f.admin.get(friendly).maxHp - 0.001f; + f.admin.get(friendly).hp = f.admin.get(friendly).maxHp - 0.001f; for (int i = 0; i < 5; ++i) { @@ -330,7 +341,7 @@ TEST_CASE("BehaviorSystem: repair ship does not heal above maxHp", "[behavior]") f.ai.tickRepairBehavior(f.admin, f.buildings); } - const Health& h = health(f.admin, friendly); + const HealthComponent& h = health(f.admin, friendly); REQUIRE(h.hp <= h.maxHp); REQUIRE(h.hp == Approx(h.maxHp)); } @@ -363,7 +374,7 @@ TEST_CASE("BehaviorSystem: salvage ship collects scrap on arrival", "[behavior]" f.ships.clearMovementIntents(); f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings); - REQUIRE(f.admin.get(ship).current == 1); + REQUIRE(f.admin.get(ship).current == 1); REQUIRE_FALSE(f.admin.isValid(scrapEntity)); } @@ -385,13 +396,13 @@ TEST_CASE("BehaviorSystem: full-cargo salvage ship moves toward SalvageBay", "[b REQUIRE(f.buildings.findBuilding(bayId) != nullptr); const entt::entity ship = f.ships.spawn("salvage_ship", 1, QVector2D(5.0f, 0.0f)); - SalvageCargo& cargo = f.admin.get(ship); + SalvageCargoComponent& cargo = f.admin.get(ship); cargo.current = cargo.capacity; // full cargo f.ships.clearMovementIntents(); f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings); - const MovementIntent& i = intent(f.admin, ship); + const MovementIntentComponent& i = intent(f.admin, ship); REQUIRE(i.priority == 1); REQUIRE(i.target.x() < pos(f.admin, ship).value.x()); } @@ -404,7 +415,7 @@ TEST_CASE("SensorRange: sensorRange is populated from config formula at spawn", { Fixture f; const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f)); - REQUIRE(f.admin.get(e).value == Approx(200.0f)); + REQUIRE(f.admin.get(e).value == Approx(200.0f)); } // --------------------------------------------------------------------------- @@ -421,7 +432,7 @@ TEST_CASE("SensorRange: player combat ship acquires enemy just inside sensor ran f.ships.clearMovementIntents(); f.ai.tickThreatResponseBehavior(f.admin, f.buildings); - REQUIRE(f.admin.get(player).currentTarget == enemy); + REQUIRE(f.admin.get(player).currentTarget == enemy); } TEST_CASE("SensorRange: player combat ship ignores enemy just outside sensor range", "[sensor]") @@ -433,7 +444,7 @@ TEST_CASE("SensorRange: player combat ship ignores enemy just outside sensor ran f.ships.clearMovementIntents(); f.ai.tickThreatResponseBehavior(f.admin, f.buildings); - REQUIRE_FALSE(f.admin.get(player).currentTarget.has_value()); + REQUIRE_FALSE(f.admin.get(player).currentTarget.has_value()); } TEST_CASE("SensorRange: enemy ship ignores player just outside sensor range", "[sensor]") @@ -446,7 +457,7 @@ TEST_CASE("SensorRange: enemy ship ignores player just outside sensor range", "[ f.ships.clearMovementIntents(); f.ai.tickThreatResponseBehavior(f.admin, f.buildings); - REQUIRE_FALSE(f.admin.get(enemy).currentTarget.has_value()); + REQUIRE_FALSE(f.admin.get(enemy).currentTarget.has_value()); } // --------------------------------------------------------------------------- @@ -483,12 +494,12 @@ TEST_CASE("SensorRange: repair ship does not acquire damaged ally beyond sensor Fixture f; const entt::entity repairShip = f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f)); const entt::entity friendly = f.ships.spawn("interceptor", 1, QVector2D(300.0f, 0.0f)); - f.admin.get(friendly).hp = f.admin.get(friendly).maxHp * 0.5f; + f.admin.get(friendly).hp = f.admin.get(friendly).maxHp * 0.5f; f.ships.clearMovementIntents(); f.ai.tickRepairBehavior(f.admin, f.buildings); - REQUIRE_FALSE(f.admin.get(repairShip).currentTarget.has_value()); + REQUIRE_FALSE(f.admin.get(repairShip).currentTarget.has_value()); } // --------------------------------------------------------------------------- @@ -504,6 +515,6 @@ TEST_CASE("SensorRange: salvage ship ignores scrap beyond sensor range", "[senso f.ships.clearMovementIntents(); f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings); - REQUIRE_FALSE(f.admin.get(ship).scrapTarget.has_value()); + REQUIRE_FALSE(f.admin.get(ship).scrapTarget.has_value()); REQUIRE(intent(f.admin, ship).target.x() > pos(f.admin, ship).value.x()); } diff --git a/src/test/CombatSystemTest.cpp b/src/test/CombatSystemTest.cpp index bb60b20..1528700 100644 --- a/src/test/CombatSystemTest.cpp +++ b/src/test/CombatSystemTest.cpp @@ -8,14 +8,18 @@ #include "BuildingType.h" #include "CombatSystem.h" #include "ConfigLoader.h" -#include "EcsComponents.h" #include "EntityAdmin.h" +#include "FactionComponent.h" #include "FireEvent.h" +#include "HealthComponent.h" +#include "HqProxyComponent.h" #include "ScrapSystem.h" -#include "Ship.h" #include "ShipSystem.h" #include "Simulation.h" +#include "StationBodyComponent.h" #include "Tick.h" +#include "ThreatResponseBehaviorComponent.h" +#include "WeaponComponent.h" static GameConfig loadConfig() { @@ -63,14 +67,14 @@ struct CombatFixture void wireEnemyTarget(entt::entity enemy, entt::entity playerTarget) { - if (admin.hasAll(enemy)) + if (admin.hasAll(enemy)) { - admin.get(enemy).currentTarget = playerTarget; - admin.get(enemy).cooldownTicks = 0.0f; + admin.get(enemy).currentTarget = playerTarget; + admin.get(enemy).cooldownTicks = 0.0f; } - if (admin.hasAll(enemy)) + if (admin.hasAll(enemy)) { - admin.get(enemy).currentTarget = playerTarget; + admin.get(enemy).currentTarget = playerTarget; } } }; @@ -89,13 +93,13 @@ TEST_CASE("CombatSystem: ship fires when cooldown=0 and target in range", "[comb const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false); f.wireEnemyTarget(enemy, player); - const float hpBefore = f.admin.get(player).hp; + const float hpBefore = f.admin.get(player).hp; std::vector events; f.combat.tick(0, f.admin, f.buildings, events); f.combat.applyPendingDamage(5, f.admin); - REQUIRE(f.admin.get(player).hp < hpBefore); + REQUIRE(f.admin.get(player).hp < hpBefore); REQUIRE(events.size() >= 1); } @@ -109,7 +113,7 @@ TEST_CASE("CombatSystem: cooldown prevents firing before it expires", "[combat]" const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false); f.wireEnemyTarget(enemy, player); - f.admin.get(enemy).cooldownTicks = 3.0f; // override to 3 + f.admin.get(enemy).cooldownTicks = 3.0f; // override to 3 std::vector events; f.combat.tick(0, f.admin, f.buildings, events); @@ -146,8 +150,8 @@ TEST_CASE("CombatSystem: player station fires at enemy ship in range", "[combat] // Find the player station entity via ECS. entt::entity stationEntity = entt::null; QVector2D stationCenter; - sim.admin().forEach( - [&](entt::entity e, const StationBody& sb, const Faction& f) + sim.admin().forEach( + [&](entt::entity e, const StationBodyComponent& sb, const FactionComponent& f) { if (!f.isEnemy && stationEntity == entt::null) { @@ -184,8 +188,8 @@ TEST_CASE("CombatSystem: enemy station fires at player ship in range", "[combat] entt::entity stationEntity = entt::null; QVector2D stationCenter; - sim.admin().forEach( - [&](entt::entity e, const StationBody& sb, const Faction& f) + sim.admin().forEach( + [&](entt::entity e, const StationBodyComponent& sb, const FactionComponent& f) { if (f.isEnemy && stationEntity == entt::null) { @@ -222,8 +226,8 @@ TEST_CASE("CombatSystem: player ship fires at enemy station in range", "[combat] entt::entity stationEntity = entt::null; QVector2D stationCenter; - sim.admin().forEach( - [&](entt::entity e, const StationBody& sb, const Faction& f) + sim.admin().forEach( + [&](entt::entity e, const StationBodyComponent& sb, const FactionComponent& f) { if (f.isEnemy && stationEntity == entt::null) { @@ -271,7 +275,7 @@ TEST_CASE("CombatSystem: damage not applied before impact tick", "[combat]") const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false); f.wireEnemyTarget(enemy, player); - const float hpBefore = f.admin.get(player).hp; + const float hpBefore = f.admin.get(player).hp; std::vector events; f.combat.tick(0, f.admin, f.buildings, events); @@ -279,7 +283,7 @@ TEST_CASE("CombatSystem: damage not applied before impact tick", "[combat]") for (Tick t = 1; t < 5; ++t) { f.combat.applyPendingDamage(t, f.admin); - REQUIRE(f.admin.get(player).hp == Approx(hpBefore)); + REQUIRE(f.admin.get(player).hp == Approx(hpBefore)); } } @@ -293,13 +297,13 @@ TEST_CASE("CombatSystem: damage applied exactly at impact tick", "[combat]") const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false); f.wireEnemyTarget(enemy, player); - const float hpBefore = f.admin.get(player).hp; + const float hpBefore = f.admin.get(player).hp; std::vector events; f.combat.tick(0, f.admin, f.buildings, events); f.combat.applyPendingDamage(5, f.admin); - REQUIRE(f.admin.get(player).hp < hpBefore); + REQUIRE(f.admin.get(player).hp < hpBefore); } TEST_CASE("CombatSystem: damage silently dropped if target already dead", "[combat]") @@ -333,7 +337,7 @@ TEST_CASE("CombatSystem: damage still applied if shooter already dead", "[combat const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false); f.wireEnemyTarget(enemy, player); - const float hpBefore = f.admin.get(player).hp; + const float hpBefore = f.admin.get(player).hp; std::vector events; f.combat.tick(0, f.admin, f.buildings, events); @@ -342,7 +346,7 @@ TEST_CASE("CombatSystem: damage still applied if shooter already dead", "[combat f.combat.applyPendingDamage(5, f.admin); - REQUIRE(f.admin.get(player).hp < hpBefore); + REQUIRE(f.admin.get(player).hp < hpBefore); } // --------------------------------------------------------------------------- @@ -359,7 +363,7 @@ TEST_CASE("CombatSystem: dead ship is removed after tick step 9", "[combat]") const entt::entity ship = sim.ships().spawn(combatDef->id, 1, QVector2D(10.0f, 10.0f)); - sim.admin().get(ship).hp = -1.0f; + sim.admin().get(ship).hp = -1.0f; sim.tick(); @@ -383,7 +387,7 @@ TEST_CASE("CombatSystem: scrap is spawned on ship death", "[combat]") const entt::entity ship = sim.ships().spawn(droppingDef->id, 1, QVector2D(10.0f, 10.0f)); - sim.admin().get(ship).hp = -1.0f; + sim.admin().get(ship).hp = -1.0f; sim.tick(); @@ -395,8 +399,8 @@ TEST_CASE("CombatSystem: HQ death sets game over", "[combat]") Simulation sim(loadConfig(), 42); // Damage the HQ proxy entity (has HqProxy + Health). - sim.admin().forEach( - [](entt::entity /*e*/, const HqProxy& /*hq*/, Health& h) + sim.admin().forEach( + [](entt::entity /*e*/, const HqProxyComponent& /*hq*/, HealthComponent& h) { h.hp = -1.0f; }); diff --git a/src/test/ScrapTest.cpp b/src/test/ScrapTest.cpp index 8f4b3a3..b34848c 100644 --- a/src/test/ScrapTest.cpp +++ b/src/test/ScrapTest.cpp @@ -2,7 +2,9 @@ #include +#include "DespawnAtComponent.h" #include "EntityAdmin.h" +#include "ScrapDataComponent.h" #include "ScrapSystem.h" // --------------------------------------------------------------------------- @@ -17,8 +19,8 @@ TEST_CASE("ScrapSystem: spawn returns a valid entity with correct scrap data", " 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).tick == 100); + REQUIRE(admin.get(e).amount == 5); + REQUIRE(admin.get(e).tick == 100); } // --------------------------------------------------------------------------- diff --git a/src/test/ShipModuleTest.cpp b/src/test/ShipModuleTest.cpp index a42839b..973bc82 100644 --- a/src/test/ShipModuleTest.cpp +++ b/src/test/ShipModuleTest.cpp @@ -4,13 +4,13 @@ #include "BuildingSystem.h" #include "BuildingType.h" #include "ConfigLoader.h" -#include "EcsComponents.h" #include "EntityAdmin.h" #include "GameConfig.h" +#include "HealthComponent.h" #include "ItemType.h" #include "ModulesConfig.h" #include "Rotation.h" -#include "Ship.h" +#include "SensorRangeComponent.h" #include "ShipLayout.h" #include "ShipSystem.h" #include "Simulation.h" @@ -102,7 +102,7 @@ TEST_CASE("Ship spawn: no modules leaves base stats unchanged", "[modules]") QVector2D(5.0f, 5.0f), false, std::nullopt); REQUIRE(sim.admin().isValid(e)); - CHECK(sim.admin().get(e).maxHp == Approx(expectedHp)); + CHECK(sim.admin().get(e).maxHp == Approx(expectedHp)); } TEST_CASE("Ship spawn: multiplicative HP module applies correctly", "[modules]") @@ -128,8 +128,8 @@ TEST_CASE("Ship spawn: multiplicative HP module applies correctly", "[modules]") REQUIRE(sim.admin().isValid(e)); // armor_plate has multiplied_hp_formula = "1.5" // final = base * (1 + (1.5 - 1)) + 0 = base * 1.5 - CHECK(sim.admin().get(e).maxHp == Approx(baseHp * 1.5f)); - CHECK(sim.admin().get(e).hp == sim.admin().get(e).maxHp); + CHECK(sim.admin().get(e).maxHp == Approx(baseHp * 1.5f)); + CHECK(sim.admin().get(e).hp == sim.admin().get(e).maxHp); } TEST_CASE("Ship spawn: additive sensor module applies correctly", "[modules]") @@ -155,7 +155,7 @@ TEST_CASE("Ship spawn: additive sensor module applies correctly", "[modules]") REQUIRE(sim.admin().isValid(e)); // sensor_booster has added_sensor_range_formula = "10" // final = base * 1.0 + 10 = base + 10 - CHECK(sim.admin().get(e).value == Approx(baseRange + 10.0f)); + CHECK(sim.admin().get(e).value == Approx(baseRange + 10.0f)); } TEST_CASE("Ship spawn: multiple modules stack correctly", "[modules]") @@ -185,7 +185,7 @@ TEST_CASE("Ship spawn: multiple modules stack correctly", "[modules]") // Two armor_plates: each 1.5 multiplier // total_mult = 1 + (1.5 - 1) + (1.5 - 1) = 2.0 // final = base * 2.0 - CHECK(sim.admin().get(e).maxHp == Approx(baseHp * 2.0f)); + CHECK(sim.admin().get(e).maxHp == Approx(baseHp * 2.0f)); } // --------------------------------------------------------------------------- diff --git a/src/test/ShipTest.cpp b/src/test/ShipTest.cpp index 687b949..4475719 100644 --- a/src/test/ShipTest.cpp +++ b/src/test/ShipTest.cpp @@ -5,14 +5,20 @@ #include +#include "BuildingId.h" #include "ConfigLoader.h" #include "DynamicBodyComponent.h" -#include "EcsComponents.h" #include "EntityAdmin.h" -#include "BuildingId.h" -#include "Ship.h" +#include "HealthComponent.h" +#include "RepairBehaviorComponent.h" +#include "RepairToolComponent.h" +#include "SalvageBehaviorComponent.h" +#include "SalvageCargoComponent.h" +#include "SensorRangeComponent.h" #include "ShipSystem.h" #include "Tick.h" +#include "ThreatResponseBehaviorComponent.h" +#include "WeaponComponent.h" static GameConfig loadConfig() { @@ -33,12 +39,12 @@ TEST_CASE("ShipSystem: interceptor spawn has weapon and threatResponse, no cargo const entt::entity e = ss.spawn("interceptor", 1, QVector2D(0.0f, 0.0f)); REQUIRE(admin.isValid(e)); - REQUIRE(admin.hasAll(e)); - REQUIRE(admin.hasAll(e)); - REQUIRE_FALSE(admin.hasAll(e)); - REQUIRE_FALSE(admin.hasAll(e)); - REQUIRE_FALSE(admin.hasAll(e)); - REQUIRE_FALSE(admin.hasAll(e)); + REQUIRE(admin.hasAll(e)); + REQUIRE(admin.hasAll(e)); + REQUIRE_FALSE(admin.hasAll(e)); + REQUIRE_FALSE(admin.hasAll(e)); + REQUIRE_FALSE(admin.hasAll(e)); + REQUIRE_FALSE(admin.hasAll(e)); } TEST_CASE("ShipSystem: interceptor level 1 stats match config formulas", "[ship]") @@ -50,16 +56,16 @@ TEST_CASE("ShipSystem: interceptor level 1 stats match config formulas", "[ship] const entt::entity e = ss.spawn("interceptor", 1, QVector2D(0.0f, 0.0f)); // hp_formula = "40 + 5*x" at x=1 → 45 - REQUIRE(admin.get(e).maxHp == Approx(45.0f)); - REQUIRE(admin.get(e).hp == Approx(45.0f)); + REQUIRE(admin.get(e).maxHp == Approx(45.0f)); + REQUIRE(admin.get(e).hp == Approx(45.0f)); // damage_formula = "10 + 2*x" at x=1 → 12 - REQUIRE(admin.get(e).damage == Approx(12.0f)); + REQUIRE(admin.get(e).damage == Approx(12.0f)); // attack_range_formula = "150" - REQUIRE(admin.get(e).range == Approx(150.0f)); + REQUIRE(admin.get(e).range == Approx(150.0f)); // sensor_range_formula = "200" - REQUIRE(admin.get(e).value == Approx(200.0f)); + REQUIRE(admin.get(e).value == Approx(200.0f)); // cooldownTicks starts at 0 - REQUIRE(admin.get(e).cooldownTicks == Approx(0.0f)); + REQUIRE(admin.get(e).cooldownTicks == Approx(0.0f)); } TEST_CASE("ShipSystem: interceptor level 5 hp matches formula", "[ship]") @@ -71,7 +77,7 @@ TEST_CASE("ShipSystem: interceptor level 5 hp matches formula", "[ship]") const entt::entity e = ss.spawn("interceptor", 5, QVector2D(0.0f, 0.0f)); // hp_formula = "40 + 5*x" at x=5 → 65 - REQUIRE(admin.get(e).maxHp == Approx(65.0f)); + REQUIRE(admin.get(e).maxHp == Approx(65.0f)); } TEST_CASE("ShipSystem: interceptor level 0 maxSpeedPerTick matches formula / kTickRateHz", "[ship]") @@ -100,10 +106,10 @@ TEST_CASE("ShipSystem: salvage_ship spawn has cargo and scrapCollector, no weapo const entt::entity e = ss.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f)); - REQUIRE(admin.hasAll(e)); - REQUIRE(admin.hasAll(e)); - REQUIRE_FALSE(admin.hasAll(e)); - REQUIRE_FALSE(admin.hasAll(e)); + REQUIRE(admin.hasAll(e)); + REQUIRE(admin.hasAll(e)); + REQUIRE_FALSE(admin.hasAll(e)); + REQUIRE_FALSE(admin.hasAll(e)); } TEST_CASE("ShipSystem: salvage_ship cargo capacity matches config", "[ship]") @@ -115,10 +121,10 @@ TEST_CASE("ShipSystem: salvage_ship cargo capacity matches config", "[ship]") const entt::entity e = ss.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f)); // cargo_capacity = 10 - REQUIRE(admin.get(e).capacity == 10); - REQUIRE(admin.get(e).current == 0); - REQUIRE(admin.get(e).deliveryBay == kInvalidBuildingId); - REQUIRE_FALSE(admin.get(e).scrapTarget.has_value()); + REQUIRE(admin.get(e).capacity == 10); + REQUIRE(admin.get(e).current == 0); + REQUIRE(admin.get(e).deliveryBay == kInvalidBuildingId); + REQUIRE_FALSE(admin.get(e).scrapTarget.has_value()); } // --------------------------------------------------------------------------- @@ -134,10 +140,10 @@ TEST_CASE("ShipSystem: repair_ship spawn has repairTool and repairBehavior, no w const entt::entity e = ss.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f)); - REQUIRE(admin.hasAll(e)); - REQUIRE(admin.hasAll(e)); - REQUIRE_FALSE(admin.hasAll(e)); - REQUIRE_FALSE(admin.hasAll(e)); + REQUIRE(admin.hasAll(e)); + REQUIRE(admin.hasAll(e)); + REQUIRE_FALSE(admin.hasAll(e)); + REQUIRE_FALSE(admin.hasAll(e)); } TEST_CASE("ShipSystem: repair_ship level 1 repair stats match config formulas", "[ship]") @@ -149,9 +155,9 @@ TEST_CASE("ShipSystem: repair_ship level 1 repair stats match config formulas", const entt::entity e = ss.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f)); // repair_rate_formula = "5 + x" at x=1 → 6 - REQUIRE(admin.get(e).ratePerTick == Approx(6.0f)); + REQUIRE(admin.get(e).ratePerTick == Approx(6.0f)); // repair_range_formula = "80" - REQUIRE(admin.get(e).range == Approx(80.0f)); + REQUIRE(admin.get(e).range == Approx(80.0f)); } // --------------------------------------------------------------------------- diff --git a/src/test/ShipyardTest.cpp b/src/test/ShipyardTest.cpp index a722125..bd1afd8 100644 --- a/src/test/ShipyardTest.cpp +++ b/src/test/ShipyardTest.cpp @@ -4,12 +4,12 @@ #include "BuildingSystem.h" #include "BuildingType.h" #include "ConfigLoader.h" -#include "EcsComponents.h" #include "EntityAdmin.h" +#include "FactionComponent.h" #include "GameConfig.h" #include "ItemType.h" #include "Rotation.h" -#include "Ship.h" +#include "ShipIdentityComponent.h" #include "ShipSystem.h" #include "Simulation.h" #include "Tick.h" @@ -55,8 +55,8 @@ static BuildingId placeShipyard(Simulation& sim, const BuildingDef& yardDef) static int countShips(Simulation& sim) { int n = 0; - sim.admin().forEach( - [&n](entt::entity /*e*/, const ShipIdentity& /*si*/) { ++n; }); + sim.admin().forEach( + [&n](entt::entity /*e*/, const ShipIdentityComponent& /*si*/) { ++n; }); return n; } @@ -114,8 +114,8 @@ TEST_CASE("Shipyard: spawns a player ship after production cycle completes", REQUIRE(countShips(sim) == shipsBefore + 1); bool foundPlayerShip = false; - sim.admin().forEach( - [&](entt::entity /*e*/, const ShipIdentity& si, const Faction& f) + sim.admin().forEach( + [&](entt::entity /*e*/, const ShipIdentityComponent& si, const FactionComponent& f) { if (!f.isEnemy && si.schematicId == def->id) { diff --git a/src/test/WaveSystemTest.cpp b/src/test/WaveSystemTest.cpp index 1a5878d..e49847d 100644 --- a/src/test/WaveSystemTest.cpp +++ b/src/test/WaveSystemTest.cpp @@ -6,11 +6,15 @@ #include "BuildingSystem.h" #include "BuildingType.h" #include "ConfigLoader.h" -#include "EcsComponents.h" #include "EntityAdmin.h" +#include "FactionComponent.h" +#include "HealthComponent.h" +#include "HqProxyComponent.h" #include "Rotation.h" -#include "Ship.h" +#include "ShipIdentityComponent.h" #include "ShipSystem.h" +#include "StationBodyComponent.h" +#include "WeaponComponent.h" #include "Simulation.h" #include "Tick.h" #include "WaveSystem.h" @@ -112,8 +116,8 @@ TEST_CASE("WaveSystem: Simulation pre-places HQ + 2 player + 2 enemy stations", // Stations are ECS entities. int playerCount = 0; int enemyCount = 0; - sim.admin().forEach( - [&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f) + sim.admin().forEach( + [&](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f) { if (f.isEnemy) { ++enemyCount; } else { ++playerCount; } @@ -132,8 +136,8 @@ TEST_CASE("WaveSystem: HQ has correct initial HP from config", "[wave]") static_cast(sim.config().stations.hq.hpFormula.evaluate(0.0)); bool found = false; float actualHp = 0.0f; - sim.admin().forEach( - [&](entt::entity /*e*/, const HqProxy& /*hq*/, const Health& h) + sim.admin().forEach( + [&](entt::entity /*e*/, const HqProxyComponent& /*hq*/, const HealthComponent& h) { found = true; actualHp = h.hp; @@ -165,9 +169,9 @@ TEST_CASE("WaveSystem: player stations have weapon set", "[wave]") const Simulation sim(loadConfig(), 42); int armedPlayerStations = 0; - sim.admin().forEach( - [&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, - const Weapon& w) + sim.admin().forEach( + [&](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, + const WeaponComponent& w) { if (!f.isEnemy) { @@ -185,9 +189,9 @@ TEST_CASE("WaveSystem: enemy stations have weapon set", "[wave]") const Simulation sim(loadConfig(), 42); int armedEnemyStations = 0; - sim.admin().forEach( - [&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, - const Weapon& w) + sim.admin().forEach( + [&](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, + const WeaponComponent& w) { if (f.isEnemy) { @@ -217,8 +221,8 @@ TEST_CASE("WaveSystem: enemy ships spawn after the initial gap elapses", "[wave] } bool foundEnemyShip = false; - sim.admin().forEach( - [&](entt::entity /*e*/, const ShipIdentity& /*si*/, const Faction& f) + sim.admin().forEach( + [&](entt::entity /*e*/, const ShipIdentityComponent& /*si*/, const FactionComponent& f) { if (f.isEnemy) { foundEnemyShip = true; } }); @@ -236,8 +240,8 @@ TEST_CASE("WaveSystem: only eligible ships (cost > 0) appear in waves", "[wave]" sim.tick(); } - sim.admin().forEach( - [&](entt::entity /*e*/, const ShipIdentity& si, const Faction& f) + sim.admin().forEach( + [&](entt::entity /*e*/, const ShipIdentityComponent& si, const FactionComponent& f) { if (!f.isEnemy) { return; } // salvage_ship and repair_ship have cost_formula = "0" and must not spawn. @@ -255,8 +259,8 @@ TEST_CASE("WaveSystem: destroying both enemy stations triggers a push", "[wave]" Simulation sim(loadConfig(), 42); // Damage both enemy stations to 0. - sim.admin().forEach( - [](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, Health& h) + sim.admin().forEach( + [](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, HealthComponent& h) { if (f.isEnemy) { h.hp = -1.0f; } }); @@ -265,8 +269,8 @@ TEST_CASE("WaveSystem: destroying both enemy stations triggers a push", "[wave]" // After push: should have 2 new enemy stations. int enemyCount = 0; - sim.admin().forEach( - [&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f) + sim.admin().forEach( + [&](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f) { if (f.isEnemy) { ++enemyCount; } }); @@ -277,8 +281,8 @@ TEST_CASE("WaveSystem: push emits exactly one SchematicDropEvent", "[wave]") { Simulation sim(loadConfig(), 42); - sim.admin().forEach( - [](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, Health& h) + sim.admin().forEach( + [](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, HealthComponent& h) { if (f.isEnemy) { h.hp = -1.0f; } }); @@ -293,8 +297,8 @@ TEST_CASE("WaveSystem: push schematic drop awards a known ship id", "[wave]") { Simulation sim(loadConfig(), 42); - sim.admin().forEach( - [](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, Health& h) + sim.admin().forEach( + [](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, HealthComponent& h) { if (f.isEnemy) { h.hp = -1.0f; } }); @@ -322,8 +326,8 @@ TEST_CASE("WaveSystem: push places new enemy stations further right", "[wave]") // Record the X position of the initial enemy stations. int initialX = std::numeric_limits::min(); - sim.admin().forEach( - [&](entt::entity /*e*/, const StationBody& sb, const Faction& f) + sim.admin().forEach( + [&](entt::entity /*e*/, const StationBodyComponent& sb, const FactionComponent& f) { if (f.isEnemy && sb.anchor.x() > initialX) { @@ -331,16 +335,16 @@ TEST_CASE("WaveSystem: push places new enemy stations further right", "[wave]") } }); - sim.admin().forEach( - [](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, Health& h) + sim.admin().forEach( + [](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, HealthComponent& h) { if (f.isEnemy) { h.hp = -1.0f; } }); sim.tick(); int newX = std::numeric_limits::min(); - sim.admin().forEach( - [&](entt::entity /*e*/, const StationBody& sb, const Faction& f) + sim.admin().forEach( + [&](entt::entity /*e*/, const StationBodyComponent& sb, const FactionComponent& f) { if (f.isEnemy && sb.anchor.x() > newX) { diff --git a/src/ui/GameWorldView.cpp b/src/ui/GameWorldView.cpp index aaadce7..b523d58 100644 --- a/src/ui/GameWorldView.cpp +++ b/src/ui/GameWorldView.cpp @@ -17,14 +17,21 @@ #include #include +#include "BeltSystem.h" #include "Building.h" #include "BuildingSystem.h" -#include "BeltSystem.h" -#include "EcsComponents.h" +#include "FacingComponent.h" +#include "FactionComponent.h" +#include "HealthComponent.h" +#include "PositionComponent.h" +#include "RepairToolComponent.h" +#include "SalvageCargoComponent.h" #include "ScrapSystem.h" -#include "Ship.h" +#include "SensorRangeComponent.h" +#include "ShipIdentityComponent.h" #include "ShipSystem.h" #include "Simulation.h" +#include "StationBodyComponent.h" #include "SurfaceMask.h" #include "Tick.h" @@ -161,9 +168,9 @@ void GameWorldView::onFrame() { float maxRadius = 0.125f; if (m_sim->admin().isValid(fe.target) - && m_sim->admin().hasAll(fe.target)) + && m_sim->admin().hasAll(fe.target)) { - const StationBody& sb = m_sim->admin().get(fe.target); + const StationBodyComponent& sb = m_sim->admin().get(fe.target); const int shorter = std::min(sb.footprint.width(), sb.footprint.height()); maxRadius = shorter / 2.0f; } @@ -343,8 +350,8 @@ float GameWorldView::enemyStationRightEdge() const { float rightX = static_cast(m_config->world.regions.playerBufferWidth + m_config->world.regions.contestZoneWidth); - m_sim->admin().forEach( - [&rightX](entt::entity /*e*/, const StationBody& sb, const Faction& f) + m_sim->admin().forEach( + [&rightX](entt::entity /*e*/, const StationBodyComponent& sb, const FactionComponent& f) { if (!f.isEnemy) { return; } for (const QPoint& cell : sb.bodyCells) @@ -445,11 +452,11 @@ BuildingId GameWorldView::siteAtTile(QPoint tile) const std::optional GameWorldView::entityPosition(entt::entity entity) const { - if (!m_sim->admin().isValid(entity) || !m_sim->admin().hasAll(entity)) + if (!m_sim->admin().isValid(entity) || !m_sim->admin().hasAll(entity)) { return std::nullopt; } - return m_sim->admin().get(entity).value; + return m_sim->admin().get(entity).value; } void GameWorldView::stepSpeed(int delta) @@ -794,8 +801,9 @@ void GameWorldView::drawScrap(QPainter& painter) void GameWorldView::drawStations(QPainter& painter) { - m_sim->admin().forEach( - [&](entt::entity /*e*/, const StationBody& sb, const Faction& f, const Health& h) + m_sim->admin().forEach( + [&](entt::entity /*e*/, const StationBodyComponent& sb, const FactionComponent& f, + const HealthComponent& h) { const BuildingType visType = f.isEnemy ? BuildingType::EnemyDefenceStation @@ -837,12 +845,14 @@ void GameWorldView::drawStations(QPainter& painter) void GameWorldView::drawShips(QPainter& painter) { - m_sim->admin().forEach( - [&](entt::entity e, const ShipIdentity& /*si*/, const Position& pos, - const Facing& facing, const Faction& fac) + m_sim->admin().forEach( + [&](entt::entity e, const ShipIdentityComponent& /*si*/, + const PositionComponent& pos, const FacingComponent& facing, + const FactionComponent& fac) { - const bool hasCargo = m_sim->admin().hasAll(e); - const bool hasRepair = m_sim->admin().hasAll(e); + const bool hasCargo = m_sim->admin().hasAll(e); + const bool hasRepair = m_sim->admin().hasAll(e); const ShipRole role = shipRoleFromComponents(fac.isEnemy, hasCargo, hasRepair); const std::map::const_iterator it = m_visuals->ships.find(role); @@ -872,12 +882,14 @@ void GameWorldView::drawShips(QPainter& painter) void GameWorldView::drawDebugSensorRanges(QPainter& painter) { painter.setBrush(Qt::NoBrush); - m_sim->admin().forEach( - [&](entt::entity e, const ShipIdentity& /*si*/, const Position& pos, - const Facing& /*facing*/, const Faction& fac, const SensorRange& sensor) + m_sim->admin().forEach( + [&](entt::entity e, const ShipIdentityComponent& /*si*/, + const PositionComponent& pos, const FacingComponent& /*facing*/, + const FactionComponent& fac, const SensorRangeComponent& sensor) { - const bool hasCargo = m_sim->admin().hasAll(e); - const bool hasRepair = m_sim->admin().hasAll(e); + const bool hasCargo = m_sim->admin().hasAll(e); + const bool hasRepair = m_sim->admin().hasAll(e); const ShipRole role = shipRoleFromComponents(fac.isEnemy, hasCargo, hasRepair); const std::map::const_iterator it = m_visuals->ships.find(role);