refactor AI system

This commit is contained in:
2026-06-15 09:16:56 +02:00
parent 8451f5a281
commit e8dd73bcb0
67 changed files with 1731 additions and 919 deletions

View File

@@ -6,22 +6,27 @@
#include <QPoint>
#include <QVector2D>
#include "AdvanceBehavior.h"
#include "AttackBehavior.h"
#include "BuildingId.h"
#include "ConfigLoader.h"
#include "DeliverScrapBehavior.h"
#include "DynamicBodyComponent.h"
#include "EntityAdmin.h"
#include "HealthComponent.h"
#include "ModuleOwnerComponent.h"
#include "RepairBehaviorComponent.h"
#include "RallyBehavior.h"
#include "RepairBehavior.h"
#include "RepairToolComponent.h"
#include "RetreatBehavior.h"
#include "Rotation.h"
#include "SalvageBehaviorComponent.h"
#include "SalvageCargoComponent.h"
#include "SalvageScrapBehavior.h"
#include "SelectedBehaviorComponent.h"
#include "SensorRangeComponent.h"
#include "ShipLayout.h"
#include "ShipSystem.h"
#include "Tick.h"
#include "ThreatResponseBehaviorComponent.h"
#include "WeaponComponent.h"
static GameConfig loadConfig()
@@ -81,7 +86,7 @@ static ShipLayoutConfig makeSingleModuleLayout(const std::string& moduleId)
// Combat ship (interceptor has default_modules = [laser_cannon])
// ---------------------------------------------------------------------------
TEST_CASE("ShipSystem: interceptor spawn has weapon child and threatResponse, no cargo or repair",
TEST_CASE("ShipSystem: interceptor spawn has weapon child and attack behavior, no cargo or repair",
"[ship]")
{
EntityAdmin admin;
@@ -92,11 +97,47 @@ TEST_CASE("ShipSystem: interceptor spawn has weapon child and threatResponse, no
REQUIRE(admin.isValid(e));
REQUIRE(admin.isValid(firstWeaponChild(admin, e)));
REQUIRE(admin.hasAll<ThreatResponseBehaviorComponent>(e));
REQUIRE(admin.hasAll<AttackBehavior>(e));
// Every ship gets the baseline behaviors; a player combat ship also rallies
// and can retreat.
REQUIRE(admin.hasAll<AdvanceBehavior>(e));
REQUIRE(admin.hasAll<SelectedBehaviorComponent>(e));
REQUIRE(admin.hasAll<RallyBehavior>(e));
REQUIRE(admin.hasAll<RetreatBehavior>(e));
REQUIRE_FALSE(admin.isValid(firstSalvageChild(admin, e)));
REQUIRE_FALSE(admin.isValid(firstRepairChild(admin, e)));
REQUIRE_FALSE(admin.hasAll<RepairBehaviorComponent>(e));
REQUIRE_FALSE(admin.hasAll<SalvageBehaviorComponent>(e));
REQUIRE_FALSE(admin.hasAll<RepairBehavior>(e));
REQUIRE_FALSE(admin.hasAll<SalvageScrapBehavior>(e));
REQUIRE_FALSE(admin.hasAll<DeliverScrapBehavior>(e));
}
TEST_CASE("ShipSystem: enemy combat ship has no rally or retreat behavior", "[ship]")
{
EntityAdmin admin;
const GameConfig cfg = loadConfig();
ShipSystem ss(cfg, admin);
const entt::entity e = ss.spawn("interceptor", 1, QVector2D(0.0f, 0.0f), /*isEnemy=*/true);
REQUIRE(admin.hasAll<AttackBehavior>(e));
REQUIRE(admin.hasAll<AdvanceBehavior>(e));
REQUIRE_FALSE(admin.hasAll<RallyBehavior>(e));
REQUIRE_FALSE(admin.hasAll<RetreatBehavior>(e));
}
TEST_CASE("ShipSystem: setRetreatEnabled(false) suppresses player retreat behavior", "[ship]")
{
EntityAdmin admin;
const GameConfig cfg = loadConfig();
ShipSystem ss(cfg, admin);
ss.setRetreatEnabled(false);
const entt::entity e = ss.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
// Other player behaviors are unaffected; only retreat is suppressed.
REQUIRE(admin.hasAll<AttackBehavior>(e));
REQUIRE(admin.hasAll<RallyBehavior>(e));
REQUIRE_FALSE(admin.hasAll<RetreatBehavior>(e));
}
TEST_CASE("ShipSystem: interceptor level 1 stats match config formulas", "[ship]")
@@ -161,7 +202,8 @@ TEST_CASE("ShipSystem: salvage_ship spawn with salvage module has cargo child an
const entt::entity e = ss.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f), false, layout);
REQUIRE(admin.isValid(firstSalvageChild(admin, e)));
REQUIRE(admin.hasAll<SalvageBehaviorComponent>(e));
REQUIRE(admin.hasAll<SalvageScrapBehavior>(e));
REQUIRE(admin.hasAll<DeliverScrapBehavior>(e));
REQUIRE_FALSE(admin.isValid(firstWeaponChild(admin, e)));
REQUIRE_FALSE(admin.isValid(firstRepairChild(admin, e)));
}
@@ -180,9 +222,9 @@ TEST_CASE("ShipSystem: salvage_ship cargo capacity matches config", "[ship]")
REQUIRE(admin.isValid(sc));
REQUIRE(admin.get<SalvageCargoComponent>(sc).capacity == 10);
REQUIRE(admin.get<SalvageCargoComponent>(sc).current == 0);
REQUIRE(admin.get<SalvageBehaviorComponent>(e).deliveryBay == kInvalidBuildingId);
REQUIRE_FALSE(admin.get<SalvageBehaviorComponent>(e).scrapTarget.has_value());
REQUIRE(admin.get<SalvageBehaviorComponent>(e).maxCollectionRange_tiles == Approx(50.0f));
REQUIRE(admin.get<DeliverScrapBehavior>(e).deliveryBay == kInvalidBuildingId);
REQUIRE_FALSE(admin.get<SalvageScrapBehavior>(e).scrapTarget.has_value());
REQUIRE(admin.get<SalvageScrapBehavior>(e).maxCollectionRange_tiles == Approx(50.0f));
}
// ---------------------------------------------------------------------------
@@ -200,7 +242,7 @@ TEST_CASE("ShipSystem: repair_ship spawn with repair module has repair child and
const entt::entity e = ss.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f), false, layout);
REQUIRE(admin.isValid(firstRepairChild(admin, e)));
REQUIRE(admin.hasAll<RepairBehaviorComponent>(e));
REQUIRE(admin.hasAll<RepairBehavior>(e));
REQUIRE_FALSE(admin.isValid(firstWeaponChild(admin, e)));
REQUIRE_FALSE(admin.isValid(firstSalvageChild(admin, e)));
}
@@ -221,7 +263,7 @@ TEST_CASE("ShipSystem: repair_ship level 1 repair stats match config formulas",
REQUIRE(admin.get<RepairToolComponent>(rc).ratePerTick == Approx(expectedRate));
// repair_range_m_formula = "800" m → 800/10 = 80 tiles
REQUIRE(admin.get<RepairToolComponent>(rc).range_tiles == Approx(80.0f));
REQUIRE(admin.get<RepairBehaviorComponent>(e).maxRepairRange_tiles == Approx(80.0f));
REQUIRE(admin.get<RepairBehavior>(e).maxRepairRange_tiles == Approx(80.0f));
}
// ---------------------------------------------------------------------------