switch to ECS architecture
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
#include "CombatSystem.h"
|
||||
|
||||
#include "BuildingSystem.h"
|
||||
#include "BuildingType.h"
|
||||
#include "Ship.h"
|
||||
#include "ShipSystem.h"
|
||||
#include "EcsComponents.h"
|
||||
#include "EntityAdmin.h"
|
||||
|
||||
static constexpr Tick kWeaponImpactDelayTicks = 5; // 0.15 s × 30 Hz, rounded to nearest
|
||||
static constexpr Tick kWeaponImpactDelayTicks = 5;
|
||||
|
||||
CombatSystem::CombatSystem(const GameConfig& config)
|
||||
: m_config(config)
|
||||
@@ -13,199 +11,169 @@ CombatSystem::CombatSystem(const GameConfig& config)
|
||||
}
|
||||
|
||||
void CombatSystem::tick(Tick currentTick,
|
||||
ShipSystem& ships,
|
||||
BuildingSystem& buildings,
|
||||
EntityAdmin& admin,
|
||||
BuildingSystem& /*buildings*/,
|
||||
std::vector<FireEvent>& 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)
|
||||
// Ship weapons.
|
||||
admin.forEach<Weapon, ThreatResponse, Position>(
|
||||
[&](entt::entity e, Weapon& weapon, ThreatResponse& threat, Position& pos)
|
||||
{
|
||||
resolveStationWeapon(building, currentTick, ships, buildings, outFireEvents);
|
||||
}
|
||||
});
|
||||
resolveShipWeapon(e, weapon, threat, pos, currentTick, admin, outFireEvents);
|
||||
});
|
||||
|
||||
// Station weapons.
|
||||
admin.forEach<StationWeapon, Position, Faction>(
|
||||
[&](entt::entity e, StationWeapon& weapon, Position& pos, Faction& faction)
|
||||
{
|
||||
resolveStationWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents);
|
||||
});
|
||||
}
|
||||
|
||||
void CombatSystem::resolveShipWeapon(Ship& ship, Tick currentTick,
|
||||
ShipSystem& ships,
|
||||
BuildingSystem& buildings,
|
||||
void CombatSystem::resolveShipWeapon(entt::entity shipEntity, Weapon& weapon,
|
||||
const ThreatResponse& threat,
|
||||
const Position& pos, Tick currentTick,
|
||||
EntityAdmin& admin,
|
||||
std::vector<FireEvent>& out)
|
||||
{
|
||||
if (!ship.weapon || !ship.threatResponse ||
|
||||
!ship.threatResponse->currentTarget)
|
||||
if (!threat.currentTarget)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Weapon& w = *ship.weapon;
|
||||
|
||||
// Decrement cooldown toward zero.
|
||||
if (w.cooldownTicks > 0.0f)
|
||||
if (weapon.cooldownTicks > 0.0f)
|
||||
{
|
||||
w.cooldownTicks -= 1.0f;
|
||||
weapon.cooldownTicks -= 1.0f;
|
||||
}
|
||||
|
||||
if (w.cooldownTicks > 0.0f)
|
||||
if (weapon.cooldownTicks > 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const EntityId targetId = *ship.threatResponse->currentTarget;
|
||||
|
||||
const std::optional<QVector2D> tPos = targetPosition(targetId, ships, buildings);
|
||||
if (!tPos)
|
||||
{
|
||||
ship.threatResponse->currentTarget = std::nullopt;
|
||||
return;
|
||||
}
|
||||
|
||||
const float dist = (ship.position - *tPos).length();
|
||||
if (dist > w.range)
|
||||
const entt::entity targetEntity = *threat.currentTarget;
|
||||
if (!admin.isValid(targetEntity) || !admin.hasAll<Position>(targetEntity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_pendingDamage.push_back({targetId, w.damage, currentTick + kWeaponImpactDelayTicks});
|
||||
const QVector2D targetPos = admin.get<Position>(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 = ship.id;
|
||||
evt.target = targetId;
|
||||
evt.shooter = shipEntity;
|
||||
evt.target = targetEntity;
|
||||
evt.emittedAt = currentTick;
|
||||
out.push_back(evt);
|
||||
|
||||
w.cooldownTicks = static_cast<float>(kTickRateHz) / w.fireRateHz;
|
||||
weapon.cooldownTicks = static_cast<float>(kTickRateHz) / weapon.fireRateHz;
|
||||
}
|
||||
|
||||
void CombatSystem::resolveStationWeapon(Building& station, Tick currentTick,
|
||||
ShipSystem& ships,
|
||||
BuildingSystem& buildings,
|
||||
void CombatSystem::resolveStationWeapon(entt::entity stationEntity,
|
||||
StationWeapon& weapon,
|
||||
const Position& stationPos,
|
||||
const Faction& stationFaction,
|
||||
Tick currentTick,
|
||||
EntityAdmin& admin,
|
||||
std::vector<FireEvent>& 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)
|
||||
if (weapon.currentTarget)
|
||||
{
|
||||
const std::optional<QVector2D> tPos =
|
||||
targetPosition(*w.currentTarget, ships, buildings);
|
||||
if (!tPos || (stationCenter - *tPos).length() > w.range)
|
||||
const entt::entity t = *weapon.currentTarget;
|
||||
if (!admin.isValid(t) || !admin.hasAll<Position>(t))
|
||||
{
|
||||
w.currentTarget = std::nullopt;
|
||||
weapon.currentTarget = std::nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float dist = (stationPos.value - admin.get<Position>(t).value).length();
|
||||
if (dist > weapon.range)
|
||||
{
|
||||
weapon.currentTarget = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire a new target if needed.
|
||||
if (!w.currentTarget)
|
||||
// Acquire a new target if needed (nearest opposing-faction ship).
|
||||
if (!weapon.currentTarget)
|
||||
{
|
||||
w.currentTarget = acquireStationTarget(station, stationIsEnemy, ships);
|
||||
float bestDist = weapon.range;
|
||||
admin.forEach<ShipIdentity, Position, Faction>(
|
||||
[&](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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!w.currentTarget)
|
||||
if (!weapon.currentTarget)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrement cooldown.
|
||||
if (w.cooldownTicks > 0.0f)
|
||||
if (weapon.cooldownTicks > 0.0f)
|
||||
{
|
||||
w.cooldownTicks -= 1.0f;
|
||||
weapon.cooldownTicks -= 1.0f;
|
||||
}
|
||||
|
||||
if (w.cooldownTicks > 0.0f)
|
||||
if (weapon.cooldownTicks > 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const EntityId targetId = *w.currentTarget;
|
||||
const std::optional<QVector2D> tPos = targetPosition(targetId, ships, buildings);
|
||||
if (!tPos)
|
||||
const entt::entity targetEntity = *weapon.currentTarget;
|
||||
if (!admin.isValid(targetEntity) || !admin.hasAll<Position>(targetEntity))
|
||||
{
|
||||
w.currentTarget = std::nullopt;
|
||||
weapon.currentTarget = std::nullopt;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((stationCenter - *tPos).length() > w.range)
|
||||
const QVector2D targetPos = admin.get<Position>(targetEntity).value;
|
||||
if ((stationPos.value - targetPos).length() > weapon.range)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_pendingDamage.push_back({targetId, w.damage, currentTick + kWeaponImpactDelayTicks});
|
||||
m_pendingDamage.push_back({targetEntity, weapon.damage,
|
||||
currentTick + kWeaponImpactDelayTicks});
|
||||
|
||||
FireEvent evt;
|
||||
evt.shooter = station.id;
|
||||
evt.target = targetId;
|
||||
evt.shooter = stationEntity;
|
||||
evt.target = targetEntity;
|
||||
evt.emittedAt = currentTick;
|
||||
out.push_back(evt);
|
||||
|
||||
w.cooldownTicks = static_cast<float>(kTickRateHz) / w.fireRateHz;
|
||||
weapon.cooldownTicks = static_cast<float>(kTickRateHz) / weapon.fireRateHz;
|
||||
}
|
||||
|
||||
std::optional<EntityId> CombatSystem::acquireStationTarget(
|
||||
const Building& station, bool stationIsEnemy,
|
||||
const ShipSystem& ships) const
|
||||
void CombatSystem::applyPendingDamage(Tick currentTick, EntityAdmin& admin)
|
||||
{
|
||||
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<EntityId> 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();
|
||||
std::vector<PendingDamage>::iterator it = m_pendingDamage.begin();
|
||||
while (it != m_pendingDamage.end())
|
||||
{
|
||||
if (it->appliesAt <= currentTick)
|
||||
{
|
||||
if (ships.findShip(it->target))
|
||||
if (admin.isValid(it->target) && admin.hasAll<Health>(it->target))
|
||||
{
|
||||
ships.damageShip(it->target, it->amount);
|
||||
}
|
||||
else if (buildings.findBuilding(it->target))
|
||||
{
|
||||
buildings.damageBuilding(it->target, it->amount);
|
||||
admin.get<Health>(it->target).hp -= it->amount;
|
||||
}
|
||||
it = m_pendingDamage.erase(it);
|
||||
}
|
||||
@@ -215,22 +183,3 @@ void CombatSystem::applyPendingDamage(Tick currentTick,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<QVector2D> 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user