From ea79d76953302ea3a9698134deee8f1c2e057848 Mon Sep 17 00:00:00 2001 From: mlangkabel Date: Fri, 22 May 2026 22:06:30 +0200 Subject: [PATCH] unify Weapon and StationWeapon components --- src/balancing/ArenaSimulation.cpp | 5 +- src/lib/core/EcsComponents.h | 3 +- src/lib/sim/Building.h | 12 +- src/lib/sim/CombatSystem.cpp | 184 +++++++++++------------------- src/lib/sim/CombatSystem.h | 22 ++-- src/lib/sim/Simulation.cpp | 12 +- src/test/WaveSystemTest.cpp | 8 +- 7 files changed, 89 insertions(+), 157 deletions(-) diff --git a/src/balancing/ArenaSimulation.cpp b/src/balancing/ArenaSimulation.cpp index c9fa9a6..dc6fe54 100644 --- a/src/balancing/ArenaSimulation.cpp +++ b/src/balancing/ArenaSimulation.cpp @@ -114,7 +114,7 @@ void ArenaSimulation::placeStructures() auto placeArenaStation = [&](const ArenaStationEntry& entry, bool isEnemy) { float hp = 0.0f; - StationWeapon weapon; + Weapon weapon; weapon.cooldownTicks = 0.0f; weapon.currentTarget = std::nullopt; const double lv = static_cast(entry.level); @@ -155,7 +155,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, allocateId()); }; @@ -500,3 +500,4 @@ void ArenaSimulation::updateStatus() std::lock_guard lock(m_statusMutex); m_status = newStatus; } + diff --git a/src/lib/core/EcsComponents.h b/src/lib/core/EcsComponents.h index 431593a..90fcb8f 100644 --- a/src/lib/core/EcsComponents.h +++ b/src/lib/core/EcsComponents.h @@ -84,8 +84,6 @@ struct StationBody std::vector bodyCells; }; -// StationWeapon remains defined in Building.h. - // --------------------------------------------------------------------------- // Scrap components // --------------------------------------------------------------------------- @@ -105,3 +103,4 @@ struct DespawnAt // --------------------------------------------------------------------------- struct HqProxy { char unused = 0; }; + diff --git a/src/lib/sim/Building.h b/src/lib/sim/Building.h index 47af6f3..245c401 100644 --- a/src/lib/sim/Building.h +++ b/src/lib/sim/Building.h @@ -56,17 +56,6 @@ struct ConstructionSite std::optional shipLayout; }; -// Weapon state for stationary structures (defence stations). -// Distinct from Ship::Weapon; stations have no movement intent. -struct StationWeapon -{ - float damage; - float range; - float fireRateHz; - float cooldownTicks; - std::optional currentTarget; -}; - // A fully constructed, operational building. struct Building { @@ -90,3 +79,4 @@ struct Building // Module layout for shipyards (REQ-MOD-LAYOUT). std::optional shipLayout; }; + diff --git a/src/lib/sim/CombatSystem.cpp b/src/lib/sim/CombatSystem.cpp index 2cd908e..8c5af13 100644 --- a/src/lib/sim/CombatSystem.cpp +++ b/src/lib/sim/CombatSystem.cpp @@ -16,147 +16,92 @@ void CombatSystem::tick(Tick currentTick, std::vector& outFireEvents) { // Ship weapons. - admin.forEach( - [&](entt::entity e, Weapon& weapon, ThreatResponse& threat, Position& pos) + admin.forEach( + [&](entt::entity e, Weapon& weapon, ThreatResponse& threat, Position& pos, Faction& faction) { - resolveShipWeapon(e, weapon, threat, pos, currentTick, admin, outFireEvents); + weapon.currentTarget = threat.currentTarget; + resolveWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents); }); // Station weapons. - admin.forEach( - [&](entt::entity e, StationWeapon& weapon, Position& pos, Faction& faction) + admin.forEach( + [&](entt::entity e, Weapon& weapon, Position& pos, Faction& faction) { - resolveStationWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents); + resolveWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents); }); } -void CombatSystem::resolveShipWeapon(entt::entity shipEntity, Weapon& weapon, - const ThreatResponse& threat, - const Position& pos, Tick currentTick, - EntityAdmin& admin, - std::vector& out) +void CombatSystem::resolveWeapon( + entt::entity shipEntity, + Weapon& weapon, + const Position& ownPos, + const Faction& ownFaction, + Tick currentTick, + EntityAdmin& admin, + std::vector& out) { - if (!threat.currentTarget) - { - return; - } + if (weapon.cooldownTicks > 0.0f) + { + weapon.cooldownTicks -= 1.0f; + } + if (weapon.cooldownTicks > 0.0f) + { + return; + } - 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; + } + } + } - const entt::entity targetEntity = *threat.currentTarget; - if (!admin.isValid(targetEntity) || !admin.hasAll(targetEntity)) - { - return; - } - - const QVector2D targetPos = admin.get(targetEntity).value; - const float dist = (pos.value - targetPos).length(); - if (dist > weapon.range) - { - return; - } - - 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::resolveStationWeapon(entt::entity stationEntity, - StationWeapon& weapon, - const Position& stationPos, - const Faction& stationFaction, - Tick currentTick, - EntityAdmin& admin, - std::vector& out) -{ - // 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 dist = (stationPos.value - admin.get(t).value).length(); - if (dist > weapon.range) - { - weapon.currentTarget = std::nullopt; - } - } - } - - // Acquire a new target if needed (nearest opposing-faction ship). - if (!weapon.currentTarget) - { - float bestDist = weapon.range; - admin.forEach( - [&](entt::entity candidate, const ShipIdentity& /*si*/, - const Position& candidatePos, const Faction& candidateFaction) - { - const bool isValidTarget = stationFaction.isEnemy - ? !candidateFaction.isEnemy - : candidateFaction.isEnemy; - if (!isValidTarget) - { - return; - } - const float dist = (candidatePos.value - stationPos.value).length(); - if (dist < bestDist) - { - bestDist = dist; - weapon.currentTarget = candidate; - } - }); - } + // 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; } - if (weapon.cooldownTicks > 0.0f) - { - weapon.cooldownTicks -= 1.0f; - } - if (weapon.cooldownTicks > 0.0f) - { - return; - } - const entt::entity targetEntity = *weapon.currentTarget; - if (!admin.isValid(targetEntity) || !admin.hasAll(targetEntity)) - { - weapon.currentTarget = std::nullopt; - return; - } - - const QVector2D targetPos = admin.get(targetEntity).value; - if ((stationPos.value - targetPos).length() > weapon.range) - { - return; - } - m_pendingDamage.push_back({targetEntity, weapon.damage, currentTick + kWeaponImpactDelayTicks}); FireEvent evt; - evt.shooter = stationEntity; + evt.shooter = shipEntity; evt.target = targetEntity; evt.emittedAt = currentTick; out.push_back(evt); @@ -183,3 +128,4 @@ void CombatSystem::applyPendingDamage(Tick currentTick, EntityAdmin& admin) } } } + diff --git a/src/lib/sim/CombatSystem.h b/src/lib/sim/CombatSystem.h index 65a1e81..3997957 100644 --- a/src/lib/sim/CombatSystem.h +++ b/src/lib/sim/CombatSystem.h @@ -39,19 +39,15 @@ private: std::vector m_pendingDamage; - void resolveShipWeapon(entt::entity shipEntity, Weapon& weapon, - const ThreatResponse& threat, - const Position& pos, Tick currentTick, - EntityAdmin& admin, - std::vector& out); - - void resolveStationWeapon(entt::entity stationEntity, - StationWeapon& weapon, - const Position& stationPos, - const Faction& stationFaction, - Tick currentTick, - EntityAdmin& admin, - std::vector& out); + void resolveWeapon( + entt::entity shipEntity, + Weapon& weapon, + const Position& ownPos, + const Faction& ownFaction, + Tick currentTick, + EntityAdmin& admin, + std::vector& out); const GameConfig& m_config; }; + diff --git a/src/lib/sim/Simulation.cpp b/src/lib/sim/Simulation.cpp index a6955e8..bb2a951 100644 --- a/src/lib/sim/Simulation.cpp +++ b/src/lib/sim/Simulation.cpp @@ -224,7 +224,7 @@ void Simulation::placeInitialStructures() const float psHp = static_cast( m_config.stations.playerStation.hpFormula.evaluate(psLevel)); - StationWeapon psWeapon; + Weapon psWeapon; psWeapon.damage = static_cast( m_config.stations.playerStation.damageFormula.evaluate(psLevel)); psWeapon.range = static_cast( @@ -246,7 +246,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, allocateId()); } { @@ -258,7 +258,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, allocateId()); } @@ -285,7 +285,7 @@ void Simulation::placeEnemyStationSet(int generation) const float esHp = static_cast( m_config.stations.enemyStation.hpFormula.evaluate(genD)); - StationWeapon esWeapon; + Weapon esWeapon; esWeapon.damage = static_cast( m_config.stations.enemyStation.damageFormula.evaluate(genD)); esWeapon.range = static_cast( @@ -307,7 +307,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, allocateId()); } { @@ -319,7 +319,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, allocateId()); } } diff --git a/src/test/WaveSystemTest.cpp b/src/test/WaveSystemTest.cpp index 4af31ca..1a5878d 100644 --- a/src/test/WaveSystemTest.cpp +++ b/src/test/WaveSystemTest.cpp @@ -165,9 +165,9 @@ TEST_CASE("WaveSystem: player stations have weapon set", "[wave]") const Simulation sim(loadConfig(), 42); int armedPlayerStations = 0; - sim.admin().forEach( + sim.admin().forEach( [&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, - const StationWeapon& w) + const Weapon& w) { if (!f.isEnemy) { @@ -185,9 +185,9 @@ TEST_CASE("WaveSystem: enemy stations have weapon set", "[wave]") const Simulation sim(loadConfig(), 42); int armedEnemyStations = 0; - sim.admin().forEach( + sim.admin().forEach( [&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, - const StationWeapon& w) + const Weapon& w) { if (f.isEnemy) {