switch to ECS architecture

This commit is contained in:
2026-05-22 20:31:39 +02:00
parent c18c4e4804
commit ca07cbaf0e
34 changed files with 1943 additions and 2074 deletions

View File

@@ -6,6 +6,8 @@
#include "BuildingSystem.h"
#include "BuildingType.h"
#include "ConfigLoader.h"
#include "EcsComponents.h"
#include "EntityAdmin.h"
#include "Rotation.h"
#include "Ship.h"
#include "ShipSystem.h"
@@ -100,16 +102,23 @@ TEST_CASE("WaveSystem: Simulation pre-places HQ + 2 player + 2 enemy stations",
{
const Simulation sim(loadConfig(), 42);
int hqCount = 0;
int playerCount = 0;
int enemyCount = 0;
// HQ is still a Building (for belt integration).
int hqCount = 0;
for (const Building& b : sim.buildings().allBuildings())
{
if (b.type == BuildingType::Hq) { ++hqCount; }
else if (b.type == BuildingType::PlayerDefenceStation) { ++playerCount; }
else if (b.type == BuildingType::EnemyDefenceStation) { ++enemyCount; }
if (b.type == BuildingType::Hq) { ++hqCount; }
}
// Stations are ECS entities.
int playerCount = 0;
int enemyCount = 0;
sim.admin().forEach<StationBody, Faction>(
[&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f)
{
if (f.isEnemy) { ++enemyCount; }
else { ++playerCount; }
});
REQUIRE(hqCount == 1);
REQUIRE(playerCount == 2);
REQUIRE(enemyCount == 2);
@@ -159,16 +168,18 @@ TEST_CASE("WaveSystem: player stations have weapon set", "[wave]")
const Simulation sim(loadConfig(), 42);
int armedPlayerStations = 0;
for (const Building& b : sim.buildings().allBuildings())
{
if (b.type == BuildingType::PlayerDefenceStation && b.weapon)
sim.admin().forEach<StationBody, Faction, StationWeapon>(
[&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f,
const StationWeapon& w)
{
++armedPlayerStations;
REQUIRE(b.weapon->damage > 0.0f);
REQUIRE(b.weapon->range > 0.0f);
REQUIRE(b.weapon->fireRateHz > 0.0f);
}
}
if (!f.isEnemy)
{
++armedPlayerStations;
REQUIRE(w.damage > 0.0f);
REQUIRE(w.range > 0.0f);
REQUIRE(w.fireRateHz > 0.0f);
}
});
REQUIRE(armedPlayerStations == 2);
}
@@ -177,16 +188,18 @@ TEST_CASE("WaveSystem: enemy stations have weapon set", "[wave]")
const Simulation sim(loadConfig(), 42);
int armedEnemyStations = 0;
for (const Building& b : sim.buildings().allBuildings())
{
if (b.type == BuildingType::EnemyDefenceStation && b.weapon)
sim.admin().forEach<StationBody, Faction, StationWeapon>(
[&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f,
const StationWeapon& w)
{
++armedEnemyStations;
REQUIRE(b.weapon->damage > 0.0f);
REQUIRE(b.weapon->range > 0.0f);
REQUIRE(b.weapon->fireRateHz > 0.0f);
}
}
if (f.isEnemy)
{
++armedEnemyStations;
REQUIRE(w.damage > 0.0f);
REQUIRE(w.range > 0.0f);
REQUIRE(w.fireRateHz > 0.0f);
}
});
REQUIRE(armedEnemyStations == 2);
}
@@ -207,14 +220,11 @@ TEST_CASE("WaveSystem: enemy ships spawn after the initial gap elapses", "[wave]
}
bool foundEnemyShip = false;
for (const Ship& s : sim.ships().allShips())
{
if (s.isEnemy)
sim.admin().forEach<ShipIdentity, Faction>(
[&](entt::entity /*e*/, const ShipIdentity& /*si*/, const Faction& f)
{
foundEnemyShip = true;
break;
}
}
if (f.isEnemy) { foundEnemyShip = true; }
});
REQUIRE(foundEnemyShip);
}
@@ -229,13 +239,14 @@ TEST_CASE("WaveSystem: only eligible ships (cost > 0) appear in waves", "[wave]"
sim.tick();
}
for (const Ship& s : sim.ships().allShips())
{
if (!s.isEnemy) { continue; }
// salvage_ship and repair_ship have cost_formula = "0" and must not spawn.
REQUIRE(s.schematicId != "salvage_ship");
REQUIRE(s.schematicId != "repair_ship");
}
sim.admin().forEach<ShipIdentity, Faction>(
[&](entt::entity /*e*/, const ShipIdentity& si, const Faction& f)
{
if (!f.isEnemy) { return; }
// salvage_ship and repair_ship have cost_formula = "0" and must not spawn.
REQUIRE(si.schematicId != "salvage_ship");
REQUIRE(si.schematicId != "repair_ship");
});
}
// ---------------------------------------------------------------------------
@@ -247,22 +258,21 @@ TEST_CASE("WaveSystem: destroying both enemy stations triggers a push", "[wave]"
Simulation sim(loadConfig(), 42);
// Damage both enemy stations to 0.
sim.buildings().forEachBuilding([](Building& b)
{
if (b.type == BuildingType::EnemyDefenceStation)
sim.admin().forEach<StationBody, Faction, Health>(
[](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, Health& h)
{
b.hp = -1.0f;
}
});
if (f.isEnemy) { h.hp = -1.0f; }
});
sim.tick();
// After push: should have 2 new enemy stations.
int enemyCount = 0;
for (const Building& b : sim.buildings().allBuildings())
{
if (b.type == BuildingType::EnemyDefenceStation) { ++enemyCount; }
}
sim.admin().forEach<StationBody, Faction>(
[&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f)
{
if (f.isEnemy) { ++enemyCount; }
});
REQUIRE(enemyCount == 2);
}
@@ -270,13 +280,11 @@ TEST_CASE("WaveSystem: push emits exactly one SchematicDropEvent", "[wave]")
{
Simulation sim(loadConfig(), 42);
sim.buildings().forEachBuilding([](Building& b)
{
if (b.type == BuildingType::EnemyDefenceStation)
sim.admin().forEach<StationBody, Faction, Health>(
[](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, Health& h)
{
b.hp = -1.0f;
}
});
if (f.isEnemy) { h.hp = -1.0f; }
});
sim.tick();
@@ -288,13 +296,11 @@ TEST_CASE("WaveSystem: push schematic drop awards a known ship id", "[wave]")
{
Simulation sim(loadConfig(), 42);
sim.buildings().forEachBuilding([](Building& b)
{
if (b.type == BuildingType::EnemyDefenceStation)
sim.admin().forEach<StationBody, Faction, Health>(
[](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, Health& h)
{
b.hp = -1.0f;
}
});
if (f.isEnemy) { h.hp = -1.0f; }
});
sim.tick();
const std::vector<SchematicDropEvent> events = sim.drainSchematicDropEvents();
@@ -319,28 +325,31 @@ TEST_CASE("WaveSystem: push places new enemy stations further right", "[wave]")
// Record the X position of the initial enemy stations.
int initialX = std::numeric_limits<int>::min();
for (const Building& b : sim.buildings().allBuildings())
{
if (b.type == BuildingType::EnemyDefenceStation)
sim.admin().forEach<StationBody, Faction>(
[&](entt::entity /*e*/, const StationBody& sb, const Faction& f)
{
if (b.anchor.x() > initialX) { initialX = b.anchor.x(); }
}
}
if (f.isEnemy && sb.anchor.x() > initialX)
{
initialX = sb.anchor.x();
}
});
sim.buildings().forEachBuilding([](Building& b)
{
if (b.type == BuildingType::EnemyDefenceStation) { b.hp = -1.0f; }
});
sim.admin().forEach<StationBody, Faction, Health>(
[](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, Health& h)
{
if (f.isEnemy) { h.hp = -1.0f; }
});
sim.tick();
int newX = std::numeric_limits<int>::min();
for (const Building& b : sim.buildings().allBuildings())
{
if (b.type == BuildingType::EnemyDefenceStation)
sim.admin().forEach<StationBody, Faction>(
[&](entt::entity /*e*/, const StationBody& sb, const Faction& f)
{
if (b.anchor.x() > newX) { newX = b.anchor.x(); }
}
}
if (f.isEnemy && sb.anchor.x() > newX)
{
newX = sb.anchor.x();
}
});
REQUIRE(newX > initialX);
}