define ship roles via added modules and allow multiple weapons

This commit is contained in:
2026-06-01 22:57:53 +02:00
parent f363f7a67c
commit 9d0a60a93b
31 changed files with 873 additions and 407 deletions

View File

@@ -13,6 +13,7 @@
#include "FireEvent.h"
#include "HealthComponent.h"
#include "HqProxyComponent.h"
#include "ModuleOwnerComponent.h"
#include "ScrapSystem.h"
#include "ShipSystem.h"
#include "Simulation.h"
@@ -30,7 +31,7 @@ static const ShipDef* findCombatShip(const GameConfig& cfg)
{
for (const ShipDef& def : cfg.ships.ships)
{
if (def.combat)
if (!def.defaultModules.empty())
{
return &def;
}
@@ -38,6 +39,17 @@ static const ShipDef* findCombatShip(const GameConfig& cfg)
return nullptr;
}
static entt::entity findWeaponChild(EntityAdmin& admin, entt::entity ship)
{
entt::entity result = entt::null;
admin.forEach<WeaponComponent, ModuleOwnerComponent>(
[&](entt::entity ce, const WeaponComponent&, const ModuleOwnerComponent& o)
{
if (o.owner == ship && result == entt::null) { result = ce; }
});
return result;
}
// Helper fixture for unit tests that need ships + combat but not a full Simulation.
struct CombatFixture
{
@@ -67,10 +79,13 @@ struct CombatFixture
void wireEnemyTarget(entt::entity enemy, entt::entity playerTarget)
{
if (admin.hasAll<WeaponComponent>(enemy))
// Set target on weapon child entity (CombatSystem syncs from ThreatResponse each tick,
// but also setting directly ensures the first tick fires without waiting for sync).
const entt::entity wc = findWeaponChild(admin, enemy);
if (wc != entt::null)
{
admin.get<WeaponComponent>(enemy).currentTarget = playerTarget;
admin.get<WeaponComponent>(enemy).cooldownTicks = 0.0f;
admin.get<WeaponComponent>(wc).currentTarget = playerTarget;
admin.get<WeaponComponent>(wc).cooldownTicks = 0.0f;
}
if (admin.hasAll<ThreatResponseBehaviorComponent>(enemy))
{
@@ -113,7 +128,11 @@ TEST_CASE("CombatSystem: cooldown prevents firing before it expires", "[combat]"
const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false);
f.wireEnemyTarget(enemy, player);
f.admin.get<WeaponComponent>(enemy).cooldownTicks = 3.0f; // override to 3
{
const entt::entity wc = findWeaponChild(f.admin, enemy);
REQUIRE(f.admin.isValid(wc));
f.admin.get<WeaponComponent>(wc).cooldownTicks = 3.0f; // override to 3
}
auto enemyFiredIn = [&enemy](const std::vector<FireEvent>& evts)
{