refactor AI system
This commit is contained in:
16
src/lib/ecs/system/ai/AdvanceEvaluator.cpp
Normal file
16
src/lib/ecs/system/ai/AdvanceEvaluator.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "AdvanceEvaluator.h"
|
||||
|
||||
#include "AdvanceBehavior.h"
|
||||
#include "BehaviorScores.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void AdvanceEvaluator::evaluate(EntityAdmin& admin)
|
||||
{
|
||||
TRACE();
|
||||
admin.forEach<AdvanceBehavior>(
|
||||
[](entt::entity /*e*/, AdvanceBehavior& advance)
|
||||
{
|
||||
advance.score = BehaviorScores::kAdvance;
|
||||
});
|
||||
}
|
||||
11
src/lib/ecs/system/ai/AdvanceEvaluator.h
Normal file
11
src/lib/ecs/system/ai/AdvanceEvaluator.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
|
||||
// Baseline fallback: gives every ship a constant low score so there is always a
|
||||
// winning behavior. The actual movement direction is decided by AdvanceExecutor.
|
||||
class AdvanceEvaluator
|
||||
{
|
||||
public:
|
||||
void evaluate(EntityAdmin& admin);
|
||||
};
|
||||
30
src/lib/ecs/system/ai/AdvanceExecutor.cpp
Normal file
30
src/lib/ecs/system/ai/AdvanceExecutor.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "AdvanceExecutor.h"
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
#include "AdvanceBehavior.h"
|
||||
#include "BehaviorKind.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "FactionComponent.h"
|
||||
#include "MovementIntentComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "SelectedBehaviorComponent.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void AdvanceExecutor::execute(EntityAdmin& admin)
|
||||
{
|
||||
TRACE();
|
||||
admin.forEach<AdvanceBehavior, SelectedBehaviorComponent, PositionComponent,
|
||||
FactionComponent, MovementIntentComponent>(
|
||||
[](entt::entity /*e*/, const AdvanceBehavior& /*advance*/,
|
||||
const SelectedBehaviorComponent& selected, const PositionComponent& pos,
|
||||
const FactionComponent& faction, MovementIntentComponent& intent)
|
||||
{
|
||||
if (selected.winner != BehaviorKind::Advance) { return; }
|
||||
|
||||
const QVector2D target = faction.isEnemy
|
||||
? QVector2D(-10000.0f, pos.value.y())
|
||||
: QVector2D(pos.value.x() + 1000.0f, pos.value.y());
|
||||
intent = MovementIntentComponent{true, target};
|
||||
});
|
||||
}
|
||||
11
src/lib/ecs/system/ai/AdvanceExecutor.h
Normal file
11
src/lib/ecs/system/ai/AdvanceExecutor.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
|
||||
// Moves a ship toward the opposing side when Advance is the winning behavior:
|
||||
// player ships advance toward +x (the enemy), enemy ships toward -x (the base).
|
||||
class AdvanceExecutor
|
||||
{
|
||||
public:
|
||||
void execute(EntityAdmin& admin);
|
||||
};
|
||||
71
src/lib/ecs/system/ai/AttackEvaluator.cpp
Normal file
71
src/lib/ecs/system/ai/AttackEvaluator.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "AttackEvaluator.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
#include "AttackBehavior.h"
|
||||
#include "BehaviorScores.h"
|
||||
#include "BehaviorTargeting.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "FactionComponent.h"
|
||||
#include "HealthComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "SensorRangeComponent.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void AttackEvaluator::evaluate(EntityAdmin& admin)
|
||||
{
|
||||
TRACE();
|
||||
const std::vector<CombatantInfo> combatants = buildCombatants(admin);
|
||||
|
||||
admin.forEach<AttackBehavior, PositionComponent, FactionComponent,
|
||||
SensorRangeComponent, HealthComponent>(
|
||||
[&](entt::entity e, AttackBehavior& attack, const PositionComponent& pos,
|
||||
const FactionComponent& faction, const SensorRangeComponent& sensor,
|
||||
const HealthComponent& health)
|
||||
{
|
||||
const float range = sensor.value_tiles;
|
||||
|
||||
// Validate current target: still valid, still in range.
|
||||
bool targetValid = false;
|
||||
if (attack.currentTarget)
|
||||
{
|
||||
const entt::entity t = *attack.currentTarget;
|
||||
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
|
||||
{
|
||||
const float dist =
|
||||
(admin.get<PositionComponent>(t).value - pos.value).length();
|
||||
if (dist <= range) { targetValid = true; }
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire nearest valid target if needed.
|
||||
if (!targetValid)
|
||||
{
|
||||
attack.currentTarget = std::nullopt;
|
||||
float bestDist = range;
|
||||
for (const CombatantInfo& c : combatants)
|
||||
{
|
||||
if (c.entity == e) { continue; }
|
||||
const bool isValidTarget =
|
||||
faction.isEnemy ? !c.isEnemy : c.isEnemy;
|
||||
if (!isValidTarget) { continue; }
|
||||
|
||||
const float dist = (c.position - pos.value).length();
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
attack.currentTarget = c.entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool healthy =
|
||||
(health.maxHp > 0.0f)
|
||||
&& (health.hp / health.maxHp >= BehaviorScores::kLowHpFraction);
|
||||
attack.score = (healthy && attack.currentTarget)
|
||||
? BehaviorScores::kAttack
|
||||
: BehaviorScores::kInactive;
|
||||
});
|
||||
}
|
||||
11
src/lib/ecs/system/ai/AttackEvaluator.h
Normal file
11
src/lib/ecs/system/ai/AttackEvaluator.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
|
||||
// Acquires/validates a combat target for ships with weapons. Scores high only
|
||||
// when the ship's health is not low and a valid target is within sensor range.
|
||||
class AttackEvaluator
|
||||
{
|
||||
public:
|
||||
void evaluate(EntityAdmin& admin);
|
||||
};
|
||||
61
src/lib/ecs/system/ai/AttackExecutor.cpp
Normal file
61
src/lib/ecs/system/ai/AttackExecutor.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "AttackExecutor.h"
|
||||
|
||||
#include "AttackBehavior.h"
|
||||
#include "BehaviorKind.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "ModuleOwnerComponent.h"
|
||||
#include "MovementIntentComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "SelectedBehaviorComponent.h"
|
||||
#include "tracing.h"
|
||||
#include "WeaponComponent.h"
|
||||
|
||||
void AttackExecutor::execute(EntityAdmin& admin)
|
||||
{
|
||||
TRACE();
|
||||
|
||||
// Ships: move toward the behavior target.
|
||||
admin.forEach<AttackBehavior, SelectedBehaviorComponent, PositionComponent,
|
||||
MovementIntentComponent>(
|
||||
[&](entt::entity /*e*/, const AttackBehavior& attack,
|
||||
const SelectedBehaviorComponent& selected, const PositionComponent& pos,
|
||||
MovementIntentComponent& intent)
|
||||
{
|
||||
if (selected.winner != BehaviorKind::Attack) { return; }
|
||||
if (!attack.currentTarget) { return; }
|
||||
|
||||
const entt::entity t = *attack.currentTarget;
|
||||
QVector2D dest = pos.value;
|
||||
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
|
||||
{
|
||||
dest = admin.get<PositionComponent>(t).value;
|
||||
}
|
||||
intent = MovementIntentComponent{true, dest};
|
||||
});
|
||||
|
||||
// Weapons: assign the behavior target only if it is within this weapon's range.
|
||||
admin.forEach<WeaponComponent, ModuleOwnerComponent>(
|
||||
[&](entt::entity /*we*/, WeaponComponent& weapon, const ModuleOwnerComponent& owner)
|
||||
{
|
||||
if (!admin.hasAll<AttackBehavior, SelectedBehaviorComponent>(owner.owner))
|
||||
{
|
||||
return;
|
||||
}
|
||||
const SelectedBehaviorComponent& selected =
|
||||
admin.get<SelectedBehaviorComponent>(owner.owner);
|
||||
if (selected.winner != BehaviorKind::Attack) { return; }
|
||||
|
||||
const AttackBehavior& attack = admin.get<AttackBehavior>(owner.owner);
|
||||
if (!attack.currentTarget) { return; }
|
||||
|
||||
const entt::entity t = *attack.currentTarget;
|
||||
if (!admin.isValid(t) || !admin.hasAll<PositionComponent>(t)) { return; }
|
||||
|
||||
const QVector2D ownerPos = admin.get<PositionComponent>(owner.owner).value;
|
||||
const float dist = (admin.get<PositionComponent>(t).value - ownerPos).length();
|
||||
if (dist <= weapon.range_tiles)
|
||||
{
|
||||
weapon.currentTarget = t;
|
||||
}
|
||||
});
|
||||
}
|
||||
12
src/lib/ecs/system/ai/AttackExecutor.h
Normal file
12
src/lib/ecs/system/ai/AttackExecutor.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
|
||||
// When Attack wins, moves the ship toward its target and assigns that target to
|
||||
// each weapon that has it in range. Weapons whose range excludes the target are
|
||||
// left untouched so CombatSystem can keep/acquire a closer target (no thrash).
|
||||
class AttackExecutor
|
||||
{
|
||||
public:
|
||||
void execute(EntityAdmin& admin);
|
||||
};
|
||||
81
src/lib/ecs/system/ai/BehaviorTargeting.cpp
Normal file
81
src/lib/ecs/system/ai/BehaviorTargeting.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "BehaviorTargeting.h"
|
||||
|
||||
#include "EntityAdmin.h"
|
||||
#include "FactionComponent.h"
|
||||
#include "HealthComponent.h"
|
||||
#include "HqProxyComponent.h"
|
||||
#include "ModuleOwnerComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "SalvageCargoComponent.h"
|
||||
#include "ShipIdentityComponent.h"
|
||||
#include "StationBodyComponent.h"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::vector<CombatantInfo> buildCombatants(EntityAdmin& admin)
|
||||
{
|
||||
std::vector<CombatantInfo> combatants;
|
||||
|
||||
admin.forEach<PositionComponent, FactionComponent, ShipIdentityComponent>(
|
||||
[&combatants](entt::entity e, const PositionComponent& pos,
|
||||
const FactionComponent& f, const ShipIdentityComponent& /*si*/)
|
||||
{
|
||||
combatants.push_back({e, pos.value, f.isEnemy, false});
|
||||
});
|
||||
|
||||
admin.forEach<PositionComponent, FactionComponent, StationBodyComponent>(
|
||||
[&combatants](entt::entity e, const PositionComponent& pos,
|
||||
const FactionComponent& f, const StationBodyComponent& /*sb*/)
|
||||
{
|
||||
combatants.push_back({e, pos.value, f.isEnemy, true});
|
||||
});
|
||||
|
||||
admin.forEach<PositionComponent, FactionComponent, HqProxyComponent>(
|
||||
[&combatants](entt::entity e, const PositionComponent& pos,
|
||||
const FactionComponent& f, const HqProxyComponent& /*hq*/)
|
||||
{
|
||||
combatants.push_back({e, pos.value, f.isEnemy, true});
|
||||
});
|
||||
|
||||
return combatants;
|
||||
}
|
||||
|
||||
std::unordered_map<entt::entity, CargoState> buildCargoByShip(EntityAdmin& admin)
|
||||
{
|
||||
std::unordered_map<entt::entity, CargoState> cargoByShip;
|
||||
admin.forEach<SalvageCargoComponent, ModuleOwnerComponent>(
|
||||
[&cargoByShip](entt::entity /*ce*/, const SalvageCargoComponent& c,
|
||||
const ModuleOwnerComponent& o)
|
||||
{
|
||||
CargoState& agg = cargoByShip[o.owner];
|
||||
agg.current += c.current;
|
||||
agg.capacity += c.capacity;
|
||||
});
|
||||
return cargoByShip;
|
||||
}
|
||||
|
||||
bool isCargoFull(const CargoState& cargo)
|
||||
{
|
||||
return cargo.capacity > 0 && cargo.current >= cargo.capacity;
|
||||
}
|
||||
49
src/lib/ecs/system/ai/BehaviorTargeting.h
Normal file
49
src/lib/ecs/system/ai/BehaviorTargeting.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
class EntityAdmin;
|
||||
|
||||
// Shared, per-call target snapshots used by behavior evaluators and the repair
|
||||
// system. Each caller builds its own snapshot (no cross-system caching).
|
||||
|
||||
struct RepairableInfo
|
||||
{
|
||||
entt::entity entity;
|
||||
QVector2D position;
|
||||
bool isEnemy;
|
||||
bool isShip;
|
||||
float hp;
|
||||
float maxHp;
|
||||
};
|
||||
|
||||
struct CombatantInfo
|
||||
{
|
||||
entt::entity entity;
|
||||
QVector2D position;
|
||||
bool isEnemy;
|
||||
bool isStation;
|
||||
};
|
||||
|
||||
struct CargoState
|
||||
{
|
||||
int current = 0;
|
||||
int capacity = 0;
|
||||
};
|
||||
|
||||
// All ships and stations with health — candidates for repair targeting.
|
||||
std::vector<RepairableInfo> buildRepairables(EntityAdmin& admin);
|
||||
|
||||
// All ships, stations, and the HQ proxy — candidates for attack targeting.
|
||||
std::vector<CombatantInfo> buildCombatants(EntityAdmin& admin);
|
||||
|
||||
// Aggregated salvage cargo per owning ship, summed across its salvage modules.
|
||||
std::unordered_map<entt::entity, CargoState> buildCargoByShip(EntityAdmin& admin);
|
||||
|
||||
// True when the ship's aggregated cargo is at capacity (and it has any capacity).
|
||||
bool isCargoFull(const CargoState& cargo);
|
||||
43
src/lib/ecs/system/ai/DeliverScrapEvaluator.cpp
Normal file
43
src/lib/ecs/system/ai/DeliverScrapEvaluator.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "DeliverScrapEvaluator.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "BehaviorScores.h"
|
||||
#include "BehaviorTargeting.h"
|
||||
#include "Building.h"
|
||||
#include "BuildingSystem.h"
|
||||
#include "BuildingType.h"
|
||||
#include "DeliverScrapBehavior.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void DeliverScrapEvaluator::evaluate(EntityAdmin& admin, const BuildingSystem& buildings)
|
||||
{
|
||||
TRACE();
|
||||
const std::unordered_map<entt::entity, CargoState> cargoByShip = buildCargoByShip(admin);
|
||||
|
||||
admin.forEach<DeliverScrapBehavior, PositionComponent>(
|
||||
[&](entt::entity e, DeliverScrapBehavior& deliver, const PositionComponent& pos)
|
||||
{
|
||||
const std::unordered_map<entt::entity, CargoState>::const_iterator it =
|
||||
cargoByShip.find(e);
|
||||
const bool cargoFull = (it != cargoByShip.end()) && isCargoFull(it->second);
|
||||
|
||||
if (!cargoFull)
|
||||
{
|
||||
deliver.score = BehaviorScores::kInactive;
|
||||
return;
|
||||
}
|
||||
|
||||
// Assign nearest SalvageBay if not yet assigned.
|
||||
if (deliver.deliveryBay == kInvalidBuildingId)
|
||||
{
|
||||
const Building* bay =
|
||||
buildings.findNearestBuilding(pos.value, BuildingType::SalvageBay);
|
||||
if (bay) { deliver.deliveryBay = bay->id; }
|
||||
}
|
||||
|
||||
deliver.score = BehaviorScores::kDeliver;
|
||||
});
|
||||
}
|
||||
12
src/lib/ecs/system/ai/DeliverScrapEvaluator.h
Normal file
12
src/lib/ecs/system/ai/DeliverScrapEvaluator.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
class BuildingSystem;
|
||||
|
||||
// Scores high only when the ship's cargo is full, and assigns the nearest
|
||||
// SalvageBay as the delivery destination.
|
||||
class DeliverScrapEvaluator
|
||||
{
|
||||
public:
|
||||
void evaluate(EntityAdmin& admin, const BuildingSystem& buildings);
|
||||
};
|
||||
38
src/lib/ecs/system/ai/DeliverScrapExecutor.cpp
Normal file
38
src/lib/ecs/system/ai/DeliverScrapExecutor.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "DeliverScrapExecutor.h"
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
#include "BehaviorKind.h"
|
||||
#include "Building.h"
|
||||
#include "BuildingSystem.h"
|
||||
#include "DeliverScrapBehavior.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "MovementIntentComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "SelectedBehaviorComponent.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void DeliverScrapExecutor::execute(EntityAdmin& admin, const BuildingSystem& buildings)
|
||||
{
|
||||
TRACE();
|
||||
admin.forEach<DeliverScrapBehavior, SelectedBehaviorComponent, PositionComponent,
|
||||
MovementIntentComponent>(
|
||||
[&](entt::entity /*e*/, const DeliverScrapBehavior& deliver,
|
||||
const SelectedBehaviorComponent& selected, const PositionComponent& pos,
|
||||
MovementIntentComponent& intent)
|
||||
{
|
||||
if (selected.winner != BehaviorKind::DeliverScrap) { return; }
|
||||
|
||||
QVector2D dest = pos.value;
|
||||
if (deliver.deliveryBay != kInvalidBuildingId)
|
||||
{
|
||||
const Building* bay = buildings.findBuilding(deliver.deliveryBay);
|
||||
if (bay)
|
||||
{
|
||||
dest = QVector2D(bay->anchor.x() + bay->footprint.width() / 2.0f,
|
||||
bay->anchor.y() + bay->footprint.height() / 2.0f);
|
||||
}
|
||||
}
|
||||
intent = MovementIntentComponent{true, dest};
|
||||
});
|
||||
}
|
||||
12
src/lib/ecs/system/ai/DeliverScrapExecutor.h
Normal file
12
src/lib/ecs/system/ai/DeliverScrapExecutor.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
class BuildingSystem;
|
||||
|
||||
// Moves a ship toward its delivery bay when DeliverScrap is the winning
|
||||
// behavior. Never decrements cargo — SalvagerSystem performs the delivery.
|
||||
class DeliverScrapExecutor
|
||||
{
|
||||
public:
|
||||
void execute(EntityAdmin& admin, const BuildingSystem& buildings);
|
||||
};
|
||||
16
src/lib/ecs/system/ai/RallyEvaluator.cpp
Normal file
16
src/lib/ecs/system/ai/RallyEvaluator.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "RallyEvaluator.h"
|
||||
|
||||
#include "BehaviorScores.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "RallyBehavior.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void RallyEvaluator::evaluate(EntityAdmin& admin)
|
||||
{
|
||||
TRACE();
|
||||
admin.forEach<RallyBehavior>(
|
||||
[](entt::entity /*e*/, RallyBehavior& rally)
|
||||
{
|
||||
rally.score = BehaviorScores::kRally;
|
||||
});
|
||||
}
|
||||
12
src/lib/ecs/system/ai/RallyEvaluator.h
Normal file
12
src/lib/ecs/system/ai/RallyEvaluator.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
|
||||
// Scores the rally behavior so player combat ships gather at the rally point
|
||||
// until an enemy appears (Attack outscores it) or the departure timer removes
|
||||
// the RallyBehavior component.
|
||||
class RallyEvaluator
|
||||
{
|
||||
public:
|
||||
void evaluate(EntityAdmin& admin);
|
||||
};
|
||||
20
src/lib/ecs/system/ai/RallyExecutor.cpp
Normal file
20
src/lib/ecs/system/ai/RallyExecutor.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "RallyExecutor.h"
|
||||
|
||||
#include "BehaviorKind.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "MovementIntentComponent.h"
|
||||
#include "RallyBehavior.h"
|
||||
#include "SelectedBehaviorComponent.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void RallyExecutor::execute(EntityAdmin& admin)
|
||||
{
|
||||
TRACE();
|
||||
admin.forEach<RallyBehavior, SelectedBehaviorComponent, MovementIntentComponent>(
|
||||
[](entt::entity /*e*/, const RallyBehavior& rally,
|
||||
const SelectedBehaviorComponent& selected, MovementIntentComponent& intent)
|
||||
{
|
||||
if (selected.winner != BehaviorKind::Rally) { return; }
|
||||
intent = MovementIntentComponent{true, rally.rallyPoint};
|
||||
});
|
||||
}
|
||||
10
src/lib/ecs/system/ai/RallyExecutor.h
Normal file
10
src/lib/ecs/system/ai/RallyExecutor.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
|
||||
// Moves a ship to its rally point when Rally is the winning behavior.
|
||||
class RallyExecutor
|
||||
{
|
||||
public:
|
||||
void execute(EntityAdmin& admin);
|
||||
};
|
||||
58
src/lib/ecs/system/ai/RepairEvaluator.cpp
Normal file
58
src/lib/ecs/system/ai/RepairEvaluator.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "RepairEvaluator.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "BehaviorScores.h"
|
||||
#include "BehaviorTargeting.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "HealthComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "RepairBehavior.h"
|
||||
#include "SensorRangeComponent.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void RepairEvaluator::evaluate(EntityAdmin& admin)
|
||||
{
|
||||
TRACE();
|
||||
const std::vector<RepairableInfo> repairables = buildRepairables(admin);
|
||||
|
||||
admin.forEach<RepairBehavior, PositionComponent, SensorRangeComponent>(
|
||||
[&](entt::entity e, RepairBehavior& repair, const PositionComponent& pos,
|
||||
const SensorRangeComponent& sensor)
|
||||
{
|
||||
// Validate current target: alive and still damaged.
|
||||
bool targetValid = false;
|
||||
if (repair.currentTarget)
|
||||
{
|
||||
const entt::entity t = *repair.currentTarget;
|
||||
if (admin.isValid(t) && admin.hasAll<HealthComponent>(t))
|
||||
{
|
||||
const HealthComponent& th = admin.get<HealthComponent>(t);
|
||||
if (th.hp > 0.0f && th.hp < th.maxHp) { targetValid = true; }
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire nearest damaged friendly within sensor range.
|
||||
if (!targetValid)
|
||||
{
|
||||
repair.currentTarget = std::nullopt;
|
||||
float bestDist = sensor.value_tiles;
|
||||
for (const RepairableInfo& r : repairables)
|
||||
{
|
||||
if (r.entity == e) { continue; }
|
||||
if (r.isEnemy) { continue; }
|
||||
if (r.hp <= 0.0f || r.hp >= r.maxHp) { continue; }
|
||||
const float dist = (r.position - pos.value).length();
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
repair.currentTarget = r.entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repair.score = repair.currentTarget
|
||||
? BehaviorScores::kRepair
|
||||
: BehaviorScores::kInactive;
|
||||
});
|
||||
}
|
||||
11
src/lib/ecs/system/ai/RepairEvaluator.h
Normal file
11
src/lib/ecs/system/ai/RepairEvaluator.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
|
||||
// Picks the nearest damaged friendly within sensor range as the repair target.
|
||||
// Scores high when such a target exists.
|
||||
class RepairEvaluator
|
||||
{
|
||||
public:
|
||||
void evaluate(EntityAdmin& admin);
|
||||
};
|
||||
61
src/lib/ecs/system/ai/RepairExecutor.cpp
Normal file
61
src/lib/ecs/system/ai/RepairExecutor.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "RepairExecutor.h"
|
||||
|
||||
#include "BehaviorKind.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "ModuleOwnerComponent.h"
|
||||
#include "MovementIntentComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "RepairBehavior.h"
|
||||
#include "RepairToolComponent.h"
|
||||
#include "SelectedBehaviorComponent.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void RepairExecutor::execute(EntityAdmin& admin)
|
||||
{
|
||||
TRACE();
|
||||
|
||||
// Ships: move toward the repair target.
|
||||
admin.forEach<RepairBehavior, SelectedBehaviorComponent, PositionComponent,
|
||||
MovementIntentComponent>(
|
||||
[&](entt::entity /*e*/, const RepairBehavior& repair,
|
||||
const SelectedBehaviorComponent& selected, const PositionComponent& pos,
|
||||
MovementIntentComponent& intent)
|
||||
{
|
||||
if (selected.winner != BehaviorKind::Repair) { return; }
|
||||
if (!repair.currentTarget) { return; }
|
||||
|
||||
const entt::entity t = *repair.currentTarget;
|
||||
QVector2D dest = pos.value;
|
||||
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
|
||||
{
|
||||
dest = admin.get<PositionComponent>(t).value;
|
||||
}
|
||||
intent = MovementIntentComponent{true, dest};
|
||||
});
|
||||
|
||||
// Repair tools: prefer the behavior target if it is within tool range.
|
||||
admin.forEach<RepairToolComponent, ModuleOwnerComponent>(
|
||||
[&](entt::entity /*re*/, RepairToolComponent& tool, const ModuleOwnerComponent& owner)
|
||||
{
|
||||
if (!admin.hasAll<RepairBehavior, SelectedBehaviorComponent>(owner.owner))
|
||||
{
|
||||
return;
|
||||
}
|
||||
const SelectedBehaviorComponent& selected =
|
||||
admin.get<SelectedBehaviorComponent>(owner.owner);
|
||||
if (selected.winner != BehaviorKind::Repair) { return; }
|
||||
|
||||
const RepairBehavior& repair = admin.get<RepairBehavior>(owner.owner);
|
||||
if (!repair.currentTarget) { return; }
|
||||
|
||||
const entt::entity t = *repair.currentTarget;
|
||||
if (!admin.isValid(t) || !admin.hasAll<PositionComponent>(t)) { return; }
|
||||
|
||||
const QVector2D ownerPos = admin.get<PositionComponent>(owner.owner).value;
|
||||
const float dist = (admin.get<PositionComponent>(t).value - ownerPos).length();
|
||||
if (dist <= tool.range_tiles)
|
||||
{
|
||||
tool.currentTarget = t;
|
||||
}
|
||||
});
|
||||
}
|
||||
12
src/lib/ecs/system/ai/RepairExecutor.h
Normal file
12
src/lib/ecs/system/ai/RepairExecutor.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
|
||||
// When Repair wins, moves the ship toward its target and assigns that target to
|
||||
// each repair tool that has it in range. RepairSystem applies the healing and
|
||||
// does fallback acquisition for tools whose preferred target is out of range.
|
||||
class RepairExecutor
|
||||
{
|
||||
public:
|
||||
void execute(EntityAdmin& admin);
|
||||
};
|
||||
56
src/lib/ecs/system/ai/RetreatEvaluator.cpp
Normal file
56
src/lib/ecs/system/ai/RetreatEvaluator.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "RetreatEvaluator.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
#include "AttackBehavior.h"
|
||||
#include "BehaviorScores.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "FactionComponent.h"
|
||||
#include "HealthComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "RetreatBehavior.h"
|
||||
#include "SensorRangeComponent.h"
|
||||
#include "ShipIdentityComponent.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void RetreatEvaluator::evaluate(EntityAdmin& admin)
|
||||
{
|
||||
TRACE();
|
||||
|
||||
// Snapshot enemy ship positions for threat detection.
|
||||
std::vector<QVector2D> enemyShips;
|
||||
admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent>(
|
||||
[&enemyShips](entt::entity /*e*/, const ShipIdentityComponent& /*si*/,
|
||||
const PositionComponent& pos, const FactionComponent& f)
|
||||
{
|
||||
if (f.isEnemy) { enemyShips.push_back(pos.value); }
|
||||
});
|
||||
|
||||
admin.forEach<RetreatBehavior, PositionComponent, HealthComponent, SensorRangeComponent>(
|
||||
[&](entt::entity e, RetreatBehavior& retreat, const PositionComponent& pos,
|
||||
const HealthComponent& health, const SensorRangeComponent& sensor)
|
||||
{
|
||||
const bool lowHp = (health.maxHp > 0.0f)
|
||||
&& (health.hp / health.maxHp < retreat.retreatHpFraction);
|
||||
|
||||
bool threatened = false;
|
||||
const bool hasWeapons = admin.hasAll<AttackBehavior>(e);
|
||||
if (!hasWeapons)
|
||||
{
|
||||
for (const QVector2D& enemy : enemyShips)
|
||||
{
|
||||
if ((enemy - pos.value).length() <= sensor.value_tiles)
|
||||
{
|
||||
threatened = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retreat.score = (lowHp || threatened)
|
||||
? BehaviorScores::kRetreat
|
||||
: BehaviorScores::kInactive;
|
||||
});
|
||||
}
|
||||
12
src/lib/ecs/system/ai/RetreatEvaluator.h
Normal file
12
src/lib/ecs/system/ai/RetreatEvaluator.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
|
||||
// Scores high (above all task behaviors) when the ship's health is below its
|
||||
// retreat threshold, or when an enemy ship is within sensor range and the ship
|
||||
// has no weapons to fight back with.
|
||||
class RetreatEvaluator
|
||||
{
|
||||
public:
|
||||
void evaluate(EntityAdmin& admin);
|
||||
};
|
||||
20
src/lib/ecs/system/ai/RetreatExecutor.cpp
Normal file
20
src/lib/ecs/system/ai/RetreatExecutor.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "RetreatExecutor.h"
|
||||
|
||||
#include "BehaviorKind.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "MovementIntentComponent.h"
|
||||
#include "RetreatBehavior.h"
|
||||
#include "SelectedBehaviorComponent.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void RetreatExecutor::execute(EntityAdmin& admin)
|
||||
{
|
||||
TRACE();
|
||||
admin.forEach<RetreatBehavior, SelectedBehaviorComponent, MovementIntentComponent>(
|
||||
[](entt::entity /*e*/, const RetreatBehavior& retreat,
|
||||
const SelectedBehaviorComponent& selected, MovementIntentComponent& intent)
|
||||
{
|
||||
if (selected.winner != BehaviorKind::Retreat) { return; }
|
||||
intent = MovementIntentComponent{true, retreat.retreatPoint};
|
||||
});
|
||||
}
|
||||
10
src/lib/ecs/system/ai/RetreatExecutor.h
Normal file
10
src/lib/ecs/system/ai/RetreatExecutor.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
|
||||
// Moves a ship to its retreat point (the rally point) when Retreat wins.
|
||||
class RetreatExecutor
|
||||
{
|
||||
public:
|
||||
void execute(EntityAdmin& admin);
|
||||
};
|
||||
55
src/lib/ecs/system/ai/SalvageScrapEvaluator.cpp
Normal file
55
src/lib/ecs/system/ai/SalvageScrapEvaluator.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "SalvageScrapEvaluator.h"
|
||||
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
#include "BehaviorScores.h"
|
||||
#include "BehaviorTargeting.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "SalvageScrapBehavior.h"
|
||||
#include "ScrapSystem.h"
|
||||
#include "SensorRangeComponent.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void SalvageScrapEvaluator::evaluate(EntityAdmin& admin, const ScrapSystem& scraps)
|
||||
{
|
||||
TRACE();
|
||||
const std::unordered_map<entt::entity, CargoState> cargoByShip = buildCargoByShip(admin);
|
||||
const std::vector<ScrapInfo> allScrap = scraps.allScrapInfo();
|
||||
|
||||
admin.forEach<SalvageScrapBehavior, PositionComponent, SensorRangeComponent>(
|
||||
[&](entt::entity e, SalvageScrapBehavior& salvage, const PositionComponent& pos,
|
||||
const SensorRangeComponent& sensor)
|
||||
{
|
||||
const std::unordered_map<entt::entity, CargoState>::const_iterator it =
|
||||
cargoByShip.find(e);
|
||||
const bool cargoFull = (it != cargoByShip.end()) && isCargoFull(it->second);
|
||||
|
||||
if (cargoFull)
|
||||
{
|
||||
salvage.scrapTarget = std::nullopt;
|
||||
salvage.score = BehaviorScores::kInactive;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find nearest scrap within sensor range.
|
||||
float bestDist = sensor.value_tiles;
|
||||
std::optional<QVector2D> bestPos;
|
||||
for (const ScrapInfo& si : allScrap)
|
||||
{
|
||||
const float dist = (si.position - pos.value).length();
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
bestPos = si.position;
|
||||
}
|
||||
}
|
||||
|
||||
salvage.scrapTarget = bestPos;
|
||||
salvage.score = bestPos ? BehaviorScores::kSalvage : BehaviorScores::kInactive;
|
||||
});
|
||||
}
|
||||
13
src/lib/ecs/system/ai/SalvageScrapEvaluator.h
Normal file
13
src/lib/ecs/system/ai/SalvageScrapEvaluator.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
class ScrapSystem;
|
||||
|
||||
// When cargo is not full, finds the nearest scrap within sensor range and sets
|
||||
// it as the target, scoring high. Scores inactive when cargo is full or no scrap
|
||||
// is in range (Advance then handles roaming).
|
||||
class SalvageScrapEvaluator
|
||||
{
|
||||
public:
|
||||
void evaluate(EntityAdmin& admin, const ScrapSystem& scraps);
|
||||
};
|
||||
21
src/lib/ecs/system/ai/SalvageScrapExecutor.cpp
Normal file
21
src/lib/ecs/system/ai/SalvageScrapExecutor.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "SalvageScrapExecutor.h"
|
||||
|
||||
#include "BehaviorKind.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "MovementIntentComponent.h"
|
||||
#include "SalvageScrapBehavior.h"
|
||||
#include "SelectedBehaviorComponent.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void SalvageScrapExecutor::execute(EntityAdmin& admin)
|
||||
{
|
||||
TRACE();
|
||||
admin.forEach<SalvageScrapBehavior, SelectedBehaviorComponent, MovementIntentComponent>(
|
||||
[](entt::entity /*e*/, const SalvageScrapBehavior& salvage,
|
||||
const SelectedBehaviorComponent& selected, MovementIntentComponent& intent)
|
||||
{
|
||||
if (selected.winner != BehaviorKind::SalvageScrap) { return; }
|
||||
if (!salvage.scrapTarget) { return; }
|
||||
intent = MovementIntentComponent{true, *salvage.scrapTarget};
|
||||
});
|
||||
}
|
||||
10
src/lib/ecs/system/ai/SalvageScrapExecutor.h
Normal file
10
src/lib/ecs/system/ai/SalvageScrapExecutor.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
class EntityAdmin;
|
||||
|
||||
// Moves a ship toward its scrap target when SalvageScrap is the winning behavior.
|
||||
class SalvageScrapExecutor
|
||||
{
|
||||
public:
|
||||
void execute(EntityAdmin& admin);
|
||||
};
|
||||
Reference in New Issue
Block a user