move ecs related code to own folder
This commit is contained in:
@@ -5,6 +5,7 @@ add_subdirectory(core)
|
||||
add_subdirectory(config)
|
||||
add_subdirectory(utility)
|
||||
add_subdirectory(sim)
|
||||
add_subdirectory(ecs)
|
||||
|
||||
SET(HDRS
|
||||
${HDRS}
|
||||
|
||||
@@ -4,14 +4,12 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Rotation.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingType.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/EntityAdmin.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodyComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/EcsComponents.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Blueprint.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingId.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/FireEvent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ItemType.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Item.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Port.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/MovementIntent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/FireEvent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SchematicDropEvent.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QPoint>
|
||||
#include <QSize>
|
||||
#include <QVector2D>
|
||||
|
||||
#include "Tick.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared components (used by ships, stations, scrap, HQ proxy)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct Position
|
||||
{
|
||||
QVector2D value;
|
||||
};
|
||||
|
||||
struct Health
|
||||
{
|
||||
float hp;
|
||||
float maxHp;
|
||||
};
|
||||
|
||||
struct Faction
|
||||
{
|
||||
bool isEnemy;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Ship components (always present on every ship)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct Facing
|
||||
{
|
||||
float radians;
|
||||
};
|
||||
|
||||
struct SensorRange
|
||||
{
|
||||
float value;
|
||||
};
|
||||
|
||||
struct ShipIdentity
|
||||
{
|
||||
int level;
|
||||
std::string schematicId;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Ship optional components (hardware + behavior, in Ship.h)
|
||||
// ---------------------------------------------------------------------------
|
||||
// Weapon, SalvageCargo, RepairTool, ThreatResponseBehavior, SalvageBehavior,
|
||||
// RepairBehavior, HomeReturnBehavior, RallyBehavior remain defined in Ship.h.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Station components
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct StationBody
|
||||
{
|
||||
QPoint anchor;
|
||||
QSize footprint;
|
||||
std::vector<QPoint> bodyCells;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scrap components
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct ScrapData
|
||||
{
|
||||
int amount;
|
||||
};
|
||||
|
||||
struct DespawnAt
|
||||
{
|
||||
Tick tick;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HQ proxy (empty tag)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct HqProxy { char unused = 0; };
|
||||
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
#include "EntityAdmin.h"
|
||||
|
||||
#include "DespawnAtComponent.h"
|
||||
#include "DynamicBodyComponent.h"
|
||||
#include "EcsComponents.h"
|
||||
#include "MovementIntent.h"
|
||||
#include "FacingComponent.h"
|
||||
#include "FactionComponent.h"
|
||||
#include "HealthComponent.h"
|
||||
#include "HqProxyComponent.h"
|
||||
#include "MovementIntentComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "ScrapDataComponent.h"
|
||||
#include "SensorRangeComponent.h"
|
||||
#include "ShipIdentityComponent.h"
|
||||
#include "StationBodyComponent.h"
|
||||
|
||||
entt::entity EntityAdmin::createEntity()
|
||||
{
|
||||
@@ -31,10 +40,10 @@ entt::entity EntityAdmin::spawnShip(QVector2D position, float hp, float maxHp,
|
||||
int level, const std::string& schematicId, bool isEnemy)
|
||||
{
|
||||
entt::entity entity = createEntity();
|
||||
add<Position>(entity, Position{position});
|
||||
add<Health>(entity, Health{hp, maxHp});
|
||||
add<Faction>(entity, Faction{isEnemy});
|
||||
add<Facing>(entity, Facing{0.0f});
|
||||
add<PositionComponent>(entity, PositionComponent{position});
|
||||
add<HealthComponent>(entity, HealthComponent{hp, maxHp});
|
||||
add<FactionComponent>(entity, FactionComponent{isEnemy});
|
||||
add<FacingComponent>(entity, FacingComponent{0.0f});
|
||||
add<DynamicBodyComponent>(entity, DynamicBodyComponent{
|
||||
maxSpeedPerTick,
|
||||
mainAccelPerTick,
|
||||
@@ -46,9 +55,9 @@ entt::entity EntityAdmin::spawnShip(QVector2D position, float hp, float maxHp,
|
||||
QVector2D(0.0f, 0.0f), // linearAcceleration
|
||||
0.0f // angularAcceleration
|
||||
});
|
||||
add<SensorRange>(entity, SensorRange{sensorRange});
|
||||
add<ShipIdentity>(entity, ShipIdentity{level, schematicId});
|
||||
add<MovementIntent>(entity, MovementIntent{0, QVector2D(0.0f, 0.0f)});
|
||||
add<SensorRangeComponent>(entity, SensorRangeComponent{sensorRange});
|
||||
add<ShipIdentityComponent>(entity, ShipIdentityComponent{level, schematicId});
|
||||
add<MovementIntentComponent>(entity, MovementIntentComponent{0, QVector2D(0.0f, 0.0f)});
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -59,28 +68,28 @@ entt::entity EntityAdmin::spawnStation(QPoint anchor, QSize footprint,
|
||||
entt::entity entity = createEntity();
|
||||
QVector2D center(anchor.x() + footprint.width() / 2.0f,
|
||||
anchor.y() + footprint.height() / 2.0f);
|
||||
add<Position>(entity, Position{center});
|
||||
add<Health>(entity, Health{hp, maxHp});
|
||||
add<Faction>(entity, Faction{isEnemy});
|
||||
add<StationBody>(entity, StationBody{anchor, footprint, bodyCells});
|
||||
add<PositionComponent>(entity, PositionComponent{center});
|
||||
add<HealthComponent>(entity, HealthComponent{hp, maxHp});
|
||||
add<FactionComponent>(entity, FactionComponent{isEnemy});
|
||||
add<StationBodyComponent>(entity, StationBodyComponent{anchor, footprint, bodyCells});
|
||||
return entity;
|
||||
}
|
||||
|
||||
entt::entity EntityAdmin::spawnScrap(QVector2D position, int amount, Tick despawnAt)
|
||||
{
|
||||
entt::entity entity = createEntity();
|
||||
add<Position>(entity, Position{position});
|
||||
add<ScrapData>(entity, ScrapData{amount});
|
||||
add<DespawnAt>(entity, DespawnAt{despawnAt});
|
||||
add<PositionComponent>(entity, PositionComponent{position});
|
||||
add<ScrapDataComponent>(entity, ScrapDataComponent{amount});
|
||||
add<DespawnAtComponent>(entity, DespawnAtComponent{despawnAt});
|
||||
return entity;
|
||||
}
|
||||
|
||||
entt::entity EntityAdmin::spawnHqProxy(QVector2D position, float hp, float maxHp)
|
||||
{
|
||||
entt::entity entity = createEntity();
|
||||
add<Position>(entity, Position{position});
|
||||
add<Health>(entity, Health{hp, maxHp});
|
||||
add<Faction>(entity, Faction{false});
|
||||
add<HqProxy>(entity);
|
||||
add<PositionComponent>(entity, PositionComponent{position});
|
||||
add<HealthComponent>(entity, HealthComponent{hp, maxHp});
|
||||
add<FactionComponent>(entity, FactionComponent{false});
|
||||
add<HqProxyComponent>(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
17
src/lib/ecs/CMakeLists.txt
Normal file
17
src/lib/ecs/CMakeLists.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
add_subdirectory(component)
|
||||
add_subdirectory(system)
|
||||
|
||||
SET(HDRS
|
||||
${HDRS}
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
SET(SRCS
|
||||
${SRCS}
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
set(LIB_INCLUDE_PATH
|
||||
${LIB_INCLUDE_PATH}
|
||||
PARENT_SCOPE
|
||||
)
|
||||
30
src/lib/ecs/component/CMakeLists.txt
Normal file
30
src/lib/ecs/component/CMakeLists.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
SET(HDRS
|
||||
${HDRS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/DespawnAtComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodyComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/FacingComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/FactionComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/HealthComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/HomeReturnBehaviorComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/HqProxyComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/PositionComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/RallyBehaviorComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/RepairBehaviorComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/RepairToolComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SalvageBehaviorComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SalvageCargoComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ScrapDataComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SensorRangeComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipIdentityComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/StationBodyComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ThreatResponseBehaviorComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/WeaponComponent.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
set(LIB_INCLUDE_PATH
|
||||
${LIB_INCLUDE_PATH}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
PARENT_SCOPE
|
||||
)
|
||||
8
src/lib/ecs/component/DespawnAtComponent.h
Normal file
8
src/lib/ecs/component/DespawnAtComponent.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tick.h"
|
||||
|
||||
struct DespawnAtComponent
|
||||
{
|
||||
Tick tick;
|
||||
};
|
||||
6
src/lib/ecs/component/FacingComponent.h
Normal file
6
src/lib/ecs/component/FacingComponent.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
struct FacingComponent
|
||||
{
|
||||
float radians;
|
||||
};
|
||||
6
src/lib/ecs/component/FactionComponent.h
Normal file
6
src/lib/ecs/component/FactionComponent.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
struct FactionComponent
|
||||
{
|
||||
bool isEnemy;
|
||||
};
|
||||
7
src/lib/ecs/component/HealthComponent.h
Normal file
7
src/lib/ecs/component/HealthComponent.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
struct HealthComponent
|
||||
{
|
||||
float hp;
|
||||
float maxHp;
|
||||
};
|
||||
9
src/lib/ecs/component/HomeReturnBehaviorComponent.h
Normal file
9
src/lib/ecs/component/HomeReturnBehaviorComponent.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
struct HomeReturnBehaviorComponent
|
||||
{
|
||||
float retreatHpFraction;
|
||||
QVector2D homePos;
|
||||
};
|
||||
6
src/lib/ecs/component/HqProxyComponent.h
Normal file
6
src/lib/ecs/component/HqProxyComponent.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
struct HqProxyComponent
|
||||
{
|
||||
char unused = 0;
|
||||
};
|
||||
@@ -5,7 +5,7 @@
|
||||
// A ship-behavior system writes this each tick before movement runs; the
|
||||
// highest-priority write wins. Priority order is fixed globally — see
|
||||
// architecture.md "Movement Arbitration".
|
||||
struct MovementIntent
|
||||
struct MovementIntentComponent
|
||||
{
|
||||
int priority;
|
||||
QVector2D target;
|
||||
8
src/lib/ecs/component/PositionComponent.h
Normal file
8
src/lib/ecs/component/PositionComponent.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
struct PositionComponent
|
||||
{
|
||||
QVector2D value;
|
||||
};
|
||||
8
src/lib/ecs/component/RallyBehaviorComponent.h
Normal file
8
src/lib/ecs/component/RallyBehaviorComponent.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
struct RallyBehaviorComponent
|
||||
{
|
||||
QVector2D rallyPoint;
|
||||
};
|
||||
10
src/lib/ecs/component/RepairBehaviorComponent.h
Normal file
10
src/lib/ecs/component/RepairBehaviorComponent.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
struct RepairBehaviorComponent
|
||||
{
|
||||
std::optional<entt::entity> currentTarget;
|
||||
};
|
||||
12
src/lib/ecs/component/RepairToolComponent.h
Normal file
12
src/lib/ecs/component/RepairToolComponent.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
struct RepairToolComponent
|
||||
{
|
||||
float ratePerTick;
|
||||
float range;
|
||||
std::optional<entt::entity> currentTarget;
|
||||
};
|
||||
13
src/lib/ecs/component/SalvageBehaviorComponent.h
Normal file
13
src/lib/ecs/component/SalvageBehaviorComponent.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
#include "BuildingId.h"
|
||||
|
||||
struct SalvageBehaviorComponent
|
||||
{
|
||||
std::optional<QVector2D> scrapTarget;
|
||||
BuildingId deliveryBay; // kInvalidBuildingId until assigned at a salvage bay
|
||||
};
|
||||
8
src/lib/ecs/component/SalvageCargoComponent.h
Normal file
8
src/lib/ecs/component/SalvageCargoComponent.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
struct SalvageCargoComponent
|
||||
{
|
||||
int capacity;
|
||||
int current;
|
||||
float collectionRange;
|
||||
};
|
||||
6
src/lib/ecs/component/ScrapDataComponent.h
Normal file
6
src/lib/ecs/component/ScrapDataComponent.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
struct ScrapDataComponent
|
||||
{
|
||||
int amount;
|
||||
};
|
||||
6
src/lib/ecs/component/SensorRangeComponent.h
Normal file
6
src/lib/ecs/component/SensorRangeComponent.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
struct SensorRangeComponent
|
||||
{
|
||||
float value;
|
||||
};
|
||||
9
src/lib/ecs/component/ShipIdentityComponent.h
Normal file
9
src/lib/ecs/component/ShipIdentityComponent.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
struct ShipIdentityComponent
|
||||
{
|
||||
int level;
|
||||
std::string schematicId;
|
||||
};
|
||||
13
src/lib/ecs/component/StationBodyComponent.h
Normal file
13
src/lib/ecs/component/StationBodyComponent.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QPoint>
|
||||
#include <QSize>
|
||||
|
||||
struct StationBodyComponent
|
||||
{
|
||||
QPoint anchor;
|
||||
QSize footprint;
|
||||
std::vector<QPoint> bodyCells;
|
||||
};
|
||||
10
src/lib/ecs/component/ThreatResponseBehaviorComponent.h
Normal file
10
src/lib/ecs/component/ThreatResponseBehaviorComponent.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
struct ThreatResponseBehaviorComponent
|
||||
{
|
||||
std::optional<entt::entity> currentTarget;
|
||||
};
|
||||
14
src/lib/ecs/component/WeaponComponent.h
Normal file
14
src/lib/ecs/component/WeaponComponent.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
struct WeaponComponent
|
||||
{
|
||||
float damage;
|
||||
float range;
|
||||
float fireRateHz;
|
||||
float cooldownTicks;
|
||||
std::optional<entt::entity> currentTarget;
|
||||
};
|
||||
@@ -8,12 +8,24 @@
|
||||
#include "Building.h"
|
||||
#include "BuildingSystem.h"
|
||||
#include "BuildingType.h"
|
||||
#include "EcsComponents.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "BuildingId.h"
|
||||
#include "MovementIntent.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "FactionComponent.h"
|
||||
#include "HealthComponent.h"
|
||||
#include "HomeReturnBehaviorComponent.h"
|
||||
#include "HqProxyComponent.h"
|
||||
#include "MovementIntentComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "RallyBehaviorComponent.h"
|
||||
#include "RepairBehaviorComponent.h"
|
||||
#include "RepairToolComponent.h"
|
||||
#include "SalvageBehaviorComponent.h"
|
||||
#include "SalvageCargoComponent.h"
|
||||
#include "ScrapSystem.h"
|
||||
#include "Ship.h"
|
||||
#include "SensorRangeComponent.h"
|
||||
#include "ShipIdentityComponent.h"
|
||||
#include "StationBodyComponent.h"
|
||||
#include "ThreatResponseBehaviorComponent.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// tickHomeReturnBehavior (priority 4)
|
||||
@@ -21,14 +33,15 @@
|
||||
|
||||
void AiSystem::tickHomeReturnBehavior(EntityAdmin& admin)
|
||||
{
|
||||
admin.forEach<HomeReturnBehavior, Health, MovementIntent>(
|
||||
[](entt::entity /*e*/, const HomeReturnBehavior& homeReturnBehavior, const Health& h, MovementIntent& intent)
|
||||
admin.forEach<HomeReturnBehaviorComponent, HealthComponent, MovementIntentComponent>(
|
||||
[](entt::entity /*e*/, const HomeReturnBehaviorComponent& homeReturnBehavior,
|
||||
const HealthComponent& h, MovementIntentComponent& intent)
|
||||
{
|
||||
if (h.hp / h.maxHp < homeReturnBehavior.retreatHpFraction)
|
||||
{
|
||||
if (4 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{4, homeReturnBehavior.homePos};
|
||||
intent = MovementIntentComponent{4, homeReturnBehavior.homePos};
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -50,27 +63,32 @@ void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSyst
|
||||
};
|
||||
std::vector<CombatantInfo> combatants;
|
||||
|
||||
admin.forEach<Position, Faction, ShipIdentity>(
|
||||
[&combatants](entt::entity e, const Position& pos, const Faction& f, const ShipIdentity& /*si*/)
|
||||
admin.forEach<PositionComponent, FactionComponent, ShipIdentityComponent>(
|
||||
[&combatants](entt::entity e, const PositionComponent& pos,
|
||||
const FactionComponent& f, const ShipIdentityComponent& /*si*/)
|
||||
{
|
||||
combatants.push_back({e, pos.value, f.isEnemy, false});
|
||||
});
|
||||
|
||||
admin.forEach<Position, Faction, StationBody>(
|
||||
[&combatants](entt::entity e, const Position& pos, const Faction& f, const StationBody& /*sb*/)
|
||||
admin.forEach<PositionComponent, FactionComponent, StationBodyComponent>(
|
||||
[&combatants](entt::entity e, const PositionComponent& pos,
|
||||
const FactionComponent& f, const StationBodyComponent& /*sb*/)
|
||||
{
|
||||
combatants.push_back({e, pos.value, f.isEnemy, true});
|
||||
});
|
||||
|
||||
admin.forEach<Position, Faction, HqProxy>(
|
||||
[&combatants](entt::entity e, const Position& pos, const Faction& f, const HqProxy& /*hq*/)
|
||||
admin.forEach<PositionComponent, FactionComponent, HqProxyComponent>(
|
||||
[&combatants](entt::entity e, const PositionComponent& pos,
|
||||
const FactionComponent& f, const HqProxyComponent& /*hq*/)
|
||||
{
|
||||
combatants.push_back({e, pos.value, f.isEnemy, true});
|
||||
});
|
||||
|
||||
admin.forEach<ThreatResponseBehavior, Position, Faction, SensorRange, MovementIntent>(
|
||||
[&](entt::entity e, ThreatResponseBehavior& threatResponseBehavior, Position& pos, Faction& faction,
|
||||
SensorRange& sensor, MovementIntent& intent)
|
||||
admin.forEach<ThreatResponseBehaviorComponent, PositionComponent, FactionComponent,
|
||||
SensorRangeComponent, MovementIntentComponent>(
|
||||
[&](entt::entity e, ThreatResponseBehaviorComponent& threatResponseBehavior,
|
||||
PositionComponent& pos, FactionComponent& faction,
|
||||
SensorRangeComponent& sensor, MovementIntentComponent& intent)
|
||||
{
|
||||
const float range = sensor.value;
|
||||
|
||||
@@ -79,9 +97,10 @@ void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSyst
|
||||
if (threatResponseBehavior.currentTarget)
|
||||
{
|
||||
const entt::entity t = *threatResponseBehavior.currentTarget;
|
||||
if (admin.isValid(t) && admin.hasAll<Position>(t))
|
||||
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
|
||||
{
|
||||
const float dist = (admin.get<Position>(t).value - pos.value).length();
|
||||
const float dist =
|
||||
(admin.get<PositionComponent>(t).value - pos.value).length();
|
||||
if (dist <= range)
|
||||
{
|
||||
targetValid = true;
|
||||
@@ -122,31 +141,33 @@ void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSyst
|
||||
{
|
||||
const entt::entity t = *threatResponseBehavior.currentTarget;
|
||||
QVector2D dest = pos.value;
|
||||
if (admin.isValid(t) && admin.hasAll<Position>(t))
|
||||
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
|
||||
{
|
||||
dest = admin.get<Position>(t).value;
|
||||
dest = admin.get<PositionComponent>(t).value;
|
||||
}
|
||||
if (3 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{3, dest};
|
||||
intent = MovementIntentComponent{3, dest};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (3 > intent.priority)
|
||||
{
|
||||
if (admin.hasAll<RallyBehavior>(e))
|
||||
if (admin.hasAll<RallyBehaviorComponent>(e))
|
||||
{
|
||||
intent = MovementIntent{3, admin.get<RallyBehavior>(e).rallyPoint};
|
||||
intent = MovementIntentComponent{
|
||||
3, admin.get<RallyBehaviorComponent>(e).rallyPoint};
|
||||
}
|
||||
else if (!faction.isEnemy)
|
||||
{
|
||||
intent = MovementIntent{3, QVector2D(pos.value.x() + 1000.0f,
|
||||
pos.value.y())};
|
||||
intent = MovementIntentComponent{
|
||||
3, QVector2D(pos.value.x() + 1000.0f, pos.value.y())};
|
||||
}
|
||||
else
|
||||
{
|
||||
intent = MovementIntent{3, QVector2D(-10000.0f, pos.value.y())};
|
||||
intent = MovementIntentComponent{
|
||||
3, QVector2D(-10000.0f, pos.value.y())};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,16 +192,18 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
||||
};
|
||||
std::vector<RepairableInfo> repairables;
|
||||
|
||||
admin.forEach<ShipIdentity, Position, Faction, Health>(
|
||||
[&repairables](entt::entity e, const ShipIdentity& /*si*/,
|
||||
const Position& pos, const Faction& f, const Health& h)
|
||||
admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent, HealthComponent>(
|
||||
[&repairables](entt::entity e, const ShipIdentityComponent& /*si*/,
|
||||
const PositionComponent& pos, const FactionComponent& f,
|
||||
const HealthComponent& h)
|
||||
{
|
||||
repairables.push_back({e, pos.value, f.isEnemy, true, h.hp, h.maxHp});
|
||||
});
|
||||
|
||||
admin.forEach<StationBody, Position, Faction, Health>(
|
||||
[&repairables](entt::entity e, const StationBody& /*sb*/,
|
||||
const Position& pos, const Faction& f, const Health& h)
|
||||
admin.forEach<StationBodyComponent, PositionComponent, FactionComponent, HealthComponent>(
|
||||
[&repairables](entt::entity e, const StationBodyComponent& /*sb*/,
|
||||
const PositionComponent& pos, const FactionComponent& f,
|
||||
const HealthComponent& h)
|
||||
{
|
||||
repairables.push_back({e, pos.value, f.isEnemy, false, h.hp, h.maxHp});
|
||||
});
|
||||
@@ -191,9 +214,9 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
||||
QVector2D position;
|
||||
};
|
||||
std::vector<EnemyInfo> enemies;
|
||||
admin.forEach<ShipIdentity, Position, Faction>(
|
||||
[&enemies](entt::entity /*e*/, const ShipIdentity& /*si*/,
|
||||
const Position& pos, const Faction& f)
|
||||
admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent>(
|
||||
[&enemies](entt::entity /*e*/, const ShipIdentityComponent& /*si*/,
|
||||
const PositionComponent& pos, const FactionComponent& f)
|
||||
{
|
||||
if (f.isEnemy)
|
||||
{
|
||||
@@ -201,9 +224,11 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
||||
}
|
||||
});
|
||||
|
||||
admin.forEach<RepairBehavior, RepairTool, Position, Faction, SensorRange, MovementIntent>(
|
||||
[&](entt::entity e, RepairBehavior& rb, RepairTool& rt, Position& pos,
|
||||
Faction& /*faction*/, SensorRange& sensor, MovementIntent& intent)
|
||||
admin.forEach<RepairBehaviorComponent, RepairToolComponent, PositionComponent,
|
||||
FactionComponent, SensorRangeComponent, MovementIntentComponent>(
|
||||
[&](entt::entity e, RepairBehaviorComponent& rb, RepairToolComponent& rt,
|
||||
PositionComponent& pos, FactionComponent& /*faction*/,
|
||||
SensorRangeComponent& sensor, MovementIntentComponent& intent)
|
||||
{
|
||||
const float repairRange = rt.range;
|
||||
|
||||
@@ -221,7 +246,8 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
||||
{
|
||||
if (2 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{2, QVector2D(-10000.0f, pos.value.y())};
|
||||
intent = MovementIntentComponent{
|
||||
2, QVector2D(-10000.0f, pos.value.y())};
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -231,9 +257,9 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
||||
if (rb.currentTarget)
|
||||
{
|
||||
const entt::entity t = *rb.currentTarget;
|
||||
if (admin.isValid(t) && admin.hasAll<Health>(t))
|
||||
if (admin.isValid(t) && admin.hasAll<HealthComponent>(t))
|
||||
{
|
||||
const Health& th = admin.get<Health>(t);
|
||||
const HealthComponent& th = admin.get<HealthComponent>(t);
|
||||
if (th.hp > 0.0f && th.hp < th.maxHp)
|
||||
{
|
||||
targetValid = true;
|
||||
@@ -264,27 +290,25 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
||||
{
|
||||
if (2 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{2, QVector2D(pos.value.x() + 1000.0f,
|
||||
pos.value.y())};
|
||||
intent = MovementIntentComponent{
|
||||
2, QVector2D(pos.value.x() + 1000.0f, pos.value.y())};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const entt::entity target = *rb.currentTarget;
|
||||
QVector2D targetPos = pos.value;
|
||||
bool isShipTarget = false;
|
||||
if (admin.isValid(target) && admin.hasAll<Position>(target))
|
||||
if (admin.isValid(target) && admin.hasAll<PositionComponent>(target))
|
||||
{
|
||||
targetPos = admin.get<Position>(target).value;
|
||||
isShipTarget = admin.hasAll<ShipIdentity>(target);
|
||||
targetPos = admin.get<PositionComponent>(target).value;
|
||||
}
|
||||
|
||||
const float distToTarget = (targetPos - pos.value).length();
|
||||
if (distToTarget <= repairRange)
|
||||
{
|
||||
if (admin.isValid(target) && admin.hasAll<Health>(target))
|
||||
if (admin.isValid(target) && admin.hasAll<HealthComponent>(target))
|
||||
{
|
||||
Health& targetHealth = admin.get<Health>(target);
|
||||
HealthComponent& targetHealth = admin.get<HealthComponent>(target);
|
||||
targetHealth.hp = std::min(targetHealth.hp + rt.ratePerTick,
|
||||
targetHealth.maxHp);
|
||||
}
|
||||
@@ -292,7 +316,7 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
||||
|
||||
if (2 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{2, targetPos};
|
||||
intent = MovementIntentComponent{2, targetPos};
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -310,9 +334,9 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
||||
QVector2D position;
|
||||
};
|
||||
std::vector<EnemyShipPos> enemyShips;
|
||||
admin.forEach<ShipIdentity, Position, Faction>(
|
||||
[&enemyShips](entt::entity /*e*/, const ShipIdentity& /*si*/,
|
||||
const Position& pos, const Faction& f)
|
||||
admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent>(
|
||||
[&enemyShips](entt::entity /*e*/, const ShipIdentityComponent& /*si*/,
|
||||
const PositionComponent& pos, const FactionComponent& f)
|
||||
{
|
||||
if (f.isEnemy)
|
||||
{
|
||||
@@ -322,9 +346,11 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
||||
|
||||
const std::vector<ScrapInfo> allScrap = scraps.allScrapInfo();
|
||||
|
||||
admin.forEach<SalvageBehavior, SalvageCargo, Position, SensorRange, MovementIntent>(
|
||||
[&](entt::entity /*e*/, SalvageBehavior& salvageBehavior, SalvageCargo& cargo,
|
||||
Position& pos, SensorRange& sensor, MovementIntent& intent)
|
||||
admin.forEach<SalvageBehaviorComponent, SalvageCargoComponent, PositionComponent,
|
||||
SensorRangeComponent, MovementIntentComponent>(
|
||||
[&](entt::entity /*e*/, SalvageBehaviorComponent& salvageBehavior,
|
||||
SalvageCargoComponent& cargo, PositionComponent& pos,
|
||||
SensorRangeComponent& sensor, MovementIntentComponent& intent)
|
||||
{
|
||||
const float collectRange = cargo.collectionRange;
|
||||
|
||||
@@ -358,7 +384,7 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
||||
{
|
||||
if (1 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{1, bayPos};
|
||||
intent = MovementIntentComponent{1, bayPos};
|
||||
}
|
||||
if (bayId != kInvalidBuildingId
|
||||
&& (pos.value - bayPos).length() <= 1.0f)
|
||||
@@ -381,7 +407,8 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
||||
{
|
||||
if (1 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{1, QVector2D(-10000.0f, pos.value.y())};
|
||||
intent = MovementIntentComponent{
|
||||
1, QVector2D(-10000.0f, pos.value.y())};
|
||||
}
|
||||
retreating = true;
|
||||
break;
|
||||
@@ -409,7 +436,7 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
||||
{
|
||||
if (1 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{1, *salvageBehavior.scrapTarget};
|
||||
intent = MovementIntentComponent{1, *salvageBehavior.scrapTarget};
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -430,15 +457,15 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
||||
salvageBehavior.scrapTarget = bestPos;
|
||||
if (1 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{1, *bestPos};
|
||||
intent = MovementIntentComponent{1, *bestPos};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (1 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{1, QVector2D(pos.value.x() + 1000.0f,
|
||||
pos.value.y())};
|
||||
intent = MovementIntentComponent{
|
||||
1, QVector2D(pos.value.x() + 1000.0f, pos.value.y())};
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/lib/ecs/system/CMakeLists.txt
Normal file
27
src/lib/ecs/system/CMakeLists.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
SET(HDRS
|
||||
${HDRS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/AiSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/CombatSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodySystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
SET(SRCS
|
||||
${SRCS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/AiSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/CombatSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodySystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
set(LIB_INCLUDE_PATH
|
||||
${LIB_INCLUDE_PATH}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
PARENT_SCOPE
|
||||
)
|
||||
142
src/lib/ecs/system/CombatSystem.cpp
Normal file
142
src/lib/ecs/system/CombatSystem.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include "CombatSystem.h"
|
||||
|
||||
#include "EntityAdmin.h"
|
||||
#include "FactionComponent.h"
|
||||
#include "HealthComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "ShipIdentityComponent.h"
|
||||
#include "ThreatResponseBehaviorComponent.h"
|
||||
#include "WeaponComponent.h"
|
||||
|
||||
static constexpr Tick kWeaponImpactDelayTicks = 5;
|
||||
|
||||
CombatSystem::CombatSystem(const GameConfig& config)
|
||||
: m_config(config)
|
||||
{
|
||||
}
|
||||
|
||||
void CombatSystem::tick(Tick currentTick,
|
||||
EntityAdmin& admin,
|
||||
BuildingSystem& /*buildings*/,
|
||||
std::vector<FireEvent>& outFireEvents)
|
||||
{
|
||||
// Ship weapons.
|
||||
admin.forEach<WeaponComponent, ThreatResponseBehaviorComponent,
|
||||
PositionComponent, FactionComponent>(
|
||||
[&](entt::entity e, WeaponComponent& weapon,
|
||||
ThreatResponseBehaviorComponent& threatResponseBehavior,
|
||||
PositionComponent& pos, FactionComponent& faction)
|
||||
{
|
||||
weapon.currentTarget = threatResponseBehavior.currentTarget;
|
||||
resolveWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents);
|
||||
});
|
||||
|
||||
// Station weapons.
|
||||
admin.forEach<WeaponComponent, PositionComponent, FactionComponent>(
|
||||
[&](entt::entity e, WeaponComponent& weapon, PositionComponent& pos,
|
||||
FactionComponent& faction)
|
||||
{
|
||||
resolveWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents);
|
||||
});
|
||||
}
|
||||
|
||||
void CombatSystem::resolveWeapon(
|
||||
entt::entity shipEntity,
|
||||
WeaponComponent& weapon,
|
||||
const PositionComponent& ownPos,
|
||||
const FactionComponent& ownFaction,
|
||||
Tick currentTick,
|
||||
EntityAdmin& admin,
|
||||
std::vector<FireEvent>& out)
|
||||
{
|
||||
if (weapon.cooldownTicks > 0.0f)
|
||||
{
|
||||
weapon.cooldownTicks -= 1.0f;
|
||||
}
|
||||
if (weapon.cooldownTicks > 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate or clear existing target.
|
||||
if (weapon.currentTarget)
|
||||
{
|
||||
const entt::entity t = *weapon.currentTarget;
|
||||
if (!admin.isValid(t) || !admin.hasAll<PositionComponent>(t))
|
||||
{
|
||||
weapon.currentTarget = std::nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float distanceSquared =
|
||||
(ownPos.value - admin.get<PositionComponent>(t).value).lengthSquared();
|
||||
if (distanceSquared > weapon.range * weapon.range)
|
||||
{
|
||||
weapon.currentTarget = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire a new target if needed (nearest opposing-faction ship).
|
||||
if (!weapon.currentTarget)
|
||||
{
|
||||
float bestDistanceSquared = weapon.range * weapon.range;
|
||||
admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent>(
|
||||
[&](entt::entity candidate, const ShipIdentityComponent& /*si*/,
|
||||
const PositionComponent& candidatePos,
|
||||
const FactionComponent& candidateFaction)
|
||||
{
|
||||
const bool isValidTarget = ownFaction.isEnemy
|
||||
? !candidateFaction.isEnemy
|
||||
: candidateFaction.isEnemy;
|
||||
if (!isValidTarget)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const float distanceSquared =
|
||||
(candidatePos.value - ownPos.value).lengthSquared();
|
||||
if (distanceSquared < bestDistanceSquared)
|
||||
{
|
||||
bestDistanceSquared = distanceSquared;
|
||||
weapon.currentTarget = candidate;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!weapon.currentTarget)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const entt::entity targetEntity = *weapon.currentTarget;
|
||||
m_pendingDamage.push_back({targetEntity, weapon.damage,
|
||||
currentTick + kWeaponImpactDelayTicks});
|
||||
|
||||
FireEvent evt;
|
||||
evt.shooter = shipEntity;
|
||||
evt.target = targetEntity;
|
||||
evt.emittedAt = currentTick;
|
||||
out.push_back(evt);
|
||||
|
||||
weapon.cooldownTicks = static_cast<float>(kTickRateHz) / weapon.fireRateHz;
|
||||
}
|
||||
|
||||
void CombatSystem::applyPendingDamage(Tick currentTick, EntityAdmin& admin)
|
||||
{
|
||||
std::vector<PendingDamage>::iterator it = m_pendingDamage.begin();
|
||||
while (it != m_pendingDamage.end())
|
||||
{
|
||||
if (it->appliesAt <= currentTick)
|
||||
{
|
||||
if (admin.isValid(it->target) && admin.hasAll<HealthComponent>(it->target))
|
||||
{
|
||||
admin.get<HealthComponent>(it->target).hp -= it->amount;
|
||||
}
|
||||
it = m_pendingDamage.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,12 @@
|
||||
#include <QVector2D>
|
||||
|
||||
#include "Building.h"
|
||||
#include "EcsComponents.h"
|
||||
#include "FactionComponent.h"
|
||||
#include "FireEvent.h"
|
||||
#include "GameConfig.h"
|
||||
#include "Ship.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "Tick.h"
|
||||
#include "WeaponComponent.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
@@ -40,14 +41,13 @@ private:
|
||||
std::vector<PendingDamage> m_pendingDamage;
|
||||
|
||||
void resolveWeapon(
|
||||
entt::entity shipEntity,
|
||||
Weapon& weapon,
|
||||
const Position& ownPos,
|
||||
const Faction& ownFaction,
|
||||
Tick currentTick,
|
||||
EntityAdmin& admin,
|
||||
std::vector<FireEvent>& out);
|
||||
entt::entity shipEntity,
|
||||
WeaponComponent& weapon,
|
||||
const PositionComponent& ownPos,
|
||||
const FactionComponent& ownFaction,
|
||||
Tick currentTick,
|
||||
EntityAdmin& admin,
|
||||
std::vector<FireEvent>& out);
|
||||
|
||||
const GameConfig& m_config;
|
||||
};
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
#include <QVector2D>
|
||||
|
||||
#include "DynamicBodyComponent.h"
|
||||
#include "EcsComponents.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "FacingComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
|
||||
static float wrapAngle(float a)
|
||||
{
|
||||
@@ -20,8 +21,9 @@ static float wrapAngle(float a)
|
||||
|
||||
void DynamicBodySystem::tick(EntityAdmin& admin)
|
||||
{
|
||||
admin.forEach<Position, Facing, DynamicBodyComponent>(
|
||||
[](entt::entity /*e*/, Position& pos, Facing& facing, DynamicBodyComponent& body)
|
||||
admin.forEach<PositionComponent, FacingComponent, DynamicBodyComponent>(
|
||||
[](entt::entity /*e*/, PositionComponent& pos, FacingComponent& facing,
|
||||
DynamicBodyComponent& body)
|
||||
{
|
||||
// Integrate angular velocity, clamp to max rotation speed, then advance facing.
|
||||
body.angularVelocity += body.angularAcceleration;
|
||||
@@ -6,9 +6,10 @@
|
||||
#include <QVector2D>
|
||||
|
||||
#include "DynamicBodyComponent.h"
|
||||
#include "EcsComponents.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "MovementIntent.h"
|
||||
#include "FacingComponent.h"
|
||||
#include "MovementIntentComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
|
||||
static float wrapAngle(float a)
|
||||
{
|
||||
@@ -21,9 +22,10 @@ static float wrapAngle(float a)
|
||||
|
||||
void MovementIntentSystem::tick(EntityAdmin& admin)
|
||||
{
|
||||
admin.forEach<Position, Facing, DynamicBodyComponent, MovementIntent>(
|
||||
[](entt::entity /*e*/, const Position& pos, const Facing& facing,
|
||||
DynamicBodyComponent& body, const MovementIntent& intent)
|
||||
admin.forEach<PositionComponent, FacingComponent, DynamicBodyComponent,
|
||||
MovementIntentComponent>(
|
||||
[](entt::entity /*e*/, const PositionComponent& pos, const FacingComponent& facing,
|
||||
DynamicBodyComponent& body, const MovementIntentComponent& intent)
|
||||
{
|
||||
if (intent.priority == 0)
|
||||
{
|
||||
@@ -36,7 +38,8 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
||||
|
||||
const float angBraking = std::min(std::abs(body.angularVelocity),
|
||||
body.angularAccelerationPerTick);
|
||||
body.angularAcceleration = (body.angularVelocity >= 0.0f) ? -angBraking : angBraking;
|
||||
body.angularAcceleration =
|
||||
(body.angularVelocity >= 0.0f) ? -angBraking : angBraking;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,7 +61,8 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
||||
const float angleDiff = wrapAngle(desiredAngle - facing.radians);
|
||||
|
||||
const float rotDelta = std::max(-body.angularAccelerationPerTick,
|
||||
std::min(angleDiff, body.angularAccelerationPerTick));
|
||||
std::min(angleDiff,
|
||||
body.angularAccelerationPerTick));
|
||||
|
||||
float newAngVel = body.angularVelocity + rotDelta;
|
||||
|
||||
@@ -81,7 +85,8 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
||||
// pointing when DynamicBodySystem applies the forces.
|
||||
|
||||
const float projectedRadians = wrapAngle(facing.radians + newAngVel);
|
||||
const QVector2D facingVec(std::cos(projectedRadians), std::sin(projectedRadians));
|
||||
const QVector2D facingVec(std::cos(projectedRadians),
|
||||
std::sin(projectedRadians));
|
||||
|
||||
const float manAccel = body.maneuveringAccelerationPerTick;
|
||||
const float stoppingDist = (body.maxSpeedPerTick * body.maxSpeedPerTick)
|
||||
@@ -95,7 +100,8 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
|
||||
|
||||
const float mainAligned = std::max(0.0f,
|
||||
QVector2D::dotProduct(velError, facingVec));
|
||||
const float mainApplied = std::min(mainAligned, body.mainAccelerationPerTick);
|
||||
const float mainApplied = std::min(mainAligned,
|
||||
body.mainAccelerationPerTick);
|
||||
const QVector2D mainDelta = facingVec * mainApplied;
|
||||
|
||||
const QVector2D remaining = velError - mainDelta;
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "ScrapSystem.h"
|
||||
|
||||
#include "DespawnAtComponent.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "ScrapDataComponent.h"
|
||||
|
||||
ScrapSystem::ScrapSystem(EntityAdmin& admin)
|
||||
: m_admin(admin)
|
||||
@@ -15,8 +18,8 @@ entt::entity ScrapSystem::spawn(QVector2D position, int amount, Tick despawnAt)
|
||||
void ScrapSystem::tickDespawn(Tick currentTick)
|
||||
{
|
||||
std::vector<entt::entity> expired;
|
||||
m_admin.forEach<DespawnAt>(
|
||||
[&expired, currentTick](entt::entity e, DespawnAt& d)
|
||||
m_admin.forEach<DespawnAtComponent>(
|
||||
[&expired, currentTick](entt::entity e, DespawnAtComponent& d)
|
||||
{
|
||||
if (d.tick <= currentTick)
|
||||
{
|
||||
@@ -32,11 +35,11 @@ void ScrapSystem::tickDespawn(Tick currentTick)
|
||||
|
||||
std::optional<int> ScrapSystem::consume(entt::entity entity)
|
||||
{
|
||||
if (!m_admin.isValid(entity) || !m_admin.hasAll<ScrapData>(entity))
|
||||
if (!m_admin.isValid(entity) || !m_admin.hasAll<ScrapDataComponent>(entity))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
int amount = m_admin.get<ScrapData>(entity).amount;
|
||||
int amount = m_admin.get<ScrapDataComponent>(entity).amount;
|
||||
m_admin.destroy(entity);
|
||||
return amount;
|
||||
}
|
||||
@@ -44,10 +47,10 @@ std::optional<int> ScrapSystem::consume(entt::entity entity)
|
||||
std::vector<ScrapInfo> ScrapSystem::allScrapInfo() const
|
||||
{
|
||||
std::vector<ScrapInfo> result;
|
||||
m_admin.forEach<ScrapData>(
|
||||
[&result, this](entt::entity e, const ScrapData& /*sd*/)
|
||||
m_admin.forEach<ScrapDataComponent>(
|
||||
[&result, this](entt::entity e, const ScrapDataComponent& /*sd*/)
|
||||
{
|
||||
result.push_back(ScrapInfo{e, m_admin.get<Position>(e).value});
|
||||
result.push_back(ScrapInfo{e, m_admin.get<PositionComponent>(e).value});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
#include "EcsComponents.h"
|
||||
#include "Tick.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
@@ -5,10 +5,20 @@
|
||||
#include <utility>
|
||||
|
||||
#include "DynamicBodyComponent.h"
|
||||
#include "EcsComponents.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "FactionComponent.h"
|
||||
#include "HealthComponent.h"
|
||||
#include "ModulesConfig.h"
|
||||
#include "MovementIntentComponent.h"
|
||||
#include "RallyBehaviorComponent.h"
|
||||
#include "RepairBehaviorComponent.h"
|
||||
#include "RepairToolComponent.h"
|
||||
#include "SalvageBehaviorComponent.h"
|
||||
#include "SalvageCargoComponent.h"
|
||||
#include "SensorRangeComponent.h"
|
||||
#include "Tick.h"
|
||||
#include "ThreatResponseBehaviorComponent.h"
|
||||
#include "WeaponComponent.h"
|
||||
|
||||
ShipSystem::ShipSystem(const GameConfig& config, EntityAdmin& admin)
|
||||
: m_config(config)
|
||||
@@ -40,8 +50,8 @@ const ModuleDef* ShipSystem::findModuleDef(const std::string& id) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVector2D position,
|
||||
bool isEnemy,
|
||||
entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||
QVector2D position, bool isEnemy,
|
||||
const std::optional<ShipLayoutConfig>& layout)
|
||||
{
|
||||
const ShipDef* def = findShipDef(schematicId);
|
||||
@@ -52,12 +62,22 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto
|
||||
|
||||
float hp = static_cast<float>(def->health.hpFormula.evaluate(x));
|
||||
float maxHp = hp;
|
||||
float maxSpeedPerTick = static_cast<float>(def->movement.speedFormula.evaluate(x)) / tickRate;
|
||||
float mainAccelPerTick = static_cast<float>(def->movement.mainAccelerationFormula.evaluate(x)) / tickRate;
|
||||
float maneuveringAccelPerTick = static_cast<float>(def->movement.maneuveringAccelerationFormula.evaluate(x)) / tickRate;
|
||||
float angularAccelPerTick = static_cast<float>(def->movement.angularAccelerationFormula.evaluate(x)) / tickRate;
|
||||
float maxRotationSpeedPerTick = static_cast<float>(def->movement.maxRotationSpeedFormula.evaluate(x)) / tickRate;
|
||||
float sensorRange = static_cast<float>(def->sensor.sensorRangeFormula.evaluate(x));
|
||||
float maxSpeedPerTick = static_cast<float>(def->movement.speedFormula.evaluate(x))
|
||||
/ tickRate;
|
||||
float mainAccelPerTick = static_cast<float>(
|
||||
def->movement.mainAccelerationFormula.evaluate(x))
|
||||
/ tickRate;
|
||||
float maneuveringAccelPerTick = static_cast<float>(
|
||||
def->movement.maneuveringAccelerationFormula.evaluate(x))
|
||||
/ tickRate;
|
||||
float angularAccelPerTick = static_cast<float>(
|
||||
def->movement.angularAccelerationFormula.evaluate(x))
|
||||
/ tickRate;
|
||||
float maxRotationSpeedPerTick = static_cast<float>(
|
||||
def->movement.maxRotationSpeedFormula.evaluate(x))
|
||||
/ tickRate;
|
||||
float sensorRange = static_cast<float>(
|
||||
def->sensor.sensorRangeFormula.evaluate(x));
|
||||
|
||||
entt::entity entity = m_admin.spawnShip(
|
||||
position, hp, maxHp,
|
||||
@@ -68,45 +88,47 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto
|
||||
// Optional components based on ship role.
|
||||
if (def->combat)
|
||||
{
|
||||
Weapon w;
|
||||
WeaponComponent w;
|
||||
w.damage = static_cast<float>(def->combat->damageFormula.evaluate(x));
|
||||
w.range = static_cast<float>(def->combat->attackRangeFormula.evaluate(x));
|
||||
w.fireRateHz = static_cast<float>(def->combat->attackRateFormula.evaluate(x));
|
||||
w.cooldownTicks = 0.0f;
|
||||
w.currentTarget = std::nullopt;
|
||||
m_admin.addComponent<Weapon>(entity, w);
|
||||
m_admin.addComponent<WeaponComponent>(entity, w);
|
||||
|
||||
m_admin.addComponent<ThreatResponseBehavior>(entity, ThreatResponseBehavior{});
|
||||
m_admin.addComponent<ThreatResponseBehaviorComponent>(
|
||||
entity, ThreatResponseBehaviorComponent{});
|
||||
|
||||
if (!isEnemy)
|
||||
{
|
||||
m_admin.addComponent<RallyBehavior>(entity, RallyBehavior{m_rallyPoint});
|
||||
m_admin.addComponent<RallyBehaviorComponent>(
|
||||
entity, RallyBehaviorComponent{m_rallyPoint});
|
||||
}
|
||||
}
|
||||
|
||||
if (def->salvage)
|
||||
{
|
||||
SalvageCargo cargo;
|
||||
SalvageCargoComponent cargo;
|
||||
cargo.capacity = def->salvage->cargoCapacity;
|
||||
cargo.current = 0;
|
||||
cargo.collectionRange = static_cast<float>(def->salvage->collectionRange);
|
||||
m_admin.addComponent<SalvageCargo>(entity, cargo);
|
||||
m_admin.addComponent<SalvageCargoComponent>(entity, cargo);
|
||||
|
||||
SalvageBehavior salvageBehavior;
|
||||
SalvageBehaviorComponent salvageBehavior;
|
||||
salvageBehavior.scrapTarget = std::nullopt;
|
||||
salvageBehavior.deliveryBay = kInvalidBuildingId;
|
||||
m_admin.addComponent<SalvageBehavior>(entity, salvageBehavior);
|
||||
m_admin.addComponent<SalvageBehaviorComponent>(entity, salvageBehavior);
|
||||
}
|
||||
|
||||
if (def->repair)
|
||||
{
|
||||
RepairTool rt;
|
||||
RepairToolComponent rt;
|
||||
rt.ratePerTick = static_cast<float>(def->repair->repairRateFormula.evaluate(x));
|
||||
rt.range = static_cast<float>(def->repair->repairRangeFormula.evaluate(x));
|
||||
rt.currentTarget = std::nullopt;
|
||||
m_admin.addComponent<RepairTool>(entity, rt);
|
||||
m_admin.addComponent<RepairToolComponent>(entity, rt);
|
||||
|
||||
m_admin.addComponent<RepairBehavior>(entity, RepairBehavior{});
|
||||
m_admin.addComponent<RepairBehaviorComponent>(entity, RepairBehaviorComponent{});
|
||||
}
|
||||
|
||||
// Apply module stat modifiers (REQ-MOD-STAT-CALC).
|
||||
@@ -142,13 +164,14 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto
|
||||
if (it != mods.end())
|
||||
{
|
||||
stat = static_cast<float>(
|
||||
static_cast<double>(stat) * (1.0 + it->second.first) + it->second.second);
|
||||
static_cast<double>(stat) * (1.0 + it->second.first)
|
||||
+ it->second.second);
|
||||
}
|
||||
};
|
||||
|
||||
Health& health = m_admin.get<Health>(entity);
|
||||
DynamicBodyComponent& dynamics = m_admin.get<DynamicBodyComponent>(entity);
|
||||
SensorRange& sensor = m_admin.get<SensorRange>(entity);
|
||||
HealthComponent& health = m_admin.get<HealthComponent>(entity);
|
||||
DynamicBodyComponent& dynamics = m_admin.get<DynamicBodyComponent>(entity);
|
||||
SensorRangeComponent& sensor = m_admin.get<SensorRangeComponent>(entity);
|
||||
|
||||
applyMod(health.maxHp, "hp");
|
||||
health.hp = health.maxHp;
|
||||
@@ -159,16 +182,16 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto
|
||||
applyMod(dynamics.maxRotationSpeedPerTick, "max_rotation_speed");
|
||||
applyMod(sensor.value, "sensor_range");
|
||||
|
||||
if (m_admin.hasAll<Weapon>(entity))
|
||||
if (m_admin.hasAll<WeaponComponent>(entity))
|
||||
{
|
||||
Weapon& weapon = m_admin.get<Weapon>(entity);
|
||||
WeaponComponent& weapon = m_admin.get<WeaponComponent>(entity);
|
||||
applyMod(weapon.damage, "damage");
|
||||
applyMod(weapon.range, "attack_range");
|
||||
applyMod(weapon.fireRateHz, "attack_rate");
|
||||
}
|
||||
if (m_admin.hasAll<RepairTool>(entity))
|
||||
if (m_admin.hasAll<RepairToolComponent>(entity))
|
||||
{
|
||||
RepairTool& repairTool = m_admin.get<RepairTool>(entity);
|
||||
RepairToolComponent& repairTool = m_admin.get<RepairToolComponent>(entity);
|
||||
applyMod(repairTool.ratePerTick, "repair_rate");
|
||||
applyMod(repairTool.range, "repair_range");
|
||||
}
|
||||
@@ -184,10 +207,11 @@ void ShipSystem::despawn(entt::entity entity)
|
||||
|
||||
void ShipSystem::clearMovementIntents()
|
||||
{
|
||||
m_admin.forEach<MovementIntent>([](entt::entity /*e*/, MovementIntent& i)
|
||||
{
|
||||
i = MovementIntent{0, QVector2D(0.0f, 0.0f)};
|
||||
});
|
||||
m_admin.forEach<MovementIntentComponent>(
|
||||
[](entt::entity /*e*/, MovementIntentComponent& i)
|
||||
{
|
||||
i = MovementIntentComponent{0, QVector2D(0.0f, 0.0f)};
|
||||
});
|
||||
}
|
||||
|
||||
void ShipSystem::setRallyPoint(QVector2D point)
|
||||
@@ -198,8 +222,9 @@ void ShipSystem::setRallyPoint(QVector2D point)
|
||||
void ShipSystem::triggerRallyDeparture()
|
||||
{
|
||||
std::vector<entt::entity> toRemove;
|
||||
m_admin.forEach<RallyBehavior, Faction>(
|
||||
[&toRemove](entt::entity e, const RallyBehavior& /*rb*/, const Faction& f)
|
||||
m_admin.forEach<RallyBehaviorComponent, FactionComponent>(
|
||||
[&toRemove](entt::entity e, const RallyBehaviorComponent& /*rb*/,
|
||||
const FactionComponent& f)
|
||||
{
|
||||
if (!f.isEnemy)
|
||||
{
|
||||
@@ -208,6 +233,6 @@ void ShipSystem::triggerRallyDeparture()
|
||||
});
|
||||
for (entt::entity e : toRemove)
|
||||
{
|
||||
m_admin.removeComponent<RallyBehavior>(e);
|
||||
m_admin.removeComponent<RallyBehaviorComponent>(e);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <QVector2D>
|
||||
|
||||
#include "GameConfig.h"
|
||||
#include "Ship.h"
|
||||
#include "ShipLayout.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
@@ -5,16 +5,9 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Building.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Ship.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutBlueprint.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/AiSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodySystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/WaveSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/CombatSystem.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -24,13 +17,7 @@ SET(SRCS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/TickDriver.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/AiSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodySystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/WaveSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/CombatSystem.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
#include "CombatSystem.h"
|
||||
|
||||
#include "EcsComponents.h"
|
||||
#include "EntityAdmin.h"
|
||||
|
||||
static constexpr Tick kWeaponImpactDelayTicks = 5;
|
||||
|
||||
CombatSystem::CombatSystem(const GameConfig& config)
|
||||
: m_config(config)
|
||||
{
|
||||
}
|
||||
|
||||
void CombatSystem::tick(Tick currentTick,
|
||||
EntityAdmin& admin,
|
||||
BuildingSystem& /*buildings*/,
|
||||
std::vector<FireEvent>& outFireEvents)
|
||||
{
|
||||
// Ship weapons.
|
||||
admin.forEach<Weapon, ThreatResponseBehavior, Position, Faction>(
|
||||
[&](entt::entity e, Weapon& weapon, ThreatResponseBehavior& threatResponseBehavior, Position& pos, Faction& faction)
|
||||
{
|
||||
weapon.currentTarget = threatResponseBehavior.currentTarget;
|
||||
resolveWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents);
|
||||
});
|
||||
|
||||
// Station weapons.
|
||||
admin.forEach<Weapon, Position, Faction>(
|
||||
[&](entt::entity e, Weapon& weapon, Position& pos, Faction& faction)
|
||||
{
|
||||
resolveWeapon(e, weapon, pos, faction, currentTick, admin, outFireEvents);
|
||||
});
|
||||
}
|
||||
|
||||
void CombatSystem::resolveWeapon(
|
||||
entt::entity shipEntity,
|
||||
Weapon& weapon,
|
||||
const Position& ownPos,
|
||||
const Faction& ownFaction,
|
||||
Tick currentTick,
|
||||
EntityAdmin& admin,
|
||||
std::vector<FireEvent>& out)
|
||||
{
|
||||
if (weapon.cooldownTicks > 0.0f)
|
||||
{
|
||||
weapon.cooldownTicks -= 1.0f;
|
||||
}
|
||||
if (weapon.cooldownTicks > 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate or clear existing target.
|
||||
if (weapon.currentTarget)
|
||||
{
|
||||
const entt::entity t = *weapon.currentTarget;
|
||||
if (!admin.isValid(t) || !admin.hasAll<Position>(t))
|
||||
{
|
||||
weapon.currentTarget = std::nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float distanceSquared = (ownPos.value - admin.get<Position>(t).value).lengthSquared();
|
||||
if (distanceSquared > weapon.range * weapon.range)
|
||||
{
|
||||
weapon.currentTarget = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire a new target if needed (nearest opposing-faction ship).
|
||||
if (!weapon.currentTarget)
|
||||
{
|
||||
float bestDistanceSquared = weapon.range * weapon.range;
|
||||
admin.forEach<ShipIdentity, Position, Faction>(
|
||||
[&](entt::entity candidate, const ShipIdentity& /*si*/,
|
||||
const Position& candidatePos, const Faction& candidateFaction)
|
||||
{
|
||||
const bool isValidTarget = ownFaction.isEnemy
|
||||
? !candidateFaction.isEnemy
|
||||
: candidateFaction.isEnemy;
|
||||
if (!isValidTarget)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const float distanceSquared = (candidatePos.value - ownPos.value).lengthSquared();
|
||||
if (distanceSquared < bestDistanceSquared)
|
||||
{
|
||||
bestDistanceSquared = distanceSquared;
|
||||
weapon.currentTarget = candidate;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!weapon.currentTarget)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const entt::entity targetEntity = *weapon.currentTarget;
|
||||
m_pendingDamage.push_back({targetEntity, weapon.damage,
|
||||
currentTick + kWeaponImpactDelayTicks});
|
||||
|
||||
FireEvent evt;
|
||||
evt.shooter = shipEntity;
|
||||
evt.target = targetEntity;
|
||||
evt.emittedAt = currentTick;
|
||||
out.push_back(evt);
|
||||
|
||||
weapon.cooldownTicks = static_cast<float>(kTickRateHz) / weapon.fireRateHz;
|
||||
}
|
||||
|
||||
void CombatSystem::applyPendingDamage(Tick currentTick, EntityAdmin& admin)
|
||||
{
|
||||
std::vector<PendingDamage>::iterator it = m_pendingDamage.begin();
|
||||
while (it != m_pendingDamage.end())
|
||||
{
|
||||
if (it->appliesAt <= currentTick)
|
||||
{
|
||||
if (admin.isValid(it->target) && admin.hasAll<Health>(it->target))
|
||||
{
|
||||
admin.get<Health>(it->target).hp -= it->amount;
|
||||
}
|
||||
it = m_pendingDamage.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
#include "BuildingId.h"
|
||||
#include "MovementIntent.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hardware components — derived from config at spawn, stored on ship
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct Weapon
|
||||
{
|
||||
float damage;
|
||||
float range;
|
||||
float fireRateHz;
|
||||
float cooldownTicks;
|
||||
std::optional<entt::entity> currentTarget;
|
||||
};
|
||||
|
||||
struct SalvageCargo
|
||||
{
|
||||
int capacity;
|
||||
int current;
|
||||
float collectionRange;
|
||||
};
|
||||
|
||||
struct RepairTool
|
||||
{
|
||||
float ratePerTick;
|
||||
float range;
|
||||
std::optional<entt::entity> currentTarget;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Behavior components — AI state consumed by step-6 behavior systems
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct ThreatResponseBehavior
|
||||
{
|
||||
std::optional<entt::entity> currentTarget;
|
||||
};
|
||||
|
||||
struct SalvageBehavior
|
||||
{
|
||||
std::optional<QVector2D> scrapTarget;
|
||||
BuildingId deliveryBay; // kInvalidBuildingId until assigned at a salvage bay
|
||||
};
|
||||
|
||||
struct RepairBehavior
|
||||
{
|
||||
std::optional<entt::entity> currentTarget;
|
||||
};
|
||||
|
||||
struct HomeReturnBehavior
|
||||
{
|
||||
float retreatHpFraction;
|
||||
QVector2D homePos;
|
||||
};
|
||||
|
||||
struct RallyBehavior
|
||||
{
|
||||
QVector2D rallyPoint;
|
||||
};
|
||||
@@ -4,14 +4,19 @@
|
||||
|
||||
#include "AiSystem.h"
|
||||
#include "BuildingSystem.h"
|
||||
#include "EcsComponents.h"
|
||||
#include "CombatSystem.h"
|
||||
#include "DynamicBodySystem.h"
|
||||
#include "FactionComponent.h"
|
||||
#include "HealthComponent.h"
|
||||
#include "MovementIntentSystem.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "ScrapSystem.h"
|
||||
#include "ShipIdentityComponent.h"
|
||||
#include "ShipSystem.h"
|
||||
#include "StationBodyComponent.h"
|
||||
#include "SurfaceMask.h"
|
||||
#include "WaveSystem.h"
|
||||
#include "WeaponComponent.h"
|
||||
|
||||
Simulation::Simulation(GameConfig config, unsigned int seed)
|
||||
: m_config(std::move(config))
|
||||
@@ -228,7 +233,7 @@ void Simulation::placeInitialStructures()
|
||||
const float psHp = static_cast<float>(
|
||||
m_config.stations.playerStation.hpFormula.evaluate(psLevel));
|
||||
|
||||
Weapon psWeapon;
|
||||
WeaponComponent psWeapon;
|
||||
psWeapon.damage = static_cast<float>(
|
||||
m_config.stations.playerStation.damageFormula.evaluate(psLevel));
|
||||
psWeapon.range = static_cast<float>(
|
||||
@@ -250,7 +255,7 @@ void Simulation::placeInitialStructures()
|
||||
}
|
||||
m_playerStation1Entity = m_admin.spawnStation(
|
||||
anchor, psParsed.footprint, absCells, psHp, psHp, false);
|
||||
m_admin.addComponent<Weapon>(m_playerStation1Entity, psWeapon);
|
||||
m_admin.addComponent<WeaponComponent>(m_playerStation1Entity, psWeapon);
|
||||
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
|
||||
}
|
||||
{
|
||||
@@ -262,7 +267,7 @@ void Simulation::placeInitialStructures()
|
||||
}
|
||||
m_playerStation2Entity = m_admin.spawnStation(
|
||||
anchor, psParsed.footprint, absCells, psHp, psHp, false);
|
||||
m_admin.addComponent<Weapon>(m_playerStation2Entity, psWeapon);
|
||||
m_admin.addComponent<WeaponComponent>(m_playerStation2Entity, psWeapon);
|
||||
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
|
||||
}
|
||||
|
||||
@@ -289,7 +294,7 @@ void Simulation::placeEnemyStationSet(int generation)
|
||||
const float esHp = static_cast<float>(
|
||||
m_config.stations.enemyStation.hpFormula.evaluate(genD));
|
||||
|
||||
Weapon esWeapon;
|
||||
WeaponComponent esWeapon;
|
||||
esWeapon.damage = static_cast<float>(
|
||||
m_config.stations.enemyStation.damageFormula.evaluate(genD));
|
||||
esWeapon.range = static_cast<float>(
|
||||
@@ -311,7 +316,7 @@ void Simulation::placeEnemyStationSet(int generation)
|
||||
}
|
||||
m_currentEnemyStationEntities[0] = m_admin.spawnStation(
|
||||
anchor, esParsed.footprint, absCells, esHp, esHp, true);
|
||||
m_admin.addComponent<Weapon>(m_currentEnemyStationEntities[0], esWeapon);
|
||||
m_admin.addComponent<WeaponComponent>(m_currentEnemyStationEntities[0], esWeapon);
|
||||
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
|
||||
}
|
||||
{
|
||||
@@ -323,7 +328,7 @@ void Simulation::placeEnemyStationSet(int generation)
|
||||
}
|
||||
m_currentEnemyStationEntities[1] = m_admin.spawnStation(
|
||||
anchor, esParsed.footprint, absCells, esHp, esHp, true);
|
||||
m_admin.addComponent<Weapon>(m_currentEnemyStationEntities[1], esWeapon);
|
||||
m_admin.addComponent<WeaponComponent>(m_currentEnemyStationEntities[1], esWeapon);
|
||||
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
|
||||
}
|
||||
}
|
||||
@@ -336,8 +341,9 @@ void Simulation::tickDeathsAndLoot()
|
||||
{
|
||||
// --- Dead ships ---
|
||||
std::vector<entt::entity> deadShips;
|
||||
m_admin.forEach<ShipIdentity, Health>(
|
||||
[&deadShips](entt::entity e, const ShipIdentity& /*si*/, const Health& h)
|
||||
m_admin.forEach<ShipIdentityComponent, HealthComponent>(
|
||||
[&deadShips](entt::entity e, const ShipIdentityComponent& /*si*/,
|
||||
const HealthComponent& h)
|
||||
{
|
||||
if (h.hp <= 0.0f)
|
||||
{
|
||||
@@ -347,8 +353,8 @@ void Simulation::tickDeathsAndLoot()
|
||||
|
||||
for (entt::entity deadEntity : deadShips)
|
||||
{
|
||||
const ShipIdentity& si = m_admin.get<ShipIdentity>(deadEntity);
|
||||
const Position& pos = m_admin.get<Position>(deadEntity);
|
||||
const ShipIdentityComponent& si = m_admin.get<ShipIdentityComponent>(deadEntity);
|
||||
const PositionComponent& pos = m_admin.get<PositionComponent>(deadEntity);
|
||||
for (const ShipDef& def : m_config.ships.ships)
|
||||
{
|
||||
if (def.id == si.schematicId && def.loot.scrapDrop > 0)
|
||||
@@ -364,8 +370,9 @@ void Simulation::tickDeathsAndLoot()
|
||||
|
||||
// --- Dead stations ---
|
||||
std::vector<entt::entity> deadStations;
|
||||
m_admin.forEach<StationBody, Health>(
|
||||
[&deadStations](entt::entity e, const StationBody& /*sb*/, const Health& h)
|
||||
m_admin.forEach<StationBodyComponent, HealthComponent>(
|
||||
[&deadStations](entt::entity e, const StationBodyComponent& /*sb*/,
|
||||
const HealthComponent& h)
|
||||
{
|
||||
if (h.hp <= 0.0f)
|
||||
{
|
||||
@@ -375,9 +382,9 @@ void Simulation::tickDeathsAndLoot()
|
||||
|
||||
for (entt::entity deadEntity : deadStations)
|
||||
{
|
||||
const StationBody& sb = m_admin.get<StationBody>(deadEntity);
|
||||
const Position& pos = m_admin.get<Position>(deadEntity);
|
||||
const Faction& fac = m_admin.get<Faction>(deadEntity);
|
||||
const StationBodyComponent& sb = m_admin.get<StationBodyComponent>(deadEntity);
|
||||
const PositionComponent& pos = m_admin.get<PositionComponent>(deadEntity);
|
||||
const FactionComponent& fac = m_admin.get<FactionComponent>(deadEntity);
|
||||
|
||||
const Tick despawnAt = m_currentTick
|
||||
+ secondsToTicks(m_config.world.scrapDespawnSeconds);
|
||||
@@ -406,7 +413,7 @@ void Simulation::tickDeathsAndLoot()
|
||||
// --- HQ death check ---
|
||||
if (m_admin.isValid(m_hqProxyEntity))
|
||||
{
|
||||
const Health& hqHealth = m_admin.get<Health>(m_hqProxyEntity);
|
||||
const HealthComponent& hqHealth = m_admin.get<HealthComponent>(m_hqProxyEntity);
|
||||
if (hqHealth.hp <= 0.0f)
|
||||
{
|
||||
m_gameOver = true;
|
||||
@@ -415,9 +422,9 @@ void Simulation::tickDeathsAndLoot()
|
||||
|
||||
// --- Push check: if both current enemy stations are gone, trigger push ---
|
||||
const bool es0Gone = !m_admin.isValid(m_currentEnemyStationEntities[0])
|
||||
|| m_admin.get<Health>(m_currentEnemyStationEntities[0]).hp <= 0.0f;
|
||||
|| m_admin.get<HealthComponent>(m_currentEnemyStationEntities[0]).hp <= 0.0f;
|
||||
const bool es1Gone = !m_admin.isValid(m_currentEnemyStationEntities[1])
|
||||
|| m_admin.get<Health>(m_currentEnemyStationEntities[1]).hp <= 0.0f;
|
||||
|| m_admin.get<HealthComponent>(m_currentEnemyStationEntities[1]).hp <= 0.0f;
|
||||
|
||||
if (es0Gone && es1Gone &&
|
||||
m_currentEnemyStationEntities[0] != entt::null)
|
||||
|
||||
Reference in New Issue
Block a user