#include "Simulation.h" #include #include "AiSystem.h" #include "BuildingSystem.h" #include "CombatSystem.h" #include "DynamicBodySystem.h" #include "FactionComponent.h" #include "EventManager.h" #include "HealthComponent.h" #include "ModuleOwnerComponent.h" #include "MovementIntentSystem.h" #include "PositionComponent.h" #include "ScrapSystem.h" #include "ShipIdentityComponent.h" #include "ShipSystem.h" #include "StationBodyComponent.h" #include "SurfaceMask.h" #include "tracing.h" #include "WaveSystem.h" #include "WeaponComponent.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_nextBuildingId(1) , m_buildingBlocksStock(m_config.world.startingBuildingBlocks) , m_gameOver(false) , m_hqBuildingId(kInvalidBuildingId) , m_hqProxyEntity(entt::null) , m_playerStation1Entity(entt::null) , m_playerStation2Entity(entt::null) , m_beltSystem(m_config.world.beltSpeed_tps) { m_currentEnemyStationEntities[0] = entt::null; m_currentEnemyStationEntities[1] = entt::null; m_buildingSystem = std::make_unique( m_config, m_beltSystem, [this]() { return allocateBuildingId(); }, [this](int amount) { m_buildingBlocksStock += amount; }, [this](const std::string& id, QVector2D pos, const std::optional& layout) { const std::map::const_iterator it = m_schematicLevels.find(id); if (it == m_schematicLevels.end() || !it->second.unlocked) { return; } std::map moduleLevels; for (const auto& [mId, mState] : m_moduleSchematicLevels) { moduleLevels[mId] = mState.level; } m_shipSystem->spawn(id, it->second.level, pos, /*isEnemy=*/false, layout, moduleLevels); }, [this](const std::string& itemId) -> bool { return isItemUnlocked(itemId); }, m_rng); m_shipSystem = std::make_unique(m_config, m_admin); m_aiSystem = std::make_unique(); m_movementIntentSystem = std::make_unique(); m_dynamicBodySystem = std::make_unique(); m_scrapSystem = std::make_unique(m_admin); m_waveSystem = std::make_unique(m_config, m_rng); m_combatSystem = std::make_unique(m_config); // Initialize ship schematic unlock state. for (const ShipDef& def : m_config.ships.ships) { SchematicState state; state.unlocked = (def.unlockAtStationLevel == -1); state.level = (def.unlockAtStationLevel == -1) ? def.schematic.playerProductionLevel : 0; m_schematicLevels[def.id] = state; } // Initialize module schematic unlock state. for (const ModuleDef& def : m_config.modules.modules) { SchematicState state; state.unlocked = (def.unlockAtStationLevel == -1); state.level = (def.unlockAtStationLevel == -1) ? def.playerProductionLevel : 0; m_moduleSchematicLevels[def.id] = state; } // Initialize assembler recipe schematic unlock state. for (const RecipeDef& def : m_config.recipes.recipes) { if (def.building == BuildingType::Assembler && def.unlockAtStationLevel.has_value() && def.unlockAtStationLevel.value() == -1) { m_unlockedRecipeSchematicIds.insert(def.id); } } recomputeUnlocked(); placeInitialStructures(); registerForEvents(); } Simulation::~Simulation() { unregisterForEvents(); } 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) { EventManager::getInstance()->clearEvents(); m_rng.seed(seed); m_currentTick = 0; m_nextDepartureTick = secondsToTicks(m_config.world.departureIntervalSeconds); m_nextBuildingId = 1; m_buildingBlocksStock = m_config.world.startingBuildingBlocks; m_gameOver = false; m_hqBuildingId = kInvalidBuildingId; 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.beltSpeed_tps); m_buildingSystem = std::make_unique( m_config, m_beltSystem, [this]() { return allocateBuildingId(); }, [this](int amount) { m_buildingBlocksStock += amount; }, [this](const std::string& id, QVector2D pos, const std::optional& layout) { const std::map::const_iterator it = m_schematicLevels.find(id); if (it == m_schematicLevels.end() || !it->second.unlocked) { return; } std::map moduleLevels; for (const auto& [mId, mState] : m_moduleSchematicLevels) { moduleLevels[mId] = mState.level; } m_shipSystem->spawn(id, it->second.level, pos, /*isEnemy=*/false, layout, moduleLevels); }, [this](const std::string& itemId) -> bool { return isItemUnlocked(itemId); }, m_rng); m_shipSystem = std::make_unique(m_config, m_admin); m_aiSystem = std::make_unique(); m_movementIntentSystem = std::make_unique(); m_dynamicBodySystem = std::make_unique(); m_scrapSystem = std::make_unique(m_admin); m_waveSystem = std::make_unique(m_config, m_rng); m_combatSystem = std::make_unique(m_config); m_schematicLevels.clear(); for (const ShipDef& def : m_config.ships.ships) { SchematicState state; state.unlocked = (def.unlockAtStationLevel == -1); state.level = (def.unlockAtStationLevel == -1) ? def.schematic.playerProductionLevel : 0; m_schematicLevels[def.id] = state; } m_moduleSchematicLevels.clear(); for (const ModuleDef& def : m_config.modules.modules) { SchematicState state; state.unlocked = (def.unlockAtStationLevel == -1); state.level = (def.unlockAtStationLevel == -1) ? def.playerProductionLevel : 0; m_moduleSchematicLevels[def.id] = state; } m_unlockedRecipeSchematicIds.clear(); for (const RecipeDef& def : m_config.recipes.recipes) { if (def.building == BuildingType::Assembler && def.unlockAtStationLevel.has_value() && def.unlockAtStationLevel.value() == -1) { m_unlockedRecipeSchematicIds.insert(def.id); } } recomputeUnlocked(); placeInitialStructures(); } // --------------------------------------------------------------------------- // tick // --------------------------------------------------------------------------- void Simulation::tick() { EventManager::getInstance()->processEvents(); // Step 1: wave scheduler m_waveSystem->tickWaveScheduler(m_currentTick, *m_shipSystem, m_config.world.heightTiles); // Step 2: threat accumulation m_waveSystem->tickThreatAccumulation(); // 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->tickHomeReturnBehavior(m_admin); // priority 4 m_aiSystem->tickThreatResponseBehavior(m_admin, *m_buildingSystem); // priority 3 m_aiSystem->tickRepairBehavior(m_admin, *m_buildingSystem); // priority 2 m_aiSystem->tickRepairTools(m_admin); m_aiSystem->tickSalvageBehavior(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_movementIntentSystem->tick(m_admin); m_dynamicBodySystem->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(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); 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_tiles - psParsed.footprint.width(); const double psLevel = static_cast(m_config.stations.playerStation.level); const float psHp = static_cast( m_config.stations.playerStation.hpFormula.evaluate(psLevel)); const float tileSize = static_cast(m_config.world.tileSize_m); WeaponComponent psWeapon; psWeapon.damage = static_cast( m_config.stations.playerStation.damageFormula.evaluate(psLevel)); psWeapon.range_tiles = static_cast( m_config.stations.playerStation.rangeFormula.evaluate(psLevel)) / tileSize; psWeapon.fireRateHz = static_cast( 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 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); { entt::entity wChild = m_admin.createModuleEntity(); m_admin.addComponent(wChild, psWeapon); m_admin.addComponent(wChild, ModuleOwnerComponent{m_playerStation1Entity}); } m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId()); } { const QPoint anchor(psAnchorX, ps2Y); std::vector 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); { entt::entity wChild = m_admin.createModuleEntity(); m_admin.addComponent(wChild, psWeapon); m_admin.addComponent(wChild, ModuleOwnerComponent{m_playerStation2Entity}); } m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId()); } // Rally point: center of the player defence stations' X column, world vertical midpoint. const float rallyX = static_cast(psAnchorX) + psParsed.footprint.width() / 2.0f; const float rallyY = static_cast(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 float tileSize = static_cast(m_config.world.tileSize_m); const ParsedSurfaceMask esParsed = parseSurfaceMask(m_config.stations.enemyStation.surfaceMask, Rotation::East); const int rightEdgeX = m_config.world.regions.playerBufferWidth_tiles + m_config.world.regions.contestZoneWidth_tiles + generation * m_config.world.push.pushExpandColumns_tiles; const int anchorX = rightEdgeX - esParsed.footprint.width(); const double genD = static_cast(generation); const float esHp = static_cast( m_config.stations.enemyStation.hpFormula.evaluate(genD)); WeaponComponent esWeapon; esWeapon.damage = static_cast( m_config.stations.enemyStation.damageFormula.evaluate(genD)); esWeapon.range_tiles = static_cast( m_config.stations.enemyStation.rangeFormula.evaluate(genD)) / tileSize; esWeapon.fireRateHz = static_cast( 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 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); { entt::entity wChild = m_admin.createModuleEntity(); m_admin.addComponent(wChild, esWeapon); m_admin.addComponent(wChild, ModuleOwnerComponent{m_currentEnemyStationEntities[0]}); } m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId()); } { const QPoint anchor(anchorX, y2); std::vector 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); { entt::entity wChild = m_admin.createModuleEntity(); m_admin.addComponent(wChild, esWeapon); m_admin.addComponent(wChild, ModuleOwnerComponent{m_currentEnemyStationEntities[1]}); } m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId()); } } // --------------------------------------------------------------------------- // Deaths & loot (tick step 9) // --------------------------------------------------------------------------- void Simulation::tickDeathsAndLoot() { TRACE(); // --- Dead ships --- std::vector deadShips; m_admin.forEach( [&deadShips](entt::entity e, const ShipIdentityComponent& /*si*/, const HealthComponent& h) { if (h.hp <= 0.0f) { deadShips.push_back(e); } }); for (entt::entity deadEntity : deadShips) { const ShipIdentityComponent& si = m_admin.get(deadEntity); const PositionComponent& pos = m_admin.get(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 deadStations; m_admin.forEach( [&deadStations](entt::entity e, const StationBodyComponent& /*sb*/, const HealthComponent& h) { if (h.hp <= 0.0f) { deadStations.push_back(e); } }); for (entt::entity deadEntity : deadStations) { const StationBodyComponent& sb = m_admin.get(deadEntity); const PositionComponent& pos = m_admin.get(deadEntity); const FactionComponent& fac = m_admin.get(deadEntity); const Tick despawnAt = m_currentTick + secondsToTicks(m_config.world.scrapDespawnSeconds); int scrap = 0; if (!fac.isEnemy) { const double lv = static_cast( m_config.stations.playerStation.level); scrap = static_cast( m_config.stations.playerStation.scrapDropFormula.evaluate(lv)); } else { const double genD = static_cast(m_waveSystem->generation()); scrap = static_cast( m_config.stations.enemyStation.scrapDropFormula.evaluate(genD)); } if (scrap > 0) { m_scrapSystem->spawn(pos.value, scrap, despawnAt); } m_buildingSystem->unregisterTileOccupancy(sb.bodyCells); { std::vector stationChildren; m_admin.forEach( [&](entt::entity ce, const ModuleOwnerComponent& o) { if (o.owner == deadEntity) { stationChildren.push_back(ce); } }); for (entt::entity ce : stationChildren) { m_admin.destroy(ce); } } m_admin.destroy(deadEntity); } // --- HQ death check --- if (m_admin.isValid(m_hqProxyEntity)) { const HealthComponent& hqHealth = m_admin.get(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(m_currentEnemyStationEntities[0]).hp <= 0.0f; const bool es1Gone = !m_admin.isValid(m_currentEnemyStationEntities[1]) || m_admin.get(m_currentEnemyStationEntities[1]).hp <= 0.0f; if (es0Gone && es1Gone && m_currentEnemyStationEntities[0] != entt::null) { const int destroyedLevel = m_waveSystem->generation(); m_waveSystem->onEnemyStationsDestroyed(); placeEnemyStationSet(m_waveSystem->generation()); awardSchematicDrop(destroyedLevel); } } void Simulation::awardSchematicDrop(int destroyedStationLevel) { enum class DropType { Ship, Module, Recipe }; struct PoolEntry { std::string id; DropType type; }; std::vector pool; for (const ShipDef& def : m_config.ships.ships) { if (def.unlockAtStationLevel == -1 || def.unlockAtStationLevel <= destroyedStationLevel) { pool.push_back({def.id, DropType::Ship}); } } for (const ModuleDef& def : m_config.modules.modules) { if (def.unlockAtStationLevel == -1 || def.unlockAtStationLevel <= destroyedStationLevel) { pool.push_back({def.id, DropType::Module}); } } for (const RecipeDef& def : m_config.recipes.recipes) { if (def.building != BuildingType::Assembler) { continue; } if (!def.unlockAtStationLevel.has_value()) { continue; } const int level = def.unlockAtStationLevel.value(); if (level < 0 || level > destroyedStationLevel) { continue; } if (m_unlockedRecipeSchematicIds.count(def.id) > 0) { continue; } bool outputUnlocked = false; for (const RecipeOutput& out : def.outputs) { if (m_unlockedItemIds.count(out.item) > 0) { outputUnlocked = true; break; } } if (!outputUnlocked) { continue; } pool.push_back({def.id, DropType::Recipe}); } std::uniform_int_distribution dist(0, static_cast(pool.size()) - 1); const PoolEntry& chosen = pool[static_cast(dist(m_rng))]; if (chosen.type == DropType::Recipe) { m_unlockedRecipeSchematicIds.insert(chosen.id); recomputeUnlocked(); } else { SchematicState& state = (chosen.type == DropType::Module) ? m_moduleSchematicLevels.at(chosen.id) : m_schematicLevels.at(chosen.id); const bool wasNew = !state.unlocked; state.unlocked = true; state.level += 1; SchematicDropEvent evt; evt.schematicId = chosen.id; evt.newLevel = state.level; evt.wasNewUnlock = wasNew; evt.isModuleSchematic = (chosen.type == DropType::Module); m_schematicDropEvents.push_back(evt); recomputeUnlocked(); } } // --------------------------------------------------------------------------- // Implicit unlock computation (REQ-LOCK-IMPLICIT) // --------------------------------------------------------------------------- void Simulation::recomputeUnlocked() { m_unlockedItemIds.clear(); m_unlockedRecipeIds.clear(); for (const ShipDef& def : m_config.ships.ships) { if (!isSchematicUnlocked(def.id)) { continue; } for (const RecipeIngredient& mat : def.schematic.materials) { m_unlockedItemIds.insert(mat.item); } } for (const ModuleDef& def : m_config.modules.modules) { if (!isModuleSchematicUnlocked(def.id)) { continue; } for (const RecipeIngredient& mat : def.materials) { m_unlockedItemIds.insert(mat.item); } } for (const RecipeDef& def : m_config.recipes.recipes) { if (def.building == BuildingType::Assembler && def.unlockAtStationLevel.has_value() && m_unlockedRecipeSchematicIds.count(def.id) > 0) { for (const RecipeOutput& out : def.outputs) { m_unlockedItemIds.insert(out.item); } } } bool changed = true; while (changed) { changed = false; for (const RecipeDef& recipe : m_config.recipes.recipes) { if (recipe.building != BuildingType::Miner && recipe.building != BuildingType::Smelter && recipe.building != BuildingType::Assembler) { continue; } if (recipe.building == BuildingType::Assembler && recipe.unlockAtStationLevel.has_value() && m_unlockedRecipeSchematicIds.count(recipe.id) == 0) { continue; } bool producesUnlocked = false; for (const RecipeOutput& out : recipe.outputs) { if (m_unlockedItemIds.count(out.item) > 0) { producesUnlocked = true; break; } } if (!producesUnlocked) { continue; } if (recipe.building == BuildingType::Miner || recipe.building == BuildingType::Assembler) { m_unlockedRecipeIds.insert(recipe.id); } for (const RecipeIngredient& ing : recipe.inputs) { if (m_unlockedItemIds.insert(ing.item).second) { changed = true; } } } } } bool Simulation::isRecipeUnlocked(const std::string& recipeId) const { return m_unlockedRecipeIds.count(recipeId) > 0; } bool Simulation::isItemUnlocked(const std::string& itemId) const { return m_unlockedItemIds.count(itemId) > 0; } // --------------------------------------------------------------------------- // Drains // --------------------------------------------------------------------------- std::vector Simulation::drainFireEvents() { std::vector result; result.swap(m_fireEvents); return result; } std::vector Simulation::drainSchematicDropEvents() { std::vector 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::bossWaveCounter() const { return m_waveSystem->bossWaveCounter(); } Tick Simulation::bossCountdownTicks() const { return m_waveSystem->bossCountdownTicks(); } Tick Simulation::normalGapRemainingTicks() const { return m_waveSystem->normalGapRemainingTicks(); } int Simulation::schematicLevel(const std::string& shipId) const { const std::map::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::const_iterator it = m_schematicLevels.find(shipId); if (it == m_schematicLevels.end()) { return false; } return it->second.unlocked; } int Simulation::moduleSchematicLevel(const std::string& moduleId) const { const std::map::const_iterator it = m_moduleSchematicLevels.find(moduleId); if (it == m_moduleSchematicLevels.end()) { return 0; } return it->second.level; } bool Simulation::isModuleSchematicUnlocked(const std::string& moduleId) const { const std::map::const_iterator it = m_moduleSchematicLevels.find(moduleId); if (it == m_moduleSchematicLevels.end()) { return false; } return it->second.unlocked; } BuildingId 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 kInvalidBuildingId; } m_buildingBlocksStock -= cost; return m_buildingSystem->place(type, anchor, rotation, m_currentTick); } void Simulation::demolish(BuildingId 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; } void Simulation::handleEvent(std::shared_ptr event) { PRINT_TRACES(); } BuildingId Simulation::allocateBuildingId() { return m_nextBuildingId++; }