#pragma once #include #include #include #include #include #include #include #include #include #include #include "BeltSystem.h" #include "Building.h" #include "BuildingType.h" #include "BuildingId.h" #include "GameConfig.h" #include "Rotation.h" #include "ModulesConfig.h" #include "ShipLayout.h" #include "ShipsConfig.h" #include "Tick.h" // Manages building placement, construction queuing, and the per-tick // production loop (belt→building pull, production, building→belt push). // All types including Belt and Splitter are stored as Building instances; // BeltSystem owns the per-tile simulation data (item slots, flow). class BuildingSystem { public: BuildingSystem(const GameConfig& config, BeltSystem& belts, std::function allocateBuildingId, std::function addBuildingBlocks, std::function&)> spawnShip, std::function isItemUnlocked, std::mt19937& rng); // -- Placement / demolish ------------------------------------------------ // Returns the new entity id. Belt and Splitter register with BeltSystem // directly; other types enter the construction queue. BuildingId place(BuildingType type, QPoint anchor, Rotation rotation, Tick currentTick); // Remove a building or construction site by id. Returns the refund in // building blocks (floor(cost * refundPercentage / 100)). Returns 0 for // unknown ids. int demolish(BuildingId id); // Set the recipe (or schematic id for shipyard) on a building or queued // construction site. Clears both buffers on an operational building. void setRecipe(BuildingId id, const std::string& recipeId); // Set the module layout for a shipyard. Cancels in-progress production // (materials discarded) and reinitializes input buffers (REQ-BLD-SHIPYARD). void setShipLayout(BuildingId id, const ShipLayoutConfig& layout); // -- Tick hooks (called from Simulation::tick in the documented order) --- void tickConstruction(Tick currentTick); void tickBeltPull(); void tickProduction(Tick currentTick); void tickShipyardProduction(Tick currentTick); void tickBeltPush(); // -- Queries ------------------------------------------------------------- struct BeltTileInfo { BuildingId buildingId; QPoint tile; BuildingType type; // Belt or Splitter Rotation directionA; // Belt: its direction; Splitter: first output Rotation directionB; // Splitter: second output; Belt: same as directionA }; const Building* findBuilding(BuildingId id) const; const ConstructionSite* findSite(BuildingId id) const; std::vector allBuildings() const; std::vector allSites() const; // REQ-UI-DEBUG-OVERLAY "Max Factory Production": count of completed // (operational) Miner/Smelter/Assembler/ReprocessingPlant/Shipyard buildings. int productionBuildingCount() const; // REQ-UI-DEBUG-OVERLAY "Current Factory Production": subset of the above // that currently has an active production cycle. int activeProductionBuildingCount() const; std::vector allBeltTiles() const; bool isTileOccupied(QPoint tile) const; // Returns the entity id of the building or construction site whose footprint // exactly coincides with the ghost (type, anchor, rot) and is of the same // building type. Returns nullopt otherwise. std::optional findRotateInPlaceTarget(BuildingType type, QPoint anchor, Rotation rot) const; // Rotate an existing building or construction site to newRotation in place. // For belt-type operational buildings, re-registers with BeltSystem (items // currently on the tile are discarded by BeltSystem::removeTile). void rotateInPlace(BuildingId id, Rotation newRotation); // Find nearest operational building of the given type; nullptr if none. const Building* findNearestBuilding(QVector2D worldPos, BuildingType type) const; // Register / unregister tile occupancy for ECS station entities. void registerTileOccupancy(const std::vector& cells, BuildingId ownerPlaceholder); void unregisterTileOccupancy(const std::vector& cells); // Place one "scrap" item into a SalvageBay's output buffer. // Returns false if bay not found, wrong type, or output buffer is full. bool deliverScrapToSalvageBay(BuildingId bayId); // Bypass the construction queue and create a fully-operational Building // immediately. Used for pre-placed structures (HQ, defence stations). // surfaceMask comes from the relevant config struct. BuildingId placeImmediate(BuildingType type, const std::vector& surfaceMask, QPoint anchor, Rotation rotation); // Remove an operational building by id without refund (used for deaths). // Returns true if found and removed. bool removeBuilding(BuildingId id); // Mutable iteration over all operational buildings. void forEachBuilding(std::function fn); private: const BuildingDef* findBuildingDef(BuildingType type) const; const RecipeDef* findRecipe(const std::string& id, BuildingType type) const; const ShipDef* findShipDef(const std::string& id) const; const ModuleDef* findModuleDef(const std::string& id) const; void initBuffers(Building& b, const RecipeDef& recipe) const; void initShipyardBuffers(Building& b) const; std::vector computeInputPorts(const Building& b) const; std::vector rollReprocessingOutput(const RecipeDef& recipe); const GameConfig& m_config; BeltSystem& m_belts; std::function m_allocateBuildingId; std::function m_addBuildingBlocks; std::function&)> m_spawnShip; std::function m_isItemUnlocked; std::mt19937& m_rng; std::vector m_buildings; std::deque m_constructionQueue; // Maps every occupied body-cell coordinate to the entity that owns it. std::map, BuildingId> m_tileOccupancy; };