split off MovementSystem and AiSystem from ShipSystem

This commit is contained in:
2026-05-20 22:26:45 +02:00
parent 34c6dea505
commit 452c26c8b3
12 changed files with 684 additions and 703 deletions

View File

@@ -4,11 +4,13 @@
#include <QVector2D>
#include "AiSystem.h"
#include "BeltSystem.h"
#include "Building.h"
#include "BuildingSystem.h"
#include "BuildingType.h"
#include "ConfigLoader.h"
#include "MovementSystem.h"
#include "Rotation.h"
#include "Scrap.h"
#include "ScrapSystem.h"
@@ -27,15 +29,17 @@ static GameConfig loadConfig()
struct Fixture
{
GameConfig cfg;
BeltSystem belts;
EntityId nextId;
int stock;
std::mt19937 rng;
GameConfig cfg;
BeltSystem belts;
EntityId nextId;
int stock;
std::mt19937 rng;
BuildingSystem buildings;
ShipSystem ships;
ScrapSystem scraps;
Tick tick;
ShipSystem ships;
AiSystem ai;
MovementSystem movement;
ScrapSystem scraps;
Tick tick;
explicit Fixture()
: cfg(loadConfig())
@@ -58,11 +62,11 @@ struct Fixture
void runBehaviorTick()
{
ships.clearMovementIntents();
ships.tickHomeReturn();
ships.tickThreatResponse(buildings);
ships.tickRepairBehavior(buildings);
ships.tickScrapCollector(scraps, buildings);
ships.tickMovement();
ai.tickHomeReturn(ships);
ai.tickThreatResponse(ships, buildings);
ai.tickRepairBehavior(ships, buildings);
ai.tickScrapCollector(ships, scraps, buildings);
movement.tick(ships);
++tick;
}
};
@@ -107,7 +111,7 @@ TEST_CASE("BehaviorSystem: tickMovement advances ship by maxSpeedPerTick toward
f.ships.forEach([&target](Ship& s) {
s.intent = MovementIntent{1, target};
});
f.ships.tickMovement();
f.movement.tick(f.ships);
const Ship* s = f.ships.findShip(id);
REQUIRE(s->position.x() == Approx(speed));
@@ -129,7 +133,7 @@ TEST_CASE("BehaviorSystem: tickMovement stops exactly at target without overshoo
f.ships.forEach([&target](Ship& s) {
s.intent = MovementIntent{1, target};
});
f.ships.tickMovement();
f.movement.tick(f.ships);
const Ship* s = f.ships.findShip(id);
REQUIRE(s->position.x() == Approx(target.x()));
@@ -152,7 +156,7 @@ TEST_CASE("BehaviorSystem: tickHomeReturn does nothing when HP is above threshol
});
f.ships.clearMovementIntents();
f.ships.tickHomeReturn();
f.ai.tickHomeReturn(f.ships);
REQUIRE(f.ships.findShip(id)->intent.priority == 0);
}
@@ -170,7 +174,7 @@ TEST_CASE("BehaviorSystem: tickHomeReturn writes priority-4 intent toward homePo
});
f.ships.clearMovementIntents();
f.ships.tickHomeReturn();
f.ai.tickHomeReturn(f.ships);
const Ship* s = f.ships.findShip(id);
REQUIRE(s->intent.priority == 4);
@@ -195,8 +199,8 @@ TEST_CASE("BehaviorSystem: tickHomeReturn priority-4 beats tickThreatResponse pr
});
f.ships.clearMovementIntents();
f.ships.tickHomeReturn();
f.ships.tickThreatResponse(f.buildings);
f.ai.tickHomeReturn(f.ships);
f.ai.tickThreatResponse(f.ships, f.buildings);
const Ship* s = f.ships.findShip(playerId);
REQUIRE(s->intent.priority == 4);
@@ -217,7 +221,7 @@ TEST_CASE("BehaviorSystem: player combat ship acquires nearest enemy ship in ran
/*isEnemy=*/true);
f.ships.clearMovementIntents();
f.ships.tickThreatResponse(f.buildings);
f.ai.tickThreatResponse(f.ships, f.buildings);
const Ship* player = f.ships.findShip(playerId);
REQUIRE(player->threatResponse.has_value());
@@ -233,7 +237,7 @@ TEST_CASE("BehaviorSystem: player combat ship does not target friendly ships",
f.ships.spawn("interceptor", 1, QVector2D(5.0f, 0.0f)); // also player (isEnemy=false)
f.ships.clearMovementIntents();
f.ships.tickThreatResponse(f.buildings);
f.ai.tickThreatResponse(f.ships, f.buildings);
const Ship* s = f.ships.findShip(id1);
REQUIRE(s->threatResponse.has_value());
@@ -249,7 +253,7 @@ 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.clearMovementIntents();
f.ships.tickThreatResponse(f.buildings);
f.ai.tickThreatResponse(f.ships, f.buildings);
const Ship* s = f.ships.findShip(playerId);
REQUIRE_FALSE(s->threatResponse->currentTarget.has_value());
@@ -268,7 +272,7 @@ TEST_CASE("BehaviorSystem: enemy ship acquires nearest player ship in range",
/*isEnemy=*/true);
f.ships.clearMovementIntents();
f.ships.tickThreatResponse(f.buildings);
f.ai.tickThreatResponse(f.ships, f.buildings);
const Ship* enemy = f.ships.findShip(enemyId);
REQUIRE(enemy->threatResponse.has_value());
@@ -284,7 +288,7 @@ TEST_CASE("BehaviorSystem: enemy ship with no target writes leftward movement in
/*isEnemy=*/true);
f.ships.clearMovementIntents();
f.ships.tickThreatResponse(f.buildings);
f.ai.tickThreatResponse(f.ships, f.buildings);
const Ship* enemy = f.ships.findShip(enemyId);
REQUIRE(enemy->intent.priority == 3);
@@ -311,7 +315,7 @@ TEST_CASE("BehaviorSystem: repair ship writes intent toward damaged friendly shi
});
f.ships.clearMovementIntents();
f.ships.tickRepairBehavior(f.buildings);
f.ai.tickRepairBehavior(f.ships, f.buildings);
const Ship* repair = f.ships.findShip(repairId);
REQUIRE(repair->intent.priority == 2);
@@ -335,7 +339,7 @@ TEST_CASE("BehaviorSystem: repair ship heals damaged ally within repair range",
});
f.ships.clearMovementIntents();
f.ships.tickRepairBehavior(f.buildings);
f.ai.tickRepairBehavior(f.ships, f.buildings);
// repair_rate_formula = "5 + x" at x=1 → 6; hp should have increased.
const Ship* friendly = f.ships.findShip(friendlyId);
@@ -359,7 +363,7 @@ TEST_CASE("BehaviorSystem: repair ship does not heal above maxHp", "[behavior]")
for (int i = 0; i < 5; ++i)
{
f.ships.clearMovementIntents();
f.ships.tickRepairBehavior(f.buildings);
f.ai.tickRepairBehavior(f.ships, f.buildings);
}
const Ship* friendly = f.ships.findShip(friendlyId);
@@ -382,7 +386,7 @@ TEST_CASE("BehaviorSystem: salvage ship writes intent toward nearest scrap", "[b
f.scraps.spawn(scrapPos, 1, farFuture);
f.ships.clearMovementIntents();
f.ships.tickScrapCollector(f.scraps, f.buildings);
f.ai.tickScrapCollector(f.ships, f.scraps, f.buildings);
const Ship* s = f.ships.findShip(shipId);
REQUIRE(s->intent.priority == 1);
@@ -398,7 +402,7 @@ TEST_CASE("BehaviorSystem: salvage ship collects scrap on arrival", "[behavior]"
const EntityId scrapId = f.scraps.spawn(QVector2D(0.0f, 0.0f), 1, farFuture);
f.ships.clearMovementIntents();
f.ships.tickScrapCollector(f.scraps, f.buildings);
f.ai.tickScrapCollector(f.ships, f.scraps, f.buildings);
const Ship* s = f.ships.findShip(shipId);
REQUIRE(s->cargo->current == 1);
@@ -436,7 +440,7 @@ TEST_CASE("BehaviorSystem: full-cargo salvage ship moves toward SalvageBay", "[b
});
f.ships.clearMovementIntents();
f.ships.tickScrapCollector(f.scraps, f.buildings);
f.ai.tickScrapCollector(f.ships, f.scraps, f.buildings);
// Intent should point toward the bay (x < 0 area), not rightward.
const Ship* s = f.ships.findShip(shipId);
@@ -469,7 +473,7 @@ TEST_CASE("SensorRange: player combat ship acquires enemy just inside sensor ran
/*isEnemy=*/true);
f.ships.clearMovementIntents();
f.ships.tickThreatResponse(f.buildings);
f.ai.tickThreatResponse(f.ships, f.buildings);
const Ship* player = f.ships.findShip(playerId);
REQUIRE(player->threatResponse->currentTarget == enemyId);
@@ -483,7 +487,7 @@ 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.clearMovementIntents();
f.ships.tickThreatResponse(f.buildings);
f.ai.tickThreatResponse(f.ships, f.buildings);
const Ship* player = f.ships.findShip(playerId);
REQUIRE_FALSE(player->threatResponse->currentTarget.has_value());
@@ -498,7 +502,7 @@ TEST_CASE("SensorRange: enemy ship ignores player just outside sensor range", "[
/*isEnemy=*/true);
f.ships.clearMovementIntents();
f.ships.tickThreatResponse(f.buildings);
f.ai.tickThreatResponse(f.ships, f.buildings);
const Ship* enemy = f.ships.findShip(enemyId);
REQUIRE_FALSE(enemy->threatResponse->currentTarget.has_value());
@@ -516,7 +520,7 @@ TEST_CASE("SensorRange: repair ship retreats from enemy within sensor range", "[
f.ships.spawn("interceptor", 1, QVector2D(200.0f, 0.0f), /*isEnemy=*/true);
f.ships.clearMovementIntents();
f.ships.tickRepairBehavior(f.buildings);
f.ai.tickRepairBehavior(f.ships, f.buildings);
const Ship* repair = f.ships.findShip(repairId);
REQUIRE(repair->intent.priority == 2);
@@ -531,7 +535,7 @@ TEST_CASE("SensorRange: repair ship does not retreat from enemy beyond sensor ra
f.ships.spawn("interceptor", 1, QVector2D(300.0f, 0.0f), /*isEnemy=*/true);
f.ships.clearMovementIntents();
f.ships.tickRepairBehavior(f.buildings);
f.ai.tickRepairBehavior(f.ships, f.buildings);
// Enemy outside sensor range → repair ship patrols rightward instead of retreating.
const Ship* repair = f.ships.findShip(repairId);
@@ -549,7 +553,7 @@ TEST_CASE("SensorRange: repair ship does not acquire damaged ally beyond sensor
});
f.ships.clearMovementIntents();
f.ships.tickRepairBehavior(f.buildings);
f.ai.tickRepairBehavior(f.ships, f.buildings);
REQUIRE_FALSE(f.ships.findShip(repairId)->repairBehavior->currentTarget.has_value());
}
@@ -566,7 +570,7 @@ TEST_CASE("SensorRange: salvage ship ignores scrap beyond sensor range", "[senso
f.scraps.spawn(QVector2D(300.0f, 0.0f), 1, 100000);
f.ships.clearMovementIntents();
f.ships.tickScrapCollector(f.scraps, f.buildings);
f.ai.tickScrapCollector(f.ships, f.scraps, f.buildings);
const Ship* s = f.ships.findShip(shipId);
REQUIRE(s->scrapCollector->scrapTarget == std::nullopt);