110 lines
3.9 KiB
C++
110 lines
3.9 KiB
C++
#include "RepairSystem.h"
|
|
|
|
#include <algorithm>
|
|
#include <optional>
|
|
#include <vector>
|
|
|
|
#include <QVector2D>
|
|
|
|
#include "BehaviorTargeting.h"
|
|
#include "EntityAdmin.h"
|
|
#include "HealthComponent.h"
|
|
#include "ModuleOwnerComponent.h"
|
|
#include "PositionComponent.h"
|
|
#include "RepairToolComponent.h"
|
|
#include "tracing.h"
|
|
|
|
RepairSystem::RepairSystem(EntityAdmin& admin)
|
|
: m_admin(admin)
|
|
{
|
|
}
|
|
|
|
void RepairSystem::tick(Tick currentTick, std::vector<BeamFiredEvent>& outBeamFiredEvents)
|
|
{
|
|
TRACE();
|
|
// Apply heals whose mid-beam delay has elapsed (cycles started on prior ticks).
|
|
applyPendingHeals(currentTick);
|
|
|
|
const std::vector<RepairableInfo> repairables = buildRepairables(m_admin);
|
|
|
|
m_admin.forEach<RepairToolComponent, ModuleOwnerComponent>(
|
|
[&](entt::entity /*re*/, RepairToolComponent& tool, const ModuleOwnerComponent& owner)
|
|
{
|
|
if (tool.cooldownTicksRemaining > 0) { --tool.cooldownTicksRemaining; }
|
|
if (tool.cooldownTicksRemaining > 0) { return; }
|
|
if (tool.repairIntervalTicks <= 0) { return; }
|
|
if (!m_admin.hasAll<PositionComponent>(owner.owner)) { return; }
|
|
const QVector2D ownerPos = m_admin.get<PositionComponent>(owner.owner).value;
|
|
|
|
// Choose a target: honour the executor-set target if it is still valid
|
|
// and in range, else fall back to the nearest damaged friendly in range.
|
|
std::optional<entt::entity> target;
|
|
if (tool.currentTarget)
|
|
{
|
|
const entt::entity t = *tool.currentTarget;
|
|
if (m_admin.isValid(t) && m_admin.hasAll<HealthComponent, PositionComponent>(t))
|
|
{
|
|
const HealthComponent& th = m_admin.get<HealthComponent>(t);
|
|
const float dist =
|
|
(m_admin.get<PositionComponent>(t).value - ownerPos).length();
|
|
if (th.hp > 0.0f && th.hp < th.maxHp && dist <= tool.range_tiles)
|
|
{
|
|
target = t;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!target)
|
|
{
|
|
tool.currentTarget = std::nullopt;
|
|
float bestDist = tool.range_tiles;
|
|
for (const RepairableInfo& r : repairables)
|
|
{
|
|
if (r.isEnemy) { continue; }
|
|
if (r.hp <= 0.0f || r.hp >= r.maxHp) { continue; }
|
|
const float dist = (r.position - ownerPos).length();
|
|
if (dist < bestDist)
|
|
{
|
|
bestDist = dist;
|
|
tool.currentTarget = r.entity;
|
|
}
|
|
}
|
|
target = tool.currentTarget;
|
|
}
|
|
|
|
if (!target) { return; }
|
|
|
|
// Start a repair cycle: emit the beam now, apply the heal mid-beam, and
|
|
// begin the cooldown at cycle start (not at effect application).
|
|
outBeamFiredEvents.push_back(
|
|
BeamFiredEvent{BeamKind::Repair, owner.owner, *target, currentTick});
|
|
m_pendingHeals.push_back({*target, tool.repairAmountHp,
|
|
currentTick + kBeamImpactDelayTicks});
|
|
tool.cooldownTicksRemaining = tool.repairIntervalTicks;
|
|
});
|
|
}
|
|
|
|
void RepairSystem::applyPendingHeals(Tick currentTick)
|
|
{
|
|
std::vector<PendingHeal>::iterator it = m_pendingHeals.begin();
|
|
while (it != m_pendingHeals.end())
|
|
{
|
|
if (it->appliesAt <= currentTick)
|
|
{
|
|
if (m_admin.isValid(it->target) && m_admin.hasAll<HealthComponent>(it->target))
|
|
{
|
|
HealthComponent& h = m_admin.get<HealthComponent>(it->target);
|
|
if (h.hp > 0.0f && h.hp < h.maxHp)
|
|
{
|
|
h.hp = std::min(h.hp + it->amountHp, h.maxHp);
|
|
}
|
|
}
|
|
it = m_pendingHeals.erase(it);
|
|
}
|
|
else
|
|
{
|
|
++it;
|
|
}
|
|
}
|
|
}
|