allow one target per repair tool module

This commit is contained in:
2026-06-02 22:06:54 +02:00
parent 64f7c9dcc1
commit 090dc64bc4
3 changed files with 308 additions and 37 deletions

View File

@@ -29,6 +29,43 @@
#include "StationBodyComponent.h"
#include "ThreatResponseBehaviorComponent.h"
// ---------------------------------------------------------------------------
// Shared helpers for repair targeting
// ---------------------------------------------------------------------------
struct RepairableInfo
{
entt::entity entity;
QVector2D position;
bool isEnemy;
bool isShip;
float hp;
float maxHp;
};
static std::vector<RepairableInfo> buildRepairables(EntityAdmin& admin)
{
std::vector<RepairableInfo> repairables;
admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent, HealthComponent>(
[&repairables](entt::entity e, const ShipIdentityComponent& /*si*/,
const PositionComponent& pos, const FactionComponent& f,
const HealthComponent& h)
{
repairables.push_back({e, pos.value, f.isEnemy, true, h.hp, h.maxHp});
});
admin.forEach<StationBodyComponent, PositionComponent, FactionComponent, HealthComponent>(
[&repairables](entt::entity e, const StationBodyComponent& /*sb*/,
const PositionComponent& pos, const FactionComponent& f,
const HealthComponent& h)
{
repairables.push_back({e, pos.value, f.isEnemy, false, h.hp, h.maxHp});
});
return repairables;
}
// ---------------------------------------------------------------------------
// tickHomeReturnBehavior (priority 4)
// ---------------------------------------------------------------------------
@@ -182,33 +219,7 @@ void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSyst
void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
{
// Snapshot all entities with health for repair targeting.
struct RepairableInfo
{
entt::entity entity;
QVector2D position;
bool isEnemy;
bool isShip;
float hp;
float maxHp;
};
std::vector<RepairableInfo> repairables;
admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent, HealthComponent>(
[&repairables](entt::entity e, const ShipIdentityComponent& /*si*/,
const PositionComponent& pos, const FactionComponent& f,
const HealthComponent& h)
{
repairables.push_back({e, pos.value, f.isEnemy, true, h.hp, h.maxHp});
});
admin.forEach<StationBodyComponent, PositionComponent, FactionComponent, HealthComponent>(
[&repairables](entt::entity e, const StationBodyComponent& /*sb*/,
const PositionComponent& pos, const FactionComponent& f,
const HealthComponent& h)
{
repairables.push_back({e, pos.value, f.isEnemy, false, h.hp, h.maxHp});
});
std::vector<RepairableInfo> repairables = buildRepairables(admin);
// Snapshot enemy ships for threat detection.
struct EnemyInfo
@@ -232,8 +243,6 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
PositionComponent& pos, FactionComponent& /*faction*/,
SensorRangeComponent& sensor, MovementIntentComponent& intent)
{
const float repairRange = rb.maxRepairRange;
// Flee if enemy nearby.
bool enemyNearby = false;
for (const EnemyInfo& enemy : enemies)
@@ -318,23 +327,57 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
void AiSystem::tickRepairTools(EntityAdmin& admin)
{
const std::vector<RepairableInfo> repairables = buildRepairables(admin);
admin.forEach<RepairToolComponent, ModuleOwnerComponent>(
[&](entt::entity /*e*/, RepairToolComponent& rt, const ModuleOwnerComponent& owner)
{
if (!admin.hasAll<RepairBehaviorComponent>(owner.owner)) { return; }
const RepairBehaviorComponent& rb =
admin.get<RepairBehaviorComponent>(owner.owner);
if (!rb.currentTarget) { return; }
const PositionComponent& ownerPos =
admin.get<PositionComponent>(owner.owner);
const entt::entity target = *rb.currentTarget;
if (!admin.isValid(target) || !admin.hasAll<HealthComponent>(target)) { return; }
// Try the ship's preferred nav target first.
if (rb.currentTarget)
{
const entt::entity preferred = *rb.currentTarget;
if (admin.isValid(preferred) && admin.hasAll<HealthComponent>(preferred)
&& admin.hasAll<PositionComponent>(preferred))
{
HealthComponent& th = admin.get<HealthComponent>(preferred);
const float dist =
(admin.get<PositionComponent>(preferred).value
- ownerPos.value).length();
if (th.hp > 0.0f && th.hp < th.maxHp && dist <= rt.range)
{
rt.currentTarget = rb.currentTarget;
th.hp = std::min(th.hp + rt.ratePerTick, th.maxHp);
return;
}
}
}
const PositionComponent& ownerPos = admin.get<PositionComponent>(owner.owner);
const PositionComponent& targetPos = admin.get<PositionComponent>(target);
const float dist = (targetPos.value - ownerPos.value).length();
if (dist > rt.range) { return; }
// Preferred target unavailable; scan for nearest damaged friendly in range.
rt.currentTarget = std::nullopt;
float bestDist = rt.range;
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.value).length();
if (dist < bestDist)
{
bestDist = dist;
rt.currentTarget = r.entity;
}
}
HealthComponent& targetHealth = admin.get<HealthComponent>(target);
if (!rt.currentTarget) { return; }
HealthComponent& targetHealth =
admin.get<HealthComponent>(*rt.currentTarget);
targetHealth.hp = std::min(targetHealth.hp + rt.ratePerTick, targetHealth.maxHp);
});
}