#pragma once #include #include #include #include #include #include #include #include "BeltSystem.h" #include "EntityAdmin.h" #include "entt/entity/entity.hpp" #include "SchematicChoiceOption.h" #include "BuildingType.h" #include "BuildingId.h" #include "EventHandler.h" #include "WeaponFiredEvent.h" #include "GameConfig.h" #include "Rotation.h" #include "Tick.h" #include "TracePrintRequestedEvent.h" class AiSystem; class BuildingSystem; class CombatSystem; class DynamicBodySystem; class MovementIntentSystem; class ShipSystem; class ScrapSystem; class WaveSystem; class Simulation: public CombinedEventHandler { public: explicit Simulation(GameConfig config, unsigned int seed = 0); ~Simulation(); const GameConfig& config() const; // Reinitializes all simulation state as if constructed fresh. void reset(unsigned int seed = 0); // Reloads config then reinitializes all simulation state. void reset(GameConfig newConfig, unsigned int seed = 0); // Advances the simulation by one tick. Tick order per architecture.md §Tick Order. void tick(); // Returns all fire events accumulated since the last drain, clearing the // internal queue. Call once per rendered frame (REQ-SHP-FIRING-BEAM). std::vector drainWeaponFiredEvents(); // Returns the pending schematic choices (empty if no drop is pending). const std::vector& getPendingSchematicChoices() const; // Returns true if there are pending schematic choices waiting for player input. bool hasSchematicChoicesPending() const; // Applies the player's chosen schematic from the pending choices. // choiceIndex must be in [0, pendingChoices.size()). // Clears the pending choices after application. void applySchematicChoice(int choiceIndex); Tick currentTick() const; int buildingBlocksStock() const; bool isGameOver() const; double threatLevel() const; double threatAccumulationRate() const; double maxFactoryProductionThreatRate() const; double currentFactoryProductionThreatRate() const; int bossWaveCounter() const; Tick bossCountdownTicks() const; Tick normalGapRemainingTicks() const; // Ship schematic state queries. int schematicLevel(const std::string& shipId) const; bool isSchematicUnlocked(const std::string& shipId) const; // Module schematic state queries. int moduleSchematicLevel(const std::string& moduleId) const; bool isModuleSchematicUnlocked(const std::string& moduleId) const; // Implicit recipe/item unlock queries (REQ-LOCK-IMPLICIT). bool isRecipeUnlocked(const std::string& recipeId) const; bool isItemUnlocked(const std::string& itemId) const; // Checks affordability, deducts building blocks, and places the building. // Returns the new entity id, or kInvalidBuildingId if blocks are insufficient. BuildingId tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation); // Demolishes the building with the given id and refunds building blocks. void demolish(BuildingId id); BuildingSystem& buildings(); const BuildingSystem& buildings() const; BeltSystem& belts(); const BeltSystem& belts() const; ShipSystem& ships(); const ShipSystem& ships() const; ScrapSystem& scraps(); const ScrapSystem& scraps() const; EntityAdmin& admin(); const EntityAdmin& admin() const; private: void handleEvent(std::shared_ptr event) override; BuildingId allocateBuildingId(); // Strictly increasing; never returns kInvalidBuildingId. // Populate HQ, player defence stations, and the first enemy station set. void placeInitialStructures(); // Place two enemy defence stations for the given generation level. // Stores their IDs in m_currentEnemyStationIds. void placeEnemyStationSet(int generation); // Tick step 9: remove dead ships and buildings, drop scrap, handle push. void tickDeathsAndLoot(); // Generate up to 3 schematic choices (REQ-DEF-SCHEMATIC-DROP) for the player. void generateSchematicChoices(int destroyedStationLevel); GameConfig m_config; std::mt19937 m_rng; Tick m_currentTick; Tick m_nextDepartureTick; BuildingId m_nextBuildingId; int m_buildingBlocksStock; bool m_gameOver = false; // Pre-placed structure IDs. BuildingId m_hqBuildingId; // Building id (for belt integration) entt::entity m_hqProxyEntity; // ECS entity (HP, targeting) entt::entity m_playerStation1Entity; entt::entity m_playerStation2Entity; entt::entity m_currentEnemyStationEntities[2]; // Schematic unlock state (REQ-DEF-SCHEMATIC-DROP). struct SchematicState { bool unlocked; int level; }; std::map m_schematicLevels; std::map m_moduleSchematicLevels; // Explicitly unlocked assembler recipe schematics (REQ-LOCK-EXPLICIT). std::set m_unlockedRecipeSchematicIds; // Implicit unlock sets derived from schematic state (REQ-LOCK-IMPLICIT). std::set m_unlockedRecipeIds; std::set m_unlockedItemIds; // Recomputes m_unlockedRecipeIds and m_unlockedItemIds from current schematic state. void recomputeUnlocked(); // Result of the REQ-LOCK-IMPLICIT traversal. struct UnlockedSets { std::set itemIds; std::set recipeIds; }; // Pure REQ-LOCK-IMPLICIT traversal given hypothetical explicit-unlock sets. UnlockedSets computeUnlockedSets(const std::set& unlockedShipSchematicIds, const std::set& unlockedModuleSchematicIds, const std::set& unlockedRecipeSchematicIds) const; // Current explicit-unlock id sets, derived from m_schematicLevels / m_moduleSchematicLevels. std::set getUnlockedShipSchematicIds() const; std::set getUnlockedModuleSchematicIds() const; // Display names (deduplicated, alphabetical) of output items of recipes in // hypothetical.recipeIds that are not yet in m_unlockedRecipeIds. std::vector computeNewlyUnlockedItemNames(const UnlockedSets& hypothetical) const; EntityAdmin m_admin; BeltSystem m_beltSystem; std::unique_ptr m_buildingSystem; std::unique_ptr m_shipSystem; std::unique_ptr m_aiSystem; std::unique_ptr m_movementIntentSystem; std::unique_ptr m_dynamicBodySystem; std::unique_ptr m_scrapSystem; std::unique_ptr m_waveSystem; std::unique_ptr m_combatSystem; std::vector m_weaponFiredEvents; std::vector m_pendingSchematicChoices; };