Compare commits
5 Commits
f5f4453e2c
...
9e36c13635
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e36c13635 | |||
| 25ff3c56c5 | |||
| 8ad7530740 | |||
| fa714335dc | |||
| 0cd0529468 |
@@ -10,15 +10,20 @@
|
|||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
#include "CombatSystem.h"
|
#include "CombatSystem.h"
|
||||||
#include "EcsComponents.h"
|
#include "DynamicBodySystem.h"
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
#include "MovementSystem.h"
|
#include "FactionComponent.h"
|
||||||
|
#include "HealthComponent.h"
|
||||||
|
#include "MovementIntentSystem.h"
|
||||||
|
#include "PositionComponent.h"
|
||||||
#include "ScrapSystem.h"
|
#include "ScrapSystem.h"
|
||||||
#include "Ship.h"
|
#include "ShipIdentityComponent.h"
|
||||||
#include "ShipSystem.h"
|
#include "ShipSystem.h"
|
||||||
#include "ShipsConfig.h"
|
#include "ShipsConfig.h"
|
||||||
|
#include "StationBodyComponent.h"
|
||||||
#include "StationsConfig.h"
|
#include "StationsConfig.h"
|
||||||
#include "SurfaceMask.h"
|
#include "SurfaceMask.h"
|
||||||
|
#include "WeaponComponent.h"
|
||||||
|
|
||||||
ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig,
|
ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig,
|
||||||
ArenaConfig arenaConfig,
|
ArenaConfig arenaConfig,
|
||||||
@@ -45,7 +50,8 @@ ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig,
|
|||||||
|
|
||||||
m_shipSystem = std::make_unique<ShipSystem>(m_gameConfig, m_admin);
|
m_shipSystem = std::make_unique<ShipSystem>(m_gameConfig, m_admin);
|
||||||
m_aiSystem = std::make_unique<AiSystem>();
|
m_aiSystem = std::make_unique<AiSystem>();
|
||||||
m_movementSystem = std::make_unique<MovementSystem>();
|
m_movementIntentSystem = std::make_unique<MovementIntentSystem>();
|
||||||
|
m_dynamicBodySystem = std::make_unique<DynamicBodySystem>();
|
||||||
m_combatSystem = std::make_unique<CombatSystem>(m_gameConfig);
|
m_combatSystem = std::make_unique<CombatSystem>(m_gameConfig);
|
||||||
m_scrapSystem = std::make_unique<ScrapSystem>(m_admin);
|
m_scrapSystem = std::make_unique<ScrapSystem>(m_admin);
|
||||||
|
|
||||||
@@ -85,8 +91,6 @@ void ArenaSimulation::placeStructures()
|
|||||||
{
|
{
|
||||||
absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
|
absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
|
||||||
}
|
}
|
||||||
const QVector2D center(anchorX + hqParsed.footprint.width() / 2.0f,
|
|
||||||
anchorY + hqParsed.footprint.height() / 2.0f);
|
|
||||||
m_team1HqEntity = m_admin.spawnStation(anchor, hqParsed.footprint, absCells,
|
m_team1HqEntity = m_admin.spawnStation(anchor, hqParsed.footprint, absCells,
|
||||||
hp, hp, false);
|
hp, hp, false);
|
||||||
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
|
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
|
||||||
@@ -114,7 +118,7 @@ void ArenaSimulation::placeStructures()
|
|||||||
auto placeArenaStation = [&](const ArenaStationEntry& entry, bool isEnemy)
|
auto placeArenaStation = [&](const ArenaStationEntry& entry, bool isEnemy)
|
||||||
{
|
{
|
||||||
float hp = 0.0f;
|
float hp = 0.0f;
|
||||||
Weapon weapon;
|
WeaponComponent weapon;
|
||||||
weapon.cooldownTicks = 0.0f;
|
weapon.cooldownTicks = 0.0f;
|
||||||
weapon.currentTarget = std::nullopt;
|
weapon.currentTarget = std::nullopt;
|
||||||
const double lv = static_cast<double>(entry.level);
|
const double lv = static_cast<double>(entry.level);
|
||||||
@@ -155,7 +159,7 @@ void ArenaSimulation::placeStructures()
|
|||||||
}
|
}
|
||||||
const entt::entity stationEntity = m_admin.spawnStation(
|
const entt::entity stationEntity = m_admin.spawnStation(
|
||||||
anchor, parsed.footprint, absCells, hp, hp, isEnemy);
|
anchor, parsed.footprint, absCells, hp, hp, isEnemy);
|
||||||
m_admin.addComponent<Weapon>(stationEntity, weapon);
|
m_admin.addComponent<WeaponComponent>(stationEntity, weapon);
|
||||||
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
|
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -255,7 +259,8 @@ void ArenaSimulation::tick()
|
|||||||
tickDeaths();
|
tickDeaths();
|
||||||
|
|
||||||
// Movement (tick step 10).
|
// Movement (tick step 10).
|
||||||
m_movementSystem->tick(m_admin);
|
m_movementIntentSystem->tick(m_admin);
|
||||||
|
m_dynamicBodySystem->tick(m_admin);
|
||||||
|
|
||||||
// Scrap despawn (tick step 11).
|
// Scrap despawn (tick step 11).
|
||||||
m_scrapSystem->tickDespawn(m_currentTick);
|
m_scrapSystem->tickDespawn(m_currentTick);
|
||||||
@@ -272,8 +277,9 @@ void ArenaSimulation::tickDeaths()
|
|||||||
{
|
{
|
||||||
// Dead ships.
|
// Dead ships.
|
||||||
std::vector<entt::entity> deadShips;
|
std::vector<entt::entity> deadShips;
|
||||||
m_admin.forEach<ShipIdentity, Health>(
|
m_admin.forEach<ShipIdentityComponent, HealthComponent>(
|
||||||
[&deadShips](entt::entity e, const ShipIdentity& /*si*/, const Health& h)
|
[&deadShips](entt::entity e, const ShipIdentityComponent& /*si*/,
|
||||||
|
const HealthComponent& h)
|
||||||
{
|
{
|
||||||
if (h.hp <= 0.0f)
|
if (h.hp <= 0.0f)
|
||||||
{
|
{
|
||||||
@@ -283,8 +289,8 @@ void ArenaSimulation::tickDeaths()
|
|||||||
|
|
||||||
for (entt::entity deadEntity : deadShips)
|
for (entt::entity deadEntity : deadShips)
|
||||||
{
|
{
|
||||||
const ShipIdentity& si = m_admin.get<ShipIdentity>(deadEntity);
|
const ShipIdentityComponent& si = m_admin.get<ShipIdentityComponent>(deadEntity);
|
||||||
const Position& pos = m_admin.get<Position>(deadEntity);
|
const PositionComponent& pos = m_admin.get<PositionComponent>(deadEntity);
|
||||||
for (const ShipDef& def : m_gameConfig.ships.ships)
|
for (const ShipDef& def : m_gameConfig.ships.ships)
|
||||||
{
|
{
|
||||||
if (def.id == si.schematicId && def.loot.scrapDrop > 0)
|
if (def.id == si.schematicId && def.loot.scrapDrop > 0)
|
||||||
@@ -300,8 +306,9 @@ void ArenaSimulation::tickDeaths()
|
|||||||
|
|
||||||
// Dead stations.
|
// Dead stations.
|
||||||
std::vector<entt::entity> deadStations;
|
std::vector<entt::entity> deadStations;
|
||||||
m_admin.forEach<StationBody, Health>(
|
m_admin.forEach<StationBodyComponent, HealthComponent>(
|
||||||
[&deadStations](entt::entity e, const StationBody& /*sb*/, const Health& h)
|
[&deadStations](entt::entity e, const StationBodyComponent& /*sb*/,
|
||||||
|
const HealthComponent& h)
|
||||||
{
|
{
|
||||||
if (h.hp <= 0.0f)
|
if (h.hp <= 0.0f)
|
||||||
{
|
{
|
||||||
@@ -311,16 +318,16 @@ void ArenaSimulation::tickDeaths()
|
|||||||
|
|
||||||
for (entt::entity deadEntity : deadStations)
|
for (entt::entity deadEntity : deadStations)
|
||||||
{
|
{
|
||||||
const StationBody& sb = m_admin.get<StationBody>(deadEntity);
|
const StationBodyComponent& sb = m_admin.get<StationBodyComponent>(deadEntity);
|
||||||
m_buildingSystem->unregisterTileOccupancy(sb.bodyCells);
|
m_buildingSystem->unregisterTileOccupancy(sb.bodyCells);
|
||||||
m_admin.destroy(deadEntity);
|
m_admin.destroy(deadEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check end conditions — HQ proxy entities.
|
// Check end conditions — HQ proxy entities.
|
||||||
const bool team1HqGone = !m_admin.isValid(m_team1HqEntity)
|
const bool team1HqGone = !m_admin.isValid(m_team1HqEntity)
|
||||||
|| m_admin.get<Health>(m_team1HqEntity).hp <= 0.0f;
|
|| m_admin.get<HealthComponent>(m_team1HqEntity).hp <= 0.0f;
|
||||||
const bool team2HqGone = !m_admin.isValid(m_team2HqEntity)
|
const bool team2HqGone = !m_admin.isValid(m_team2HqEntity)
|
||||||
|| m_admin.get<Health>(m_team2HqEntity).hp <= 0.0f;
|
|| m_admin.get<HealthComponent>(m_team2HqEntity).hp <= 0.0f;
|
||||||
|
|
||||||
if (team1HqGone || team2HqGone)
|
if (team1HqGone || team2HqGone)
|
||||||
{
|
{
|
||||||
@@ -333,19 +340,19 @@ void ArenaSimulation::tickDeaths()
|
|||||||
// Check if all ships and defence stations of one team are destroyed.
|
// Check if all ships and defence stations of one team are destroyed.
|
||||||
bool team1HasUnits = false;
|
bool team1HasUnits = false;
|
||||||
bool team2HasUnits = false;
|
bool team2HasUnits = false;
|
||||||
m_admin.forEach<ShipIdentity, Faction>(
|
m_admin.forEach<ShipIdentityComponent, FactionComponent>(
|
||||||
[&team1HasUnits, &team2HasUnits](entt::entity /*e*/,
|
[&team1HasUnits, &team2HasUnits](entt::entity /*e*/,
|
||||||
const ShipIdentity& /*si*/,
|
const ShipIdentityComponent& /*si*/,
|
||||||
const Faction& f)
|
const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (f.isEnemy) { team2HasUnits = true; }
|
if (f.isEnemy) { team2HasUnits = true; }
|
||||||
else { team1HasUnits = true; }
|
else { team1HasUnits = true; }
|
||||||
});
|
});
|
||||||
|
|
||||||
m_admin.forEach<StationBody, Faction>(
|
m_admin.forEach<StationBodyComponent, FactionComponent>(
|
||||||
[&team1HasUnits, &team2HasUnits](entt::entity /*e*/,
|
[&team1HasUnits, &team2HasUnits](entt::entity /*e*/,
|
||||||
const StationBody& /*sb*/,
|
const StationBodyComponent& /*sb*/,
|
||||||
const Faction& f)
|
const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (f.isEnemy) { team2HasUnits = true; }
|
if (f.isEnemy) { team2HasUnits = true; }
|
||||||
else { team1HasUnits = true; }
|
else { team1HasUnits = true; }
|
||||||
@@ -439,7 +446,7 @@ void ArenaSimulation::updateStatus()
|
|||||||
hqEntry.total = 1;
|
hqEntry.total = 1;
|
||||||
const entt::entity hqEntity = (ti == 0) ? m_team1HqEntity : m_team2HqEntity;
|
const entt::entity hqEntity = (ti == 0) ? m_team1HqEntity : m_team2HqEntity;
|
||||||
hqEntry.surviving = (m_admin.isValid(hqEntity)
|
hqEntry.surviving = (m_admin.isValid(hqEntity)
|
||||||
&& m_admin.get<Health>(hqEntity).hp > 0.0f) ? 1 : 0;
|
&& m_admin.get<HealthComponent>(hqEntity).hp > 0.0f) ? 1 : 0;
|
||||||
teamStatus.entries.push_back(hqEntry);
|
teamStatus.entries.push_back(hqEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,9 +460,10 @@ void ArenaSimulation::updateStatus()
|
|||||||
|
|
||||||
int surviving = 0;
|
int surviving = 0;
|
||||||
const bool isEnemyTeam = (ti == 1);
|
const bool isEnemyTeam = (ti == 1);
|
||||||
m_admin.forEach<ShipIdentity, Faction, Health>(
|
m_admin.forEach<ShipIdentityComponent, FactionComponent, HealthComponent>(
|
||||||
[&surviving, &shipEntry, isEnemyTeam](entt::entity /*e*/,
|
[&surviving, &shipEntry, isEnemyTeam](entt::entity /*e*/,
|
||||||
const ShipIdentity& si, const Faction& f, const Health& h)
|
const ShipIdentityComponent& si, const FactionComponent& f,
|
||||||
|
const HealthComponent& h)
|
||||||
{
|
{
|
||||||
if (f.isEnemy == isEnemyTeam
|
if (f.isEnemy == isEnemyTeam
|
||||||
&& si.schematicId == shipEntry.schematicId
|
&& si.schematicId == shipEntry.schematicId
|
||||||
@@ -481,9 +489,10 @@ void ArenaSimulation::updateStatus()
|
|||||||
|
|
||||||
int surviving = 0;
|
int surviving = 0;
|
||||||
const bool isEnemyTeam = (ti == 1);
|
const bool isEnemyTeam = (ti == 1);
|
||||||
m_admin.forEach<StationBody, Faction, Health>(
|
m_admin.forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||||
[&surviving, &stationEntry, isEnemyTeam](entt::entity /*e*/,
|
[&surviving, &stationEntry, isEnemyTeam](entt::entity /*e*/,
|
||||||
const StationBody& sb, const Faction& f, const Health& h)
|
const StationBodyComponent& sb, const FactionComponent& f,
|
||||||
|
const HealthComponent& h)
|
||||||
{
|
{
|
||||||
if (f.isEnemy == isEnemyTeam
|
if (f.isEnemy == isEnemyTeam
|
||||||
&& sb.anchor == stationEntry.position
|
&& sb.anchor == stationEntry.position
|
||||||
@@ -500,4 +509,3 @@ void ArenaSimulation::updateStatus()
|
|||||||
std::lock_guard<std::mutex> lock(m_statusMutex);
|
std::lock_guard<std::mutex> lock(m_statusMutex);
|
||||||
m_status = newStatus;
|
m_status = newStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
#include "BalancingConfig.h"
|
#include "BalancingConfig.h"
|
||||||
#include "BeltSystem.h"
|
#include "BeltSystem.h"
|
||||||
#include "EcsComponents.h"
|
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
#include "BuildingId.h"
|
#include "BuildingId.h"
|
||||||
|
|
||||||
@@ -21,7 +20,8 @@
|
|||||||
class AiSystem;
|
class AiSystem;
|
||||||
class BuildingSystem;
|
class BuildingSystem;
|
||||||
class CombatSystem;
|
class CombatSystem;
|
||||||
class MovementSystem;
|
class DynamicBodySystem;
|
||||||
|
class MovementIntentSystem;
|
||||||
class ShipSystem;
|
class ShipSystem;
|
||||||
class ScrapSystem;
|
class ScrapSystem;
|
||||||
|
|
||||||
@@ -92,7 +92,8 @@ private:
|
|||||||
std::unique_ptr<BuildingSystem> m_buildingSystem;
|
std::unique_ptr<BuildingSystem> m_buildingSystem;
|
||||||
std::unique_ptr<ShipSystem> m_shipSystem;
|
std::unique_ptr<ShipSystem> m_shipSystem;
|
||||||
std::unique_ptr<AiSystem> m_aiSystem;
|
std::unique_ptr<AiSystem> m_aiSystem;
|
||||||
std::unique_ptr<MovementSystem> m_movementSystem;
|
std::unique_ptr<MovementIntentSystem> m_movementIntentSystem;
|
||||||
|
std::unique_ptr<DynamicBodySystem> m_dynamicBodySystem;
|
||||||
std::unique_ptr<CombatSystem> m_combatSystem;
|
std::unique_ptr<CombatSystem> m_combatSystem;
|
||||||
std::unique_ptr<ScrapSystem> m_scrapSystem;
|
std::unique_ptr<ScrapSystem> m_scrapSystem;
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,15 @@
|
|||||||
#include "ArenaSimulation.h"
|
#include "ArenaSimulation.h"
|
||||||
#include "Building.h"
|
#include "Building.h"
|
||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
#include "EcsComponents.h"
|
#include "FacingComponent.h"
|
||||||
|
#include "FactionComponent.h"
|
||||||
|
#include "HealthComponent.h"
|
||||||
|
#include "PositionComponent.h"
|
||||||
|
#include "RepairToolComponent.h"
|
||||||
|
#include "SalvageCargoComponent.h"
|
||||||
#include "ScrapSystem.h"
|
#include "ScrapSystem.h"
|
||||||
#include "Ship.h"
|
#include "ShipIdentityComponent.h"
|
||||||
|
#include "StationBodyComponent.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -100,9 +106,9 @@ void ArenaView::onFrame()
|
|||||||
{
|
{
|
||||||
float maxRadius = 0.125f;
|
float maxRadius = 0.125f;
|
||||||
if (m_sim->admin().isValid(fe.target)
|
if (m_sim->admin().isValid(fe.target)
|
||||||
&& m_sim->admin().hasAll<StationBody>(fe.target))
|
&& m_sim->admin().hasAll<StationBodyComponent>(fe.target))
|
||||||
{
|
{
|
||||||
const StationBody& sb = m_sim->admin().get<StationBody>(fe.target);
|
const StationBodyComponent& sb = m_sim->admin().get<StationBodyComponent>(fe.target);
|
||||||
const int shorter = std::min(sb.footprint.width(),
|
const int shorter = std::min(sb.footprint.width(),
|
||||||
sb.footprint.height());
|
sb.footprint.height());
|
||||||
maxRadius = shorter / 2.0f;
|
maxRadius = shorter / 2.0f;
|
||||||
@@ -196,11 +202,11 @@ QRectF ArenaView::tileRect(QPoint tile) const
|
|||||||
|
|
||||||
std::optional<QVector2D> ArenaView::entityPosition(entt::entity entity) const
|
std::optional<QVector2D> ArenaView::entityPosition(entt::entity entity) const
|
||||||
{
|
{
|
||||||
if (!m_sim->admin().isValid(entity) || !m_sim->admin().hasAll<Position>(entity))
|
if (!m_sim->admin().isValid(entity) || !m_sim->admin().hasAll<PositionComponent>(entity))
|
||||||
{
|
{
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return m_sim->admin().get<Position>(entity).value;
|
return m_sim->admin().get<PositionComponent>(entity).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -272,8 +278,8 @@ void ArenaView::drawScrap(QPainter& painter)
|
|||||||
|
|
||||||
void ArenaView::drawStations(QPainter& painter)
|
void ArenaView::drawStations(QPainter& painter)
|
||||||
{
|
{
|
||||||
m_sim->admin().forEach<StationBody, Faction, Health>(
|
m_sim->admin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||||
[&](entt::entity /*e*/, const StationBody& sb, const Faction& f, const Health& h)
|
[&](entt::entity /*e*/, const StationBodyComponent& sb, const FactionComponent& f, const HealthComponent& h)
|
||||||
{
|
{
|
||||||
const BuildingType visType = f.isEnemy
|
const BuildingType visType = f.isEnemy
|
||||||
? BuildingType::EnemyDefenceStation
|
? BuildingType::EnemyDefenceStation
|
||||||
@@ -314,21 +320,21 @@ void ArenaView::drawStations(QPainter& painter)
|
|||||||
|
|
||||||
void ArenaView::drawShips(QPainter& painter)
|
void ArenaView::drawShips(QPainter& painter)
|
||||||
{
|
{
|
||||||
m_sim->admin().forEach<ShipIdentity, Position, Velocity, Faction>(
|
m_sim->admin().forEach<ShipIdentityComponent, PositionComponent, FacingComponent,
|
||||||
[&](entt::entity e, const ShipIdentity& /*si*/, const Position& pos,
|
FactionComponent>(
|
||||||
const Velocity& vel, const Faction& fac)
|
[&](entt::entity e, const ShipIdentityComponent& /*si*/,
|
||||||
|
const PositionComponent& pos, const FacingComponent& facing,
|
||||||
|
const FactionComponent& fac)
|
||||||
{
|
{
|
||||||
const bool hasCargo = m_sim->admin().hasAll<SalvageCargo>(e);
|
const bool hasCargo = m_sim->admin().hasAll<SalvageCargoComponent>(e);
|
||||||
const bool hasRepair = m_sim->admin().hasAll<RepairTool>(e);
|
const bool hasRepair = m_sim->admin().hasAll<RepairToolComponent>(e);
|
||||||
const ShipRole role = shipRoleFromComponents(fac.isEnemy, hasCargo, hasRepair);
|
const ShipRole role = shipRoleFromComponents(fac.isEnemy, hasCargo, hasRepair);
|
||||||
const std::map<ShipRole, ShipVisuals>::const_iterator it =
|
const std::map<ShipRole, ShipVisuals>::const_iterator it =
|
||||||
m_visuals->ships.find(role);
|
m_visuals->ships.find(role);
|
||||||
if (it == m_visuals->ships.end()) { return; }
|
if (it == m_visuals->ships.end()) { return; }
|
||||||
|
|
||||||
const QPointF center = worldToWidget(pos.value);
|
const QPointF center = worldToWidget(pos.value);
|
||||||
const QVector2D dir = (vel.value.length() > 0.0001f)
|
const QVector2D dir(std::cos(facing.radians), std::sin(facing.radians));
|
||||||
? vel.value.normalized()
|
|
||||||
: QVector2D(1.0f, 0.0f);
|
|
||||||
const QVector2D perp(-dir.y(), dir.x());
|
const QVector2D perp(-dir.y(), dir.x());
|
||||||
|
|
||||||
const float fwd = tilePx() * 0.45f;
|
const float fwd = tilePx() * 0.45f;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ add_subdirectory(core)
|
|||||||
add_subdirectory(config)
|
add_subdirectory(config)
|
||||||
add_subdirectory(utility)
|
add_subdirectory(utility)
|
||||||
add_subdirectory(sim)
|
add_subdirectory(sim)
|
||||||
|
add_subdirectory(ecs)
|
||||||
|
|
||||||
SET(HDRS
|
SET(HDRS
|
||||||
${HDRS}
|
${HDRS}
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ SET(HDRS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/Rotation.h
|
${CMAKE_CURRENT_SOURCE_DIR}/Rotation.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingType.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildingType.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/EntityAdmin.h
|
${CMAKE_CURRENT_SOURCE_DIR}/EntityAdmin.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/EcsComponents.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Blueprint.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}/ItemType.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Item.h
|
${CMAKE_CURRENT_SOURCE_DIR}/Item.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Port.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
|
${CMAKE_CURRENT_SOURCE_DIR}/SchematicDropEvent.h
|
||||||
PARENT_SCOPE
|
PARENT_SCOPE
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,106 +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 Velocity
|
|
||||||
{
|
|
||||||
QVector2D value;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Facing
|
|
||||||
{
|
|
||||||
float radians;
|
|
||||||
float rotationSpeed;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ShipDynamics
|
|
||||||
{
|
|
||||||
float maxSpeedPerTick;
|
|
||||||
float mainAccelerationPerTick;
|
|
||||||
float maneuveringAccelerationPerTick;
|
|
||||||
float angularAccelerationPerTick;
|
|
||||||
float maxRotationSpeedPerTick;
|
|
||||||
};
|
|
||||||
|
|
||||||
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,7 +1,17 @@
|
|||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
|
|
||||||
#include "EcsComponents.h"
|
#include "DespawnAtComponent.h"
|
||||||
#include "MovementIntent.h"
|
#include "DynamicBodyComponent.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()
|
entt::entity EntityAdmin::createEntity()
|
||||||
{
|
{
|
||||||
@@ -30,17 +40,24 @@ entt::entity EntityAdmin::spawnShip(QVector2D position, float hp, float maxHp,
|
|||||||
int level, const std::string& schematicId, bool isEnemy)
|
int level, const std::string& schematicId, bool isEnemy)
|
||||||
{
|
{
|
||||||
entt::entity entity = createEntity();
|
entt::entity entity = createEntity();
|
||||||
add<Position>(entity, Position{position});
|
add<PositionComponent>(entity, PositionComponent{position});
|
||||||
add<Health>(entity, Health{hp, maxHp});
|
add<HealthComponent>(entity, HealthComponent{hp, maxHp});
|
||||||
add<Faction>(entity, Faction{isEnemy});
|
add<FactionComponent>(entity, FactionComponent{isEnemy});
|
||||||
add<Velocity>(entity, Velocity{QVector2D(0.0f, 0.0f)});
|
add<FacingComponent>(entity, FacingComponent{0.0f});
|
||||||
add<Facing>(entity, Facing{0.0f, 0.0f});
|
add<DynamicBodyComponent>(entity, DynamicBodyComponent{
|
||||||
add<ShipDynamics>(entity, ShipDynamics{
|
maxSpeedPerTick,
|
||||||
maxSpeedPerTick, mainAccelPerTick, maneuveringAccelPerTick,
|
mainAccelPerTick,
|
||||||
angularAccelPerTick, maxRotationSpeedPerTick});
|
maneuveringAccelPerTick,
|
||||||
add<SensorRange>(entity, SensorRange{sensorRange});
|
angularAccelPerTick,
|
||||||
add<ShipIdentity>(entity, ShipIdentity{level, schematicId});
|
maxRotationSpeedPerTick,
|
||||||
add<MovementIntent>(entity, MovementIntent{0, QVector2D(0.0f, 0.0f)});
|
QVector2D(0.0f, 0.0f), // velocity
|
||||||
|
0.0f, // angularVelocity
|
||||||
|
QVector2D(0.0f, 0.0f), // linearAcceleration
|
||||||
|
0.0f // angularAcceleration
|
||||||
|
});
|
||||||
|
add<SensorRangeComponent>(entity, SensorRangeComponent{sensorRange});
|
||||||
|
add<ShipIdentityComponent>(entity, ShipIdentityComponent{level, schematicId});
|
||||||
|
add<MovementIntentComponent>(entity, MovementIntentComponent{0, QVector2D(0.0f, 0.0f)});
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,28 +68,28 @@ entt::entity EntityAdmin::spawnStation(QPoint anchor, QSize footprint,
|
|||||||
entt::entity entity = createEntity();
|
entt::entity entity = createEntity();
|
||||||
QVector2D center(anchor.x() + footprint.width() / 2.0f,
|
QVector2D center(anchor.x() + footprint.width() / 2.0f,
|
||||||
anchor.y() + footprint.height() / 2.0f);
|
anchor.y() + footprint.height() / 2.0f);
|
||||||
add<Position>(entity, Position{center});
|
add<PositionComponent>(entity, PositionComponent{center});
|
||||||
add<Health>(entity, Health{hp, maxHp});
|
add<HealthComponent>(entity, HealthComponent{hp, maxHp});
|
||||||
add<Faction>(entity, Faction{isEnemy});
|
add<FactionComponent>(entity, FactionComponent{isEnemy});
|
||||||
add<StationBody>(entity, StationBody{anchor, footprint, bodyCells});
|
add<StationBodyComponent>(entity, StationBodyComponent{anchor, footprint, bodyCells});
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
entt::entity EntityAdmin::spawnScrap(QVector2D position, int amount, Tick despawnAt)
|
entt::entity EntityAdmin::spawnScrap(QVector2D position, int amount, Tick despawnAt)
|
||||||
{
|
{
|
||||||
entt::entity entity = createEntity();
|
entt::entity entity = createEntity();
|
||||||
add<Position>(entity, Position{position});
|
add<PositionComponent>(entity, PositionComponent{position});
|
||||||
add<ScrapData>(entity, ScrapData{amount});
|
add<ScrapDataComponent>(entity, ScrapDataComponent{amount});
|
||||||
add<DespawnAt>(entity, DespawnAt{despawnAt});
|
add<DespawnAtComponent>(entity, DespawnAtComponent{despawnAt});
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
entt::entity EntityAdmin::spawnHqProxy(QVector2D position, float hp, float maxHp)
|
entt::entity EntityAdmin::spawnHqProxy(QVector2D position, float hp, float maxHp)
|
||||||
{
|
{
|
||||||
entt::entity entity = createEntity();
|
entt::entity entity = createEntity();
|
||||||
add<Position>(entity, Position{position});
|
add<PositionComponent>(entity, PositionComponent{position});
|
||||||
add<Health>(entity, Health{hp, maxHp});
|
add<HealthComponent>(entity, HealthComponent{hp, maxHp});
|
||||||
add<Faction>(entity, Faction{false});
|
add<FactionComponent>(entity, FactionComponent{false});
|
||||||
add<HqProxy>(entity);
|
add<HqProxyComponent>(entity);
|
||||||
return 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;
|
||||||
|
};
|
||||||
21
src/lib/ecs/component/DynamicBodyComponent.h
Normal file
21
src/lib/ecs/component/DynamicBodyComponent.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QVector2D>
|
||||||
|
|
||||||
|
struct DynamicBodyComponent
|
||||||
|
{
|
||||||
|
// --- dynamics parameters (formerly ShipDynamics) ---
|
||||||
|
float maxSpeedPerTick;
|
||||||
|
float mainAccelerationPerTick;
|
||||||
|
float maneuveringAccelerationPerTick;
|
||||||
|
float angularAccelerationPerTick;
|
||||||
|
float maxRotationSpeedPerTick;
|
||||||
|
|
||||||
|
// --- integrated state ---
|
||||||
|
QVector2D velocity;
|
||||||
|
float angularVelocity;
|
||||||
|
|
||||||
|
// --- written each tick by MovementIntentSystem, consumed by DynamicBodySystem ---
|
||||||
|
QVector2D linearAcceleration;
|
||||||
|
float angularAcceleration;
|
||||||
|
};
|
||||||
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
|
// A ship-behavior system writes this each tick before movement runs; the
|
||||||
// highest-priority write wins. Priority order is fixed globally — see
|
// highest-priority write wins. Priority order is fixed globally — see
|
||||||
// architecture.md "Movement Arbitration".
|
// architecture.md "Movement Arbitration".
|
||||||
struct MovementIntent
|
struct MovementIntentComponent
|
||||||
{
|
{
|
||||||
int priority;
|
int priority;
|
||||||
QVector2D target;
|
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 "Building.h"
|
||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
#include "EcsComponents.h"
|
|
||||||
#include "EntityAdmin.h"
|
|
||||||
#include "BuildingId.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 "ScrapSystem.h"
|
||||||
#include "Ship.h"
|
#include "SensorRangeComponent.h"
|
||||||
|
#include "ShipIdentityComponent.h"
|
||||||
|
#include "StationBodyComponent.h"
|
||||||
|
#include "ThreatResponseBehaviorComponent.h"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// tickHomeReturnBehavior (priority 4)
|
// tickHomeReturnBehavior (priority 4)
|
||||||
@@ -21,14 +33,15 @@
|
|||||||
|
|
||||||
void AiSystem::tickHomeReturnBehavior(EntityAdmin& admin)
|
void AiSystem::tickHomeReturnBehavior(EntityAdmin& admin)
|
||||||
{
|
{
|
||||||
admin.forEach<HomeReturnBehavior, Health, MovementIntent>(
|
admin.forEach<HomeReturnBehaviorComponent, HealthComponent, MovementIntentComponent>(
|
||||||
[](entt::entity /*e*/, const HomeReturnBehavior& homeReturnBehavior, const Health& h, MovementIntent& intent)
|
[](entt::entity /*e*/, const HomeReturnBehaviorComponent& homeReturnBehavior,
|
||||||
|
const HealthComponent& h, MovementIntentComponent& intent)
|
||||||
{
|
{
|
||||||
if (h.hp / h.maxHp < homeReturnBehavior.retreatHpFraction)
|
if (h.hp / h.maxHp < homeReturnBehavior.retreatHpFraction)
|
||||||
{
|
{
|
||||||
if (4 > intent.priority)
|
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;
|
std::vector<CombatantInfo> combatants;
|
||||||
|
|
||||||
admin.forEach<Position, Faction, ShipIdentity>(
|
admin.forEach<PositionComponent, FactionComponent, ShipIdentityComponent>(
|
||||||
[&combatants](entt::entity e, const Position& pos, const Faction& f, const ShipIdentity& /*si*/)
|
[&combatants](entt::entity e, const PositionComponent& pos,
|
||||||
|
const FactionComponent& f, const ShipIdentityComponent& /*si*/)
|
||||||
{
|
{
|
||||||
combatants.push_back({e, pos.value, f.isEnemy, false});
|
combatants.push_back({e, pos.value, f.isEnemy, false});
|
||||||
});
|
});
|
||||||
|
|
||||||
admin.forEach<Position, Faction, StationBody>(
|
admin.forEach<PositionComponent, FactionComponent, StationBodyComponent>(
|
||||||
[&combatants](entt::entity e, const Position& pos, const Faction& f, const StationBody& /*sb*/)
|
[&combatants](entt::entity e, const PositionComponent& pos,
|
||||||
|
const FactionComponent& f, const StationBodyComponent& /*sb*/)
|
||||||
{
|
{
|
||||||
combatants.push_back({e, pos.value, f.isEnemy, true});
|
combatants.push_back({e, pos.value, f.isEnemy, true});
|
||||||
});
|
});
|
||||||
|
|
||||||
admin.forEach<Position, Faction, HqProxy>(
|
admin.forEach<PositionComponent, FactionComponent, HqProxyComponent>(
|
||||||
[&combatants](entt::entity e, const Position& pos, const Faction& f, const HqProxy& /*hq*/)
|
[&combatants](entt::entity e, const PositionComponent& pos,
|
||||||
|
const FactionComponent& f, const HqProxyComponent& /*hq*/)
|
||||||
{
|
{
|
||||||
combatants.push_back({e, pos.value, f.isEnemy, true});
|
combatants.push_back({e, pos.value, f.isEnemy, true});
|
||||||
});
|
});
|
||||||
|
|
||||||
admin.forEach<ThreatResponseBehavior, Position, Faction, SensorRange, MovementIntent>(
|
admin.forEach<ThreatResponseBehaviorComponent, PositionComponent, FactionComponent,
|
||||||
[&](entt::entity e, ThreatResponseBehavior& threatResponseBehavior, Position& pos, Faction& faction,
|
SensorRangeComponent, MovementIntentComponent>(
|
||||||
SensorRange& sensor, MovementIntent& intent)
|
[&](entt::entity e, ThreatResponseBehaviorComponent& threatResponseBehavior,
|
||||||
|
PositionComponent& pos, FactionComponent& faction,
|
||||||
|
SensorRangeComponent& sensor, MovementIntentComponent& intent)
|
||||||
{
|
{
|
||||||
const float range = sensor.value;
|
const float range = sensor.value;
|
||||||
|
|
||||||
@@ -79,9 +97,10 @@ void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSyst
|
|||||||
if (threatResponseBehavior.currentTarget)
|
if (threatResponseBehavior.currentTarget)
|
||||||
{
|
{
|
||||||
const entt::entity t = *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)
|
if (dist <= range)
|
||||||
{
|
{
|
||||||
targetValid = true;
|
targetValid = true;
|
||||||
@@ -122,31 +141,33 @@ void AiSystem::tickThreatResponseBehavior(EntityAdmin& admin, const BuildingSyst
|
|||||||
{
|
{
|
||||||
const entt::entity t = *threatResponseBehavior.currentTarget;
|
const entt::entity t = *threatResponseBehavior.currentTarget;
|
||||||
QVector2D dest = pos.value;
|
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)
|
if (3 > intent.priority)
|
||||||
{
|
{
|
||||||
intent = MovementIntent{3, dest};
|
intent = MovementIntentComponent{3, dest};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (3 > intent.priority)
|
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)
|
else if (!faction.isEnemy)
|
||||||
{
|
{
|
||||||
intent = MovementIntent{3, QVector2D(pos.value.x() + 1000.0f,
|
intent = MovementIntentComponent{
|
||||||
pos.value.y())};
|
3, QVector2D(pos.value.x() + 1000.0f, pos.value.y())};
|
||||||
}
|
}
|
||||||
else
|
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;
|
std::vector<RepairableInfo> repairables;
|
||||||
|
|
||||||
admin.forEach<ShipIdentity, Position, Faction, Health>(
|
admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent, HealthComponent>(
|
||||||
[&repairables](entt::entity e, const ShipIdentity& /*si*/,
|
[&repairables](entt::entity e, const ShipIdentityComponent& /*si*/,
|
||||||
const Position& pos, const Faction& f, const Health& h)
|
const PositionComponent& pos, const FactionComponent& f,
|
||||||
|
const HealthComponent& h)
|
||||||
{
|
{
|
||||||
repairables.push_back({e, pos.value, f.isEnemy, true, h.hp, h.maxHp});
|
repairables.push_back({e, pos.value, f.isEnemy, true, h.hp, h.maxHp});
|
||||||
});
|
});
|
||||||
|
|
||||||
admin.forEach<StationBody, Position, Faction, Health>(
|
admin.forEach<StationBodyComponent, PositionComponent, FactionComponent, HealthComponent>(
|
||||||
[&repairables](entt::entity e, const StationBody& /*sb*/,
|
[&repairables](entt::entity e, const StationBodyComponent& /*sb*/,
|
||||||
const Position& pos, const Faction& f, const Health& h)
|
const PositionComponent& pos, const FactionComponent& f,
|
||||||
|
const HealthComponent& h)
|
||||||
{
|
{
|
||||||
repairables.push_back({e, pos.value, f.isEnemy, false, h.hp, h.maxHp});
|
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;
|
QVector2D position;
|
||||||
};
|
};
|
||||||
std::vector<EnemyInfo> enemies;
|
std::vector<EnemyInfo> enemies;
|
||||||
admin.forEach<ShipIdentity, Position, Faction>(
|
admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent>(
|
||||||
[&enemies](entt::entity /*e*/, const ShipIdentity& /*si*/,
|
[&enemies](entt::entity /*e*/, const ShipIdentityComponent& /*si*/,
|
||||||
const Position& pos, const Faction& f)
|
const PositionComponent& pos, const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (f.isEnemy)
|
if (f.isEnemy)
|
||||||
{
|
{
|
||||||
@@ -201,9 +224,11 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
admin.forEach<RepairBehavior, RepairTool, Position, Faction, SensorRange, MovementIntent>(
|
admin.forEach<RepairBehaviorComponent, RepairToolComponent, PositionComponent,
|
||||||
[&](entt::entity e, RepairBehavior& rb, RepairTool& rt, Position& pos,
|
FactionComponent, SensorRangeComponent, MovementIntentComponent>(
|
||||||
Faction& /*faction*/, SensorRange& sensor, MovementIntent& intent)
|
[&](entt::entity e, RepairBehaviorComponent& rb, RepairToolComponent& rt,
|
||||||
|
PositionComponent& pos, FactionComponent& /*faction*/,
|
||||||
|
SensorRangeComponent& sensor, MovementIntentComponent& intent)
|
||||||
{
|
{
|
||||||
const float repairRange = rt.range;
|
const float repairRange = rt.range;
|
||||||
|
|
||||||
@@ -221,7 +246,8 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
|||||||
{
|
{
|
||||||
if (2 > intent.priority)
|
if (2 > intent.priority)
|
||||||
{
|
{
|
||||||
intent = MovementIntent{2, QVector2D(-10000.0f, pos.value.y())};
|
intent = MovementIntentComponent{
|
||||||
|
2, QVector2D(-10000.0f, pos.value.y())};
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -231,9 +257,9 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
|||||||
if (rb.currentTarget)
|
if (rb.currentTarget)
|
||||||
{
|
{
|
||||||
const entt::entity t = *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)
|
if (th.hp > 0.0f && th.hp < th.maxHp)
|
||||||
{
|
{
|
||||||
targetValid = true;
|
targetValid = true;
|
||||||
@@ -264,27 +290,25 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
|||||||
{
|
{
|
||||||
if (2 > intent.priority)
|
if (2 > intent.priority)
|
||||||
{
|
{
|
||||||
intent = MovementIntent{2, QVector2D(pos.value.x() + 1000.0f,
|
intent = MovementIntentComponent{
|
||||||
pos.value.y())};
|
2, QVector2D(pos.value.x() + 1000.0f, pos.value.y())};
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const entt::entity target = *rb.currentTarget;
|
const entt::entity target = *rb.currentTarget;
|
||||||
QVector2D targetPos = pos.value;
|
QVector2D targetPos = pos.value;
|
||||||
bool isShipTarget = false;
|
if (admin.isValid(target) && admin.hasAll<PositionComponent>(target))
|
||||||
if (admin.isValid(target) && admin.hasAll<Position>(target))
|
|
||||||
{
|
{
|
||||||
targetPos = admin.get<Position>(target).value;
|
targetPos = admin.get<PositionComponent>(target).value;
|
||||||
isShipTarget = admin.hasAll<ShipIdentity>(target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const float distToTarget = (targetPos - pos.value).length();
|
const float distToTarget = (targetPos - pos.value).length();
|
||||||
if (distToTarget <= repairRange)
|
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.hp = std::min(targetHealth.hp + rt.ratePerTick,
|
||||||
targetHealth.maxHp);
|
targetHealth.maxHp);
|
||||||
}
|
}
|
||||||
@@ -292,7 +316,7 @@ void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
|||||||
|
|
||||||
if (2 > intent.priority)
|
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;
|
QVector2D position;
|
||||||
};
|
};
|
||||||
std::vector<EnemyShipPos> enemyShips;
|
std::vector<EnemyShipPos> enemyShips;
|
||||||
admin.forEach<ShipIdentity, Position, Faction>(
|
admin.forEach<ShipIdentityComponent, PositionComponent, FactionComponent>(
|
||||||
[&enemyShips](entt::entity /*e*/, const ShipIdentity& /*si*/,
|
[&enemyShips](entt::entity /*e*/, const ShipIdentityComponent& /*si*/,
|
||||||
const Position& pos, const Faction& f)
|
const PositionComponent& pos, const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (f.isEnemy)
|
if (f.isEnemy)
|
||||||
{
|
{
|
||||||
@@ -322,9 +346,11 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
|||||||
|
|
||||||
const std::vector<ScrapInfo> allScrap = scraps.allScrapInfo();
|
const std::vector<ScrapInfo> allScrap = scraps.allScrapInfo();
|
||||||
|
|
||||||
admin.forEach<SalvageBehavior, SalvageCargo, Position, SensorRange, MovementIntent>(
|
admin.forEach<SalvageBehaviorComponent, SalvageCargoComponent, PositionComponent,
|
||||||
[&](entt::entity /*e*/, SalvageBehavior& salvageBehavior, SalvageCargo& cargo,
|
SensorRangeComponent, MovementIntentComponent>(
|
||||||
Position& pos, SensorRange& sensor, MovementIntent& intent)
|
[&](entt::entity /*e*/, SalvageBehaviorComponent& salvageBehavior,
|
||||||
|
SalvageCargoComponent& cargo, PositionComponent& pos,
|
||||||
|
SensorRangeComponent& sensor, MovementIntentComponent& intent)
|
||||||
{
|
{
|
||||||
const float collectRange = cargo.collectionRange;
|
const float collectRange = cargo.collectionRange;
|
||||||
|
|
||||||
@@ -358,7 +384,7 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
|||||||
{
|
{
|
||||||
if (1 > intent.priority)
|
if (1 > intent.priority)
|
||||||
{
|
{
|
||||||
intent = MovementIntent{1, bayPos};
|
intent = MovementIntentComponent{1, bayPos};
|
||||||
}
|
}
|
||||||
if (bayId != kInvalidBuildingId
|
if (bayId != kInvalidBuildingId
|
||||||
&& (pos.value - bayPos).length() <= 1.0f)
|
&& (pos.value - bayPos).length() <= 1.0f)
|
||||||
@@ -381,7 +407,8 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
|||||||
{
|
{
|
||||||
if (1 > intent.priority)
|
if (1 > intent.priority)
|
||||||
{
|
{
|
||||||
intent = MovementIntent{1, QVector2D(-10000.0f, pos.value.y())};
|
intent = MovementIntentComponent{
|
||||||
|
1, QVector2D(-10000.0f, pos.value.y())};
|
||||||
}
|
}
|
||||||
retreating = true;
|
retreating = true;
|
||||||
break;
|
break;
|
||||||
@@ -409,7 +436,7 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
|||||||
{
|
{
|
||||||
if (1 > intent.priority)
|
if (1 > intent.priority)
|
||||||
{
|
{
|
||||||
intent = MovementIntent{1, *salvageBehavior.scrapTarget};
|
intent = MovementIntentComponent{1, *salvageBehavior.scrapTarget};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -430,15 +457,15 @@ void AiSystem::tickSalvageBehavior(EntityAdmin& admin, ScrapSystem& scraps,
|
|||||||
salvageBehavior.scrapTarget = bestPos;
|
salvageBehavior.scrapTarget = bestPos;
|
||||||
if (1 > intent.priority)
|
if (1 > intent.priority)
|
||||||
{
|
{
|
||||||
intent = MovementIntent{1, *bestPos};
|
intent = MovementIntentComponent{1, *bestPos};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (1 > intent.priority)
|
if (1 > intent.priority)
|
||||||
{
|
{
|
||||||
intent = MovementIntent{1, QVector2D(pos.value.x() + 1000.0f,
|
intent = MovementIntentComponent{
|
||||||
pos.value.y())};
|
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
|
||||||
|
)
|
||||||
149
src/lib/ecs/system/CombatSystem.cpp
Normal file
149
src/lib/ecs/system/CombatSystem.cpp
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#include "CombatSystem.h"
|
||||||
|
|
||||||
|
#include "EntityAdmin.h"
|
||||||
|
#include "FactionComponent.h"
|
||||||
|
#include "StationBodyComponent.h"
|
||||||
|
#include "HealthComponent.h"
|
||||||
|
#include "PositionComponent.h"
|
||||||
|
#include "SensorRangeComponent.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 (entities with StationBodyComponent; ships are excluded because
|
||||||
|
// they lack that component and are already handled by the ship loop above).
|
||||||
|
admin.forEach<WeaponComponent, PositionComponent, FactionComponent, StationBodyComponent>(
|
||||||
|
[&](entt::entity e, WeaponComponent& weapon, PositionComponent& pos,
|
||||||
|
FactionComponent& faction, const StationBodyComponent& /*sb*/)
|
||||||
|
{
|
||||||
|
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.
|
||||||
|
// Ships use their sensor range; stations fall back to weapon range.
|
||||||
|
if (!weapon.currentTarget)
|
||||||
|
{
|
||||||
|
const float acquisitionRange = admin.hasAll<SensorRangeComponent>(shipEntity)
|
||||||
|
? admin.get<SensorRangeComponent>(shipEntity).value
|
||||||
|
: weapon.range;
|
||||||
|
float bestDistanceSquared = acquisitionRange * acquisitionRange;
|
||||||
|
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 <QVector2D>
|
||||||
|
|
||||||
#include "Building.h"
|
#include "Building.h"
|
||||||
#include "EcsComponents.h"
|
#include "FactionComponent.h"
|
||||||
#include "FireEvent.h"
|
#include "FireEvent.h"
|
||||||
#include "GameConfig.h"
|
#include "GameConfig.h"
|
||||||
#include "Ship.h"
|
#include "PositionComponent.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
|
#include "WeaponComponent.h"
|
||||||
|
|
||||||
#include "entt/entity/entity.hpp"
|
#include "entt/entity/entity.hpp"
|
||||||
|
|
||||||
@@ -40,14 +41,13 @@ private:
|
|||||||
std::vector<PendingDamage> m_pendingDamage;
|
std::vector<PendingDamage> m_pendingDamage;
|
||||||
|
|
||||||
void resolveWeapon(
|
void resolveWeapon(
|
||||||
entt::entity shipEntity,
|
entt::entity shipEntity,
|
||||||
Weapon& weapon,
|
WeaponComponent& weapon,
|
||||||
const Position& ownPos,
|
const PositionComponent& ownPos,
|
||||||
const Faction& ownFaction,
|
const FactionComponent& ownFaction,
|
||||||
Tick currentTick,
|
Tick currentTick,
|
||||||
EntityAdmin& admin,
|
EntityAdmin& admin,
|
||||||
std::vector<FireEvent>& out);
|
std::vector<FireEvent>& out);
|
||||||
|
|
||||||
const GameConfig& m_config;
|
const GameConfig& m_config;
|
||||||
};
|
};
|
||||||
|
|
||||||
51
src/lib/ecs/system/DynamicBodySystem.cpp
Normal file
51
src/lib/ecs/system/DynamicBodySystem.cpp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#include "DynamicBodySystem.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <QVector2D>
|
||||||
|
|
||||||
|
#include "DynamicBodyComponent.h"
|
||||||
|
#include "EntityAdmin.h"
|
||||||
|
#include "FacingComponent.h"
|
||||||
|
#include "PositionComponent.h"
|
||||||
|
|
||||||
|
static float wrapAngle(float a)
|
||||||
|
{
|
||||||
|
constexpr float kPi = 3.14159265f;
|
||||||
|
a = std::fmod(a, 2.0f * kPi);
|
||||||
|
if (a > kPi) { a -= 2.0f * kPi; }
|
||||||
|
if (a < -kPi) { a += 2.0f * kPi; }
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicBodySystem::tick(EntityAdmin& admin)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
body.angularVelocity = std::max(-body.maxRotationSpeedPerTick,
|
||||||
|
std::min(body.angularVelocity,
|
||||||
|
body.maxRotationSpeedPerTick));
|
||||||
|
facing.radians = wrapAngle(facing.radians + body.angularVelocity);
|
||||||
|
|
||||||
|
// Integrate linear velocity and cap to max speed.
|
||||||
|
body.velocity += body.linearAcceleration;
|
||||||
|
const float speed = body.velocity.length();
|
||||||
|
if (speed > body.maxSpeedPerTick)
|
||||||
|
{
|
||||||
|
body.velocity = body.velocity.normalized() * body.maxSpeedPerTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance position.
|
||||||
|
pos.value += body.velocity;
|
||||||
|
|
||||||
|
// Reset per-tick fields so stale values don't linger if the intent
|
||||||
|
// system is skipped for this entity in a future tick.
|
||||||
|
body.linearAcceleration = QVector2D(0.0f, 0.0f);
|
||||||
|
body.angularAcceleration = 0.0f;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class EntityAdmin;
|
class EntityAdmin;
|
||||||
|
|
||||||
class MovementSystem
|
class DynamicBodySystem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void tick(EntityAdmin& admin);
|
void tick(EntityAdmin& admin);
|
||||||
117
src/lib/ecs/system/MovementIntentSystem.cpp
Normal file
117
src/lib/ecs/system/MovementIntentSystem.cpp
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
#include "MovementIntentSystem.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <QVector2D>
|
||||||
|
|
||||||
|
#include "DynamicBodyComponent.h"
|
||||||
|
#include "EntityAdmin.h"
|
||||||
|
#include "FacingComponent.h"
|
||||||
|
#include "MovementIntentComponent.h"
|
||||||
|
#include "PositionComponent.h"
|
||||||
|
|
||||||
|
static float wrapAngle(float a)
|
||||||
|
{
|
||||||
|
constexpr float kPi = 3.14159265f;
|
||||||
|
a = std::fmod(a, 2.0f * kPi);
|
||||||
|
if (a > kPi) { a -= 2.0f * kPi; }
|
||||||
|
if (a < -kPi) { a += 2.0f * kPi; }
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MovementIntentSystem::tick(EntityAdmin& admin)
|
||||||
|
{
|
||||||
|
admin.forEach<PositionComponent, FacingComponent, DynamicBodyComponent,
|
||||||
|
MovementIntentComponent>(
|
||||||
|
[](entt::entity /*e*/, const PositionComponent& pos, const FacingComponent& facing,
|
||||||
|
DynamicBodyComponent& body, const MovementIntentComponent& intent)
|
||||||
|
{
|
||||||
|
if (intent.priority == 0)
|
||||||
|
{
|
||||||
|
// No movement intent: brake using available thrust.
|
||||||
|
const float linearBraking = std::min(body.velocity.length(),
|
||||||
|
body.maneuveringAccelerationPerTick);
|
||||||
|
body.linearAcceleration = (body.velocity.length() > 0.0001f)
|
||||||
|
? -body.velocity.normalized() * linearBraking
|
||||||
|
: QVector2D(0.0f, 0.0f);
|
||||||
|
|
||||||
|
const float angBraking = std::min(std::abs(body.angularVelocity),
|
||||||
|
body.angularAccelerationPerTick);
|
||||||
|
body.angularAcceleration =
|
||||||
|
(body.angularVelocity >= 0.0f) ? -angBraking : angBraking;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVector2D delta = intent.target - pos.value;
|
||||||
|
const float dist = delta.length();
|
||||||
|
|
||||||
|
if (dist < 0.001f)
|
||||||
|
{
|
||||||
|
// Already at target: no new thrust. The ship drifts; it will
|
||||||
|
// re-approach next tick once it has moved away.
|
||||||
|
body.linearAcceleration = QVector2D(0.0f, 0.0f);
|
||||||
|
body.angularAcceleration = 0.0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Angular acceleration ---
|
||||||
|
|
||||||
|
const float desiredAngle = std::atan2(delta.y(), delta.x());
|
||||||
|
const float angleDiff = wrapAngle(desiredAngle - facing.radians);
|
||||||
|
|
||||||
|
const float rotDelta = std::max(-body.angularAccelerationPerTick,
|
||||||
|
std::min(angleDiff,
|
||||||
|
body.angularAccelerationPerTick));
|
||||||
|
|
||||||
|
float newAngVel = body.angularVelocity + rotDelta;
|
||||||
|
|
||||||
|
// Overshoot prevention: if the accumulated angular velocity already
|
||||||
|
// exceeds the remaining angle, snap it to exactly that angle so the
|
||||||
|
// ship doesn't rotate past its heading.
|
||||||
|
const bool sameSign = (newAngVel >= 0.0f) == (angleDiff >= 0.0f);
|
||||||
|
if (sameSign && std::abs(newAngVel) > std::abs(angleDiff))
|
||||||
|
{
|
||||||
|
newAngVel = angleDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.angularAcceleration = newAngVel - body.angularVelocity;
|
||||||
|
// DynamicBodySystem applies the clamp to maxRotationSpeedPerTick after
|
||||||
|
// integrating, so we do not clamp here.
|
||||||
|
|
||||||
|
// --- Linear acceleration ---
|
||||||
|
// Use the projected facing (after this tick's angular integration) so
|
||||||
|
// that the main thruster aligns with where the ship will actually be
|
||||||
|
// pointing when DynamicBodySystem applies the forces.
|
||||||
|
|
||||||
|
const float projectedRadians = wrapAngle(facing.radians + newAngVel);
|
||||||
|
const QVector2D facingVec(std::cos(projectedRadians),
|
||||||
|
std::sin(projectedRadians));
|
||||||
|
|
||||||
|
const float manAccel = body.maneuveringAccelerationPerTick;
|
||||||
|
const float stoppingDist = (body.maxSpeedPerTick * body.maxSpeedPerTick)
|
||||||
|
/ (2.0f * manAccel);
|
||||||
|
// Cap to dist so the ship never overshoots the target in a single tick.
|
||||||
|
const float baseDesiredSpeed = (dist <= stoppingDist)
|
||||||
|
? std::sqrt(2.0f * manAccel * dist)
|
||||||
|
: body.maxSpeedPerTick;
|
||||||
|
const float desiredSpeed = std::min(dist, baseDesiredSpeed);
|
||||||
|
|
||||||
|
const QVector2D desiredVel = delta.normalized() * desiredSpeed;
|
||||||
|
const QVector2D velError = desiredVel - body.velocity;
|
||||||
|
|
||||||
|
const float mainAligned = std::max(0.0f,
|
||||||
|
QVector2D::dotProduct(velError, facingVec));
|
||||||
|
const float mainApplied = std::min(mainAligned,
|
||||||
|
body.mainAccelerationPerTick);
|
||||||
|
const QVector2D mainDelta = facingVec * mainApplied;
|
||||||
|
|
||||||
|
const QVector2D remaining = velError - mainDelta;
|
||||||
|
const float remainLen = remaining.length();
|
||||||
|
const QVector2D maneuverDelta = (remainLen > manAccel)
|
||||||
|
? remaining.normalized() * manAccel
|
||||||
|
: remaining;
|
||||||
|
|
||||||
|
body.linearAcceleration = mainDelta + maneuverDelta;
|
||||||
|
});
|
||||||
|
}
|
||||||
9
src/lib/ecs/system/MovementIntentSystem.h
Normal file
9
src/lib/ecs/system/MovementIntentSystem.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class EntityAdmin;
|
||||||
|
|
||||||
|
class MovementIntentSystem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void tick(EntityAdmin& admin);
|
||||||
|
};
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
#include "ScrapSystem.h"
|
#include "ScrapSystem.h"
|
||||||
|
|
||||||
|
#include "DespawnAtComponent.h"
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
|
#include "PositionComponent.h"
|
||||||
|
#include "ScrapDataComponent.h"
|
||||||
|
|
||||||
ScrapSystem::ScrapSystem(EntityAdmin& admin)
|
ScrapSystem::ScrapSystem(EntityAdmin& admin)
|
||||||
: m_admin(admin)
|
: m_admin(admin)
|
||||||
@@ -15,8 +18,8 @@ entt::entity ScrapSystem::spawn(QVector2D position, int amount, Tick despawnAt)
|
|||||||
void ScrapSystem::tickDespawn(Tick currentTick)
|
void ScrapSystem::tickDespawn(Tick currentTick)
|
||||||
{
|
{
|
||||||
std::vector<entt::entity> expired;
|
std::vector<entt::entity> expired;
|
||||||
m_admin.forEach<DespawnAt>(
|
m_admin.forEach<DespawnAtComponent>(
|
||||||
[&expired, currentTick](entt::entity e, DespawnAt& d)
|
[&expired, currentTick](entt::entity e, DespawnAtComponent& d)
|
||||||
{
|
{
|
||||||
if (d.tick <= currentTick)
|
if (d.tick <= currentTick)
|
||||||
{
|
{
|
||||||
@@ -32,11 +35,11 @@ void ScrapSystem::tickDespawn(Tick currentTick)
|
|||||||
|
|
||||||
std::optional<int> ScrapSystem::consume(entt::entity entity)
|
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;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
int amount = m_admin.get<ScrapData>(entity).amount;
|
int amount = m_admin.get<ScrapDataComponent>(entity).amount;
|
||||||
m_admin.destroy(entity);
|
m_admin.destroy(entity);
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
@@ -44,10 +47,10 @@ std::optional<int> ScrapSystem::consume(entt::entity entity)
|
|||||||
std::vector<ScrapInfo> ScrapSystem::allScrapInfo() const
|
std::vector<ScrapInfo> ScrapSystem::allScrapInfo() const
|
||||||
{
|
{
|
||||||
std::vector<ScrapInfo> result;
|
std::vector<ScrapInfo> result;
|
||||||
m_admin.forEach<ScrapData>(
|
m_admin.forEach<ScrapDataComponent>(
|
||||||
[&result, this](entt::entity e, const ScrapData& /*sd*/)
|
[&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;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include <QVector2D>
|
#include <QVector2D>
|
||||||
|
|
||||||
#include "EcsComponents.h"
|
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
|
|
||||||
#include "entt/entity/entity.hpp"
|
#include "entt/entity/entity.hpp"
|
||||||
@@ -4,10 +4,21 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "EcsComponents.h"
|
#include "DynamicBodyComponent.h"
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
|
#include "FactionComponent.h"
|
||||||
|
#include "HealthComponent.h"
|
||||||
#include "ModulesConfig.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 "Tick.h"
|
||||||
|
#include "ThreatResponseBehaviorComponent.h"
|
||||||
|
#include "WeaponComponent.h"
|
||||||
|
|
||||||
ShipSystem::ShipSystem(const GameConfig& config, EntityAdmin& admin)
|
ShipSystem::ShipSystem(const GameConfig& config, EntityAdmin& admin)
|
||||||
: m_config(config)
|
: m_config(config)
|
||||||
@@ -39,8 +50,8 @@ const ModuleDef* ShipSystem::findModuleDef(const std::string& id) const
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVector2D position,
|
entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||||
bool isEnemy,
|
QVector2D position, bool isEnemy,
|
||||||
const std::optional<ShipLayoutConfig>& layout)
|
const std::optional<ShipLayoutConfig>& layout)
|
||||||
{
|
{
|
||||||
const ShipDef* def = findShipDef(schematicId);
|
const ShipDef* def = findShipDef(schematicId);
|
||||||
@@ -51,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 hp = static_cast<float>(def->health.hpFormula.evaluate(x));
|
||||||
float maxHp = hp;
|
float maxHp = hp;
|
||||||
float maxSpeedPerTick = static_cast<float>(def->movement.speedFormula.evaluate(x)) / tickRate;
|
float maxSpeedPerTick = static_cast<float>(def->movement.speedFormula.evaluate(x))
|
||||||
float mainAccelPerTick = static_cast<float>(def->movement.mainAccelerationFormula.evaluate(x)) / tickRate;
|
/ tickRate;
|
||||||
float maneuveringAccelPerTick = static_cast<float>(def->movement.maneuveringAccelerationFormula.evaluate(x)) / tickRate;
|
float mainAccelPerTick = static_cast<float>(
|
||||||
float angularAccelPerTick = static_cast<float>(def->movement.angularAccelerationFormula.evaluate(x)) / tickRate;
|
def->movement.mainAccelerationFormula.evaluate(x))
|
||||||
float maxRotationSpeedPerTick = static_cast<float>(def->movement.maxRotationSpeedFormula.evaluate(x)) / tickRate;
|
/ tickRate;
|
||||||
float sensorRange = static_cast<float>(def->sensor.sensorRangeFormula.evaluate(x));
|
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(
|
entt::entity entity = m_admin.spawnShip(
|
||||||
position, hp, maxHp,
|
position, hp, maxHp,
|
||||||
@@ -67,45 +88,47 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto
|
|||||||
// Optional components based on ship role.
|
// Optional components based on ship role.
|
||||||
if (def->combat)
|
if (def->combat)
|
||||||
{
|
{
|
||||||
Weapon w;
|
WeaponComponent w;
|
||||||
w.damage = static_cast<float>(def->combat->damageFormula.evaluate(x));
|
w.damage = static_cast<float>(def->combat->damageFormula.evaluate(x));
|
||||||
w.range = static_cast<float>(def->combat->attackRangeFormula.evaluate(x));
|
w.range = static_cast<float>(def->combat->attackRangeFormula.evaluate(x));
|
||||||
w.fireRateHz = static_cast<float>(def->combat->attackRateFormula.evaluate(x));
|
w.fireRateHz = static_cast<float>(def->combat->attackRateFormula.evaluate(x));
|
||||||
w.cooldownTicks = 0.0f;
|
w.cooldownTicks = 0.0f;
|
||||||
w.currentTarget = std::nullopt;
|
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)
|
if (!isEnemy)
|
||||||
{
|
{
|
||||||
m_admin.addComponent<RallyBehavior>(entity, RallyBehavior{m_rallyPoint});
|
m_admin.addComponent<RallyBehaviorComponent>(
|
||||||
|
entity, RallyBehaviorComponent{m_rallyPoint});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (def->salvage)
|
if (def->salvage)
|
||||||
{
|
{
|
||||||
SalvageCargo cargo;
|
SalvageCargoComponent cargo;
|
||||||
cargo.capacity = def->salvage->cargoCapacity;
|
cargo.capacity = def->salvage->cargoCapacity;
|
||||||
cargo.current = 0;
|
cargo.current = 0;
|
||||||
cargo.collectionRange = static_cast<float>(def->salvage->collectionRange);
|
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.scrapTarget = std::nullopt;
|
||||||
salvageBehavior.deliveryBay = kInvalidBuildingId;
|
salvageBehavior.deliveryBay = kInvalidBuildingId;
|
||||||
m_admin.addComponent<SalvageBehavior>(entity, salvageBehavior);
|
m_admin.addComponent<SalvageBehaviorComponent>(entity, salvageBehavior);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (def->repair)
|
if (def->repair)
|
||||||
{
|
{
|
||||||
RepairTool rt;
|
RepairToolComponent rt;
|
||||||
rt.ratePerTick = static_cast<float>(def->repair->repairRateFormula.evaluate(x));
|
rt.ratePerTick = static_cast<float>(def->repair->repairRateFormula.evaluate(x));
|
||||||
rt.range = static_cast<float>(def->repair->repairRangeFormula.evaluate(x));
|
rt.range = static_cast<float>(def->repair->repairRangeFormula.evaluate(x));
|
||||||
rt.currentTarget = std::nullopt;
|
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).
|
// Apply module stat modifiers (REQ-MOD-STAT-CALC).
|
||||||
@@ -141,13 +164,14 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto
|
|||||||
if (it != mods.end())
|
if (it != mods.end())
|
||||||
{
|
{
|
||||||
stat = static_cast<float>(
|
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);
|
HealthComponent& health = m_admin.get<HealthComponent>(entity);
|
||||||
ShipDynamics& dynamics = m_admin.get<ShipDynamics>(entity);
|
DynamicBodyComponent& dynamics = m_admin.get<DynamicBodyComponent>(entity);
|
||||||
SensorRange& sensor = m_admin.get<SensorRange>(entity);
|
SensorRangeComponent& sensor = m_admin.get<SensorRangeComponent>(entity);
|
||||||
|
|
||||||
applyMod(health.maxHp, "hp");
|
applyMod(health.maxHp, "hp");
|
||||||
health.hp = health.maxHp;
|
health.hp = health.maxHp;
|
||||||
@@ -158,16 +182,16 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto
|
|||||||
applyMod(dynamics.maxRotationSpeedPerTick, "max_rotation_speed");
|
applyMod(dynamics.maxRotationSpeedPerTick, "max_rotation_speed");
|
||||||
applyMod(sensor.value, "sensor_range");
|
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.damage, "damage");
|
||||||
applyMod(weapon.range, "attack_range");
|
applyMod(weapon.range, "attack_range");
|
||||||
applyMod(weapon.fireRateHz, "attack_rate");
|
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.ratePerTick, "repair_rate");
|
||||||
applyMod(repairTool.range, "repair_range");
|
applyMod(repairTool.range, "repair_range");
|
||||||
}
|
}
|
||||||
@@ -183,10 +207,11 @@ void ShipSystem::despawn(entt::entity entity)
|
|||||||
|
|
||||||
void ShipSystem::clearMovementIntents()
|
void ShipSystem::clearMovementIntents()
|
||||||
{
|
{
|
||||||
m_admin.forEach<MovementIntent>([](entt::entity /*e*/, MovementIntent& i)
|
m_admin.forEach<MovementIntentComponent>(
|
||||||
{
|
[](entt::entity /*e*/, MovementIntentComponent& i)
|
||||||
i = MovementIntent{0, QVector2D(0.0f, 0.0f)};
|
{
|
||||||
});
|
i = MovementIntentComponent{0, QVector2D(0.0f, 0.0f)};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShipSystem::setRallyPoint(QVector2D point)
|
void ShipSystem::setRallyPoint(QVector2D point)
|
||||||
@@ -197,8 +222,9 @@ void ShipSystem::setRallyPoint(QVector2D point)
|
|||||||
void ShipSystem::triggerRallyDeparture()
|
void ShipSystem::triggerRallyDeparture()
|
||||||
{
|
{
|
||||||
std::vector<entt::entity> toRemove;
|
std::vector<entt::entity> toRemove;
|
||||||
m_admin.forEach<RallyBehavior, Faction>(
|
m_admin.forEach<RallyBehaviorComponent, FactionComponent>(
|
||||||
[&toRemove](entt::entity e, const RallyBehavior& /*rb*/, const Faction& f)
|
[&toRemove](entt::entity e, const RallyBehaviorComponent& /*rb*/,
|
||||||
|
const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (!f.isEnemy)
|
if (!f.isEnemy)
|
||||||
{
|
{
|
||||||
@@ -207,6 +233,6 @@ void ShipSystem::triggerRallyDeparture()
|
|||||||
});
|
});
|
||||||
for (entt::entity e : toRemove)
|
for (entt::entity e : toRemove)
|
||||||
{
|
{
|
||||||
m_admin.removeComponent<RallyBehavior>(e);
|
m_admin.removeComponent<RallyBehaviorComponent>(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
#include <QVector2D>
|
#include <QVector2D>
|
||||||
|
|
||||||
#include "GameConfig.h"
|
#include "GameConfig.h"
|
||||||
#include "Ship.h"
|
|
||||||
#include "ShipLayout.h"
|
#include "ShipLayout.h"
|
||||||
|
|
||||||
#include "entt/entity/entity.hpp"
|
#include "entt/entity/entity.hpp"
|
||||||
@@ -5,15 +5,9 @@ SET(HDRS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Building.h
|
${CMAKE_CURRENT_SOURCE_DIR}/Building.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Ship.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutBlueprint.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutBlueprint.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/AiSystem.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/MovementSystem.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/WaveSystem.h
|
${CMAKE_CURRENT_SOURCE_DIR}/WaveSystem.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/CombatSystem.h
|
|
||||||
PARENT_SCOPE
|
PARENT_SCOPE
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,12 +17,7 @@ SET(SRCS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/TickDriver.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/TickDriver.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/AiSystem.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/MovementSystem.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/WaveSystem.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/WaveSystem.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/CombatSystem.cpp
|
|
||||||
PARENT_SCOPE
|
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,105 +0,0 @@
|
|||||||
#include "MovementSystem.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include <QVector2D>
|
|
||||||
|
|
||||||
#include "EcsComponents.h"
|
|
||||||
#include "EntityAdmin.h"
|
|
||||||
#include "MovementIntent.h"
|
|
||||||
|
|
||||||
static float wrapAngle(float a)
|
|
||||||
{
|
|
||||||
constexpr float kPi = 3.14159265f;
|
|
||||||
a = std::fmod(a, 2.0f * kPi);
|
|
||||||
if (a > kPi) { a -= 2.0f * kPi; }
|
|
||||||
if (a < -kPi) { a += 2.0f * kPi; }
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MovementSystem::tick(EntityAdmin& admin)
|
|
||||||
{
|
|
||||||
admin.forEach<Position, Velocity, Facing, ShipDynamics, MovementIntent>(
|
|
||||||
[](entt::entity /*e*/, Position& pos, Velocity& vel, Facing& facing,
|
|
||||||
ShipDynamics& dynamics, MovementIntent& intent)
|
|
||||||
{
|
|
||||||
if (intent.priority == 0)
|
|
||||||
{
|
|
||||||
vel.value = QVector2D(0.0f, 0.0f);
|
|
||||||
facing.rotationSpeed = 0.0f;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QVector2D delta = intent.target - pos.value;
|
|
||||||
const float dist = delta.length();
|
|
||||||
|
|
||||||
if (dist < 0.001f)
|
|
||||||
{
|
|
||||||
vel.value = QVector2D(0.0f, 0.0f);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate toward target.
|
|
||||||
const float desiredAngle = std::atan2(delta.y(), delta.x());
|
|
||||||
const float angleDiff = wrapAngle(desiredAngle - facing.radians);
|
|
||||||
|
|
||||||
const float rotDelta = std::max(-dynamics.angularAccelerationPerTick,
|
|
||||||
std::min(angleDiff, dynamics.angularAccelerationPerTick));
|
|
||||||
facing.rotationSpeed += rotDelta;
|
|
||||||
facing.rotationSpeed = std::max(-dynamics.maxRotationSpeedPerTick,
|
|
||||||
std::min(facing.rotationSpeed, dynamics.maxRotationSpeedPerTick));
|
|
||||||
|
|
||||||
const bool sameSign = (facing.rotationSpeed >= 0.0f) == (angleDiff >= 0.0f);
|
|
||||||
if (sameSign && std::abs(facing.rotationSpeed) > std::abs(angleDiff))
|
|
||||||
{
|
|
||||||
facing.rotationSpeed = angleDiff;
|
|
||||||
}
|
|
||||||
|
|
||||||
facing.radians = wrapAngle(facing.radians + facing.rotationSpeed);
|
|
||||||
|
|
||||||
// Desired velocity (with braking near target).
|
|
||||||
const float manAccel = dynamics.maneuveringAccelerationPerTick;
|
|
||||||
const float stoppingDist = (dynamics.maxSpeedPerTick * dynamics.maxSpeedPerTick)
|
|
||||||
/ (2.0f * manAccel);
|
|
||||||
const float desiredSpeed = (dist <= stoppingDist)
|
|
||||||
? std::sqrt(2.0f * manAccel * dist)
|
|
||||||
: dynamics.maxSpeedPerTick;
|
|
||||||
const QVector2D desiredVel = delta.normalized() * desiredSpeed;
|
|
||||||
const QVector2D velError = desiredVel - vel.value;
|
|
||||||
|
|
||||||
// Main acceleration: forward only, along facing.
|
|
||||||
const QVector2D facingVec(std::cos(facing.radians), std::sin(facing.radians));
|
|
||||||
const float mainAligned = std::max(0.0f,
|
|
||||||
QVector2D::dotProduct(velError, facingVec));
|
|
||||||
const float mainApplied = std::min(mainAligned, dynamics.mainAccelerationPerTick);
|
|
||||||
const QVector2D mainDelta = facingVec * mainApplied;
|
|
||||||
|
|
||||||
// Maneuvering acceleration: any direction, handles the remainder.
|
|
||||||
const QVector2D remaining = velError - mainDelta;
|
|
||||||
const float remainLen = remaining.length();
|
|
||||||
const QVector2D maneuverDelta = (remainLen > manAccel)
|
|
||||||
? remaining.normalized() * manAccel
|
|
||||||
: remaining;
|
|
||||||
|
|
||||||
vel.value += mainDelta + maneuverDelta;
|
|
||||||
|
|
||||||
// Speed cap.
|
|
||||||
const float speed = vel.value.length();
|
|
||||||
if (speed > dynamics.maxSpeedPerTick)
|
|
||||||
{
|
|
||||||
vel.value = vel.value.normalized() * dynamics.maxSpeedPerTick;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snap to target or advance.
|
|
||||||
if (dist <= vel.value.length())
|
|
||||||
{
|
|
||||||
pos.value = intent.target;
|
|
||||||
vel.value = QVector2D(0.0f, 0.0f);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pos.value += vel.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -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,13 +4,19 @@
|
|||||||
|
|
||||||
#include "AiSystem.h"
|
#include "AiSystem.h"
|
||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
#include "EcsComponents.h"
|
|
||||||
#include "CombatSystem.h"
|
#include "CombatSystem.h"
|
||||||
#include "MovementSystem.h"
|
#include "DynamicBodySystem.h"
|
||||||
|
#include "FactionComponent.h"
|
||||||
|
#include "HealthComponent.h"
|
||||||
|
#include "MovementIntentSystem.h"
|
||||||
|
#include "PositionComponent.h"
|
||||||
#include "ScrapSystem.h"
|
#include "ScrapSystem.h"
|
||||||
|
#include "ShipIdentityComponent.h"
|
||||||
#include "ShipSystem.h"
|
#include "ShipSystem.h"
|
||||||
|
#include "StationBodyComponent.h"
|
||||||
#include "SurfaceMask.h"
|
#include "SurfaceMask.h"
|
||||||
#include "WaveSystem.h"
|
#include "WaveSystem.h"
|
||||||
|
#include "WeaponComponent.h"
|
||||||
|
|
||||||
Simulation::Simulation(GameConfig config, unsigned int seed)
|
Simulation::Simulation(GameConfig config, unsigned int seed)
|
||||||
: m_config(std::move(config))
|
: m_config(std::move(config))
|
||||||
@@ -47,7 +53,8 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
|
|||||||
m_rng);
|
m_rng);
|
||||||
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
||||||
m_aiSystem = std::make_unique<AiSystem>();
|
m_aiSystem = std::make_unique<AiSystem>();
|
||||||
m_movementSystem = std::make_unique<MovementSystem>();
|
m_movementIntentSystem = std::make_unique<MovementIntentSystem>();
|
||||||
|
m_dynamicBodySystem = std::make_unique<DynamicBodySystem>();
|
||||||
m_scrapSystem = std::make_unique<ScrapSystem>(m_admin);
|
m_scrapSystem = std::make_unique<ScrapSystem>(m_admin);
|
||||||
m_waveSystem = std::make_unique<WaveSystem>(m_config, m_rng);
|
m_waveSystem = std::make_unique<WaveSystem>(m_config, m_rng);
|
||||||
m_combatSystem = std::make_unique<CombatSystem>(m_config);
|
m_combatSystem = std::make_unique<CombatSystem>(m_config);
|
||||||
@@ -114,7 +121,8 @@ void Simulation::reset(unsigned int seed)
|
|||||||
m_rng);
|
m_rng);
|
||||||
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
||||||
m_aiSystem = std::make_unique<AiSystem>();
|
m_aiSystem = std::make_unique<AiSystem>();
|
||||||
m_movementSystem = std::make_unique<MovementSystem>();
|
m_movementIntentSystem = std::make_unique<MovementIntentSystem>();
|
||||||
|
m_dynamicBodySystem = std::make_unique<DynamicBodySystem>();
|
||||||
m_scrapSystem = std::make_unique<ScrapSystem>(m_admin);
|
m_scrapSystem = std::make_unique<ScrapSystem>(m_admin);
|
||||||
m_waveSystem = std::make_unique<WaveSystem>(m_config, m_rng);
|
m_waveSystem = std::make_unique<WaveSystem>(m_config, m_rng);
|
||||||
m_combatSystem = std::make_unique<CombatSystem>(m_config);
|
m_combatSystem = std::make_unique<CombatSystem>(m_config);
|
||||||
@@ -181,7 +189,8 @@ void Simulation::tick()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 10: advance ship positions
|
// Step 10: advance ship positions
|
||||||
m_movementSystem->tick(m_admin);
|
m_movementIntentSystem->tick(m_admin);
|
||||||
|
m_dynamicBodySystem->tick(m_admin);
|
||||||
|
|
||||||
// Step 11: scrap despawn
|
// Step 11: scrap despawn
|
||||||
m_scrapSystem->tickDespawn(m_currentTick);
|
m_scrapSystem->tickDespawn(m_currentTick);
|
||||||
@@ -224,7 +233,7 @@ void Simulation::placeInitialStructures()
|
|||||||
const float psHp = static_cast<float>(
|
const float psHp = static_cast<float>(
|
||||||
m_config.stations.playerStation.hpFormula.evaluate(psLevel));
|
m_config.stations.playerStation.hpFormula.evaluate(psLevel));
|
||||||
|
|
||||||
Weapon psWeapon;
|
WeaponComponent psWeapon;
|
||||||
psWeapon.damage = static_cast<float>(
|
psWeapon.damage = static_cast<float>(
|
||||||
m_config.stations.playerStation.damageFormula.evaluate(psLevel));
|
m_config.stations.playerStation.damageFormula.evaluate(psLevel));
|
||||||
psWeapon.range = static_cast<float>(
|
psWeapon.range = static_cast<float>(
|
||||||
@@ -246,7 +255,7 @@ void Simulation::placeInitialStructures()
|
|||||||
}
|
}
|
||||||
m_playerStation1Entity = m_admin.spawnStation(
|
m_playerStation1Entity = m_admin.spawnStation(
|
||||||
anchor, psParsed.footprint, absCells, psHp, psHp, false);
|
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());
|
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -258,7 +267,7 @@ void Simulation::placeInitialStructures()
|
|||||||
}
|
}
|
||||||
m_playerStation2Entity = m_admin.spawnStation(
|
m_playerStation2Entity = m_admin.spawnStation(
|
||||||
anchor, psParsed.footprint, absCells, psHp, psHp, false);
|
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());
|
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,7 +294,7 @@ void Simulation::placeEnemyStationSet(int generation)
|
|||||||
const float esHp = static_cast<float>(
|
const float esHp = static_cast<float>(
|
||||||
m_config.stations.enemyStation.hpFormula.evaluate(genD));
|
m_config.stations.enemyStation.hpFormula.evaluate(genD));
|
||||||
|
|
||||||
Weapon esWeapon;
|
WeaponComponent esWeapon;
|
||||||
esWeapon.damage = static_cast<float>(
|
esWeapon.damage = static_cast<float>(
|
||||||
m_config.stations.enemyStation.damageFormula.evaluate(genD));
|
m_config.stations.enemyStation.damageFormula.evaluate(genD));
|
||||||
esWeapon.range = static_cast<float>(
|
esWeapon.range = static_cast<float>(
|
||||||
@@ -307,7 +316,7 @@ void Simulation::placeEnemyStationSet(int generation)
|
|||||||
}
|
}
|
||||||
m_currentEnemyStationEntities[0] = m_admin.spawnStation(
|
m_currentEnemyStationEntities[0] = m_admin.spawnStation(
|
||||||
anchor, esParsed.footprint, absCells, esHp, esHp, true);
|
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());
|
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -319,7 +328,7 @@ void Simulation::placeEnemyStationSet(int generation)
|
|||||||
}
|
}
|
||||||
m_currentEnemyStationEntities[1] = m_admin.spawnStation(
|
m_currentEnemyStationEntities[1] = m_admin.spawnStation(
|
||||||
anchor, esParsed.footprint, absCells, esHp, esHp, true);
|
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());
|
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,8 +341,9 @@ void Simulation::tickDeathsAndLoot()
|
|||||||
{
|
{
|
||||||
// --- Dead ships ---
|
// --- Dead ships ---
|
||||||
std::vector<entt::entity> deadShips;
|
std::vector<entt::entity> deadShips;
|
||||||
m_admin.forEach<ShipIdentity, Health>(
|
m_admin.forEach<ShipIdentityComponent, HealthComponent>(
|
||||||
[&deadShips](entt::entity e, const ShipIdentity& /*si*/, const Health& h)
|
[&deadShips](entt::entity e, const ShipIdentityComponent& /*si*/,
|
||||||
|
const HealthComponent& h)
|
||||||
{
|
{
|
||||||
if (h.hp <= 0.0f)
|
if (h.hp <= 0.0f)
|
||||||
{
|
{
|
||||||
@@ -343,8 +353,8 @@ void Simulation::tickDeathsAndLoot()
|
|||||||
|
|
||||||
for (entt::entity deadEntity : deadShips)
|
for (entt::entity deadEntity : deadShips)
|
||||||
{
|
{
|
||||||
const ShipIdentity& si = m_admin.get<ShipIdentity>(deadEntity);
|
const ShipIdentityComponent& si = m_admin.get<ShipIdentityComponent>(deadEntity);
|
||||||
const Position& pos = m_admin.get<Position>(deadEntity);
|
const PositionComponent& pos = m_admin.get<PositionComponent>(deadEntity);
|
||||||
for (const ShipDef& def : m_config.ships.ships)
|
for (const ShipDef& def : m_config.ships.ships)
|
||||||
{
|
{
|
||||||
if (def.id == si.schematicId && def.loot.scrapDrop > 0)
|
if (def.id == si.schematicId && def.loot.scrapDrop > 0)
|
||||||
@@ -360,8 +370,9 @@ void Simulation::tickDeathsAndLoot()
|
|||||||
|
|
||||||
// --- Dead stations ---
|
// --- Dead stations ---
|
||||||
std::vector<entt::entity> deadStations;
|
std::vector<entt::entity> deadStations;
|
||||||
m_admin.forEach<StationBody, Health>(
|
m_admin.forEach<StationBodyComponent, HealthComponent>(
|
||||||
[&deadStations](entt::entity e, const StationBody& /*sb*/, const Health& h)
|
[&deadStations](entt::entity e, const StationBodyComponent& /*sb*/,
|
||||||
|
const HealthComponent& h)
|
||||||
{
|
{
|
||||||
if (h.hp <= 0.0f)
|
if (h.hp <= 0.0f)
|
||||||
{
|
{
|
||||||
@@ -371,9 +382,9 @@ void Simulation::tickDeathsAndLoot()
|
|||||||
|
|
||||||
for (entt::entity deadEntity : deadStations)
|
for (entt::entity deadEntity : deadStations)
|
||||||
{
|
{
|
||||||
const StationBody& sb = m_admin.get<StationBody>(deadEntity);
|
const StationBodyComponent& sb = m_admin.get<StationBodyComponent>(deadEntity);
|
||||||
const Position& pos = m_admin.get<Position>(deadEntity);
|
const PositionComponent& pos = m_admin.get<PositionComponent>(deadEntity);
|
||||||
const Faction& fac = m_admin.get<Faction>(deadEntity);
|
const FactionComponent& fac = m_admin.get<FactionComponent>(deadEntity);
|
||||||
|
|
||||||
const Tick despawnAt = m_currentTick
|
const Tick despawnAt = m_currentTick
|
||||||
+ secondsToTicks(m_config.world.scrapDespawnSeconds);
|
+ secondsToTicks(m_config.world.scrapDespawnSeconds);
|
||||||
@@ -402,7 +413,7 @@ void Simulation::tickDeathsAndLoot()
|
|||||||
// --- HQ death check ---
|
// --- HQ death check ---
|
||||||
if (m_admin.isValid(m_hqProxyEntity))
|
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)
|
if (hqHealth.hp <= 0.0f)
|
||||||
{
|
{
|
||||||
m_gameOver = true;
|
m_gameOver = true;
|
||||||
@@ -411,9 +422,9 @@ void Simulation::tickDeathsAndLoot()
|
|||||||
|
|
||||||
// --- Push check: if both current enemy stations are gone, trigger push ---
|
// --- Push check: if both current enemy stations are gone, trigger push ---
|
||||||
const bool es0Gone = !m_admin.isValid(m_currentEnemyStationEntities[0])
|
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])
|
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 &&
|
if (es0Gone && es1Gone &&
|
||||||
m_currentEnemyStationEntities[0] != entt::null)
|
m_currentEnemyStationEntities[0] != entt::null)
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
class AiSystem;
|
class AiSystem;
|
||||||
class BuildingSystem;
|
class BuildingSystem;
|
||||||
class CombatSystem;
|
class CombatSystem;
|
||||||
class MovementSystem;
|
class DynamicBodySystem;
|
||||||
|
class MovementIntentSystem;
|
||||||
class ShipSystem;
|
class ShipSystem;
|
||||||
class ScrapSystem;
|
class ScrapSystem;
|
||||||
class WaveSystem;
|
class WaveSystem;
|
||||||
@@ -124,7 +125,8 @@ private:
|
|||||||
std::unique_ptr<BuildingSystem> m_buildingSystem;
|
std::unique_ptr<BuildingSystem> m_buildingSystem;
|
||||||
std::unique_ptr<ShipSystem> m_shipSystem;
|
std::unique_ptr<ShipSystem> m_shipSystem;
|
||||||
std::unique_ptr<AiSystem> m_aiSystem;
|
std::unique_ptr<AiSystem> m_aiSystem;
|
||||||
std::unique_ptr<MovementSystem> m_movementSystem;
|
std::unique_ptr<MovementIntentSystem> m_movementIntentSystem;
|
||||||
|
std::unique_ptr<DynamicBodySystem> m_dynamicBodySystem;
|
||||||
std::unique_ptr<ScrapSystem> m_scrapSystem;
|
std::unique_ptr<ScrapSystem> m_scrapSystem;
|
||||||
std::unique_ptr<WaveSystem> m_waveSystem;
|
std::unique_ptr<WaveSystem> m_waveSystem;
|
||||||
std::unique_ptr<CombatSystem> m_combatSystem;
|
std::unique_ptr<CombatSystem> m_combatSystem;
|
||||||
|
|||||||
@@ -10,14 +10,27 @@
|
|||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
#include "ConfigLoader.h"
|
#include "ConfigLoader.h"
|
||||||
#include "EcsComponents.h"
|
#include "DynamicBodyComponent.h"
|
||||||
|
#include "DynamicBodySystem.h"
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
#include "MovementSystem.h"
|
#include "FactionComponent.h"
|
||||||
|
#include "HealthComponent.h"
|
||||||
|
#include "HomeReturnBehaviorComponent.h"
|
||||||
|
#include "MovementIntentComponent.h"
|
||||||
|
#include "MovementIntentSystem.h"
|
||||||
|
#include "PositionComponent.h"
|
||||||
|
#include "RallyBehaviorComponent.h"
|
||||||
|
#include "RepairBehaviorComponent.h"
|
||||||
|
#include "RepairToolComponent.h"
|
||||||
#include "Rotation.h"
|
#include "Rotation.h"
|
||||||
|
#include "SalvageBehaviorComponent.h"
|
||||||
|
#include "SalvageCargoComponent.h"
|
||||||
#include "ScrapSystem.h"
|
#include "ScrapSystem.h"
|
||||||
#include "Ship.h"
|
#include "SensorRangeComponent.h"
|
||||||
|
#include "ShipIdentityComponent.h"
|
||||||
#include "ShipSystem.h"
|
#include "ShipSystem.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
|
#include "ThreatResponseBehaviorComponent.h"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Fixture
|
// Fixture
|
||||||
@@ -38,9 +51,10 @@ struct Fixture
|
|||||||
EntityAdmin admin;
|
EntityAdmin admin;
|
||||||
BuildingSystem buildings;
|
BuildingSystem buildings;
|
||||||
ShipSystem ships;
|
ShipSystem ships;
|
||||||
AiSystem ai;
|
AiSystem ai;
|
||||||
MovementSystem movement;
|
MovementIntentSystem movementIntent;
|
||||||
ScrapSystem scraps;
|
DynamicBodySystem dynamicBody;
|
||||||
|
ScrapSystem scraps;
|
||||||
Tick tick;
|
Tick tick;
|
||||||
|
|
||||||
explicit Fixture()
|
explicit Fixture()
|
||||||
@@ -68,25 +82,26 @@ struct Fixture
|
|||||||
ai.tickThreatResponseBehavior(admin, buildings);
|
ai.tickThreatResponseBehavior(admin, buildings);
|
||||||
ai.tickRepairBehavior(admin, buildings);
|
ai.tickRepairBehavior(admin, buildings);
|
||||||
ai.tickSalvageBehavior(admin, scraps, buildings);
|
ai.tickSalvageBehavior(admin, scraps, buildings);
|
||||||
movement.tick(admin);
|
movementIntent.tick(admin);
|
||||||
|
dynamicBody.tick(admin);
|
||||||
++tick;
|
++tick;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helpers to read ECS data for a ship entity.
|
// Helpers to read ECS data for a ship entity.
|
||||||
static const MovementIntent& intent(EntityAdmin& a, entt::entity e)
|
static const MovementIntentComponent& intent(EntityAdmin& a, entt::entity e)
|
||||||
{
|
{
|
||||||
return a.get<MovementIntent>(e);
|
return a.get<MovementIntentComponent>(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const Health& health(EntityAdmin& a, entt::entity e)
|
static const HealthComponent& health(EntityAdmin& a, entt::entity e)
|
||||||
{
|
{
|
||||||
return a.get<Health>(e);
|
return a.get<HealthComponent>(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const Position& pos(EntityAdmin& a, entt::entity e)
|
static const PositionComponent& pos(EntityAdmin& a, entt::entity e)
|
||||||
{
|
{
|
||||||
return a.get<Position>(e);
|
return a.get<PositionComponent>(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -99,7 +114,7 @@ TEST_CASE("BehaviorSystem: clearMovementIntents resets all ships to priority 0",
|
|||||||
Fixture f;
|
Fixture f;
|
||||||
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
f.admin.get<MovementIntent>(e) = MovementIntent{3, QVector2D(10.0f, 0.0f)};
|
f.admin.get<MovementIntentComponent>(e) = MovementIntentComponent{3, QVector2D(10.0f, 0.0f)};
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
|
|
||||||
REQUIRE(intent(f.admin, e).priority == 0);
|
REQUIRE(intent(f.admin, e).priority == 0);
|
||||||
@@ -115,9 +130,10 @@ TEST_CASE("BehaviorSystem: tickMovement advances ship by maxSpeedPerTick toward
|
|||||||
Fixture f;
|
Fixture f;
|
||||||
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
const float speed = f.admin.get<ShipDynamics>(e).maxSpeedPerTick;
|
const float speed = f.admin.get<DynamicBodyComponent>(e).maxSpeedPerTick;
|
||||||
f.admin.get<MovementIntent>(e) = MovementIntent{1, QVector2D(100.0f, 0.0f)};
|
f.admin.get<MovementIntentComponent>(e) = MovementIntentComponent{1, QVector2D(100.0f, 0.0f)};
|
||||||
f.movement.tick(f.admin);
|
f.movementIntent.tick(f.admin);
|
||||||
|
f.dynamicBody.tick(f.admin);
|
||||||
|
|
||||||
REQUIRE(pos(f.admin, e).value.x() == Approx(speed));
|
REQUIRE(pos(f.admin, e).value.x() == Approx(speed));
|
||||||
REQUIRE(pos(f.admin, e).value.y() == Approx(0.0f));
|
REQUIRE(pos(f.admin, e).value.y() == Approx(0.0f));
|
||||||
@@ -129,10 +145,11 @@ TEST_CASE("BehaviorSystem: tickMovement stops exactly at target without overshoo
|
|||||||
Fixture f;
|
Fixture f;
|
||||||
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
const float speed = f.admin.get<ShipDynamics>(e).maxSpeedPerTick;
|
const float speed = f.admin.get<DynamicBodyComponent>(e).maxSpeedPerTick;
|
||||||
const QVector2D target(speed * 0.5f, 0.0f);
|
const QVector2D target(speed * 0.5f, 0.0f);
|
||||||
f.admin.get<MovementIntent>(e) = MovementIntent{1, target};
|
f.admin.get<MovementIntentComponent>(e) = MovementIntentComponent{1, target};
|
||||||
f.movement.tick(f.admin);
|
f.movementIntent.tick(f.admin);
|
||||||
|
f.dynamicBody.tick(f.admin);
|
||||||
|
|
||||||
REQUIRE(pos(f.admin, e).value.x() == Approx(target.x()));
|
REQUIRE(pos(f.admin, e).value.x() == Approx(target.x()));
|
||||||
REQUIRE(pos(f.admin, e).value.y() == Approx(target.y()));
|
REQUIRE(pos(f.admin, e).value.y() == Approx(target.y()));
|
||||||
@@ -147,8 +164,8 @@ TEST_CASE("BehaviorSystem: tickHomeReturnBehavior does nothing when HP is above
|
|||||||
{
|
{
|
||||||
Fixture f;
|
Fixture f;
|
||||||
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||||
f.admin.addComponent<HomeReturnBehavior>(e, HomeReturnBehavior{0.3f, QVector2D(-10.0f, 0.0f)});
|
f.admin.addComponent<HomeReturnBehaviorComponent>(e, HomeReturnBehaviorComponent{0.3f, QVector2D(-10.0f, 0.0f)});
|
||||||
f.admin.get<Health>(e).hp = f.admin.get<Health>(e).maxHp; // full HP
|
f.admin.get<HealthComponent>(e).hp = f.admin.get<HealthComponent>(e).maxHp; // full HP
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickHomeReturnBehavior(f.admin);
|
f.ai.tickHomeReturnBehavior(f.admin);
|
||||||
@@ -162,8 +179,8 @@ TEST_CASE("BehaviorSystem: tickHomeReturnBehavior writes priority-4 intent towar
|
|||||||
Fixture f;
|
Fixture f;
|
||||||
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||||
const QVector2D homePos(-10.0f, 0.0f);
|
const QVector2D homePos(-10.0f, 0.0f);
|
||||||
f.admin.addComponent<HomeReturnBehavior>(e, HomeReturnBehavior{0.5f, homePos});
|
f.admin.addComponent<HomeReturnBehaviorComponent>(e, HomeReturnBehaviorComponent{0.5f, homePos});
|
||||||
f.admin.get<Health>(e).hp = f.admin.get<Health>(e).maxHp * 0.2f; // below threshold
|
f.admin.get<HealthComponent>(e).hp = f.admin.get<HealthComponent>(e).maxHp * 0.2f; // below threshold
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickHomeReturnBehavior(f.admin);
|
f.ai.tickHomeReturnBehavior(f.admin);
|
||||||
@@ -180,8 +197,8 @@ TEST_CASE("BehaviorSystem: tickHomeReturnBehavior priority-4 beats tickThreatRes
|
|||||||
f.ships.spawn("interceptor", 1, QVector2D(5.0f, 0.0f), /*isEnemy=*/true);
|
f.ships.spawn("interceptor", 1, QVector2D(5.0f, 0.0f), /*isEnemy=*/true);
|
||||||
|
|
||||||
const QVector2D homePos(-50.0f, 0.0f);
|
const QVector2D homePos(-50.0f, 0.0f);
|
||||||
f.admin.addComponent<HomeReturnBehavior>(player, HomeReturnBehavior{0.5f, homePos});
|
f.admin.addComponent<HomeReturnBehaviorComponent>(player, HomeReturnBehaviorComponent{0.5f, homePos});
|
||||||
f.admin.get<Health>(player).hp = f.admin.get<Health>(player).maxHp * 0.1f;
|
f.admin.get<HealthComponent>(player).hp = f.admin.get<HealthComponent>(player).maxHp * 0.1f;
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickHomeReturnBehavior(f.admin);
|
f.ai.tickHomeReturnBehavior(f.admin);
|
||||||
@@ -206,8 +223,8 @@ TEST_CASE("BehaviorSystem: player combat ship acquires nearest enemy ship in ran
|
|||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE(f.admin.hasAll<ThreatResponseBehavior>(player));
|
REQUIRE(f.admin.hasAll<ThreatResponseBehaviorComponent>(player));
|
||||||
const ThreatResponseBehavior& threatResponseBehavior = f.admin.get<ThreatResponseBehavior>(player);
|
const ThreatResponseBehaviorComponent& threatResponseBehavior = f.admin.get<ThreatResponseBehaviorComponent>(player);
|
||||||
REQUIRE(threatResponseBehavior.currentTarget.has_value());
|
REQUIRE(threatResponseBehavior.currentTarget.has_value());
|
||||||
REQUIRE(*threatResponseBehavior.currentTarget == enemy);
|
REQUIRE(*threatResponseBehavior.currentTarget == enemy);
|
||||||
}
|
}
|
||||||
@@ -222,8 +239,8 @@ TEST_CASE("BehaviorSystem: player combat ship does not target friendly ships",
|
|||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE(f.admin.hasAll<ThreatResponseBehavior>(e1));
|
REQUIRE(f.admin.hasAll<ThreatResponseBehaviorComponent>(e1));
|
||||||
REQUIRE_FALSE(f.admin.get<ThreatResponseBehavior>(e1).currentTarget.has_value());
|
REQUIRE_FALSE(f.admin.get<ThreatResponseBehaviorComponent>(e1).currentTarget.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BehaviorSystem: player combat ship ignores enemy beyond engagement range",
|
TEST_CASE("BehaviorSystem: player combat ship ignores enemy beyond engagement range",
|
||||||
@@ -236,7 +253,7 @@ TEST_CASE("BehaviorSystem: player combat ship ignores enemy beyond engagement ra
|
|||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE_FALSE(f.admin.get<ThreatResponseBehavior>(player).currentTarget.has_value());
|
REQUIRE_FALSE(f.admin.get<ThreatResponseBehaviorComponent>(player).currentTarget.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -254,8 +271,8 @@ TEST_CASE("BehaviorSystem: enemy ship acquires nearest player ship in range",
|
|||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE(f.admin.hasAll<ThreatResponseBehavior>(enemy));
|
REQUIRE(f.admin.hasAll<ThreatResponseBehaviorComponent>(enemy));
|
||||||
const ThreatResponseBehavior& threatResponseBehavior = f.admin.get<ThreatResponseBehavior>(enemy);
|
const ThreatResponseBehaviorComponent& threatResponseBehavior = f.admin.get<ThreatResponseBehaviorComponent>(enemy);
|
||||||
REQUIRE(threatResponseBehavior.currentTarget.has_value());
|
REQUIRE(threatResponseBehavior.currentTarget.has_value());
|
||||||
REQUIRE(*threatResponseBehavior.currentTarget == player);
|
REQUIRE(*threatResponseBehavior.currentTarget == player);
|
||||||
}
|
}
|
||||||
@@ -285,7 +302,7 @@ TEST_CASE("BehaviorSystem: repair ship writes intent toward damaged friendly shi
|
|||||||
const entt::entity repairShip = f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity repairShip = f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
||||||
const entt::entity friendly = f.ships.spawn("interceptor", 1, QVector2D(5.0f, 0.0f));
|
const entt::entity friendly = f.ships.spawn("interceptor", 1, QVector2D(5.0f, 0.0f));
|
||||||
|
|
||||||
f.admin.get<Health>(friendly).hp = f.admin.get<Health>(friendly).maxHp * 0.5f;
|
f.admin.get<HealthComponent>(friendly).hp = f.admin.get<HealthComponent>(friendly).maxHp * 0.5f;
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickRepairBehavior(f.admin, f.buildings);
|
f.ai.tickRepairBehavior(f.admin, f.buildings);
|
||||||
@@ -301,8 +318,8 @@ TEST_CASE("BehaviorSystem: repair ship heals damaged ally within repair range",
|
|||||||
const entt::entity repairShip = f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity repairShip = f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
||||||
const entt::entity friendly = f.ships.spawn("interceptor", 1, QVector2D(1.0f, 0.0f));
|
const entt::entity friendly = f.ships.spawn("interceptor", 1, QVector2D(1.0f, 0.0f));
|
||||||
|
|
||||||
const float initialHp = f.admin.get<Health>(friendly).maxHp * 0.5f;
|
const float initialHp = f.admin.get<HealthComponent>(friendly).maxHp * 0.5f;
|
||||||
f.admin.get<Health>(friendly).hp = initialHp;
|
f.admin.get<HealthComponent>(friendly).hp = initialHp;
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickRepairBehavior(f.admin, f.buildings);
|
f.ai.tickRepairBehavior(f.admin, f.buildings);
|
||||||
@@ -316,7 +333,7 @@ TEST_CASE("BehaviorSystem: repair ship does not heal above maxHp", "[behavior]")
|
|||||||
f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
||||||
const entt::entity friendly = f.ships.spawn("interceptor", 1, QVector2D(1.0f, 0.0f));
|
const entt::entity friendly = f.ships.spawn("interceptor", 1, QVector2D(1.0f, 0.0f));
|
||||||
|
|
||||||
f.admin.get<Health>(friendly).hp = f.admin.get<Health>(friendly).maxHp - 0.001f;
|
f.admin.get<HealthComponent>(friendly).hp = f.admin.get<HealthComponent>(friendly).maxHp - 0.001f;
|
||||||
|
|
||||||
for (int i = 0; i < 5; ++i)
|
for (int i = 0; i < 5; ++i)
|
||||||
{
|
{
|
||||||
@@ -324,7 +341,7 @@ TEST_CASE("BehaviorSystem: repair ship does not heal above maxHp", "[behavior]")
|
|||||||
f.ai.tickRepairBehavior(f.admin, f.buildings);
|
f.ai.tickRepairBehavior(f.admin, f.buildings);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Health& h = health(f.admin, friendly);
|
const HealthComponent& h = health(f.admin, friendly);
|
||||||
REQUIRE(h.hp <= h.maxHp);
|
REQUIRE(h.hp <= h.maxHp);
|
||||||
REQUIRE(h.hp == Approx(h.maxHp));
|
REQUIRE(h.hp == Approx(h.maxHp));
|
||||||
}
|
}
|
||||||
@@ -357,7 +374,7 @@ TEST_CASE("BehaviorSystem: salvage ship collects scrap on arrival", "[behavior]"
|
|||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
|
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
|
||||||
|
|
||||||
REQUIRE(f.admin.get<SalvageCargo>(ship).current == 1);
|
REQUIRE(f.admin.get<SalvageCargoComponent>(ship).current == 1);
|
||||||
REQUIRE_FALSE(f.admin.isValid(scrapEntity));
|
REQUIRE_FALSE(f.admin.isValid(scrapEntity));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,13 +396,13 @@ TEST_CASE("BehaviorSystem: full-cargo salvage ship moves toward SalvageBay", "[b
|
|||||||
REQUIRE(f.buildings.findBuilding(bayId) != nullptr);
|
REQUIRE(f.buildings.findBuilding(bayId) != nullptr);
|
||||||
|
|
||||||
const entt::entity ship = f.ships.spawn("salvage_ship", 1, QVector2D(5.0f, 0.0f));
|
const entt::entity ship = f.ships.spawn("salvage_ship", 1, QVector2D(5.0f, 0.0f));
|
||||||
SalvageCargo& cargo = f.admin.get<SalvageCargo>(ship);
|
SalvageCargoComponent& cargo = f.admin.get<SalvageCargoComponent>(ship);
|
||||||
cargo.current = cargo.capacity; // full cargo
|
cargo.current = cargo.capacity; // full cargo
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
|
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
|
||||||
|
|
||||||
const MovementIntent& i = intent(f.admin, ship);
|
const MovementIntentComponent& i = intent(f.admin, ship);
|
||||||
REQUIRE(i.priority == 1);
|
REQUIRE(i.priority == 1);
|
||||||
REQUIRE(i.target.x() < pos(f.admin, ship).value.x());
|
REQUIRE(i.target.x() < pos(f.admin, ship).value.x());
|
||||||
}
|
}
|
||||||
@@ -398,7 +415,7 @@ TEST_CASE("SensorRange: sensorRange is populated from config formula at spawn",
|
|||||||
{
|
{
|
||||||
Fixture f;
|
Fixture f;
|
||||||
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||||
REQUIRE(f.admin.get<SensorRange>(e).value == Approx(200.0f));
|
REQUIRE(f.admin.get<SensorRangeComponent>(e).value == Approx(200.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -415,7 +432,7 @@ TEST_CASE("SensorRange: player combat ship acquires enemy just inside sensor ran
|
|||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE(f.admin.get<ThreatResponseBehavior>(player).currentTarget == enemy);
|
REQUIRE(f.admin.get<ThreatResponseBehaviorComponent>(player).currentTarget == enemy);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("SensorRange: player combat ship ignores enemy just outside sensor range", "[sensor]")
|
TEST_CASE("SensorRange: player combat ship ignores enemy just outside sensor range", "[sensor]")
|
||||||
@@ -427,7 +444,7 @@ TEST_CASE("SensorRange: player combat ship ignores enemy just outside sensor ran
|
|||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE_FALSE(f.admin.get<ThreatResponseBehavior>(player).currentTarget.has_value());
|
REQUIRE_FALSE(f.admin.get<ThreatResponseBehaviorComponent>(player).currentTarget.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("SensorRange: enemy ship ignores player just outside sensor range", "[sensor]")
|
TEST_CASE("SensorRange: enemy ship ignores player just outside sensor range", "[sensor]")
|
||||||
@@ -440,7 +457,7 @@ TEST_CASE("SensorRange: enemy ship ignores player just outside sensor range", "[
|
|||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
f.ai.tickThreatResponseBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE_FALSE(f.admin.get<ThreatResponseBehavior>(enemy).currentTarget.has_value());
|
REQUIRE_FALSE(f.admin.get<ThreatResponseBehaviorComponent>(enemy).currentTarget.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -477,12 +494,12 @@ TEST_CASE("SensorRange: repair ship does not acquire damaged ally beyond sensor
|
|||||||
Fixture f;
|
Fixture f;
|
||||||
const entt::entity repairShip = f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity repairShip = f.ships.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
||||||
const entt::entity friendly = f.ships.spawn("interceptor", 1, QVector2D(300.0f, 0.0f));
|
const entt::entity friendly = f.ships.spawn("interceptor", 1, QVector2D(300.0f, 0.0f));
|
||||||
f.admin.get<Health>(friendly).hp = f.admin.get<Health>(friendly).maxHp * 0.5f;
|
f.admin.get<HealthComponent>(friendly).hp = f.admin.get<HealthComponent>(friendly).maxHp * 0.5f;
|
||||||
|
|
||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickRepairBehavior(f.admin, f.buildings);
|
f.ai.tickRepairBehavior(f.admin, f.buildings);
|
||||||
|
|
||||||
REQUIRE_FALSE(f.admin.get<RepairBehavior>(repairShip).currentTarget.has_value());
|
REQUIRE_FALSE(f.admin.get<RepairBehaviorComponent>(repairShip).currentTarget.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -498,6 +515,6 @@ TEST_CASE("SensorRange: salvage ship ignores scrap beyond sensor range", "[senso
|
|||||||
f.ships.clearMovementIntents();
|
f.ships.clearMovementIntents();
|
||||||
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
|
f.ai.tickSalvageBehavior(f.admin, f.scraps, f.buildings);
|
||||||
|
|
||||||
REQUIRE_FALSE(f.admin.get<SalvageBehavior>(ship).scrapTarget.has_value());
|
REQUIRE_FALSE(f.admin.get<SalvageBehaviorComponent>(ship).scrapTarget.has_value());
|
||||||
REQUIRE(intent(f.admin, ship).target.x() > pos(f.admin, ship).value.x());
|
REQUIRE(intent(f.admin, ship).target.x() > pos(f.admin, ship).value.x());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,18 @@
|
|||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
#include "CombatSystem.h"
|
#include "CombatSystem.h"
|
||||||
#include "ConfigLoader.h"
|
#include "ConfigLoader.h"
|
||||||
#include "EcsComponents.h"
|
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
|
#include "FactionComponent.h"
|
||||||
#include "FireEvent.h"
|
#include "FireEvent.h"
|
||||||
|
#include "HealthComponent.h"
|
||||||
|
#include "HqProxyComponent.h"
|
||||||
#include "ScrapSystem.h"
|
#include "ScrapSystem.h"
|
||||||
#include "Ship.h"
|
|
||||||
#include "ShipSystem.h"
|
#include "ShipSystem.h"
|
||||||
#include "Simulation.h"
|
#include "Simulation.h"
|
||||||
|
#include "StationBodyComponent.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
|
#include "ThreatResponseBehaviorComponent.h"
|
||||||
|
#include "WeaponComponent.h"
|
||||||
|
|
||||||
static GameConfig loadConfig()
|
static GameConfig loadConfig()
|
||||||
{
|
{
|
||||||
@@ -63,14 +67,14 @@ struct CombatFixture
|
|||||||
|
|
||||||
void wireEnemyTarget(entt::entity enemy, entt::entity playerTarget)
|
void wireEnemyTarget(entt::entity enemy, entt::entity playerTarget)
|
||||||
{
|
{
|
||||||
if (admin.hasAll<Weapon>(enemy))
|
if (admin.hasAll<WeaponComponent>(enemy))
|
||||||
{
|
{
|
||||||
admin.get<Weapon>(enemy).currentTarget = playerTarget;
|
admin.get<WeaponComponent>(enemy).currentTarget = playerTarget;
|
||||||
admin.get<Weapon>(enemy).cooldownTicks = 0.0f;
|
admin.get<WeaponComponent>(enemy).cooldownTicks = 0.0f;
|
||||||
}
|
}
|
||||||
if (admin.hasAll<ThreatResponseBehavior>(enemy))
|
if (admin.hasAll<ThreatResponseBehaviorComponent>(enemy))
|
||||||
{
|
{
|
||||||
admin.get<ThreatResponseBehavior>(enemy).currentTarget = playerTarget;
|
admin.get<ThreatResponseBehaviorComponent>(enemy).currentTarget = playerTarget;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -89,13 +93,13 @@ TEST_CASE("CombatSystem: ship fires when cooldown=0 and target in range", "[comb
|
|||||||
const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false);
|
const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false);
|
||||||
f.wireEnemyTarget(enemy, player);
|
f.wireEnemyTarget(enemy, player);
|
||||||
|
|
||||||
const float hpBefore = f.admin.get<Health>(player).hp;
|
const float hpBefore = f.admin.get<HealthComponent>(player).hp;
|
||||||
|
|
||||||
std::vector<FireEvent> events;
|
std::vector<FireEvent> events;
|
||||||
f.combat.tick(0, f.admin, f.buildings, events);
|
f.combat.tick(0, f.admin, f.buildings, events);
|
||||||
f.combat.applyPendingDamage(5, f.admin);
|
f.combat.applyPendingDamage(5, f.admin);
|
||||||
|
|
||||||
REQUIRE(f.admin.get<Health>(player).hp < hpBefore);
|
REQUIRE(f.admin.get<HealthComponent>(player).hp < hpBefore);
|
||||||
REQUIRE(events.size() >= 1);
|
REQUIRE(events.size() >= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,15 +113,26 @@ 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);
|
const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false);
|
||||||
|
|
||||||
f.wireEnemyTarget(enemy, player);
|
f.wireEnemyTarget(enemy, player);
|
||||||
f.admin.get<Weapon>(enemy).cooldownTicks = 3.0f; // override to 3
|
f.admin.get<WeaponComponent>(enemy).cooldownTicks = 3.0f; // override to 3
|
||||||
|
|
||||||
|
auto enemyFiredIn = [&enemy](const std::vector<FireEvent>& evts)
|
||||||
|
{
|
||||||
|
for (const FireEvent& evt : evts)
|
||||||
|
{
|
||||||
|
if (evt.shooter == enemy) { return true; }
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
std::vector<FireEvent> events;
|
std::vector<FireEvent> events;
|
||||||
f.combat.tick(0, f.admin, f.buildings, events);
|
f.combat.tick(0, f.admin, f.buildings, events);
|
||||||
|
REQUIRE_FALSE(enemyFiredIn(events));
|
||||||
|
|
||||||
f.combat.tick(1, f.admin, f.buildings, events);
|
f.combat.tick(1, f.admin, f.buildings, events);
|
||||||
REQUIRE(events.empty());
|
REQUIRE_FALSE(enemyFiredIn(events));
|
||||||
|
|
||||||
f.combat.tick(2, f.admin, f.buildings, events);
|
f.combat.tick(2, f.admin, f.buildings, events);
|
||||||
REQUIRE(events.size() == 1);
|
REQUIRE(enemyFiredIn(events));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("CombatSystem: no fire when target is out of range", "[combat]")
|
TEST_CASE("CombatSystem: no fire when target is out of range", "[combat]")
|
||||||
@@ -146,8 +161,8 @@ TEST_CASE("CombatSystem: player station fires at enemy ship in range", "[combat]
|
|||||||
// Find the player station entity via ECS.
|
// Find the player station entity via ECS.
|
||||||
entt::entity stationEntity = entt::null;
|
entt::entity stationEntity = entt::null;
|
||||||
QVector2D stationCenter;
|
QVector2D stationCenter;
|
||||||
sim.admin().forEach<StationBody, Faction>(
|
sim.admin().forEach<StationBodyComponent, FactionComponent>(
|
||||||
[&](entt::entity e, const StationBody& sb, const Faction& f)
|
[&](entt::entity e, const StationBodyComponent& sb, const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (!f.isEnemy && stationEntity == entt::null)
|
if (!f.isEnemy && stationEntity == entt::null)
|
||||||
{
|
{
|
||||||
@@ -184,8 +199,8 @@ TEST_CASE("CombatSystem: enemy station fires at player ship in range", "[combat]
|
|||||||
|
|
||||||
entt::entity stationEntity = entt::null;
|
entt::entity stationEntity = entt::null;
|
||||||
QVector2D stationCenter;
|
QVector2D stationCenter;
|
||||||
sim.admin().forEach<StationBody, Faction>(
|
sim.admin().forEach<StationBodyComponent, FactionComponent>(
|
||||||
[&](entt::entity e, const StationBody& sb, const Faction& f)
|
[&](entt::entity e, const StationBodyComponent& sb, const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (f.isEnemy && stationEntity == entt::null)
|
if (f.isEnemy && stationEntity == entt::null)
|
||||||
{
|
{
|
||||||
@@ -222,8 +237,8 @@ TEST_CASE("CombatSystem: player ship fires at enemy station in range", "[combat]
|
|||||||
|
|
||||||
entt::entity stationEntity = entt::null;
|
entt::entity stationEntity = entt::null;
|
||||||
QVector2D stationCenter;
|
QVector2D stationCenter;
|
||||||
sim.admin().forEach<StationBody, Faction>(
|
sim.admin().forEach<StationBodyComponent, FactionComponent>(
|
||||||
[&](entt::entity e, const StationBody& sb, const Faction& f)
|
[&](entt::entity e, const StationBodyComponent& sb, const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (f.isEnemy && stationEntity == entt::null)
|
if (f.isEnemy && stationEntity == entt::null)
|
||||||
{
|
{
|
||||||
@@ -271,7 +286,7 @@ TEST_CASE("CombatSystem: damage not applied before impact tick", "[combat]")
|
|||||||
const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false);
|
const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false);
|
||||||
f.wireEnemyTarget(enemy, player);
|
f.wireEnemyTarget(enemy, player);
|
||||||
|
|
||||||
const float hpBefore = f.admin.get<Health>(player).hp;
|
const float hpBefore = f.admin.get<HealthComponent>(player).hp;
|
||||||
|
|
||||||
std::vector<FireEvent> events;
|
std::vector<FireEvent> events;
|
||||||
f.combat.tick(0, f.admin, f.buildings, events);
|
f.combat.tick(0, f.admin, f.buildings, events);
|
||||||
@@ -279,7 +294,7 @@ TEST_CASE("CombatSystem: damage not applied before impact tick", "[combat]")
|
|||||||
for (Tick t = 1; t < 5; ++t)
|
for (Tick t = 1; t < 5; ++t)
|
||||||
{
|
{
|
||||||
f.combat.applyPendingDamage(t, f.admin);
|
f.combat.applyPendingDamage(t, f.admin);
|
||||||
REQUIRE(f.admin.get<Health>(player).hp == Approx(hpBefore));
|
REQUIRE(f.admin.get<HealthComponent>(player).hp == Approx(hpBefore));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,13 +308,13 @@ TEST_CASE("CombatSystem: damage applied exactly at impact tick", "[combat]")
|
|||||||
const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false);
|
const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false);
|
||||||
f.wireEnemyTarget(enemy, player);
|
f.wireEnemyTarget(enemy, player);
|
||||||
|
|
||||||
const float hpBefore = f.admin.get<Health>(player).hp;
|
const float hpBefore = f.admin.get<HealthComponent>(player).hp;
|
||||||
|
|
||||||
std::vector<FireEvent> events;
|
std::vector<FireEvent> events;
|
||||||
f.combat.tick(0, f.admin, f.buildings, events);
|
f.combat.tick(0, f.admin, f.buildings, events);
|
||||||
f.combat.applyPendingDamage(5, f.admin);
|
f.combat.applyPendingDamage(5, f.admin);
|
||||||
|
|
||||||
REQUIRE(f.admin.get<Health>(player).hp < hpBefore);
|
REQUIRE(f.admin.get<HealthComponent>(player).hp < hpBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("CombatSystem: damage silently dropped if target already dead", "[combat]")
|
TEST_CASE("CombatSystem: damage silently dropped if target already dead", "[combat]")
|
||||||
@@ -333,7 +348,7 @@ TEST_CASE("CombatSystem: damage still applied if shooter already dead", "[combat
|
|||||||
const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false);
|
const entt::entity player = f.ships.spawn(combatDef->id, 1, QVector2D(4.0f, 5.0f), false);
|
||||||
f.wireEnemyTarget(enemy, player);
|
f.wireEnemyTarget(enemy, player);
|
||||||
|
|
||||||
const float hpBefore = f.admin.get<Health>(player).hp;
|
const float hpBefore = f.admin.get<HealthComponent>(player).hp;
|
||||||
|
|
||||||
std::vector<FireEvent> events;
|
std::vector<FireEvent> events;
|
||||||
f.combat.tick(0, f.admin, f.buildings, events);
|
f.combat.tick(0, f.admin, f.buildings, events);
|
||||||
@@ -342,7 +357,7 @@ TEST_CASE("CombatSystem: damage still applied if shooter already dead", "[combat
|
|||||||
|
|
||||||
f.combat.applyPendingDamage(5, f.admin);
|
f.combat.applyPendingDamage(5, f.admin);
|
||||||
|
|
||||||
REQUIRE(f.admin.get<Health>(player).hp < hpBefore);
|
REQUIRE(f.admin.get<HealthComponent>(player).hp < hpBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -359,7 +374,7 @@ TEST_CASE("CombatSystem: dead ship is removed after tick step 9", "[combat]")
|
|||||||
const entt::entity ship = sim.ships().spawn(combatDef->id, 1,
|
const entt::entity ship = sim.ships().spawn(combatDef->id, 1,
|
||||||
QVector2D(10.0f, 10.0f));
|
QVector2D(10.0f, 10.0f));
|
||||||
|
|
||||||
sim.admin().get<Health>(ship).hp = -1.0f;
|
sim.admin().get<HealthComponent>(ship).hp = -1.0f;
|
||||||
|
|
||||||
sim.tick();
|
sim.tick();
|
||||||
|
|
||||||
@@ -383,7 +398,7 @@ TEST_CASE("CombatSystem: scrap is spawned on ship death", "[combat]")
|
|||||||
|
|
||||||
const entt::entity ship = sim.ships().spawn(droppingDef->id, 1,
|
const entt::entity ship = sim.ships().spawn(droppingDef->id, 1,
|
||||||
QVector2D(10.0f, 10.0f));
|
QVector2D(10.0f, 10.0f));
|
||||||
sim.admin().get<Health>(ship).hp = -1.0f;
|
sim.admin().get<HealthComponent>(ship).hp = -1.0f;
|
||||||
|
|
||||||
sim.tick();
|
sim.tick();
|
||||||
|
|
||||||
@@ -395,8 +410,8 @@ TEST_CASE("CombatSystem: HQ death sets game over", "[combat]")
|
|||||||
Simulation sim(loadConfig(), 42);
|
Simulation sim(loadConfig(), 42);
|
||||||
|
|
||||||
// Damage the HQ proxy entity (has HqProxy + Health).
|
// Damage the HQ proxy entity (has HqProxy + Health).
|
||||||
sim.admin().forEach<HqProxy, Health>(
|
sim.admin().forEach<HqProxyComponent, HealthComponent>(
|
||||||
[](entt::entity /*e*/, const HqProxy& /*hq*/, Health& h)
|
[](entt::entity /*e*/, const HqProxyComponent& /*hq*/, HealthComponent& h)
|
||||||
{
|
{
|
||||||
h.hp = -1.0f;
|
h.hp = -1.0f;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
#include <QVector2D>
|
#include <QVector2D>
|
||||||
|
|
||||||
|
#include "DespawnAtComponent.h"
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
|
#include "ScrapDataComponent.h"
|
||||||
#include "ScrapSystem.h"
|
#include "ScrapSystem.h"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -17,8 +19,8 @@ TEST_CASE("ScrapSystem: spawn returns a valid entity with correct scrap data", "
|
|||||||
const entt::entity e = ss.spawn(QVector2D(3.0f, 4.0f), 5, 100);
|
const entt::entity e = ss.spawn(QVector2D(3.0f, 4.0f), 5, 100);
|
||||||
|
|
||||||
REQUIRE(admin.isValid(e));
|
REQUIRE(admin.isValid(e));
|
||||||
REQUIRE(admin.get<ScrapData>(e).amount == 5);
|
REQUIRE(admin.get<ScrapDataComponent>(e).amount == 5);
|
||||||
REQUIRE(admin.get<DespawnAt>(e).tick == 100);
|
REQUIRE(admin.get<DespawnAtComponent>(e).tick == 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
#include "ConfigLoader.h"
|
#include "ConfigLoader.h"
|
||||||
#include "EcsComponents.h"
|
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
#include "GameConfig.h"
|
#include "GameConfig.h"
|
||||||
|
#include "HealthComponent.h"
|
||||||
#include "ItemType.h"
|
#include "ItemType.h"
|
||||||
#include "ModulesConfig.h"
|
#include "ModulesConfig.h"
|
||||||
#include "Rotation.h"
|
#include "Rotation.h"
|
||||||
#include "Ship.h"
|
#include "SensorRangeComponent.h"
|
||||||
#include "ShipLayout.h"
|
#include "ShipLayout.h"
|
||||||
#include "ShipSystem.h"
|
#include "ShipSystem.h"
|
||||||
#include "Simulation.h"
|
#include "Simulation.h"
|
||||||
@@ -102,7 +102,7 @@ TEST_CASE("Ship spawn: no modules leaves base stats unchanged", "[modules]")
|
|||||||
QVector2D(5.0f, 5.0f), false, std::nullopt);
|
QVector2D(5.0f, 5.0f), false, std::nullopt);
|
||||||
|
|
||||||
REQUIRE(sim.admin().isValid(e));
|
REQUIRE(sim.admin().isValid(e));
|
||||||
CHECK(sim.admin().get<Health>(e).maxHp == Approx(expectedHp));
|
CHECK(sim.admin().get<HealthComponent>(e).maxHp == Approx(expectedHp));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Ship spawn: multiplicative HP module applies correctly", "[modules]")
|
TEST_CASE("Ship spawn: multiplicative HP module applies correctly", "[modules]")
|
||||||
@@ -128,8 +128,8 @@ TEST_CASE("Ship spawn: multiplicative HP module applies correctly", "[modules]")
|
|||||||
REQUIRE(sim.admin().isValid(e));
|
REQUIRE(sim.admin().isValid(e));
|
||||||
// armor_plate has multiplied_hp_formula = "1.5"
|
// armor_plate has multiplied_hp_formula = "1.5"
|
||||||
// final = base * (1 + (1.5 - 1)) + 0 = base * 1.5
|
// final = base * (1 + (1.5 - 1)) + 0 = base * 1.5
|
||||||
CHECK(sim.admin().get<Health>(e).maxHp == Approx(baseHp * 1.5f));
|
CHECK(sim.admin().get<HealthComponent>(e).maxHp == Approx(baseHp * 1.5f));
|
||||||
CHECK(sim.admin().get<Health>(e).hp == sim.admin().get<Health>(e).maxHp);
|
CHECK(sim.admin().get<HealthComponent>(e).hp == sim.admin().get<HealthComponent>(e).maxHp);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Ship spawn: additive sensor module applies correctly", "[modules]")
|
TEST_CASE("Ship spawn: additive sensor module applies correctly", "[modules]")
|
||||||
@@ -155,7 +155,7 @@ TEST_CASE("Ship spawn: additive sensor module applies correctly", "[modules]")
|
|||||||
REQUIRE(sim.admin().isValid(e));
|
REQUIRE(sim.admin().isValid(e));
|
||||||
// sensor_booster has added_sensor_range_formula = "10"
|
// sensor_booster has added_sensor_range_formula = "10"
|
||||||
// final = base * 1.0 + 10 = base + 10
|
// final = base * 1.0 + 10 = base + 10
|
||||||
CHECK(sim.admin().get<SensorRange>(e).value == Approx(baseRange + 10.0f));
|
CHECK(sim.admin().get<SensorRangeComponent>(e).value == Approx(baseRange + 10.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Ship spawn: multiple modules stack correctly", "[modules]")
|
TEST_CASE("Ship spawn: multiple modules stack correctly", "[modules]")
|
||||||
@@ -185,7 +185,7 @@ TEST_CASE("Ship spawn: multiple modules stack correctly", "[modules]")
|
|||||||
// Two armor_plates: each 1.5 multiplier
|
// Two armor_plates: each 1.5 multiplier
|
||||||
// total_mult = 1 + (1.5 - 1) + (1.5 - 1) = 2.0
|
// total_mult = 1 + (1.5 - 1) + (1.5 - 1) = 2.0
|
||||||
// final = base * 2.0
|
// final = base * 2.0
|
||||||
CHECK(sim.admin().get<Health>(e).maxHp == Approx(baseHp * 2.0f));
|
CHECK(sim.admin().get<HealthComponent>(e).maxHp == Approx(baseHp * 2.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -5,13 +5,20 @@
|
|||||||
|
|
||||||
#include <QVector2D>
|
#include <QVector2D>
|
||||||
|
|
||||||
#include "ConfigLoader.h"
|
|
||||||
#include "EcsComponents.h"
|
|
||||||
#include "EntityAdmin.h"
|
|
||||||
#include "BuildingId.h"
|
#include "BuildingId.h"
|
||||||
#include "Ship.h"
|
#include "ConfigLoader.h"
|
||||||
|
#include "DynamicBodyComponent.h"
|
||||||
|
#include "EntityAdmin.h"
|
||||||
|
#include "HealthComponent.h"
|
||||||
|
#include "RepairBehaviorComponent.h"
|
||||||
|
#include "RepairToolComponent.h"
|
||||||
|
#include "SalvageBehaviorComponent.h"
|
||||||
|
#include "SalvageCargoComponent.h"
|
||||||
|
#include "SensorRangeComponent.h"
|
||||||
#include "ShipSystem.h"
|
#include "ShipSystem.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
|
#include "ThreatResponseBehaviorComponent.h"
|
||||||
|
#include "WeaponComponent.h"
|
||||||
|
|
||||||
static GameConfig loadConfig()
|
static GameConfig loadConfig()
|
||||||
{
|
{
|
||||||
@@ -32,12 +39,12 @@ TEST_CASE("ShipSystem: interceptor spawn has weapon and threatResponse, no cargo
|
|||||||
const entt::entity e = ss.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = ss.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
REQUIRE(admin.isValid(e));
|
REQUIRE(admin.isValid(e));
|
||||||
REQUIRE(admin.hasAll<Weapon>(e));
|
REQUIRE(admin.hasAll<WeaponComponent>(e));
|
||||||
REQUIRE(admin.hasAll<ThreatResponseBehavior>(e));
|
REQUIRE(admin.hasAll<ThreatResponseBehaviorComponent>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<SalvageCargo>(e));
|
REQUIRE_FALSE(admin.hasAll<SalvageCargoComponent>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<RepairTool>(e));
|
REQUIRE_FALSE(admin.hasAll<RepairToolComponent>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<RepairBehavior>(e));
|
REQUIRE_FALSE(admin.hasAll<RepairBehaviorComponent>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<SalvageBehavior>(e));
|
REQUIRE_FALSE(admin.hasAll<SalvageBehaviorComponent>(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("ShipSystem: interceptor level 1 stats match config formulas", "[ship]")
|
TEST_CASE("ShipSystem: interceptor level 1 stats match config formulas", "[ship]")
|
||||||
@@ -49,16 +56,16 @@ TEST_CASE("ShipSystem: interceptor level 1 stats match config formulas", "[ship]
|
|||||||
const entt::entity e = ss.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = ss.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
// hp_formula = "40 + 5*x" at x=1 → 45
|
// hp_formula = "40 + 5*x" at x=1 → 45
|
||||||
REQUIRE(admin.get<Health>(e).maxHp == Approx(45.0f));
|
REQUIRE(admin.get<HealthComponent>(e).maxHp == Approx(45.0f));
|
||||||
REQUIRE(admin.get<Health>(e).hp == Approx(45.0f));
|
REQUIRE(admin.get<HealthComponent>(e).hp == Approx(45.0f));
|
||||||
// damage_formula = "10 + 2*x" at x=1 → 12
|
// damage_formula = "10 + 2*x" at x=1 → 12
|
||||||
REQUIRE(admin.get<Weapon>(e).damage == Approx(12.0f));
|
REQUIRE(admin.get<WeaponComponent>(e).damage == Approx(12.0f));
|
||||||
// attack_range_formula = "150"
|
// attack_range_formula = "150"
|
||||||
REQUIRE(admin.get<Weapon>(e).range == Approx(150.0f));
|
REQUIRE(admin.get<WeaponComponent>(e).range == Approx(150.0f));
|
||||||
// sensor_range_formula = "200"
|
// sensor_range_formula = "200"
|
||||||
REQUIRE(admin.get<SensorRange>(e).value == Approx(200.0f));
|
REQUIRE(admin.get<SensorRangeComponent>(e).value == Approx(200.0f));
|
||||||
// cooldownTicks starts at 0
|
// cooldownTicks starts at 0
|
||||||
REQUIRE(admin.get<Weapon>(e).cooldownTicks == Approx(0.0f));
|
REQUIRE(admin.get<WeaponComponent>(e).cooldownTicks == Approx(0.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("ShipSystem: interceptor level 5 hp matches formula", "[ship]")
|
TEST_CASE("ShipSystem: interceptor level 5 hp matches formula", "[ship]")
|
||||||
@@ -70,7 +77,7 @@ TEST_CASE("ShipSystem: interceptor level 5 hp matches formula", "[ship]")
|
|||||||
const entt::entity e = ss.spawn("interceptor", 5, QVector2D(0.0f, 0.0f));
|
const entt::entity e = ss.spawn("interceptor", 5, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
// hp_formula = "40 + 5*x" at x=5 → 65
|
// hp_formula = "40 + 5*x" at x=5 → 65
|
||||||
REQUIRE(admin.get<Health>(e).maxHp == Approx(65.0f));
|
REQUIRE(admin.get<HealthComponent>(e).maxHp == Approx(65.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("ShipSystem: interceptor level 0 maxSpeedPerTick matches formula / kTickRateHz", "[ship]")
|
TEST_CASE("ShipSystem: interceptor level 0 maxSpeedPerTick matches formula / kTickRateHz", "[ship]")
|
||||||
@@ -83,7 +90,7 @@ TEST_CASE("ShipSystem: interceptor level 0 maxSpeedPerTick matches formula / kTi
|
|||||||
|
|
||||||
// speed_formula = "200 + 5*x" at x=0 → 200; maxSpeedPerTick = 200/30
|
// speed_formula = "200 + 5*x" at x=0 → 200; maxSpeedPerTick = 200/30
|
||||||
const float expected = 200.0f / static_cast<float>(kTickRateHz);
|
const float expected = 200.0f / static_cast<float>(kTickRateHz);
|
||||||
REQUIRE(admin.get<ShipDynamics>(e).maxSpeedPerTick == Approx(expected));
|
REQUIRE(admin.get<DynamicBodyComponent>(e).maxSpeedPerTick == Approx(expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -99,10 +106,10 @@ TEST_CASE("ShipSystem: salvage_ship spawn has cargo and scrapCollector, no weapo
|
|||||||
|
|
||||||
const entt::entity e = ss.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = ss.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
REQUIRE(admin.hasAll<SalvageCargo>(e));
|
REQUIRE(admin.hasAll<SalvageCargoComponent>(e));
|
||||||
REQUIRE(admin.hasAll<SalvageBehavior>(e));
|
REQUIRE(admin.hasAll<SalvageBehaviorComponent>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<Weapon>(e));
|
REQUIRE_FALSE(admin.hasAll<WeaponComponent>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<RepairTool>(e));
|
REQUIRE_FALSE(admin.hasAll<RepairToolComponent>(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("ShipSystem: salvage_ship cargo capacity matches config", "[ship]")
|
TEST_CASE("ShipSystem: salvage_ship cargo capacity matches config", "[ship]")
|
||||||
@@ -114,10 +121,10 @@ TEST_CASE("ShipSystem: salvage_ship cargo capacity matches config", "[ship]")
|
|||||||
const entt::entity e = ss.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = ss.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
// cargo_capacity = 10
|
// cargo_capacity = 10
|
||||||
REQUIRE(admin.get<SalvageCargo>(e).capacity == 10);
|
REQUIRE(admin.get<SalvageCargoComponent>(e).capacity == 10);
|
||||||
REQUIRE(admin.get<SalvageCargo>(e).current == 0);
|
REQUIRE(admin.get<SalvageCargoComponent>(e).current == 0);
|
||||||
REQUIRE(admin.get<SalvageBehavior>(e).deliveryBay == kInvalidBuildingId);
|
REQUIRE(admin.get<SalvageBehaviorComponent>(e).deliveryBay == kInvalidBuildingId);
|
||||||
REQUIRE_FALSE(admin.get<SalvageBehavior>(e).scrapTarget.has_value());
|
REQUIRE_FALSE(admin.get<SalvageBehaviorComponent>(e).scrapTarget.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -133,10 +140,10 @@ TEST_CASE("ShipSystem: repair_ship spawn has repairTool and repairBehavior, no w
|
|||||||
|
|
||||||
const entt::entity e = ss.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = ss.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
REQUIRE(admin.hasAll<RepairTool>(e));
|
REQUIRE(admin.hasAll<RepairToolComponent>(e));
|
||||||
REQUIRE(admin.hasAll<RepairBehavior>(e));
|
REQUIRE(admin.hasAll<RepairBehaviorComponent>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<Weapon>(e));
|
REQUIRE_FALSE(admin.hasAll<WeaponComponent>(e));
|
||||||
REQUIRE_FALSE(admin.hasAll<SalvageCargo>(e));
|
REQUIRE_FALSE(admin.hasAll<SalvageCargoComponent>(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("ShipSystem: repair_ship level 1 repair stats match config formulas", "[ship]")
|
TEST_CASE("ShipSystem: repair_ship level 1 repair stats match config formulas", "[ship]")
|
||||||
@@ -148,9 +155,9 @@ TEST_CASE("ShipSystem: repair_ship level 1 repair stats match config formulas",
|
|||||||
const entt::entity e = ss.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
const entt::entity e = ss.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
||||||
|
|
||||||
// repair_rate_formula = "5 + x" at x=1 → 6
|
// repair_rate_formula = "5 + x" at x=1 → 6
|
||||||
REQUIRE(admin.get<RepairTool>(e).ratePerTick == Approx(6.0f));
|
REQUIRE(admin.get<RepairToolComponent>(e).ratePerTick == Approx(6.0f));
|
||||||
// repair_range_formula = "80"
|
// repair_range_formula = "80"
|
||||||
REQUIRE(admin.get<RepairTool>(e).range == Approx(80.0f));
|
REQUIRE(admin.get<RepairToolComponent>(e).range == Approx(80.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
#include "ConfigLoader.h"
|
#include "ConfigLoader.h"
|
||||||
#include "EcsComponents.h"
|
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
|
#include "FactionComponent.h"
|
||||||
#include "GameConfig.h"
|
#include "GameConfig.h"
|
||||||
#include "ItemType.h"
|
#include "ItemType.h"
|
||||||
#include "Rotation.h"
|
#include "Rotation.h"
|
||||||
#include "Ship.h"
|
#include "ShipIdentityComponent.h"
|
||||||
#include "ShipSystem.h"
|
#include "ShipSystem.h"
|
||||||
#include "Simulation.h"
|
#include "Simulation.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
@@ -55,8 +55,8 @@ static BuildingId placeShipyard(Simulation& sim, const BuildingDef& yardDef)
|
|||||||
static int countShips(Simulation& sim)
|
static int countShips(Simulation& sim)
|
||||||
{
|
{
|
||||||
int n = 0;
|
int n = 0;
|
||||||
sim.admin().forEach<ShipIdentity>(
|
sim.admin().forEach<ShipIdentityComponent>(
|
||||||
[&n](entt::entity /*e*/, const ShipIdentity& /*si*/) { ++n; });
|
[&n](entt::entity /*e*/, const ShipIdentityComponent& /*si*/) { ++n; });
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,8 +114,8 @@ TEST_CASE("Shipyard: spawns a player ship after production cycle completes",
|
|||||||
REQUIRE(countShips(sim) == shipsBefore + 1);
|
REQUIRE(countShips(sim) == shipsBefore + 1);
|
||||||
|
|
||||||
bool foundPlayerShip = false;
|
bool foundPlayerShip = false;
|
||||||
sim.admin().forEach<ShipIdentity, Faction>(
|
sim.admin().forEach<ShipIdentityComponent, FactionComponent>(
|
||||||
[&](entt::entity /*e*/, const ShipIdentity& si, const Faction& f)
|
[&](entt::entity /*e*/, const ShipIdentityComponent& si, const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (!f.isEnemy && si.schematicId == def->id)
|
if (!f.isEnemy && si.schematicId == def->id)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,11 +6,15 @@
|
|||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
#include "ConfigLoader.h"
|
#include "ConfigLoader.h"
|
||||||
#include "EcsComponents.h"
|
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
|
#include "FactionComponent.h"
|
||||||
|
#include "HealthComponent.h"
|
||||||
|
#include "HqProxyComponent.h"
|
||||||
#include "Rotation.h"
|
#include "Rotation.h"
|
||||||
#include "Ship.h"
|
#include "ShipIdentityComponent.h"
|
||||||
#include "ShipSystem.h"
|
#include "ShipSystem.h"
|
||||||
|
#include "StationBodyComponent.h"
|
||||||
|
#include "WeaponComponent.h"
|
||||||
#include "Simulation.h"
|
#include "Simulation.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
#include "WaveSystem.h"
|
#include "WaveSystem.h"
|
||||||
@@ -112,8 +116,8 @@ TEST_CASE("WaveSystem: Simulation pre-places HQ + 2 player + 2 enemy stations",
|
|||||||
// Stations are ECS entities.
|
// Stations are ECS entities.
|
||||||
int playerCount = 0;
|
int playerCount = 0;
|
||||||
int enemyCount = 0;
|
int enemyCount = 0;
|
||||||
sim.admin().forEach<StationBody, Faction>(
|
sim.admin().forEach<StationBodyComponent, FactionComponent>(
|
||||||
[&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f)
|
[&](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (f.isEnemy) { ++enemyCount; }
|
if (f.isEnemy) { ++enemyCount; }
|
||||||
else { ++playerCount; }
|
else { ++playerCount; }
|
||||||
@@ -132,8 +136,8 @@ TEST_CASE("WaveSystem: HQ has correct initial HP from config", "[wave]")
|
|||||||
static_cast<float>(sim.config().stations.hq.hpFormula.evaluate(0.0));
|
static_cast<float>(sim.config().stations.hq.hpFormula.evaluate(0.0));
|
||||||
bool found = false;
|
bool found = false;
|
||||||
float actualHp = 0.0f;
|
float actualHp = 0.0f;
|
||||||
sim.admin().forEach<HqProxy, Health>(
|
sim.admin().forEach<HqProxyComponent, HealthComponent>(
|
||||||
[&](entt::entity /*e*/, const HqProxy& /*hq*/, const Health& h)
|
[&](entt::entity /*e*/, const HqProxyComponent& /*hq*/, const HealthComponent& h)
|
||||||
{
|
{
|
||||||
found = true;
|
found = true;
|
||||||
actualHp = h.hp;
|
actualHp = h.hp;
|
||||||
@@ -165,9 +169,9 @@ TEST_CASE("WaveSystem: player stations have weapon set", "[wave]")
|
|||||||
const Simulation sim(loadConfig(), 42);
|
const Simulation sim(loadConfig(), 42);
|
||||||
|
|
||||||
int armedPlayerStations = 0;
|
int armedPlayerStations = 0;
|
||||||
sim.admin().forEach<StationBody, Faction, Weapon>(
|
sim.admin().forEach<StationBodyComponent, FactionComponent, WeaponComponent>(
|
||||||
[&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f,
|
[&](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f,
|
||||||
const Weapon& w)
|
const WeaponComponent& w)
|
||||||
{
|
{
|
||||||
if (!f.isEnemy)
|
if (!f.isEnemy)
|
||||||
{
|
{
|
||||||
@@ -185,9 +189,9 @@ TEST_CASE("WaveSystem: enemy stations have weapon set", "[wave]")
|
|||||||
const Simulation sim(loadConfig(), 42);
|
const Simulation sim(loadConfig(), 42);
|
||||||
|
|
||||||
int armedEnemyStations = 0;
|
int armedEnemyStations = 0;
|
||||||
sim.admin().forEach<StationBody, Faction, Weapon>(
|
sim.admin().forEach<StationBodyComponent, FactionComponent, WeaponComponent>(
|
||||||
[&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f,
|
[&](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f,
|
||||||
const Weapon& w)
|
const WeaponComponent& w)
|
||||||
{
|
{
|
||||||
if (f.isEnemy)
|
if (f.isEnemy)
|
||||||
{
|
{
|
||||||
@@ -217,8 +221,8 @@ TEST_CASE("WaveSystem: enemy ships spawn after the initial gap elapses", "[wave]
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool foundEnemyShip = false;
|
bool foundEnemyShip = false;
|
||||||
sim.admin().forEach<ShipIdentity, Faction>(
|
sim.admin().forEach<ShipIdentityComponent, FactionComponent>(
|
||||||
[&](entt::entity /*e*/, const ShipIdentity& /*si*/, const Faction& f)
|
[&](entt::entity /*e*/, const ShipIdentityComponent& /*si*/, const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (f.isEnemy) { foundEnemyShip = true; }
|
if (f.isEnemy) { foundEnemyShip = true; }
|
||||||
});
|
});
|
||||||
@@ -236,8 +240,8 @@ TEST_CASE("WaveSystem: only eligible ships (cost > 0) appear in waves", "[wave]"
|
|||||||
sim.tick();
|
sim.tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
sim.admin().forEach<ShipIdentity, Faction>(
|
sim.admin().forEach<ShipIdentityComponent, FactionComponent>(
|
||||||
[&](entt::entity /*e*/, const ShipIdentity& si, const Faction& f)
|
[&](entt::entity /*e*/, const ShipIdentityComponent& si, const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (!f.isEnemy) { return; }
|
if (!f.isEnemy) { return; }
|
||||||
// salvage_ship and repair_ship have cost_formula = "0" and must not spawn.
|
// salvage_ship and repair_ship have cost_formula = "0" and must not spawn.
|
||||||
@@ -255,8 +259,8 @@ TEST_CASE("WaveSystem: destroying both enemy stations triggers a push", "[wave]"
|
|||||||
Simulation sim(loadConfig(), 42);
|
Simulation sim(loadConfig(), 42);
|
||||||
|
|
||||||
// Damage both enemy stations to 0.
|
// Damage both enemy stations to 0.
|
||||||
sim.admin().forEach<StationBody, Faction, Health>(
|
sim.admin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||||
[](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, Health& h)
|
[](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, HealthComponent& h)
|
||||||
{
|
{
|
||||||
if (f.isEnemy) { h.hp = -1.0f; }
|
if (f.isEnemy) { h.hp = -1.0f; }
|
||||||
});
|
});
|
||||||
@@ -265,8 +269,8 @@ TEST_CASE("WaveSystem: destroying both enemy stations triggers a push", "[wave]"
|
|||||||
|
|
||||||
// After push: should have 2 new enemy stations.
|
// After push: should have 2 new enemy stations.
|
||||||
int enemyCount = 0;
|
int enemyCount = 0;
|
||||||
sim.admin().forEach<StationBody, Faction>(
|
sim.admin().forEach<StationBodyComponent, FactionComponent>(
|
||||||
[&](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f)
|
[&](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (f.isEnemy) { ++enemyCount; }
|
if (f.isEnemy) { ++enemyCount; }
|
||||||
});
|
});
|
||||||
@@ -277,8 +281,8 @@ TEST_CASE("WaveSystem: push emits exactly one SchematicDropEvent", "[wave]")
|
|||||||
{
|
{
|
||||||
Simulation sim(loadConfig(), 42);
|
Simulation sim(loadConfig(), 42);
|
||||||
|
|
||||||
sim.admin().forEach<StationBody, Faction, Health>(
|
sim.admin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||||
[](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, Health& h)
|
[](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, HealthComponent& h)
|
||||||
{
|
{
|
||||||
if (f.isEnemy) { h.hp = -1.0f; }
|
if (f.isEnemy) { h.hp = -1.0f; }
|
||||||
});
|
});
|
||||||
@@ -293,8 +297,8 @@ TEST_CASE("WaveSystem: push schematic drop awards a known ship id", "[wave]")
|
|||||||
{
|
{
|
||||||
Simulation sim(loadConfig(), 42);
|
Simulation sim(loadConfig(), 42);
|
||||||
|
|
||||||
sim.admin().forEach<StationBody, Faction, Health>(
|
sim.admin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||||
[](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, Health& h)
|
[](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, HealthComponent& h)
|
||||||
{
|
{
|
||||||
if (f.isEnemy) { h.hp = -1.0f; }
|
if (f.isEnemy) { h.hp = -1.0f; }
|
||||||
});
|
});
|
||||||
@@ -322,8 +326,8 @@ TEST_CASE("WaveSystem: push places new enemy stations further right", "[wave]")
|
|||||||
|
|
||||||
// Record the X position of the initial enemy stations.
|
// Record the X position of the initial enemy stations.
|
||||||
int initialX = std::numeric_limits<int>::min();
|
int initialX = std::numeric_limits<int>::min();
|
||||||
sim.admin().forEach<StationBody, Faction>(
|
sim.admin().forEach<StationBodyComponent, FactionComponent>(
|
||||||
[&](entt::entity /*e*/, const StationBody& sb, const Faction& f)
|
[&](entt::entity /*e*/, const StationBodyComponent& sb, const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (f.isEnemy && sb.anchor.x() > initialX)
|
if (f.isEnemy && sb.anchor.x() > initialX)
|
||||||
{
|
{
|
||||||
@@ -331,16 +335,16 @@ TEST_CASE("WaveSystem: push places new enemy stations further right", "[wave]")
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
sim.admin().forEach<StationBody, Faction, Health>(
|
sim.admin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||||
[](entt::entity /*e*/, const StationBody& /*sb*/, const Faction& f, Health& h)
|
[](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, HealthComponent& h)
|
||||||
{
|
{
|
||||||
if (f.isEnemy) { h.hp = -1.0f; }
|
if (f.isEnemy) { h.hp = -1.0f; }
|
||||||
});
|
});
|
||||||
sim.tick();
|
sim.tick();
|
||||||
|
|
||||||
int newX = std::numeric_limits<int>::min();
|
int newX = std::numeric_limits<int>::min();
|
||||||
sim.admin().forEach<StationBody, Faction>(
|
sim.admin().forEach<StationBodyComponent, FactionComponent>(
|
||||||
[&](entt::entity /*e*/, const StationBody& sb, const Faction& f)
|
[&](entt::entity /*e*/, const StationBodyComponent& sb, const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (f.isEnemy && sb.anchor.x() > newX)
|
if (f.isEnemy && sb.anchor.x() > newX)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,14 +17,21 @@
|
|||||||
#include <QPolygonF>
|
#include <QPolygonF>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "BeltSystem.h"
|
||||||
#include "Building.h"
|
#include "Building.h"
|
||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
#include "BeltSystem.h"
|
#include "FacingComponent.h"
|
||||||
#include "EcsComponents.h"
|
#include "FactionComponent.h"
|
||||||
|
#include "HealthComponent.h"
|
||||||
|
#include "PositionComponent.h"
|
||||||
|
#include "RepairToolComponent.h"
|
||||||
|
#include "SalvageCargoComponent.h"
|
||||||
#include "ScrapSystem.h"
|
#include "ScrapSystem.h"
|
||||||
#include "Ship.h"
|
#include "SensorRangeComponent.h"
|
||||||
|
#include "ShipIdentityComponent.h"
|
||||||
#include "ShipSystem.h"
|
#include "ShipSystem.h"
|
||||||
#include "Simulation.h"
|
#include "Simulation.h"
|
||||||
|
#include "StationBodyComponent.h"
|
||||||
#include "SurfaceMask.h"
|
#include "SurfaceMask.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
|
|
||||||
@@ -161,9 +168,9 @@ void GameWorldView::onFrame()
|
|||||||
{
|
{
|
||||||
float maxRadius = 0.125f;
|
float maxRadius = 0.125f;
|
||||||
if (m_sim->admin().isValid(fe.target)
|
if (m_sim->admin().isValid(fe.target)
|
||||||
&& m_sim->admin().hasAll<StationBody>(fe.target))
|
&& m_sim->admin().hasAll<StationBodyComponent>(fe.target))
|
||||||
{
|
{
|
||||||
const StationBody& sb = m_sim->admin().get<StationBody>(fe.target);
|
const StationBodyComponent& sb = m_sim->admin().get<StationBodyComponent>(fe.target);
|
||||||
const int shorter = std::min(sb.footprint.width(), sb.footprint.height());
|
const int shorter = std::min(sb.footprint.width(), sb.footprint.height());
|
||||||
maxRadius = shorter / 2.0f;
|
maxRadius = shorter / 2.0f;
|
||||||
}
|
}
|
||||||
@@ -343,8 +350,8 @@ float GameWorldView::enemyStationRightEdge() const
|
|||||||
{
|
{
|
||||||
float rightX = static_cast<float>(m_config->world.regions.playerBufferWidth
|
float rightX = static_cast<float>(m_config->world.regions.playerBufferWidth
|
||||||
+ m_config->world.regions.contestZoneWidth);
|
+ m_config->world.regions.contestZoneWidth);
|
||||||
m_sim->admin().forEach<StationBody, Faction>(
|
m_sim->admin().forEach<StationBodyComponent, FactionComponent>(
|
||||||
[&rightX](entt::entity /*e*/, const StationBody& sb, const Faction& f)
|
[&rightX](entt::entity /*e*/, const StationBodyComponent& sb, const FactionComponent& f)
|
||||||
{
|
{
|
||||||
if (!f.isEnemy) { return; }
|
if (!f.isEnemy) { return; }
|
||||||
for (const QPoint& cell : sb.bodyCells)
|
for (const QPoint& cell : sb.bodyCells)
|
||||||
@@ -445,11 +452,11 @@ BuildingId GameWorldView::siteAtTile(QPoint tile) const
|
|||||||
|
|
||||||
std::optional<QVector2D> GameWorldView::entityPosition(entt::entity entity) const
|
std::optional<QVector2D> GameWorldView::entityPosition(entt::entity entity) const
|
||||||
{
|
{
|
||||||
if (!m_sim->admin().isValid(entity) || !m_sim->admin().hasAll<Position>(entity))
|
if (!m_sim->admin().isValid(entity) || !m_sim->admin().hasAll<PositionComponent>(entity))
|
||||||
{
|
{
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return m_sim->admin().get<Position>(entity).value;
|
return m_sim->admin().get<PositionComponent>(entity).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameWorldView::stepSpeed(int delta)
|
void GameWorldView::stepSpeed(int delta)
|
||||||
@@ -794,8 +801,9 @@ void GameWorldView::drawScrap(QPainter& painter)
|
|||||||
|
|
||||||
void GameWorldView::drawStations(QPainter& painter)
|
void GameWorldView::drawStations(QPainter& painter)
|
||||||
{
|
{
|
||||||
m_sim->admin().forEach<StationBody, Faction, Health>(
|
m_sim->admin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||||
[&](entt::entity /*e*/, const StationBody& sb, const Faction& f, const Health& h)
|
[&](entt::entity /*e*/, const StationBodyComponent& sb, const FactionComponent& f,
|
||||||
|
const HealthComponent& h)
|
||||||
{
|
{
|
||||||
const BuildingType visType = f.isEnemy
|
const BuildingType visType = f.isEnemy
|
||||||
? BuildingType::EnemyDefenceStation
|
? BuildingType::EnemyDefenceStation
|
||||||
@@ -837,12 +845,14 @@ void GameWorldView::drawStations(QPainter& painter)
|
|||||||
|
|
||||||
void GameWorldView::drawShips(QPainter& painter)
|
void GameWorldView::drawShips(QPainter& painter)
|
||||||
{
|
{
|
||||||
m_sim->admin().forEach<ShipIdentity, Position, Facing, Faction>(
|
m_sim->admin().forEach<ShipIdentityComponent, PositionComponent, FacingComponent,
|
||||||
[&](entt::entity e, const ShipIdentity& /*si*/, const Position& pos,
|
FactionComponent>(
|
||||||
const Facing& facing, const Faction& fac)
|
[&](entt::entity e, const ShipIdentityComponent& /*si*/,
|
||||||
|
const PositionComponent& pos, const FacingComponent& facing,
|
||||||
|
const FactionComponent& fac)
|
||||||
{
|
{
|
||||||
const bool hasCargo = m_sim->admin().hasAll<SalvageCargo>(e);
|
const bool hasCargo = m_sim->admin().hasAll<SalvageCargoComponent>(e);
|
||||||
const bool hasRepair = m_sim->admin().hasAll<RepairTool>(e);
|
const bool hasRepair = m_sim->admin().hasAll<RepairToolComponent>(e);
|
||||||
const ShipRole role = shipRoleFromComponents(fac.isEnemy, hasCargo, hasRepair);
|
const ShipRole role = shipRoleFromComponents(fac.isEnemy, hasCargo, hasRepair);
|
||||||
const std::map<ShipRole, ShipVisuals>::const_iterator it =
|
const std::map<ShipRole, ShipVisuals>::const_iterator it =
|
||||||
m_visuals->ships.find(role);
|
m_visuals->ships.find(role);
|
||||||
@@ -872,12 +882,14 @@ void GameWorldView::drawShips(QPainter& painter)
|
|||||||
void GameWorldView::drawDebugSensorRanges(QPainter& painter)
|
void GameWorldView::drawDebugSensorRanges(QPainter& painter)
|
||||||
{
|
{
|
||||||
painter.setBrush(Qt::NoBrush);
|
painter.setBrush(Qt::NoBrush);
|
||||||
m_sim->admin().forEach<ShipIdentity, Position, Facing, Faction, SensorRange>(
|
m_sim->admin().forEach<ShipIdentityComponent, PositionComponent, FacingComponent,
|
||||||
[&](entt::entity e, const ShipIdentity& /*si*/, const Position& pos,
|
FactionComponent, SensorRangeComponent>(
|
||||||
const Facing& /*facing*/, const Faction& fac, const SensorRange& sensor)
|
[&](entt::entity e, const ShipIdentityComponent& /*si*/,
|
||||||
|
const PositionComponent& pos, const FacingComponent& /*facing*/,
|
||||||
|
const FactionComponent& fac, const SensorRangeComponent& sensor)
|
||||||
{
|
{
|
||||||
const bool hasCargo = m_sim->admin().hasAll<SalvageCargo>(e);
|
const bool hasCargo = m_sim->admin().hasAll<SalvageCargoComponent>(e);
|
||||||
const bool hasRepair = m_sim->admin().hasAll<RepairTool>(e);
|
const bool hasRepair = m_sim->admin().hasAll<RepairToolComponent>(e);
|
||||||
const ShipRole role = shipRoleFromComponents(fac.isEnemy, hasCargo, hasRepair);
|
const ShipRole role = shipRoleFromComponents(fac.isEnemy, hasCargo, hasRepair);
|
||||||
const std::map<ShipRole, ShipVisuals>::const_iterator it =
|
const std::map<ShipRole, ShipVisuals>::const_iterator it =
|
||||||
m_visuals->ships.find(role);
|
m_visuals->ships.find(role);
|
||||||
|
|||||||
Reference in New Issue
Block a user