593 lines
20 KiB
C++
593 lines
20 KiB
C++
#include "Simulation.h"
|
|
|
|
#include <cassert>
|
|
|
|
#include "AiSystem.h"
|
|
#include "BuildingSystem.h"
|
|
#include "EcsComponents.h"
|
|
#include "CombatSystem.h"
|
|
#include "MovementSystem.h"
|
|
#include "ScrapSystem.h"
|
|
#include "ShipSystem.h"
|
|
#include "SurfaceMask.h"
|
|
#include "WaveSystem.h"
|
|
|
|
Simulation::Simulation(GameConfig config, unsigned int seed)
|
|
: m_config(std::move(config))
|
|
, m_rng(seed)
|
|
, m_currentTick(0)
|
|
, m_nextDepartureTick(secondsToTicks(m_config.world.departureIntervalSeconds))
|
|
, m_nextId(1)
|
|
, m_buildingBlocksStock(m_config.world.startingBuildingBlocks)
|
|
, m_gameOver(false)
|
|
, m_hqBuildingId(kInvalidEntityId)
|
|
, m_hqProxyEntity(entt::null)
|
|
, m_playerStation1Entity(entt::null)
|
|
, m_playerStation2Entity(entt::null)
|
|
, m_beltSystem(m_config.world.beltSpeedTilesPerSecond)
|
|
{
|
|
m_currentEnemyStationEntities[0] = entt::null;
|
|
m_currentEnemyStationEntities[1] = entt::null;
|
|
|
|
m_buildingSystem = std::make_unique<BuildingSystem>(
|
|
m_config,
|
|
m_beltSystem,
|
|
[this]() { return allocateId(); },
|
|
[this](int amount) { m_buildingBlocksStock += amount; },
|
|
[this](const std::string& id, QVector2D pos,
|
|
const std::optional<ShipLayoutConfig>& layout) {
|
|
const std::map<std::string, SchematicState>::const_iterator it =
|
|
m_schematicLevels.find(id);
|
|
if (it == m_schematicLevels.end() || !it->second.unlocked)
|
|
{
|
|
return;
|
|
}
|
|
m_shipSystem->spawn(id, it->second.level, pos, /*isEnemy=*/false, layout);
|
|
},
|
|
m_rng);
|
|
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
|
m_aiSystem = std::make_unique<AiSystem>();
|
|
m_movementSystem = std::make_unique<MovementSystem>();
|
|
m_scrapSystem = std::make_unique<ScrapSystem>(m_admin);
|
|
m_waveSystem = std::make_unique<WaveSystem>(m_config, m_rng);
|
|
m_combatSystem = std::make_unique<CombatSystem>(m_config);
|
|
|
|
// Initialize schematic unlock state.
|
|
for (const ShipDef& def : m_config.ships.ships)
|
|
{
|
|
SchematicState state;
|
|
state.unlocked = def.availableFromStart;
|
|
state.level = def.availableFromStart ? def.schematic.playerProductionLevel : 0;
|
|
m_schematicLevels[def.id] = state;
|
|
}
|
|
|
|
placeInitialStructures();
|
|
}
|
|
|
|
Simulation::~Simulation() = default;
|
|
|
|
const GameConfig& Simulation::config() const
|
|
{
|
|
return m_config;
|
|
}
|
|
|
|
void Simulation::reset(GameConfig newConfig, unsigned int seed)
|
|
{
|
|
m_config = std::move(newConfig);
|
|
reset(seed);
|
|
}
|
|
|
|
void Simulation::reset(unsigned int seed)
|
|
{
|
|
m_rng.seed(seed);
|
|
m_currentTick = 0;
|
|
m_nextDepartureTick = secondsToTicks(m_config.world.departureIntervalSeconds);
|
|
m_nextId = 1;
|
|
m_buildingBlocksStock = m_config.world.startingBuildingBlocks;
|
|
m_gameOver = false;
|
|
m_hqBuildingId = kInvalidEntityId;
|
|
m_hqProxyEntity = entt::null;
|
|
m_playerStation1Entity = entt::null;
|
|
m_playerStation2Entity = entt::null;
|
|
m_currentEnemyStationEntities[0] = entt::null;
|
|
m_currentEnemyStationEntities[1] = entt::null;
|
|
m_fireEvents.clear();
|
|
m_schematicDropEvents.clear();
|
|
|
|
m_admin.clear();
|
|
m_beltSystem = BeltSystem(m_config.world.beltSpeedTilesPerSecond);
|
|
m_buildingSystem = std::make_unique<BuildingSystem>(
|
|
m_config,
|
|
m_beltSystem,
|
|
[this]() { return allocateId(); },
|
|
[this](int amount) { m_buildingBlocksStock += amount; },
|
|
[this](const std::string& id, QVector2D pos,
|
|
const std::optional<ShipLayoutConfig>& layout) {
|
|
const std::map<std::string, SchematicState>::const_iterator it =
|
|
m_schematicLevels.find(id);
|
|
if (it == m_schematicLevels.end() || !it->second.unlocked)
|
|
{
|
|
return;
|
|
}
|
|
m_shipSystem->spawn(id, it->second.level, pos, /*isEnemy=*/false, layout);
|
|
},
|
|
m_rng);
|
|
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
|
m_aiSystem = std::make_unique<AiSystem>();
|
|
m_movementSystem = std::make_unique<MovementSystem>();
|
|
m_scrapSystem = std::make_unique<ScrapSystem>(m_admin);
|
|
m_waveSystem = std::make_unique<WaveSystem>(m_config, m_rng);
|
|
m_combatSystem = std::make_unique<CombatSystem>(m_config);
|
|
|
|
m_schematicLevels.clear();
|
|
for (const ShipDef& def : m_config.ships.ships)
|
|
{
|
|
SchematicState state;
|
|
state.unlocked = def.availableFromStart;
|
|
state.level = def.availableFromStart ? def.schematic.playerProductionLevel : 0;
|
|
m_schematicLevels[def.id] = state;
|
|
}
|
|
|
|
placeInitialStructures();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// tick
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void Simulation::tick()
|
|
{
|
|
// Step 1: wave scheduler
|
|
m_waveSystem->tickWaveScheduler(m_currentTick, *m_shipSystem,
|
|
m_config.world.heightTiles);
|
|
|
|
// Step 2: threat accumulation
|
|
m_waveSystem->tickThreatAccumulation(m_currentTick);
|
|
|
|
// Construction + production pipeline
|
|
m_buildingSystem->tickConstruction(m_currentTick);
|
|
m_buildingSystem->tickBeltPull(); // step 3
|
|
m_buildingSystem->tickProduction(m_currentTick); // step 4
|
|
m_buildingSystem->tickShipyardProduction(m_currentTick); // step 4b
|
|
m_buildingSystem->tickBeltPush(); // step 5
|
|
m_beltSystem.tick(); // step 6
|
|
|
|
// Step 7: ship behavior systems (movement arbitration via intent priority)
|
|
|
|
// Departure timer: release gathered combat ships on a fixed interval (REQ-SHP-RALLY).
|
|
if (m_currentTick >= m_nextDepartureTick)
|
|
{
|
|
m_shipSystem->triggerRallyDeparture();
|
|
m_nextDepartureTick += secondsToTicks(m_config.world.departureIntervalSeconds);
|
|
}
|
|
|
|
m_shipSystem->clearMovementIntents();
|
|
m_aiSystem->tickHomeReturn(m_admin); // priority 4
|
|
m_aiSystem->tickThreatResponse(m_admin, *m_buildingSystem); // priority 3
|
|
m_aiSystem->tickRepairBehavior(m_admin, *m_buildingSystem); // priority 2
|
|
m_aiSystem->tickScrapCollector(m_admin, *m_scrapSystem, *m_buildingSystem); // priority 1
|
|
|
|
// Step 8: combat resolution
|
|
m_combatSystem->tick(m_currentTick, m_admin,
|
|
*m_buildingSystem, m_fireEvents);
|
|
|
|
// Step 8b: deferred damage whose impact tick has arrived
|
|
m_combatSystem->applyPendingDamage(m_currentTick, m_admin);
|
|
|
|
// Step 9: deaths & loot
|
|
if (!m_gameOver)
|
|
{
|
|
tickDeathsAndLoot();
|
|
}
|
|
|
|
// Step 10: advance ship positions
|
|
m_movementSystem->tick(m_admin);
|
|
|
|
// Step 11: scrap despawn
|
|
m_scrapSystem->tickDespawn(m_currentTick);
|
|
|
|
++m_currentTick;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Pre-placement
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void Simulation::placeInitialStructures()
|
|
{
|
|
// HQ — right edge of asteroid (rightmost asteroid tile is x = -1).
|
|
// Placed as a Building (for belt input) plus an ECS proxy (for HP/targeting).
|
|
const ParsedSurfaceMask hqParsed =
|
|
parseSurfaceMask(m_config.stations.hq.surfaceMask, Rotation::East);
|
|
const int hqAnchorX = -hqParsed.footprint.width();
|
|
const int hqAnchorY =
|
|
(m_config.world.heightTiles - hqParsed.footprint.height()) / 2;
|
|
const float hqHp =
|
|
static_cast<float>(m_config.stations.hq.hpFormula.evaluate(0.0));
|
|
m_hqBuildingId = m_buildingSystem->placeImmediate(
|
|
BuildingType::Hq,
|
|
m_config.stations.hq.surfaceMask,
|
|
QPoint(hqAnchorX, hqAnchorY),
|
|
Rotation::East, hqHp, hqHp);
|
|
|
|
const QVector2D hqCenter(
|
|
hqAnchorX + hqParsed.footprint.width() / 2.0f,
|
|
hqAnchorY + hqParsed.footprint.height() / 2.0f);
|
|
m_hqProxyEntity = m_admin.spawnHqProxy(hqCenter, hqHp, hqHp);
|
|
|
|
// Player defence stations — ECS entities with tile occupancy.
|
|
const ParsedSurfaceMask psParsed =
|
|
parseSurfaceMask(m_config.stations.playerStation.surfaceMask, Rotation::East);
|
|
const int psAnchorX =
|
|
m_config.world.regions.playerBufferWidth - psParsed.footprint.width();
|
|
const double psLevel = static_cast<double>(m_config.stations.playerStation.level);
|
|
const float psHp = static_cast<float>(
|
|
m_config.stations.playerStation.hpFormula.evaluate(psLevel));
|
|
|
|
StationWeapon psWeapon;
|
|
psWeapon.damage = static_cast<float>(
|
|
m_config.stations.playerStation.damageFormula.evaluate(psLevel));
|
|
psWeapon.range = static_cast<float>(
|
|
m_config.stations.playerStation.rangeFormula.evaluate(psLevel));
|
|
psWeapon.fireRateHz = static_cast<float>(
|
|
m_config.stations.playerStation.fireRateFormula.evaluate(psLevel));
|
|
psWeapon.cooldownTicks = 0.0f;
|
|
psWeapon.currentTarget = std::nullopt;
|
|
|
|
const int ps1Y = m_config.world.heightTiles / 4;
|
|
const int ps2Y = 3 * m_config.world.heightTiles / 4;
|
|
|
|
{
|
|
const QPoint anchor(psAnchorX, ps1Y);
|
|
std::vector<QPoint> absCells;
|
|
for (const QPoint& rel : psParsed.bodyCells)
|
|
{
|
|
absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
|
|
}
|
|
m_playerStation1Entity = m_admin.spawnStation(
|
|
anchor, psParsed.footprint, absCells, psHp, psHp, false);
|
|
m_admin.addComponent<StationWeapon>(m_playerStation1Entity, psWeapon);
|
|
m_buildingSystem->registerTileOccupancy(absCells, allocateId());
|
|
}
|
|
{
|
|
const QPoint anchor(psAnchorX, ps2Y);
|
|
std::vector<QPoint> absCells;
|
|
for (const QPoint& rel : psParsed.bodyCells)
|
|
{
|
|
absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
|
|
}
|
|
m_playerStation2Entity = m_admin.spawnStation(
|
|
anchor, psParsed.footprint, absCells, psHp, psHp, false);
|
|
m_admin.addComponent<StationWeapon>(m_playerStation2Entity, psWeapon);
|
|
m_buildingSystem->registerTileOccupancy(absCells, allocateId());
|
|
}
|
|
|
|
// Rally point: center of the player defence stations' X column, world vertical midpoint.
|
|
const float rallyX = static_cast<float>(psAnchorX) + psParsed.footprint.width() / 2.0f;
|
|
const float rallyY = static_cast<float>(m_config.world.heightTiles) / 2.0f;
|
|
m_shipSystem->setRallyPoint(QVector2D(rallyX, rallyY));
|
|
|
|
// Enemy defence stations — generation 0 (initial set).
|
|
placeEnemyStationSet(0);
|
|
}
|
|
|
|
void Simulation::placeEnemyStationSet(int generation)
|
|
{
|
|
const ParsedSurfaceMask esParsed =
|
|
parseSurfaceMask(m_config.stations.enemyStation.surfaceMask, Rotation::East);
|
|
|
|
const int rightEdgeX = m_config.world.regions.playerBufferWidth
|
|
+ m_config.world.regions.contestZoneWidth
|
|
+ generation * m_config.world.push.pushExpandColumns;
|
|
const int anchorX = rightEdgeX - esParsed.footprint.width();
|
|
|
|
const double genD = static_cast<double>(generation);
|
|
const float esHp = static_cast<float>(
|
|
m_config.stations.enemyStation.hpFormula.evaluate(genD));
|
|
|
|
StationWeapon esWeapon;
|
|
esWeapon.damage = static_cast<float>(
|
|
m_config.stations.enemyStation.damageFormula.evaluate(genD));
|
|
esWeapon.range = static_cast<float>(
|
|
m_config.stations.enemyStation.rangeFormula.evaluate(genD));
|
|
esWeapon.fireRateHz = static_cast<float>(
|
|
m_config.stations.enemyStation.fireRateFormula.evaluate(genD));
|
|
esWeapon.cooldownTicks = 0.0f;
|
|
esWeapon.currentTarget = std::nullopt;
|
|
|
|
const int y1 = m_config.world.heightTiles / 4;
|
|
const int y2 = 3 * m_config.world.heightTiles / 4;
|
|
|
|
{
|
|
const QPoint anchor(anchorX, y1);
|
|
std::vector<QPoint> absCells;
|
|
for (const QPoint& rel : esParsed.bodyCells)
|
|
{
|
|
absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
|
|
}
|
|
m_currentEnemyStationEntities[0] = m_admin.spawnStation(
|
|
anchor, esParsed.footprint, absCells, esHp, esHp, true);
|
|
m_admin.addComponent<StationWeapon>(m_currentEnemyStationEntities[0], esWeapon);
|
|
m_buildingSystem->registerTileOccupancy(absCells, allocateId());
|
|
}
|
|
{
|
|
const QPoint anchor(anchorX, y2);
|
|
std::vector<QPoint> absCells;
|
|
for (const QPoint& rel : esParsed.bodyCells)
|
|
{
|
|
absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
|
|
}
|
|
m_currentEnemyStationEntities[1] = m_admin.spawnStation(
|
|
anchor, esParsed.footprint, absCells, esHp, esHp, true);
|
|
m_admin.addComponent<StationWeapon>(m_currentEnemyStationEntities[1], esWeapon);
|
|
m_buildingSystem->registerTileOccupancy(absCells, allocateId());
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Deaths & loot (tick step 9)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void Simulation::tickDeathsAndLoot()
|
|
{
|
|
// --- Dead ships ---
|
|
std::vector<entt::entity> deadShips;
|
|
m_admin.forEach<ShipIdentity, Health>(
|
|
[&deadShips](entt::entity e, const ShipIdentity& /*si*/, const Health& h)
|
|
{
|
|
if (h.hp <= 0.0f)
|
|
{
|
|
deadShips.push_back(e);
|
|
}
|
|
});
|
|
|
|
for (entt::entity deadEntity : deadShips)
|
|
{
|
|
const ShipIdentity& si = m_admin.get<ShipIdentity>(deadEntity);
|
|
const Position& pos = m_admin.get<Position>(deadEntity);
|
|
for (const ShipDef& def : m_config.ships.ships)
|
|
{
|
|
if (def.id == si.schematicId && def.loot.scrapDrop > 0)
|
|
{
|
|
const Tick despawnAt = m_currentTick
|
|
+ secondsToTicks(m_config.world.scrapDespawnSeconds);
|
|
m_scrapSystem->spawn(pos.value, def.loot.scrapDrop, despawnAt);
|
|
break;
|
|
}
|
|
}
|
|
m_shipSystem->despawn(deadEntity);
|
|
}
|
|
|
|
// --- Dead stations ---
|
|
std::vector<entt::entity> deadStations;
|
|
m_admin.forEach<StationBody, Health>(
|
|
[&deadStations](entt::entity e, const StationBody& /*sb*/, const Health& h)
|
|
{
|
|
if (h.hp <= 0.0f)
|
|
{
|
|
deadStations.push_back(e);
|
|
}
|
|
});
|
|
|
|
for (entt::entity deadEntity : deadStations)
|
|
{
|
|
const StationBody& sb = m_admin.get<StationBody>(deadEntity);
|
|
const Position& pos = m_admin.get<Position>(deadEntity);
|
|
const Faction& fac = m_admin.get<Faction>(deadEntity);
|
|
|
|
const Tick despawnAt = m_currentTick
|
|
+ secondsToTicks(m_config.world.scrapDespawnSeconds);
|
|
int scrap = 0;
|
|
if (!fac.isEnemy)
|
|
{
|
|
const double lv = static_cast<double>(
|
|
m_config.stations.playerStation.level);
|
|
scrap = static_cast<int>(
|
|
m_config.stations.playerStation.scrapDropFormula.evaluate(lv));
|
|
}
|
|
else
|
|
{
|
|
const double genD = static_cast<double>(m_waveSystem->generation());
|
|
scrap = static_cast<int>(
|
|
m_config.stations.enemyStation.scrapDropFormula.evaluate(genD));
|
|
}
|
|
if (scrap > 0)
|
|
{
|
|
m_scrapSystem->spawn(pos.value, scrap, despawnAt);
|
|
}
|
|
m_buildingSystem->unregisterTileOccupancy(sb.bodyCells);
|
|
m_admin.destroy(deadEntity);
|
|
}
|
|
|
|
// --- HQ death check ---
|
|
if (m_admin.isValid(m_hqProxyEntity))
|
|
{
|
|
const Health& hqHealth = m_admin.get<Health>(m_hqProxyEntity);
|
|
if (hqHealth.hp <= 0.0f)
|
|
{
|
|
m_gameOver = true;
|
|
}
|
|
}
|
|
|
|
// --- Push check: if both current enemy stations are gone, trigger push ---
|
|
const bool es0Gone = !m_admin.isValid(m_currentEnemyStationEntities[0])
|
|
|| m_admin.get<Health>(m_currentEnemyStationEntities[0]).hp <= 0.0f;
|
|
const bool es1Gone = !m_admin.isValid(m_currentEnemyStationEntities[1])
|
|
|| m_admin.get<Health>(m_currentEnemyStationEntities[1]).hp <= 0.0f;
|
|
|
|
if (es0Gone && es1Gone &&
|
|
m_currentEnemyStationEntities[0] != entt::null)
|
|
{
|
|
m_waveSystem->applyPush();
|
|
placeEnemyStationSet(m_waveSystem->generation());
|
|
awardSchematicDrop();
|
|
}
|
|
}
|
|
|
|
void Simulation::awardSchematicDrop()
|
|
{
|
|
std::vector<std::string> ids;
|
|
ids.reserve(m_config.ships.ships.size());
|
|
for (const ShipDef& def : m_config.ships.ships)
|
|
{
|
|
ids.push_back(def.id);
|
|
}
|
|
|
|
std::uniform_int_distribution<int> dist(0, static_cast<int>(ids.size()) - 1);
|
|
const std::string chosen = ids[static_cast<std::size_t>(dist(m_rng))];
|
|
|
|
SchematicState& state = m_schematicLevels.at(chosen);
|
|
const bool wasNew = !state.unlocked;
|
|
state.unlocked = true;
|
|
state.level += 1;
|
|
|
|
SchematicDropEvent evt;
|
|
evt.schematicId = chosen;
|
|
evt.newLevel = state.level;
|
|
evt.wasNewUnlock = wasNew;
|
|
m_schematicDropEvents.push_back(evt);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Drains
|
|
// ---------------------------------------------------------------------------
|
|
|
|
std::vector<FireEvent> Simulation::drainFireEvents()
|
|
{
|
|
std::vector<FireEvent> result;
|
|
result.swap(m_fireEvents);
|
|
return result;
|
|
}
|
|
|
|
std::vector<SchematicDropEvent> Simulation::drainSchematicDropEvents()
|
|
{
|
|
std::vector<SchematicDropEvent> result;
|
|
result.swap(m_schematicDropEvents);
|
|
return result;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Accessors
|
|
// ---------------------------------------------------------------------------
|
|
|
|
Tick Simulation::currentTick() const
|
|
{
|
|
return m_currentTick;
|
|
}
|
|
|
|
int Simulation::buildingBlocksStock() const
|
|
{
|
|
return m_buildingBlocksStock;
|
|
}
|
|
|
|
bool Simulation::isGameOver() const
|
|
{
|
|
return m_gameOver;
|
|
}
|
|
|
|
double Simulation::threatLevel() const
|
|
{
|
|
return m_waveSystem->threatLevel();
|
|
}
|
|
|
|
int Simulation::schematicLevel(const std::string& shipId) const
|
|
{
|
|
const std::map<std::string, SchematicState>::const_iterator it =
|
|
m_schematicLevels.find(shipId);
|
|
if (it == m_schematicLevels.end())
|
|
{
|
|
return 0;
|
|
}
|
|
return it->second.level;
|
|
}
|
|
|
|
bool Simulation::isSchematicUnlocked(const std::string& shipId) const
|
|
{
|
|
const std::map<std::string, SchematicState>::const_iterator it =
|
|
m_schematicLevels.find(shipId);
|
|
if (it == m_schematicLevels.end())
|
|
{
|
|
return false;
|
|
}
|
|
return it->second.unlocked;
|
|
}
|
|
|
|
EntityId Simulation::tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation)
|
|
{
|
|
int cost = 0;
|
|
for (const BuildingDef& def : m_config.buildings.buildings)
|
|
{
|
|
if (def.type == type)
|
|
{
|
|
cost = def.cost;
|
|
break;
|
|
}
|
|
}
|
|
if (m_buildingBlocksStock < cost)
|
|
{
|
|
return kInvalidEntityId;
|
|
}
|
|
m_buildingBlocksStock -= cost;
|
|
return m_buildingSystem->place(type, anchor, rotation, m_currentTick);
|
|
}
|
|
|
|
void Simulation::demolish(EntityId id)
|
|
{
|
|
m_buildingBlocksStock += m_buildingSystem->demolish(id);
|
|
}
|
|
|
|
BuildingSystem& Simulation::buildings()
|
|
{
|
|
return *m_buildingSystem;
|
|
}
|
|
|
|
const BuildingSystem& Simulation::buildings() const
|
|
{
|
|
return *m_buildingSystem;
|
|
}
|
|
|
|
BeltSystem& Simulation::belts()
|
|
{
|
|
return m_beltSystem;
|
|
}
|
|
|
|
const BeltSystem& Simulation::belts() const
|
|
{
|
|
return m_beltSystem;
|
|
}
|
|
|
|
ShipSystem& Simulation::ships()
|
|
{
|
|
return *m_shipSystem;
|
|
}
|
|
|
|
const ShipSystem& Simulation::ships() const
|
|
{
|
|
return *m_shipSystem;
|
|
}
|
|
|
|
ScrapSystem& Simulation::scraps()
|
|
{
|
|
return *m_scrapSystem;
|
|
}
|
|
|
|
const ScrapSystem& Simulation::scraps() const
|
|
{
|
|
return *m_scrapSystem;
|
|
}
|
|
|
|
EntityAdmin& Simulation::admin()
|
|
{
|
|
return m_admin;
|
|
}
|
|
|
|
const EntityAdmin& Simulation::admin() const
|
|
{
|
|
return m_admin;
|
|
}
|
|
|
|
EntityId Simulation::allocateId()
|
|
{
|
|
return m_nextId++;
|
|
}
|