add tests for salvager range and cooldown

This commit is contained in:
2026-06-02 21:39:05 +02:00
parent f921f00a0d
commit 64f7c9dcc1

View File

@@ -103,6 +103,22 @@ static ShipLayoutConfig makeSingleModuleLayout(const std::string& moduleId)
return layout;
}
static ShipLayoutConfig makeTwoModuleLayout(const std::string& moduleId)
{
ShipLayoutConfig layout;
PlacedModule pm1;
pm1.moduleId = moduleId;
pm1.position = QPoint(0, 0);
pm1.rotation = Rotation::East;
layout.placedModules.push_back(pm1);
PlacedModule pm2;
pm2.moduleId = moduleId;
pm2.position = QPoint(0, 1);
pm2.rotation = Rotation::East;
layout.placedModules.push_back(pm2);
return layout;
}
static entt::entity firstSalvageChild(EntityAdmin& admin, entt::entity ship)
{
entt::entity result = entt::null;
@@ -452,6 +468,165 @@ TEST_CASE("BehaviorSystem: full-cargo salvage ship moves toward SalvageBay", "[b
REQUIRE(i.target.x() < pos(f.admin, ship).value.x());
}
// ---------------------------------------------------------------------------
// Collection range (per-module)
// ---------------------------------------------------------------------------
static int totalSalvageCurrent(EntityAdmin& admin, entt::entity ship)
{
int total = 0;
admin.forEach<SalvageCargoComponent, ModuleOwnerComponent>(
[&](entt::entity /*ce*/, const SalvageCargoComponent& c, const ModuleOwnerComponent& o)
{
if (o.owner == ship) { total += c.current; }
});
return total;
}
TEST_CASE("BehaviorSystem: salvage module does not collect scrap beyond its collection range",
"[behavior]")
{
// collection_range_formula = "50"; scrap at distance 55 must not be collected.
Fixture f;
const ShipLayoutConfig salvageLayout = makeSingleModuleLayout("salvage_bay_module");
const entt::entity ship = f.ships.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f),
false, salvageLayout);
f.scraps.spawn(QVector2D(55.0f, 0.0f), 1, 100000);
f.ships.clearMovementIntents();
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
REQUIRE(f.admin.get<SalvageCargoComponent>(firstSalvageChild(f.admin, ship)).current == 0);
}
TEST_CASE("BehaviorSystem: salvage module collects scrap within its collection range",
"[behavior]")
{
// collection_range_formula = "50"; scrap at distance 45 must be collected.
Fixture f;
const ShipLayoutConfig salvageLayout = makeSingleModuleLayout("salvage_bay_module");
const entt::entity ship = f.ships.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f),
false, salvageLayout);
f.scraps.spawn(QVector2D(45.0f, 0.0f), 1, 100000);
f.ships.clearMovementIntents();
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
REQUIRE(f.admin.get<SalvageCargoComponent>(firstSalvageChild(f.admin, ship)).current == 1);
}
// ---------------------------------------------------------------------------
// Collection rate (per-module cooldown)
// ---------------------------------------------------------------------------
TEST_CASE("BehaviorSystem: salvage collection sets cooldown on module", "[behavior]")
{
Fixture f;
const ShipLayoutConfig salvageLayout = makeSingleModuleLayout("salvage_bay_module");
const entt::entity ship = f.ships.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f),
false, salvageLayout);
f.scraps.spawn(QVector2D(0.0f, 0.0f), 1, 100000);
f.ships.clearMovementIntents();
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
const SalvageCargoComponent& cargo =
f.admin.get<SalvageCargoComponent>(firstSalvageChild(f.admin, ship));
REQUIRE(cargo.current == 1);
REQUIRE(cargo.cooldownTicksRemaining == cargo.collectionIntervalTicks);
}
TEST_CASE("BehaviorSystem: salvage module on cooldown does not collect scrap", "[behavior]")
{
Fixture f;
const ShipLayoutConfig salvageLayout = makeSingleModuleLayout("salvage_bay_module");
const entt::entity ship = f.ships.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f),
false, salvageLayout);
f.scraps.spawn(QVector2D(0.0f, 0.0f), 1, 100000);
f.admin.get<SalvageCargoComponent>(firstSalvageChild(f.admin, ship)).cooldownTicksRemaining = 10;
f.ships.clearMovementIntents();
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
REQUIRE(f.admin.get<SalvageCargoComponent>(firstSalvageChild(f.admin, ship)).current == 0);
}
TEST_CASE("BehaviorSystem: salvage module collects again after cooldown expires", "[behavior]")
{
Fixture f;
const ShipLayoutConfig salvageLayout = makeSingleModuleLayout("salvage_bay_module");
const entt::entity ship = f.ships.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f),
false, salvageLayout);
const entt::entity sc = firstSalvageChild(f.admin, ship);
f.scraps.spawn(QVector2D(0.0f, 0.0f), 1, 100000);
f.ships.clearMovementIntents();
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
REQUIRE(f.admin.get<SalvageCargoComponent>(sc).current == 1);
// Shorten cooldown to 1 tick and place a second scrap.
f.admin.get<SalvageCargoComponent>(sc).cooldownTicksRemaining = 1;
f.scraps.spawn(QVector2D(0.0f, 0.0f), 1, 100000);
// Next tick: cooldown decrements to 0, module collects the second scrap.
f.ships.clearMovementIntents();
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
REQUIRE(f.admin.get<SalvageCargoComponent>(sc).current == 2);
}
// ---------------------------------------------------------------------------
// Multiple salvage modules
// ---------------------------------------------------------------------------
TEST_CASE("BehaviorSystem: two salvage modules collect independently in same tick", "[behavior]")
{
Fixture f;
const ShipLayoutConfig salvageLayout = makeTwoModuleLayout("salvage_bay_module");
const entt::entity ship = f.ships.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f),
false, salvageLayout);
f.scraps.spawn(QVector2D(0.0f, 0.0f), 1, 100000);
f.scraps.spawn(QVector2D(0.0f, 0.0f), 1, 100000);
f.ships.clearMovementIntents();
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
REQUIRE(totalSalvageCurrent(f.admin, ship) == 2);
}
TEST_CASE("BehaviorSystem: second salvage module does not collect when first module is on cooldown",
"[behavior]")
{
// One module on cooldown, one ready: only the ready module collects.
Fixture f;
const ShipLayoutConfig salvageLayout = makeTwoModuleLayout("salvage_bay_module");
const entt::entity ship = f.ships.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f),
false, salvageLayout);
// Put the first salvage child on cooldown.
entt::entity blocked = entt::null;
f.admin.forEach<SalvageCargoComponent, ModuleOwnerComponent>(
[&](entt::entity ce, SalvageCargoComponent& c, const ModuleOwnerComponent& o)
{
if (o.owner == ship && blocked == entt::null)
{
c.cooldownTicksRemaining = 99;
blocked = ce;
}
});
f.scraps.spawn(QVector2D(0.0f, 0.0f), 1, 100000);
f.scraps.spawn(QVector2D(0.0f, 0.0f), 1, 100000);
f.ships.clearMovementIntents();
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
// Only one module was ready, so only one scrap is collected.
REQUIRE(totalSalvageCurrent(f.admin, ship) == 1);
}
// ---------------------------------------------------------------------------
// Sensor range — spawn
// ---------------------------------------------------------------------------