#include "CombatSystem.h" #include "BuildingSystem.h" #include "BuildingType.h" #include "Ship.h" #include "ShipSystem.h" static constexpr Tick kWeaponImpactDelayTicks = 5; // 0.15 s × 30 Hz, rounded to nearest CombatSystem::CombatSystem(const GameConfig& config) : m_config(config) { } void CombatSystem::tick(Tick currentTick, ShipSystem& ships, BuildingSystem& buildings, std::vector& outFireEvents) { // Ships: iterate and resolve weapon for each combat ship. ships.forEach([&](Ship& ship) { resolveShipWeapon(ship, currentTick, ships, buildings, outFireEvents); }); // Defence stations: acquire targets and fire. buildings.forEachBuilding([&](Building& building) { if (building.type == BuildingType::PlayerDefenceStation || building.type == BuildingType::EnemyDefenceStation) { resolveStationWeapon(building, currentTick, ships, buildings, outFireEvents); } }); } void CombatSystem::resolveShipWeapon(Ship& ship, Tick currentTick, ShipSystem& ships, BuildingSystem& buildings, std::vector& out) { if (!ship.weapon || !ship.threatResponse || !ship.threatResponse->currentTarget) { return; } Weapon& w = *ship.weapon; // Decrement cooldown toward zero. if (w.cooldownTicks > 0.0f) { w.cooldownTicks -= 1.0f; } if (w.cooldownTicks > 0.0f) { return; } const EntityId targetId = *ship.threatResponse->currentTarget; const std::optional tPos = targetPosition(targetId, ships, buildings); if (!tPos) { ship.threatResponse->currentTarget = std::nullopt; return; } const float dist = (ship.position - *tPos).length(); if (dist > w.range) { return; } m_pendingDamage.push_back({targetId, w.damage, currentTick + kWeaponImpactDelayTicks}); FireEvent evt; evt.shooter = ship.id; evt.target = targetId; evt.emittedAt = currentTick; out.push_back(evt); w.cooldownTicks = static_cast(kTickRateHz) / w.fireRateHz; } void CombatSystem::resolveStationWeapon(Building& station, Tick currentTick, ShipSystem& ships, BuildingSystem& buildings, std::vector& out) { if (!station.weapon) { return; } StationWeapon& w = *station.weapon; const bool stationIsEnemy = (station.type == BuildingType::EnemyDefenceStation); const QVector2D stationCenter( station.anchor.x() + station.footprint.width() / 2.0f, station.anchor.y() + station.footprint.height() / 2.0f); // Validate or clear existing target. if (w.currentTarget) { const std::optional tPos = targetPosition(*w.currentTarget, ships, buildings); if (!tPos || (stationCenter - *tPos).length() > w.range) { w.currentTarget = std::nullopt; } } // Acquire a new target if needed. if (!w.currentTarget) { w.currentTarget = acquireStationTarget(station, stationIsEnemy, ships); } if (!w.currentTarget) { return; } // Decrement cooldown. if (w.cooldownTicks > 0.0f) { w.cooldownTicks -= 1.0f; } if (w.cooldownTicks > 0.0f) { return; } const EntityId targetId = *w.currentTarget; const std::optional tPos = targetPosition(targetId, ships, buildings); if (!tPos) { w.currentTarget = std::nullopt; return; } if ((stationCenter - *tPos).length() > w.range) { return; } m_pendingDamage.push_back({targetId, w.damage, currentTick + kWeaponImpactDelayTicks}); FireEvent evt; evt.shooter = station.id; evt.target = targetId; evt.emittedAt = currentTick; out.push_back(evt); w.cooldownTicks = static_cast(kTickRateHz) / w.fireRateHz; } std::optional CombatSystem::acquireStationTarget( const Building& station, bool stationIsEnemy, const ShipSystem& ships) const { const QVector2D stationCenter( station.anchor.x() + station.footprint.width() / 2.0f, station.anchor.y() + station.footprint.height() / 2.0f); const float range = station.weapon->range; std::optional best; float bestDist = range; // Scan ships for valid targets. for (const Ship& candidate : ships.allShips()) { const bool isValidTarget = stationIsEnemy ? !candidate.isEnemy : candidate.isEnemy; if (!isValidTarget) { continue; } const float dist = (candidate.position - stationCenter).length(); if (dist < bestDist) { bestDist = dist; best = candidate.id; } } return best; } void CombatSystem::applyPendingDamage(Tick currentTick, ShipSystem& ships, BuildingSystem& buildings) { auto it = m_pendingDamage.begin(); while (it != m_pendingDamage.end()) { if (it->appliesAt <= currentTick) { if (ships.findShip(it->target)) { ships.damageShip(it->target, it->amount); } else if (buildings.findBuilding(it->target)) { buildings.damageBuilding(it->target, it->amount); } it = m_pendingDamage.erase(it); } else { ++it; } } } std::optional CombatSystem::targetPosition( EntityId id, const ShipSystem& ships, const BuildingSystem& buildings) const { const Ship* ship = ships.findShip(id); if (ship) { return ship->position; } const Building* bld = buildings.findBuilding(id); if (bld) { return QVector2D(bld->anchor.x() + bld->footprint.width() / 2.0f, bld->anchor.y() + bld->footprint.height() / 2.0f); } return std::nullopt; }