add tests for salvager range and cooldown
This commit is contained in:
@@ -103,6 +103,22 @@ static ShipLayoutConfig makeSingleModuleLayout(const std::string& moduleId)
|
|||||||
return layout;
|
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)
|
static entt::entity firstSalvageChild(EntityAdmin& admin, entt::entity ship)
|
||||||
{
|
{
|
||||||
entt::entity result = entt::null;
|
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());
|
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
|
// Sensor range — spawn
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user