switch to ECS architecture
This commit is contained in:
@@ -10,6 +10,8 @@
|
||||
#include "BuildingSystem.h"
|
||||
#include "BuildingType.h"
|
||||
#include "CombatSystem.h"
|
||||
#include "EcsComponents.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "MovementSystem.h"
|
||||
#include "ScrapSystem.h"
|
||||
#include "Ship.h"
|
||||
@@ -27,8 +29,8 @@ ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig,
|
||||
, m_currentTick(0)
|
||||
, m_nextId(1)
|
||||
, m_beltSystem(1.0)
|
||||
, m_team1HqId(kInvalidEntityId)
|
||||
, m_team2HqId(kInvalidEntityId)
|
||||
, m_team1HqEntity(entt::null)
|
||||
, m_team2HqEntity(entt::null)
|
||||
, m_finished(false)
|
||||
, m_winnerTeam(-1)
|
||||
, m_stopRequested(false)
|
||||
@@ -41,13 +43,11 @@ ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig,
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
m_rng);
|
||||
|
||||
m_shipSystem = std::make_unique<ShipSystem>(
|
||||
m_gameConfig, [this]() { return allocateId(); });
|
||||
|
||||
m_shipSystem = std::make_unique<ShipSystem>(m_gameConfig, m_admin);
|
||||
m_aiSystem = std::make_unique<AiSystem>();
|
||||
m_movementSystem = std::make_unique<MovementSystem>();
|
||||
m_combatSystem = std::make_unique<CombatSystem>(m_gameConfig);
|
||||
m_scrapSystem = std::make_unique<ScrapSystem>([this]() { return allocateId(); });
|
||||
m_scrapSystem = std::make_unique<ScrapSystem>(m_admin);
|
||||
|
||||
placeStructures();
|
||||
spawnShips();
|
||||
@@ -71,7 +71,7 @@ void ArenaSimulation::placeStructures()
|
||||
+ m_arenaConfig.enemyBufferWidth;
|
||||
const int midY = m_arenaConfig.heightTiles / 2;
|
||||
|
||||
// Team 1 HQ at left edge, placed as Hq (enemy ships target Hq).
|
||||
// Team 1 HQ — ECS proxy entity, player faction (isEnemy=false).
|
||||
{
|
||||
const ParsedSurfaceMask hqParsed =
|
||||
parseSurfaceMask(m_gameConfig.stations.hq.surfaceMask, Rotation::East);
|
||||
@@ -79,16 +79,20 @@ void ArenaSimulation::placeStructures()
|
||||
const int anchorY = midY - hqParsed.footprint.height() / 2;
|
||||
const float hp = static_cast<float>(
|
||||
m_gameConfig.stations.hq.hpFormula.evaluate(1.0));
|
||||
|
||||
m_team1HqId = m_buildingSystem->placeImmediate(
|
||||
BuildingType::Hq,
|
||||
m_gameConfig.stations.hq.surfaceMask,
|
||||
QPoint(anchorX, anchorY),
|
||||
Rotation::East, hp, hp);
|
||||
const QPoint anchor(anchorX, anchorY);
|
||||
std::vector<QPoint> absCells;
|
||||
for (const QPoint& rel : hqParsed.bodyCells)
|
||||
{
|
||||
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,
|
||||
hp, hp, false);
|
||||
m_buildingSystem->registerTileOccupancy(absCells, allocateId());
|
||||
}
|
||||
|
||||
// Team 2 HQ at right edge, placed as EnemyDefenceStation (player ships target these).
|
||||
// No weapon — it's just a destructible target.
|
||||
// Team 2 HQ — ECS proxy entity, enemy faction (isEnemy=true). No weapon.
|
||||
{
|
||||
const ParsedSurfaceMask hqParsed =
|
||||
parseSurfaceMask(m_gameConfig.stations.hq.surfaceMask, Rotation::West);
|
||||
@@ -96,90 +100,72 @@ void ArenaSimulation::placeStructures()
|
||||
const int anchorY = midY - hqParsed.footprint.height() / 2;
|
||||
const float hp = static_cast<float>(
|
||||
m_gameConfig.stations.hq.hpFormula.evaluate(1.0));
|
||||
|
||||
m_team2HqId = m_buildingSystem->placeImmediate(
|
||||
BuildingType::EnemyDefenceStation,
|
||||
m_gameConfig.stations.hq.surfaceMask,
|
||||
QPoint(anchorX, anchorY),
|
||||
Rotation::West, hp, hp);
|
||||
const QPoint anchor(anchorX, anchorY);
|
||||
std::vector<QPoint> absCells;
|
||||
for (const QPoint& rel : hqParsed.bodyCells)
|
||||
{
|
||||
absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
|
||||
}
|
||||
m_team2HqEntity = m_admin.spawnStation(anchor, hqParsed.footprint, absCells,
|
||||
hp, hp, true);
|
||||
m_buildingSystem->registerTileOccupancy(absCells, allocateId());
|
||||
}
|
||||
|
||||
// Team 1 defence stations (PlayerDefenceStation — targeted by team 2).
|
||||
auto placeArenaStation = [&](const ArenaStationEntry& entry, bool isEnemy)
|
||||
{
|
||||
float hp = 0.0f;
|
||||
StationWeapon weapon;
|
||||
weapon.cooldownTicks = 0.0f;
|
||||
weapon.currentTarget = std::nullopt;
|
||||
const double lv = static_cast<double>(entry.level);
|
||||
|
||||
const std::vector<std::string>& mask = isEnemy
|
||||
? m_gameConfig.stations.enemyStation.surfaceMask
|
||||
: m_gameConfig.stations.playerStation.surfaceMask;
|
||||
|
||||
if (entry.stationType == "player_station")
|
||||
{
|
||||
hp = static_cast<float>(
|
||||
m_gameConfig.stations.playerStation.hpFormula.evaluate(lv));
|
||||
weapon.damage = static_cast<float>(
|
||||
m_gameConfig.stations.playerStation.damageFormula.evaluate(lv));
|
||||
weapon.range = static_cast<float>(
|
||||
m_gameConfig.stations.playerStation.rangeFormula.evaluate(lv));
|
||||
weapon.fireRateHz = static_cast<float>(
|
||||
m_gameConfig.stations.playerStation.fireRateFormula.evaluate(lv));
|
||||
}
|
||||
else
|
||||
{
|
||||
hp = static_cast<float>(
|
||||
m_gameConfig.stations.enemyStation.hpFormula.evaluate(lv));
|
||||
weapon.damage = static_cast<float>(
|
||||
m_gameConfig.stations.enemyStation.damageFormula.evaluate(lv));
|
||||
weapon.range = static_cast<float>(
|
||||
m_gameConfig.stations.enemyStation.rangeFormula.evaluate(lv));
|
||||
weapon.fireRateHz = static_cast<float>(
|
||||
m_gameConfig.stations.enemyStation.fireRateFormula.evaluate(lv));
|
||||
}
|
||||
|
||||
const ParsedSurfaceMask parsed = parseSurfaceMask(mask, Rotation::East);
|
||||
const QPoint& anchor = entry.position;
|
||||
std::vector<QPoint> absCells;
|
||||
for (const QPoint& rel : parsed.bodyCells)
|
||||
{
|
||||
absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
|
||||
}
|
||||
const entt::entity stationEntity = m_admin.spawnStation(
|
||||
anchor, parsed.footprint, absCells, hp, hp, isEnemy);
|
||||
m_admin.addComponent<StationWeapon>(stationEntity, weapon);
|
||||
m_buildingSystem->registerTileOccupancy(absCells, allocateId());
|
||||
};
|
||||
|
||||
for (const ArenaStationEntry& entry : m_arenaConfig.teams[0].stations)
|
||||
{
|
||||
float hp = 0.0f;
|
||||
StationWeapon weapon;
|
||||
weapon.cooldownTicks = 0.0f;
|
||||
const double lv = static_cast<double>(entry.level);
|
||||
|
||||
if (entry.stationType == "player_station")
|
||||
{
|
||||
hp = static_cast<float>(
|
||||
m_gameConfig.stations.playerStation.hpFormula.evaluate(lv));
|
||||
weapon.damage = static_cast<float>(
|
||||
m_gameConfig.stations.playerStation.damageFormula.evaluate(lv));
|
||||
weapon.range = static_cast<float>(
|
||||
m_gameConfig.stations.playerStation.rangeFormula.evaluate(lv));
|
||||
weapon.fireRateHz = static_cast<float>(
|
||||
m_gameConfig.stations.playerStation.fireRateFormula.evaluate(lv));
|
||||
}
|
||||
else
|
||||
{
|
||||
hp = static_cast<float>(
|
||||
m_gameConfig.stations.enemyStation.hpFormula.evaluate(lv));
|
||||
weapon.damage = static_cast<float>(
|
||||
m_gameConfig.stations.enemyStation.damageFormula.evaluate(lv));
|
||||
weapon.range = static_cast<float>(
|
||||
m_gameConfig.stations.enemyStation.rangeFormula.evaluate(lv));
|
||||
weapon.fireRateHz = static_cast<float>(
|
||||
m_gameConfig.stations.enemyStation.fireRateFormula.evaluate(lv));
|
||||
}
|
||||
|
||||
const EntityId stationId = m_buildingSystem->placeImmediate(
|
||||
BuildingType::PlayerDefenceStation,
|
||||
m_gameConfig.stations.playerStation.surfaceMask,
|
||||
entry.position,
|
||||
Rotation::East, hp, hp);
|
||||
m_buildingSystem->initStationWeapon(stationId, weapon);
|
||||
placeArenaStation(entry, false);
|
||||
}
|
||||
|
||||
// Team 2 defence stations (EnemyDefenceStation — targeted by team 1).
|
||||
for (const ArenaStationEntry& entry : m_arenaConfig.teams[1].stations)
|
||||
{
|
||||
float hp = 0.0f;
|
||||
StationWeapon weapon;
|
||||
weapon.cooldownTicks = 0.0f;
|
||||
const double lv = static_cast<double>(entry.level);
|
||||
|
||||
if (entry.stationType == "player_station")
|
||||
{
|
||||
hp = static_cast<float>(
|
||||
m_gameConfig.stations.playerStation.hpFormula.evaluate(lv));
|
||||
weapon.damage = static_cast<float>(
|
||||
m_gameConfig.stations.playerStation.damageFormula.evaluate(lv));
|
||||
weapon.range = static_cast<float>(
|
||||
m_gameConfig.stations.playerStation.rangeFormula.evaluate(lv));
|
||||
weapon.fireRateHz = static_cast<float>(
|
||||
m_gameConfig.stations.playerStation.fireRateFormula.evaluate(lv));
|
||||
}
|
||||
else
|
||||
{
|
||||
hp = static_cast<float>(
|
||||
m_gameConfig.stations.enemyStation.hpFormula.evaluate(lv));
|
||||
weapon.damage = static_cast<float>(
|
||||
m_gameConfig.stations.enemyStation.damageFormula.evaluate(lv));
|
||||
weapon.range = static_cast<float>(
|
||||
m_gameConfig.stations.enemyStation.rangeFormula.evaluate(lv));
|
||||
weapon.fireRateHz = static_cast<float>(
|
||||
m_gameConfig.stations.enemyStation.fireRateFormula.evaluate(lv));
|
||||
}
|
||||
|
||||
const EntityId stationId = m_buildingSystem->placeImmediate(
|
||||
BuildingType::EnemyDefenceStation,
|
||||
m_gameConfig.stations.enemyStation.surfaceMask,
|
||||
entry.position,
|
||||
Rotation::East, hp, hp);
|
||||
m_buildingSystem->initStationWeapon(stationId, weapon);
|
||||
placeArenaStation(entry, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,22 +240,22 @@ void ArenaSimulation::tick()
|
||||
{
|
||||
// Ship behavior systems (tick step 7).
|
||||
m_shipSystem->clearMovementIntents();
|
||||
m_aiSystem->tickHomeReturn(*m_shipSystem);
|
||||
m_aiSystem->tickThreatResponse(*m_shipSystem, *m_buildingSystem);
|
||||
m_aiSystem->tickRepairBehavior(*m_shipSystem, *m_buildingSystem);
|
||||
m_aiSystem->tickScrapCollector(*m_shipSystem, *m_scrapSystem, *m_buildingSystem);
|
||||
m_aiSystem->tickHomeReturn(m_admin);
|
||||
m_aiSystem->tickThreatResponse(m_admin, *m_buildingSystem);
|
||||
m_aiSystem->tickRepairBehavior(m_admin, *m_buildingSystem);
|
||||
m_aiSystem->tickScrapCollector(m_admin, *m_scrapSystem, *m_buildingSystem);
|
||||
|
||||
// Combat resolution (tick step 8).
|
||||
std::vector<FireEvent> fireEvents;
|
||||
m_combatSystem->tick(m_currentTick, *m_shipSystem, *m_buildingSystem, fireEvents);
|
||||
m_combatSystem->tick(m_currentTick, m_admin, *m_buildingSystem, fireEvents);
|
||||
m_fireEvents.insert(m_fireEvents.end(), fireEvents.begin(), fireEvents.end());
|
||||
m_combatSystem->applyPendingDamage(m_currentTick, *m_shipSystem, *m_buildingSystem);
|
||||
m_combatSystem->applyPendingDamage(m_currentTick, m_admin);
|
||||
|
||||
// Deaths (tick step 9, simplified).
|
||||
tickDeaths();
|
||||
|
||||
// Movement (tick step 10).
|
||||
m_movementSystem->tick(*m_shipSystem);
|
||||
m_movementSystem->tick(m_admin);
|
||||
|
||||
// Scrap despawn (tick step 11).
|
||||
m_scrapSystem->tickDespawn(m_currentTick);
|
||||
@@ -285,58 +271,56 @@ void ArenaSimulation::tick()
|
||||
void ArenaSimulation::tickDeaths()
|
||||
{
|
||||
// Dead ships.
|
||||
std::vector<EntityId> deadShipIds;
|
||||
m_shipSystem->forEach([&deadShipIds](Ship& s)
|
||||
{
|
||||
if (s.hp <= 0.0f)
|
||||
std::vector<entt::entity> deadShips;
|
||||
m_admin.forEach<ShipIdentity, Health>(
|
||||
[&deadShips](entt::entity e, const ShipIdentity& /*si*/, const Health& h)
|
||||
{
|
||||
deadShipIds.push_back(s.id);
|
||||
}
|
||||
});
|
||||
if (h.hp <= 0.0f)
|
||||
{
|
||||
deadShips.push_back(e);
|
||||
}
|
||||
});
|
||||
|
||||
for (EntityId deadId : deadShipIds)
|
||||
for (entt::entity deadEntity : deadShips)
|
||||
{
|
||||
const Ship* s = m_shipSystem->findShip(deadId);
|
||||
if (!s)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const ShipIdentity& si = m_admin.get<ShipIdentity>(deadEntity);
|
||||
const Position& pos = m_admin.get<Position>(deadEntity);
|
||||
for (const ShipDef& def : m_gameConfig.ships.ships)
|
||||
{
|
||||
if (def.id == s->schematicId && def.loot.scrapDrop > 0)
|
||||
if (def.id == si.schematicId && def.loot.scrapDrop > 0)
|
||||
{
|
||||
const Tick despawnAt = m_currentTick
|
||||
+ secondsToTicks(m_gameConfig.world.scrapDespawnSeconds);
|
||||
m_scrapSystem->spawn(s->position, def.loot.scrapDrop, despawnAt);
|
||||
m_scrapSystem->spawn(pos.value, def.loot.scrapDrop, despawnAt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_shipSystem->despawn(deadId);
|
||||
m_shipSystem->despawn(deadEntity);
|
||||
}
|
||||
|
||||
// Dead buildings (HQ and defence stations).
|
||||
std::vector<EntityId> deadBuildingIds;
|
||||
for (const Building& b : m_buildingSystem->allBuildings())
|
||||
{
|
||||
if (b.hp <= 0.0f
|
||||
&& (b.type == BuildingType::Hq
|
||||
|| b.type == BuildingType::PlayerDefenceStation
|
||||
|| b.type == BuildingType::EnemyDefenceStation))
|
||||
// Dead stations.
|
||||
std::vector<entt::entity> deadStations;
|
||||
m_admin.forEach<StationBody, Health>(
|
||||
[&deadStations](entt::entity e, const StationBody& /*sb*/, const Health& h)
|
||||
{
|
||||
deadBuildingIds.push_back(b.id);
|
||||
}
|
||||
}
|
||||
if (h.hp <= 0.0f)
|
||||
{
|
||||
deadStations.push_back(e);
|
||||
}
|
||||
});
|
||||
|
||||
for (EntityId deadId : deadBuildingIds)
|
||||
for (entt::entity deadEntity : deadStations)
|
||||
{
|
||||
m_buildingSystem->removeBuilding(deadId);
|
||||
const StationBody& sb = m_admin.get<StationBody>(deadEntity);
|
||||
m_buildingSystem->unregisterTileOccupancy(sb.bodyCells);
|
||||
m_admin.destroy(deadEntity);
|
||||
}
|
||||
|
||||
// Check end conditions.
|
||||
const bool team1HqGone =
|
||||
(m_buildingSystem->findBuilding(m_team1HqId) == nullptr);
|
||||
const bool team2HqGone =
|
||||
(m_buildingSystem->findBuilding(m_team2HqId) == nullptr);
|
||||
// Check end conditions — HQ proxy entities.
|
||||
const bool team1HqGone = !m_admin.isValid(m_team1HqEntity)
|
||||
|| m_admin.get<Health>(m_team1HqEntity).hp <= 0.0f;
|
||||
const bool team2HqGone = !m_admin.isValid(m_team2HqEntity)
|
||||
|| m_admin.get<Health>(m_team2HqEntity).hp <= 0.0f;
|
||||
|
||||
if (team1HqGone || team2HqGone)
|
||||
{
|
||||
@@ -349,30 +333,23 @@ void ArenaSimulation::tickDeaths()
|
||||
// Check if all ships and defence stations of one team are destroyed.
|
||||
bool team1HasUnits = false;
|
||||
bool team2HasUnits = false;
|
||||
m_shipSystem->forEach([&team1HasUnits, &team2HasUnits](Ship& s)
|
||||
{
|
||||
if (s.isEnemy)
|
||||
m_admin.forEach<ShipIdentity, Faction>(
|
||||
[&team1HasUnits, &team2HasUnits](entt::entity /*e*/,
|
||||
const ShipIdentity& /*si*/,
|
||||
const Faction& f)
|
||||
{
|
||||
team2HasUnits = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
team1HasUnits = true;
|
||||
}
|
||||
});
|
||||
if (f.isEnemy) { team2HasUnits = true; }
|
||||
else { team1HasUnits = true; }
|
||||
});
|
||||
|
||||
for (const Building& b : m_buildingSystem->allBuildings())
|
||||
{
|
||||
if (b.type == BuildingType::PlayerDefenceStation)
|
||||
m_admin.forEach<StationBody, Faction>(
|
||||
[&team1HasUnits, &team2HasUnits](entt::entity /*e*/,
|
||||
const StationBody& /*sb*/,
|
||||
const Faction& f)
|
||||
{
|
||||
team1HasUnits = true;
|
||||
}
|
||||
else if (b.type == BuildingType::EnemyDefenceStation
|
||||
&& b.id != m_team2HqId)
|
||||
{
|
||||
team2HasUnits = true;
|
||||
}
|
||||
}
|
||||
if (f.isEnemy) { team2HasUnits = true; }
|
||||
else { team1HasUnits = true; }
|
||||
});
|
||||
|
||||
if (!team1HasUnits || !team2HasUnits)
|
||||
{
|
||||
@@ -433,6 +410,16 @@ const ScrapSystem& ArenaSimulation::scraps() const
|
||||
return *m_scrapSystem;
|
||||
}
|
||||
|
||||
EntityAdmin& ArenaSimulation::admin()
|
||||
{
|
||||
return m_admin;
|
||||
}
|
||||
|
||||
const EntityAdmin& ArenaSimulation::admin() const
|
||||
{
|
||||
return m_admin;
|
||||
}
|
||||
|
||||
void ArenaSimulation::updateStatus()
|
||||
{
|
||||
ArenaStatus newStatus;
|
||||
@@ -450,8 +437,9 @@ void ArenaSimulation::updateStatus()
|
||||
hqEntry.displayName = "HQ";
|
||||
hqEntry.level = 1;
|
||||
hqEntry.total = 1;
|
||||
const EntityId hqId = (ti == 0) ? m_team1HqId : m_team2HqId;
|
||||
hqEntry.surviving = (m_buildingSystem->findBuilding(hqId) != nullptr) ? 1 : 0;
|
||||
const entt::entity hqEntity = (ti == 0) ? m_team1HqEntity : m_team2HqEntity;
|
||||
hqEntry.surviving = (m_admin.isValid(hqEntity)
|
||||
&& m_admin.get<Health>(hqEntity).hp > 0.0f) ? 1 : 0;
|
||||
teamStatus.entries.push_back(hqEntry);
|
||||
}
|
||||
|
||||
@@ -465,13 +453,14 @@ void ArenaSimulation::updateStatus()
|
||||
|
||||
int surviving = 0;
|
||||
const bool isEnemyTeam = (ti == 1);
|
||||
m_shipSystem->forEach(
|
||||
[&surviving, &shipEntry, isEnemyTeam](Ship& s)
|
||||
m_admin.forEach<ShipIdentity, Faction, Health>(
|
||||
[&surviving, &shipEntry, isEnemyTeam](entt::entity /*e*/,
|
||||
const ShipIdentity& si, const Faction& f, const Health& h)
|
||||
{
|
||||
if (s.isEnemy == isEnemyTeam
|
||||
&& s.schematicId == shipEntry.schematicId
|
||||
&& s.level == shipEntry.level
|
||||
&& s.hp > 0.0f)
|
||||
if (f.isEnemy == isEnemyTeam
|
||||
&& si.schematicId == shipEntry.schematicId
|
||||
&& si.level == shipEntry.level
|
||||
&& h.hp > 0.0f)
|
||||
{
|
||||
++surviving;
|
||||
}
|
||||
@@ -490,20 +479,19 @@ void ArenaSimulation::updateStatus()
|
||||
entry.level = stationEntry.level;
|
||||
entry.total = 1;
|
||||
|
||||
// Count surviving stations of this team at this position.
|
||||
const BuildingType expectedType = (ti == 0)
|
||||
? BuildingType::PlayerDefenceStation
|
||||
: BuildingType::EnemyDefenceStation;
|
||||
|
||||
int surviving = 0;
|
||||
for (const Building& b : m_buildingSystem->allBuildings())
|
||||
{
|
||||
if (b.type == expectedType && b.anchor == stationEntry.position)
|
||||
const bool isEnemyTeam = (ti == 1);
|
||||
m_admin.forEach<StationBody, Faction, Health>(
|
||||
[&surviving, &stationEntry, isEnemyTeam](entt::entity /*e*/,
|
||||
const StationBody& sb, const Faction& f, const Health& h)
|
||||
{
|
||||
surviving = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (f.isEnemy == isEnemyTeam
|
||||
&& sb.anchor == stationEntry.position
|
||||
&& h.hp > 0.0f)
|
||||
{
|
||||
surviving = 1;
|
||||
}
|
||||
});
|
||||
entry.surviving = surviving;
|
||||
teamStatus.entries.push_back(entry);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
|
||||
#include "BalancingConfig.h"
|
||||
#include "BeltSystem.h"
|
||||
#include "EcsComponents.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "EntityId.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
#include "FireEvent.h"
|
||||
#include "GameConfig.h"
|
||||
#include "Tick.h"
|
||||
@@ -65,6 +69,8 @@ public:
|
||||
const BuildingSystem& buildings() const;
|
||||
const ShipSystem& ships() const;
|
||||
const ScrapSystem& scraps() const;
|
||||
EntityAdmin& admin();
|
||||
const EntityAdmin& admin() const;
|
||||
|
||||
private:
|
||||
EntityId allocateId();
|
||||
@@ -81,6 +87,7 @@ private:
|
||||
Tick m_currentTick;
|
||||
EntityId m_nextId;
|
||||
|
||||
EntityAdmin m_admin;
|
||||
BeltSystem m_beltSystem;
|
||||
std::unique_ptr<BuildingSystem> m_buildingSystem;
|
||||
std::unique_ptr<ShipSystem> m_shipSystem;
|
||||
@@ -89,8 +96,8 @@ private:
|
||||
std::unique_ptr<CombatSystem> m_combatSystem;
|
||||
std::unique_ptr<ScrapSystem> m_scrapSystem;
|
||||
|
||||
EntityId m_team1HqId;
|
||||
EntityId m_team2HqId;
|
||||
entt::entity m_team1HqEntity;
|
||||
entt::entity m_team2HqEntity;
|
||||
|
||||
bool m_finished;
|
||||
int m_winnerTeam;
|
||||
|
||||
@@ -10,19 +10,18 @@
|
||||
#include "ArenaSimulation.h"
|
||||
#include "Building.h"
|
||||
#include "BuildingSystem.h"
|
||||
#include "Scrap.h"
|
||||
#include "EcsComponents.h"
|
||||
#include "ScrapSystem.h"
|
||||
#include "Ship.h"
|
||||
#include "ShipSystem.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
ShipRole shipRole(const Ship& ship)
|
||||
ShipRole shipRoleFromComponents(bool isEnemy, bool hasCargo, bool hasRepairTool)
|
||||
{
|
||||
if (ship.isEnemy) { return ShipRole::Enemy; }
|
||||
if (ship.cargo.has_value()) { return ShipRole::Salvage; }
|
||||
if (ship.repairTool.has_value()) { return ShipRole::Repair; }
|
||||
if (isEnemy) { return ShipRole::Enemy; }
|
||||
if (hasCargo) { return ShipRole::Salvage; }
|
||||
if (hasRepairTool) { return ShipRole::Repair; }
|
||||
return ShipRole::PlayerCombat;
|
||||
}
|
||||
|
||||
@@ -95,11 +94,12 @@ void ArenaView::onFrame()
|
||||
for (const FireEvent& fe : fires)
|
||||
{
|
||||
float maxRadius = 0.125f;
|
||||
const Building* tBld = m_sim->buildings().findBuilding(fe.target);
|
||||
if (tBld)
|
||||
if (m_sim->admin().isValid(fe.target)
|
||||
&& m_sim->admin().hasAll<StationBody>(fe.target))
|
||||
{
|
||||
const int shorter = std::min(tBld->footprint.width(),
|
||||
tBld->footprint.height());
|
||||
const StationBody& sb = m_sim->admin().get<StationBody>(fe.target);
|
||||
const int shorter = std::min(sb.footprint.width(),
|
||||
sb.footprint.height());
|
||||
maxRadius = shorter / 2.0f;
|
||||
}
|
||||
|
||||
@@ -188,23 +188,13 @@ QRectF ArenaView::tileRect(QPoint tile) const
|
||||
static_cast<qreal>(tilePx()), static_cast<qreal>(tilePx()));
|
||||
}
|
||||
|
||||
std::optional<QVector2D> ArenaView::entityPosition(EntityId id) const
|
||||
std::optional<QVector2D> ArenaView::entityPosition(entt::entity entity) const
|
||||
{
|
||||
for (const Ship& ship : m_sim->ships().allShips())
|
||||
if (!m_sim->admin().isValid(entity) || !m_sim->admin().hasAll<Position>(entity))
|
||||
{
|
||||
if (ship.id == id)
|
||||
{
|
||||
return ship.position;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
const Building* bldg = m_sim->buildings().findBuilding(id);
|
||||
if (bldg)
|
||||
{
|
||||
return QVector2D(
|
||||
bldg->anchor.x() + bldg->footprint.width() * 0.5f,
|
||||
bldg->anchor.y() + bldg->footprint.height() * 0.5f);
|
||||
}
|
||||
return std::nullopt;
|
||||
return m_sim->admin().get<Position>(entity).value;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -264,7 +254,7 @@ void ArenaView::drawBuildings(QPainter& painter)
|
||||
void ArenaView::drawScrap(QPainter& painter)
|
||||
{
|
||||
const float r = tilePx() * 0.2f;
|
||||
for (const Scrap& scrap : m_sim->scraps().allScraps())
|
||||
for (const ScrapInfo& scrap : m_sim->scraps().allScrapInfo())
|
||||
{
|
||||
const QPointF center = worldToWidget(scrap.position);
|
||||
painter.setBrush(QColor(128, 110, 90));
|
||||
@@ -276,35 +266,38 @@ void ArenaView::drawScrap(QPainter& painter)
|
||||
|
||||
void ArenaView::drawShips(QPainter& painter)
|
||||
{
|
||||
for (const Ship& ship : m_sim->ships().allShips())
|
||||
{
|
||||
const ShipRole role = shipRole(ship);
|
||||
const std::map<ShipRole, ShipVisuals>::const_iterator it =
|
||||
m_visuals->ships.find(role);
|
||||
if (it == m_visuals->ships.end()) { continue; }
|
||||
m_sim->admin().forEach<ShipIdentity, Position, Velocity, Faction>(
|
||||
[&](entt::entity e, const ShipIdentity& /*si*/, const Position& pos,
|
||||
const Velocity& vel, const Faction& fac)
|
||||
{
|
||||
const bool hasCargo = m_sim->admin().hasAll<SalvageCargo>(e);
|
||||
const bool hasRepair = m_sim->admin().hasAll<RepairTool>(e);
|
||||
const ShipRole role = shipRoleFromComponents(fac.isEnemy, hasCargo, hasRepair);
|
||||
const std::map<ShipRole, ShipVisuals>::const_iterator it =
|
||||
m_visuals->ships.find(role);
|
||||
if (it == m_visuals->ships.end()) { return; }
|
||||
|
||||
const QPointF center = worldToWidget(ship.position);
|
||||
const QVector2D vel = ship.velocity;
|
||||
const QVector2D dir = (vel.length() > 0.0001f)
|
||||
? vel.normalized()
|
||||
: QVector2D(1.0f, 0.0f);
|
||||
const QVector2D perp(-dir.y(), dir.x());
|
||||
const QPointF center = worldToWidget(pos.value);
|
||||
const QVector2D dir = (vel.value.length() > 0.0001f)
|
||||
? vel.value.normalized()
|
||||
: QVector2D(1.0f, 0.0f);
|
||||
const QVector2D perp(-dir.y(), dir.x());
|
||||
|
||||
const float fwd = tilePx() * 0.45f;
|
||||
const float side = tilePx() * 0.25f;
|
||||
const float fwd = tilePx() * 0.45f;
|
||||
const float side = tilePx() * 0.25f;
|
||||
|
||||
QPolygonF tri;
|
||||
tri << QPointF(center.x() + static_cast<qreal>(dir.x() * fwd),
|
||||
center.y() + static_cast<qreal>(dir.y() * fwd))
|
||||
<< QPointF(center.x() + static_cast<qreal>(perp.x() * side - dir.x() * side),
|
||||
center.y() + static_cast<qreal>(perp.y() * side - dir.y() * side))
|
||||
<< QPointF(center.x() + static_cast<qreal>(-perp.x() * side - dir.x() * side),
|
||||
center.y() + static_cast<qreal>(-perp.y() * side - dir.y() * side));
|
||||
QPolygonF tri;
|
||||
tri << QPointF(center.x() + static_cast<qreal>(dir.x() * fwd),
|
||||
center.y() + static_cast<qreal>(dir.y() * fwd))
|
||||
<< QPointF(center.x() + static_cast<qreal>(perp.x() * side - dir.x() * side),
|
||||
center.y() + static_cast<qreal>(perp.y() * side - dir.y() * side))
|
||||
<< QPointF(center.x() + static_cast<qreal>(-perp.x() * side - dir.x() * side),
|
||||
center.y() + static_cast<qreal>(-perp.y() * side - dir.y() * side));
|
||||
|
||||
painter.setPen(QPen(it->second.outline, 1));
|
||||
painter.setBrush(it->second.fill);
|
||||
painter.drawPolygon(tri);
|
||||
}
|
||||
painter.setPen(QPen(it->second.outline, 1));
|
||||
painter.setBrush(it->second.fill);
|
||||
painter.drawPolygon(tri);
|
||||
});
|
||||
}
|
||||
|
||||
void ArenaView::drawBeams(QPainter& painter)
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
#include <QTimer>
|
||||
#include <QVector2D>
|
||||
|
||||
#include "EntityId.h"
|
||||
#include "FireEvent.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
#include "Tick.h"
|
||||
#include "TickDriver.h"
|
||||
#include "VisualsConfig.h"
|
||||
@@ -51,7 +52,7 @@ private:
|
||||
QPointF tileToWidget(QPoint tile) const;
|
||||
QRectF tileRect(QPoint tile) const;
|
||||
|
||||
std::optional<QVector2D> entityPosition(EntityId id) const;
|
||||
std::optional<QVector2D> entityPosition(entt::entity entity) const;
|
||||
|
||||
struct ActiveBeam
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user