Files
dota_factory/src/lib/sim/CombatSystem.cpp

132 lines
3.4 KiB
C++

#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<FireEvent>& outFireEvents)
{
// Ship weapons.
admin.forEach<Weapon, ThreatResponse, Position, Faction>(
[&](entt::entity e, Weapon& weapon, ThreatResponse& threat, Position& pos, Faction& faction)
{
weapon.currentTarget = threat.currentTarget;
resolveWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents);
});
// Station weapons.
admin.forEach<Weapon, Position, Faction>(
[&](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<FireEvent>& 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<Position>(t))
{
weapon.currentTarget = std::nullopt;
}
else
{
const float distanceSquared = (ownPos.value - admin.get<Position>(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<ShipIdentity, Position, Faction>(
[&](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<float>(kTickRateHz) / weapon.fireRateHz;
}
void CombatSystem::applyPendingDamage(Tick currentTick, EntityAdmin& admin)
{
std::vector<PendingDamage>::iterator it = m_pendingDamage.begin();
while (it != m_pendingDamage.end())
{
if (it->appliesAt <= currentTick)
{
if (admin.isValid(it->target) && admin.hasAll<Health>(it->target))
{
admin.get<Health>(it->target).hp -= it->amount;
}
it = m_pendingDamage.erase(it);
}
else
{
++it;
}
}
}