rename behavior components
This commit is contained in:
@@ -240,10 +240,10 @@ void ArenaSimulation::tick()
|
|||||||
{
|
{
|
||||||
// Ship behavior systems (tick step 7).
|
// Ship behavior systems (tick step 7).
|
||||||
m_shipSystem->clearMovementIntents();
|
m_shipSystem->clearMovementIntents();
|
||||||
m_aiSystem->tickHomeReturn(m_admin);
|
m_aiSystem->tickHomeReturnBehavior(m_admin);
|
||||||
m_aiSystem->tickThreatResponse(m_admin, *m_buildingSystem);
|
m_aiSystem->tickThreatResponseBehavior(m_admin, *m_buildingSystem);
|
||||||
m_aiSystem->tickRepairBehavior(m_admin, *m_buildingSystem);
|
m_aiSystem->tickRepairBehavior(m_admin, *m_buildingSystem);
|
||||||
m_aiSystem->tickScrapCollector(m_admin, *m_scrapSystem, *m_buildingSystem);
|
m_aiSystem->tickSalvageBehavior(m_admin, *m_scrapSystem, *m_buildingSystem);
|
||||||
|
|
||||||
// Combat resolution (tick step 8).
|
// Combat resolution (tick step 8).
|
||||||
std::vector<FireEvent> fireEvents;
|
std::vector<FireEvent> fireEvents;
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ struct ShipIdentity
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Ship optional components (hardware + behavior, in Ship.h)
|
// Ship optional components (hardware + behavior, in Ship.h)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Weapon, SalvageCargo, RepairTool, ThreatResponse, ScrapCollector,
|
// Weapon, SalvageCargo, RepairTool, ThreatResponseBehavior, SalvageBehavior,
|
||||||
// RepairBehavior, HomeReturn, RallyBehavior remain defined in Ship.h.
|
// RepairBehavior, HomeReturnBehavior, RallyBehavior remain defined in Ship.h.
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Station components
|
// Station components
|
||||||
|
|||||||
@@ -16,29 +16,29 @@
|
|||||||
#include "Ship.h"
|
#include "Ship.h"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// tickHomeReturn (priority 4)
|
// tickHomeReturnBehavior (priority 4)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
void AiSystem::tickHomeReturn(EntityAdmin& admin)
|
void AiSystem::tickHomeReturnBehavior(EntityAdmin& admin)
|
||||||
{
|
{
|
||||||
admin.forEach<HomeReturn, Health, MovementIntent>(
|
admin.forEach<HomeReturnBehavior, Health, MovementIntent>(
|
||||||
[](entt::entity /*e*/, const HomeReturn& hr, const Health& h, MovementIntent& intent)
|
[](entt::entity /*e*/, const HomeReturnBehavior& homeReturnBehavior, const Health& h, MovementIntent& intent)
|
||||||
{
|
{
|
||||||
if (h.hp / h.maxHp < hr.retreatHpFraction)
|
if (h.hp / h.maxHp < homeReturnBehavior.retreatHpFraction)
|
||||||
{
|
{
|
||||||
if (4 > intent.priority)
|
if (4 > intent.priority)
|
||||||
{
|
{
|
||||||
intent = MovementIntent{4, hr.homePos};
|
intent = MovementIntent{4, homeReturnBehavior.homePos};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// tickThreatResponse (priority 3)
|
// tickThreatResponseBehavior (priority 3)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
void AiSystem::tickThreatResponse(EntityAdmin& admin, const BuildingSystem& buildings)
|
void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSystem& buildings)
|
||||||
{
|
{
|
||||||
// Snapshot all combatant entities for target acquisition.
|
// Snapshot all combatant entities for target acquisition.
|
||||||
struct CombatantInfo
|
struct CombatantInfo
|
||||||
@@ -68,17 +68,17 @@ void AiSystem::tickThreatResponse(EntityAdmin& admin, const BuildingSystem& buil
|
|||||||
combatants.push_back({e, pos.value, f.isEnemy, true});
|
combatants.push_back({e, pos.value, f.isEnemy, true});
|
||||||
});
|
});
|
||||||
|
|
||||||
admin.forEach<ThreatResponse, Position, Faction, SensorRange, MovementIntent>(
|
admin.forEach<ThreatResponseBehavior, Position, Faction, SensorRange, MovementIntent>(
|
||||||
[&](entt::entity e, ThreatResponse& threat, Position& pos, Faction& faction,
|
[&](entt::entity e, ThreatResponseBehavior& threatResponseBehavior, Position& pos, Faction& faction,
|
||||||
SensorRange& sensor, MovementIntent& intent)
|
SensorRange& sensor, MovementIntent& intent)
|
||||||
{
|
{
|
||||||
const float range = sensor.value;
|
const float range = sensor.value;
|
||||||
|
|
||||||
// Validate current target.
|
// Validate current target.
|
||||||
bool targetValid = false;
|
bool targetValid = false;
|
||||||
if (threat.currentTarget)
|
if (threatResponseBehavior.currentTarget)
|
||||||
{
|
{
|
||||||
const entt::entity t = *threat.currentTarget;
|
const entt::entity t = *threatResponseBehavior.currentTarget;
|
||||||
if (admin.isValid(t) && admin.hasAll<Position>(t))
|
if (admin.isValid(t) && admin.hasAll<Position>(t))
|
||||||
{
|
{
|
||||||
const float dist = (admin.get<Position>(t).value - pos.value).length();
|
const float dist = (admin.get<Position>(t).value - pos.value).length();
|
||||||
@@ -91,7 +91,7 @@ void AiSystem::tickThreatResponse(EntityAdmin& admin, const BuildingSystem& buil
|
|||||||
|
|
||||||
if (!targetValid)
|
if (!targetValid)
|
||||||
{
|
{
|
||||||
threat.currentTarget = std::nullopt;
|
threatResponseBehavior.currentTarget = std::nullopt;
|
||||||
float bestDist = range;
|
float bestDist = range;
|
||||||
|
|
||||||
for (const CombatantInfo& c : combatants)
|
for (const CombatantInfo& c : combatants)
|
||||||
@@ -113,14 +113,14 @@ void AiSystem::tickThreatResponse(EntityAdmin& admin, const BuildingSystem& buil
|
|||||||
if (dist < bestDist)
|
if (dist < bestDist)
|
||||||
{
|
{
|
||||||
bestDist = dist;
|
bestDist = dist;
|
||||||
threat.currentTarget = c.entity;
|
threatResponseBehavior.currentTarget = c.entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (threat.currentTarget)
|
if (threatResponseBehavior.currentTarget)
|
||||||
{
|
{
|
||||||
const entt::entity t = *threat.currentTarget;
|
const entt::entity t = *threatResponseBehavior.currentTarget;
|
||||||
QVector2D dest = pos.value;
|
QVector2D dest = pos.value;
|
||||||
if (admin.isValid(t) && admin.hasAll<Position>(t))
|
if (admin.isValid(t) && admin.hasAll<Position>(t))
|
||||||
{
|
{
|
||||||
@@ -298,10 +298,10 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// tickScrapCollector (priority 1)
|
// tickSalvageBehavior (priority 1)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
void AiSystem::tickScrapCollector(EntityAdmin& admin, ScrapSystem& scraps,
|
void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
||||||
BuildingSystem& buildings)
|
BuildingSystem& buildings)
|
||||||
{
|
{
|
||||||
// Snapshot enemy ships for threat detection.
|
// Snapshot enemy ships for threat detection.
|
||||||
@@ -322,24 +322,24 @@ void AiSystem::tickScrapCollector(EntityAdmin& admin, ScrapSystem& scraps,
|
|||||||
|
|
||||||
const std::vector<ScrapInfo> allScrap = scraps.allScrapInfo();
|
const std::vector<ScrapInfo> allScrap = scraps.allScrapInfo();
|
||||||
|
|
||||||
admin.forEach<ScrapCollector, SalvageCargo, Position, SensorRange, MovementIntent>(
|
admin.forEach<SalvageBehavior, SalvageCargo, Position, SensorRange, MovementIntent>(
|
||||||
[&](entt::entity /*e*/, ScrapCollector& sc, SalvageCargo& cargo,
|
[&](entt::entity /*e*/, SalvageBehavior& salvageBehavior, SalvageCargo& cargo,
|
||||||
Position& pos, SensorRange& sensor, MovementIntent& intent)
|
Position& pos, SensorRange& sensor, MovementIntent& intent)
|
||||||
{
|
{
|
||||||
const float collectRange = cargo.collectionRange;
|
const float collectRange = cargo.collectionRange;
|
||||||
|
|
||||||
// Assign nearest SalvageBay if needed.
|
// Assign nearest SalvageBay if needed.
|
||||||
if (sc.deliveryBay == kInvalidBuildingId)
|
if (salvageBehavior.deliveryBay == kInvalidBuildingId)
|
||||||
{
|
{
|
||||||
const Building* bay = buildings.findNearestBuilding(pos.value,
|
const Building* bay = buildings.findNearestBuilding(pos.value,
|
||||||
BuildingType::SalvageBay);
|
BuildingType::SalvageBay);
|
||||||
if (bay)
|
if (bay)
|
||||||
{
|
{
|
||||||
sc.deliveryBay = bay->id;
|
salvageBehavior.deliveryBay = bay->id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const BuildingId bayId = sc.deliveryBay;
|
const BuildingId bayId = salvageBehavior.deliveryBay;
|
||||||
|
|
||||||
QVector2D bayPos = pos.value;
|
QVector2D bayPos = pos.value;
|
||||||
if (bayId != kInvalidBuildingId)
|
if (bayId != kInvalidBuildingId)
|
||||||
@@ -398,18 +398,18 @@ void AiSystem::tickScrapCollector(EntityAdmin& admin, ScrapSystem& scraps,
|
|||||||
if (scraps.consume(si.entity))
|
if (scraps.consume(si.entity))
|
||||||
{
|
{
|
||||||
++cargo.current;
|
++cargo.current;
|
||||||
sc.scrapTarget = std::nullopt;
|
salvageBehavior.scrapTarget = std::nullopt;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move toward scrap target or find a new one.
|
// Move toward scrap target or find a new one.
|
||||||
if (sc.scrapTarget)
|
if (salvageBehavior.scrapTarget)
|
||||||
{
|
{
|
||||||
if (1 > intent.priority)
|
if (1 > intent.priority)
|
||||||
{
|
{
|
||||||
intent = MovementIntent{1, *sc.scrapTarget};
|
intent = MovementIntent{1, *salvageBehavior.scrapTarget};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -427,7 +427,7 @@ void AiSystem::tickScrapCollector(EntityAdmin& admin, ScrapSystem& scraps,
|
|||||||
}
|
}
|
||||||
if (bestPos)
|
if (bestPos)
|
||||||
{
|
{
|
||||||
sc.scrapTarget = bestPos;
|
salvageBehavior.scrapTarget = bestPos;
|
||||||
if (1 > intent.priority)
|
if (1 > intent.priority)
|
||||||
{
|
{
|
||||||
intent = MovementIntent{1, *bestPos};
|
intent = MovementIntent{1, *bestPos};
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ class ScrapSystem;
|
|||||||
class AiSystem
|
class AiSystem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void tickHomeReturn(EntityAdmin& admin);
|
void tickHomeReturnBehavior(EntityAdmin& admin);
|
||||||
void tickThreatResponse(EntityAdmin& admin, const BuildingSystem& buildings);
|
void tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSystem& buildings);
|
||||||
void tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings);
|
void tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings);
|
||||||
void tickScrapCollector(EntityAdmin& admin, ScrapSystem& scraps, BuildingSystem& buildings);
|
void tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps, BuildingSystem& buildings);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ void CombatSystem::tick(Tick currentTick,
|
|||||||
std::vector<FireEvent>& outFireEvents)
|
std::vector<FireEvent>& outFireEvents)
|
||||||
{
|
{
|
||||||
// Ship weapons.
|
// Ship weapons.
|
||||||
admin.forEach<Weapon, ThreatResponse, Position, Faction>(
|
admin.forEach<Weapon, ThreatResponseBehavior, Position, Faction>(
|
||||||
[&](entt::entity e, Weapon& weapon, ThreatResponse& threat, Position& pos, Faction& faction)
|
[&](entt::entity e, Weapon& weapon, ThreatResponseBehavior& threatResponseBehavior, Position& pos, Faction& faction)
|
||||||
{
|
{
|
||||||
weapon.currentTarget = threat.currentTarget;
|
weapon.currentTarget = threatResponseBehavior.currentTarget;
|
||||||
resolveWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents);
|
resolveWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ struct RepairTool
|
|||||||
// Behavior components — AI state consumed by step-6 behavior systems
|
// Behavior components — AI state consumed by step-6 behavior systems
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
struct ThreatResponse
|
struct ThreatResponseBehavior
|
||||||
{
|
{
|
||||||
std::optional<entt::entity> currentTarget;
|
std::optional<entt::entity> currentTarget;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ScrapCollector
|
struct SalvageBehavior
|
||||||
{
|
{
|
||||||
std::optional<QVector2D> scrapTarget;
|
std::optional<QVector2D> scrapTarget;
|
||||||
BuildingId deliveryBay; // kInvalidBuildingId until assigned at a salvage bay
|
BuildingId deliveryBay; // kInvalidBuildingId until assigned at a salvage bay
|
||||||
@@ -57,7 +57,7 @@ struct RepairBehavior
|
|||||||
std::optional<entt::entity> currentTarget;
|
std::optional<entt::entity> currentTarget;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HomeReturn
|
struct HomeReturnBehavior
|
||||||
{
|
{
|
||||||
float retreatHpFraction;
|
float retreatHpFraction;
|
||||||
QVector2D homePos;
|
QVector2D homePos;
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto
|
|||||||
w.currentTarget = std::nullopt;
|
w.currentTarget = std::nullopt;
|
||||||
m_admin.addComponent<Weapon>(entity, w);
|
m_admin.addComponent<Weapon>(entity, w);
|
||||||
|
|
||||||
m_admin.addComponent<ThreatResponse>(entity, ThreatResponse{});
|
m_admin.addComponent<ThreatResponseBehavior>(entity, ThreatResponseBehavior{});
|
||||||
|
|
||||||
if (!isEnemy)
|
if (!isEnemy)
|
||||||
{
|
{
|
||||||
@@ -91,10 +91,10 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto
|
|||||||
cargo.collectionRange = static_cast<float>(def->salvage->collectionRange);
|
cargo.collectionRange = static_cast<float>(def->salvage->collectionRange);
|
||||||
m_admin.addComponent<SalvageCargo>(entity, cargo);
|
m_admin.addComponent<SalvageCargo>(entity, cargo);
|
||||||
|
|
||||||
ScrapCollector sc;
|
SalvageBehavior salvageBehavior;
|
||||||
sc.scrapTarget = std::nullopt;
|
salvageBehavior.scrapTarget = std::nullopt;
|
||||||
sc.deliveryBay = kInvalidBuildingId;
|
salvageBehavior.deliveryBay = kInvalidBuildingId;
|
||||||
m_admin.addComponent<ScrapCollector>(entity, sc);
|
m_admin.addComponent<SalvageBehavior>(entity, salvageBehavior);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (def->repair)
|
if (def->repair)
|
||||||
|
|||||||
@@ -162,10 +162,10 @@ void Simulation::tick()
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_shipSystem->clearMovementIntents();
|
m_shipSystem->clearMovementIntents();
|
||||||
m_aiSystem->tickHomeReturn(m_admin); // priority 4
|
m_aiSystem->tickHomeReturnBehavior(m_admin); // priority 4
|
||||||
m_aiSystem->tickThreatResponse(m_admin, *m_buildingSystem); // priority 3
|
m_aiSystem->tickThreatResponseBehavior(m_admin, *m_buildingSystem); // priority 3
|
||||||
m_aiSystem->tickRepairBehavior(m_admin, *m_buildingSystem); // priority 2
|
m_aiSystem->tickRepairBehavior(m_admin, *m_buildingSystem); // priority 2
|
||||||
m_aiSystem->tickScrapCollector(m_admin, *m_scrapSystem, *m_buildingSystem); // priority 1
|
m_aiSystem->tickSalvageBehavior(m_admin, *m_scrapSystem, *m_buildingSystem); // priority 1
|
||||||
|
|
||||||
// Step 8: combat resolution
|
// Step 8: combat resolution
|
||||||
m_combatSystem->tick(m_currentTick, m_admin,
|
m_combatSystem->tick(m_currentTick, m_admin,
|
||||||
|
|||||||
@@ -64,10 +64,10 @@ struct Fixture
|
|||||||
void runBehaviorTick()
|
void runBehaviorTick()
|
||||||
{
|
{
|
||||||
ships.clearMovementIntents();
|
ships.clearMovementIntents();
|
||||||
ai.tickHomeReturn(admin);
|
ai.tickHomeReturnBehavior(admin);
|
||||||
ai.tickThreatResponse(admin, buildings);
|
ai.tickThreatResponseBehavior(admin, buildings);
|
||||||
ai.tickRepairBehavior(admin, buildings);
|
ai.tickRepairBehavior(admin, buildings);
|
||||||
ai.tickScrapCollector(admin, scraps, buildings);
|
ai.tickSalvageBehavior(admin, scraps, buildings);
|
||||||
movement.tick(admin);
|
movement.tick(admin);
|
||||||
++tick;
|
++tick;
|
||||||
}
|
}
|
||||||
@@ -139,40 +139,40 @@ TEST_CASE("BehaviorSystem: tickMovement stops exactly at target without overshoo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// tickHomeReturn
|
// tickHomeReturnBehavior
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEST_CASE("BehaviorSystem: tickHomeReturn does nothing when HP is above threshold",
|
TEST_CASE("BehaviorSystem: tickHomeReturnBehavior does nothing when HP is above threshold",
|
||||||
"[behavior]")
|
"[behavior]")
|
||||||
{
|
{
|
||||||
Fixture f;
|
Fixture f;
|
||||||
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||||
f.admin.addComponent<HomeReturn>(e, HomeReturn{0.3f, QVector2D(-10.0f, 0.0f)});
|
f.admin.addComponent<HomeReturnBehavior>(e, HomeReturnBehavior{0.3f, QVector2D(-10.0f, 0.0f)});
|
||||||
f.admin.get<Health>(e).hp = f.admin.get<Health>(e).maxHp; // full HP
|
f.admin.get<Health>(e).hp = f.admin.get<Health>(e).maxHp; // full HP
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickHomeReturn(f.admin);
|
f.ai.tickHomeReturnBehavior(f.admin);
|
||||||
|
|
||||||
REQUIRE(intent(f.admin, e).priority == 0);
|
REQUIRE(intent(f.admin, e).priority == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BehaviorSystem: tickHomeReturn writes priority-4 intent toward homePos when HP is low",
|
TEST_CASE("BehaviorSystem: tickHomeReturnBehavior writes priority-4 intent toward homePos when HP is low",
|
||||||
"[behavior]")
|
"[behavior]")
|
||||||
{
|
{
|
||||||
Fixture f;
|
Fixture f;
|
||||||
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||||
const QVector2D homePos(-10.0f, 0.0f);
|
const QVector2D homePos(-10.0f, 0.0f);
|
||||||
f.admin.addComponent<HomeReturn>(e, HomeReturn{0.5f, homePos});
|
f.admin.addComponent<HomeReturnBehavior>(e, HomeReturnBehavior{0.5f, homePos});
|
||||||
f.admin.get<Health>(e).hp = f.admin.get<Health>(e).maxHp * 0.2f; // below threshold
|
f.admin.get<Health>(e).hp = f.admin.get<Health>(e).maxHp * 0.2f; // below threshold
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickHomeReturn(f.admin);
|
f.ai.tickHomeReturnBehavior(f.admin);
|
||||||
|
|
||||||
REQUIRE(intent(f.admin, e).priority == 4);
|
REQUIRE(intent(f.admin, e).priority == 4);
|
||||||
REQUIRE(intent(f.admin, e).target.x() == Approx(homePos.x()));
|
REQUIRE(intent(f.admin, e).target.x() == Approx(homePos.x()));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BehaviorSystem: tickHomeReturn priority-4 beats tickThreatResponse priority-3",
|
TEST_CASE("BehaviorSystem: tickHomeReturnBehavior priority-4 beats tickThreatResponseBehavior priority-3",
|
||||||
"[behavior]")
|
"[behavior]")
|
||||||
{
|
{
|
||||||
Fixture f;
|
Fixture f;
|
||||||
@@ -180,19 +180,19 @@ TEST_CASE("BehaviorSystem: tickHomeReturn priority-4 beats tickThreatResponse pr
|
|||||||
f.ships.spawn("interceptor", 1, QVector2D(5.0f, 0.0f), /*isEnemy=*/true);
|
f.ships.spawn("interceptor", 1, QVector2D(5.0f, 0.0f), /*isEnemy=*/true);
|
||||||
|
|
||||||
const QVector2D homePos(-50.0f, 0.0f);
|
const QVector2D homePos(-50.0f, 0.0f);
|
||||||
f.admin.addComponent<HomeReturn>(player, HomeReturn{0.5f, homePos});
|
f.admin.addComponent<HomeReturnBehavior>(player, HomeReturnBehavior{0.5f, homePos});
|
||||||
f.admin.get<Health>(player).hp = f.admin.get<Health>(player).maxHp * 0.1f;
|
f.admin.get<Health>(player).hp = f.admin.get<Health>(player).maxHp * 0.1f;
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickHomeReturn(f.admin);
|
f.ai.tickHomeReturnBehavior(f.admin);
|
||||||
f.ai.tickThreatResponse(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE(intent(f.admin, player).priority == 4);
|
REQUIRE(intent(f.admin, player).priority == 4);
|
||||||
REQUIRE(intent(f.admin, player).target.x() == Approx(homePos.x()));
|
REQUIRE(intent(f.admin, player).target.x() == Approx(homePos.x()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// tickThreatResponse — player ships
|
// tickThreatResponseBehavior — player ships
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEST_CASE("BehaviorSystem: player combat ship acquires nearest enemy ship in range",
|
TEST_CASE("BehaviorSystem: player combat ship acquires nearest enemy ship in range",
|
||||||
@@ -204,12 +204,12 @@ TEST_CASE("BehaviorSystem: player combat ship acquires nearest enemy ship in ran
|
|||||||
/*isEnemy=*/true);
|
/*isEnemy=*/true);
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponse(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE(f.admin.hasAll<ThreatResponse>(player));
|
REQUIRE(f.admin.hasAll<ThreatResponseBehavior>(player));
|
||||||
const ThreatResponse& tr = f.admin.get<ThreatResponse>(player);
|
const ThreatResponseBehavior& threatResponseBehavior = f.admin.get<ThreatResponseBehavior>(player);
|
||||||
REQUIRE(tr.currentTarget.has_value());
|
REQUIRE(threatResponseBehavior.currentTarget.has_value());
|
||||||
REQUIRE(*tr.currentTarget == enemy);
|
REQUIRE(*threatResponseBehavior.currentTarget == enemy);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BehaviorSystem: player combat ship does not target friendly ships",
|
TEST_CASE("BehaviorSystem: player combat ship does not target friendly ships",
|
||||||
@@ -220,10 +220,10 @@ TEST_CASE("BehaviorSystem: player combat ship does not target friendly ships",
|
|||||||
f.ships.spawn("interceptor", 1, QVector2D(5.0f, 0.0f)); // also player
|
f.ships.spawn("interceptor", 1, QVector2D(5.0f, 0.0f)); // also player
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponse(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE(f.admin.hasAll<ThreatResponse>(e1));
|
REQUIRE(f.admin.hasAll<ThreatResponseBehavior>(e1));
|
||||||
REQUIRE_FALSE(f.admin.get<ThreatResponse>(e1).currentTarget.has_value());
|
REQUIRE_FALSE(f.admin.get<ThreatResponseBehavior>(e1).currentTarget.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BehaviorSystem: player combat ship ignores enemy beyond engagement range",
|
TEST_CASE("BehaviorSystem: player combat ship ignores enemy beyond engagement range",
|
||||||
@@ -234,13 +234,13 @@ TEST_CASE("BehaviorSystem: player combat ship ignores enemy beyond engagement ra
|
|||||||
f.ships.spawn("interceptor", 1, QVector2D(500.0f, 0.0f), /*isEnemy=*/true);
|
f.ships.spawn("interceptor", 1, QVector2D(500.0f, 0.0f), /*isEnemy=*/true);
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponse(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE_FALSE(f.admin.get<ThreatResponse>(player).currentTarget.has_value());
|
REQUIRE_FALSE(f.admin.get<ThreatResponseBehavior>(player).currentTarget.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// tickThreatResponse — enemy ships
|
// tickThreatResponseBehavior — enemy ships
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEST_CASE("BehaviorSystem: enemy ship acquires nearest player ship in range",
|
TEST_CASE("BehaviorSystem: enemy ship acquires nearest player ship in range",
|
||||||
@@ -252,12 +252,12 @@ TEST_CASE("BehaviorSystem: enemy ship acquires nearest player ship in range",
|
|||||||
/*isEnemy=*/true);
|
/*isEnemy=*/true);
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponse(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE(f.admin.hasAll<ThreatResponse>(enemy));
|
REQUIRE(f.admin.hasAll<ThreatResponseBehavior>(enemy));
|
||||||
const ThreatResponse& tr = f.admin.get<ThreatResponse>(enemy);
|
const ThreatResponseBehavior& threatResponseBehavior = f.admin.get<ThreatResponseBehavior>(enemy);
|
||||||
REQUIRE(tr.currentTarget.has_value());
|
REQUIRE(threatResponseBehavior.currentTarget.has_value());
|
||||||
REQUIRE(*tr.currentTarget == player);
|
REQUIRE(*threatResponseBehavior.currentTarget == player);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BehaviorSystem: enemy ship with no target writes leftward movement intent",
|
TEST_CASE("BehaviorSystem: enemy ship with no target writes leftward movement intent",
|
||||||
@@ -268,7 +268,7 @@ TEST_CASE("BehaviorSystem: enemy ship with no target writes leftward movement in
|
|||||||
/*isEnemy=*/true);
|
/*isEnemy=*/true);
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponse(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE(intent(f.admin, enemy).priority == 3);
|
REQUIRE(intent(f.admin, enemy).priority == 3);
|
||||||
REQUIRE(intent(f.admin, enemy).target.x() < 0.0f);
|
REQUIRE(intent(f.admin, enemy).target.x() < 0.0f);
|
||||||
@@ -330,7 +330,7 @@ TEST_CASE("BehaviorSystem: repair ship does not heal above maxHp", "[behavior]")
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// tickScrapCollector
|
// tickSalvageBehavior
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEST_CASE("BehaviorSystem: salvage ship writes intent toward nearest scrap", "[behavior]")
|
TEST_CASE("BehaviorSystem: salvage ship writes intent toward nearest scrap", "[behavior]")
|
||||||
@@ -342,7 +342,7 @@ TEST_CASE("BehaviorSystem: salvage ship writes intent toward nearest scrap", "[b
|
|||||||
f.scraps.spawn(scrapPos, 1, 100000);
|
f.scraps.spawn(scrapPos, 1, 100000);
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickScrapCollector(f.admin, f.scraps, f.buildings);
|
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
|
||||||
|
|
||||||
REQUIRE(intent(f.admin, ship).priority == 1);
|
REQUIRE(intent(f.admin, ship).priority == 1);
|
||||||
REQUIRE(intent(f.admin, ship).target.x() == Approx(scrapPos.x()));
|
REQUIRE(intent(f.admin, ship).target.x() == Approx(scrapPos.x()));
|
||||||
@@ -355,7 +355,7 @@ TEST_CASE("BehaviorSystem: salvage ship collects scrap on arrival", "[behavior]"
|
|||||||
const entt::entity scrapEntity = f.scraps.spawn(QVector2D(0.0f, 0.0f), 1, 100000);
|
const entt::entity scrapEntity = f.scraps.spawn(QVector2D(0.0f, 0.0f), 1, 100000);
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickScrapCollector(f.admin, f.scraps, f.buildings);
|
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
|
||||||
|
|
||||||
REQUIRE(f.admin.get<SalvageCargo>(ship).current == 1);
|
REQUIRE(f.admin.get<SalvageCargo>(ship).current == 1);
|
||||||
REQUIRE_FALSE(f.admin.isValid(scrapEntity));
|
REQUIRE_FALSE(f.admin.isValid(scrapEntity));
|
||||||
@@ -383,7 +383,7 @@ TEST_CASE("BehaviorSystem: full-cargo salvage ship moves toward SalvageBay", "[b
|
|||||||
cargo.current = cargo.capacity; // full cargo
|
cargo.current = cargo.capacity; // full cargo
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickScrapCollector(f.admin, f.scraps, f.buildings);
|
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
|
||||||
|
|
||||||
const MovementIntent& i = intent(f.admin, ship);
|
const MovementIntent& i = intent(f.admin, ship);
|
||||||
REQUIRE(i.priority == 1);
|
REQUIRE(i.priority == 1);
|
||||||
@@ -402,7 +402,7 @@ TEST_CASE("SensorRange: sensorRange is populated from config formula at spawn",
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Sensor range — tickThreatResponse
|
// Sensor range — tickThreatResponseBehavior
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEST_CASE("SensorRange: player combat ship acquires enemy just inside sensor range", "[sensor]")
|
TEST_CASE("SensorRange: player combat ship acquires enemy just inside sensor range", "[sensor]")
|
||||||
@@ -413,9 +413,9 @@ TEST_CASE("SensorRange: player combat ship acquires enemy just inside sensor ran
|
|||||||
/*isEnemy=*/true);
|
/*isEnemy=*/true);
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponse(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE(f.admin.get<ThreatResponse>(player).currentTarget == enemy);
|
REQUIRE(f.admin.get<ThreatResponseBehavior>(player).currentTarget == enemy);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("SensorRange: player combat ship ignores enemy just outside sensor range", "[sensor]")
|
TEST_CASE("SensorRange: player combat ship ignores enemy just outside sensor range", "[sensor]")
|
||||||
@@ -425,9 +425,9 @@ TEST_CASE("SensorRange: player combat ship ignores enemy just outside sensor ran
|
|||||||
f.ships.spawn("interceptor", 1, QVector2D(210.0f, 0.0f), /*isEnemy=*/true);
|
f.ships.spawn("interceptor", 1, QVector2D(210.0f, 0.0f), /*isEnemy=*/true);
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponse(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE_FALSE(f.admin.get<ThreatResponse>(player).currentTarget.has_value());
|
REQUIRE_FALSE(f.admin.get<ThreatResponseBehavior>(player).currentTarget.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("SensorRange: enemy ship ignores player just outside sensor range", "[sensor]")
|
TEST_CASE("SensorRange: enemy ship ignores player just outside sensor range", "[sensor]")
|
||||||
@@ -438,9 +438,9 @@ TEST_CASE("SensorRange: enemy ship ignores player just outside sensor range", "[
|
|||||||
/*isEnemy=*/true);
|
/*isEnemy=*/true);
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponse(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE_FALSE(f.admin.get<ThreatResponse>(enemy).currentTarget.has_value());
|
REQUIRE_FALSE(f.admin.get<ThreatResponseBehavior>(enemy).currentTarget.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -486,7 +486,7 @@ TEST_CASE("SensorRange: repair ship does not acquire damaged ally beyond sensor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Sensor range — tickScrapCollector
|
// Sensor range — tickSalvageBehavior
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEST_CASE("SensorRange: salvage ship ignores scrap beyond sensor range", "[sensor]")
|
TEST_CASE("SensorRange: salvage ship ignores scrap beyond sensor range", "[sensor]")
|
||||||
@@ -496,8 +496,8 @@ TEST_CASE("SensorRange: salvage ship ignores scrap beyond sensor range", "[senso
|
|||||||
f.scraps.spawn(QVector2D(300.0f, 0.0f), 1, 100000);
|
f.scraps.spawn(QVector2D(300.0f, 0.0f), 1, 100000);
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickScrapCollector(f.admin, f.scraps, f.buildings);
|
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
|
||||||
|
|
||||||
REQUIRE_FALSE(f.admin.get<ScrapCollector>(ship).scrapTarget.has_value());
|
REQUIRE_FALSE(f.admin.get<SalvageBehavior>(ship).scrapTarget.has_value());
|
||||||
REQUIRE(intent(f.admin, ship).target.x() > pos(f.admin, ship).value.x());
|
REQUIRE(intent(f.admin, ship).target.x() > pos(f.admin, ship).value.x());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,9 +68,9 @@ struct CombatFixture
|
|||||||
admin.get<Weapon>(enemy).currentTarget = playerTarget;
|
admin.get<Weapon>(enemy).currentTarget = playerTarget;
|
||||||
admin.get<Weapon>(enemy).cooldownTicks = 0.0f;
|
admin.get<Weapon>(enemy).cooldownTicks = 0.0f;
|
||||||
}
|
}
|
||||||
if (admin.hasAll<ThreatResponse>(enemy))
|
if (admin.hasAll<ThreatResponseBehavior>(enemy))
|
||||||
{
|
{
|
||||||
admin.get<ThreatResponse>(enemy).currentTarget = playerTarget;
|
admin.get<ThreatResponseBehavior>(enemy).currentTarget = playerTarget;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ TEST_CASE("ShipSystem: interceptor spawn has weapon and threatResponse, no cargo
|
|||||||
|
|
||||||
REQUIRE(admin.isValid(e));
|
REQUIRE(admin.isValid(e));
|
||||||
REQUIRE(admin.hasAll<Weapon>(e));
|
REQUIRE(admin.hasAll<Weapon>(e));
|
||||||
REQUIRE(admin.hasAll<ThreatResponse>(e));
|
REQUIRE(admin.hasAll<ThreatResponseBehavior>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<SalvageCargo>(e));
|
REQUIRE_FALSE(admin.hasAll<SalvageCargo>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<RepairTool>(e));
|
REQUIRE_FALSE(admin.hasAll<RepairTool>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<RepairBehavior>(e));
|
REQUIRE_FALSE(admin.hasAll<RepairBehavior>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<ScrapCollector>(e));
|
REQUIRE_FALSE(admin.hasAll<SalvageBehavior>(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("ShipSystem: interceptor level 1 stats match config formulas", "[ship]")
|
TEST_CASE("ShipSystem: interceptor level 1 stats match config formulas", "[ship]")
|
||||||
@@ -100,7 +100,7 @@ TEST_CASE("ShipSystem: salvage_ship spawn has cargo and scrapCollector, no weapo
|
|||||||
const entt::entity e = ss.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = ss.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
REQUIRE(admin.hasAll<SalvageCargo>(e));
|
REQUIRE(admin.hasAll<SalvageCargo>(e));
|
||||||
REQUIRE(admin.hasAll<ScrapCollector>(e));
|
REQUIRE(admin.hasAll<SalvageBehavior>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<Weapon>(e));
|
REQUIRE_FALSE(admin.hasAll<Weapon>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<RepairTool>(e));
|
REQUIRE_FALSE(admin.hasAll<RepairTool>(e));
|
||||||
}
|
}
|
||||||
@@ -116,8 +116,8 @@ TEST_CASE("ShipSystem: salvage_ship cargo capacity matches config", "[ship]")
|
|||||||
// cargo_capacity = 10
|
// cargo_capacity = 10
|
||||||
REQUIRE(admin.get<SalvageCargo>(e).capacity == 10);
|
REQUIRE(admin.get<SalvageCargo>(e).capacity == 10);
|
||||||
REQUIRE(admin.get<SalvageCargo>(e).current == 0);
|
REQUIRE(admin.get<SalvageCargo>(e).current == 0);
|
||||||
REQUIRE(admin.get<ScrapCollector>(e).deliveryBay == kInvalidBuildingId);
|
REQUIRE(admin.get<SalvageBehavior>(e).deliveryBay == kInvalidBuildingId);
|
||||||
REQUIRE_FALSE(admin.get<ScrapCollector>(e).scrapTarget.has_value());
|
REQUIRE_FALSE(admin.get<SalvageBehavior>(e).scrapTarget.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user