Third declaration left behind by a move — the definition went to PlacementRules.cpp but the declaration stayed. Checked the rest of the header mechanically this time: every other declared method has a definition. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
248 lines
13 KiB
C++
248 lines
13 KiB
C++
#pragma once
|
|
|
|
#include <deque>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <optional>
|
|
#include <random>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <QPoint>
|
|
#include <QPointF>
|
|
#include <QVector2D>
|
|
|
|
#include "BeltSystem.h"
|
|
#include "Building.h"
|
|
#include "FactoryState.h"
|
|
#include "PlacementRules.h"
|
|
#include "ProductionRules.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"
|
|
|
|
class Hasher;
|
|
|
|
// 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,
|
|
FactoryState& state,
|
|
BeltSystem& belts,
|
|
std::function<BuildingId()> allocateBuildingId,
|
|
std::function<void(int)> addBuildingBlocks,
|
|
std::function<void(const std::string&, QVector2D,
|
|
const std::optional<ShipLayoutConfig>&)> spawnShip,
|
|
std::function<bool(const std::string&)> isItemUnlocked,
|
|
std::mt19937& rng);
|
|
|
|
// -- Placement / deconstruct ------------------------------------------------
|
|
// Returns the new entity id, or nullopt if the placement falls outside the
|
|
// world bounds (vertical extent and asteroid left edge). Belt and Splitter
|
|
// register with BeltSystem directly; other types enter the construction
|
|
// queue. Terrain type (A vs S) is NOT checked here so that tests can stage
|
|
// arbitrary layouts; the player-facing entry point
|
|
// (Simulation::tryPlaceBuilding) enforces the full rule via isPlacementValid.
|
|
std::optional<BuildingId> place(BuildingType type, QPoint anchor, Rotation rotation,
|
|
Tick currentTick);
|
|
|
|
// Returns true if the placement satisfies REQ-BLD-PLACE-VALID terrain and
|
|
// world-bounds rules: every ship-dock (S) cell sits in space (x >= 0), every
|
|
// other body (A) cell sits on the asteroid (x < 0 and x >= the left edge),
|
|
// and every cell has 0 <= y < world.height_tiles. There is no right-side
|
|
// bound — space extends rightward. Tile occupancy is NOT checked here.
|
|
|
|
// Sets the current buildable asteroid width in tiles. Grows the left
|
|
// placement bound as the player unlocks asteroid expansions (REQ-EXP-UNLOCK).
|
|
// Defaults to world.regions.asteroid_width_tiles at construction.
|
|
void setAsteroidWidth_tiles(int widthTiles) { m_state.asteroidWidth_tiles = widthTiles; }
|
|
|
|
// Mark a building or construction site for demolition (REQ-BLD-DECONSTRUCT).
|
|
// A construction site is removed instantly and the full cost is returned.
|
|
// A fully-built building is instead appended to the deconstruction queue
|
|
// (REQ-BLD-DECON-QUEUE) and stops operating at once; its (partial) refund is
|
|
// credited later, on completion in tickDeconstruction, so this returns 0 for
|
|
// it. Returns 0 for unknown ids and for a building already queued.
|
|
int deconstruct(BuildingId id, Tick currentTick);
|
|
|
|
// Take a building back out of the deconstruction queue before it is removed
|
|
// (REQ-BLD-DECON-QUEUE). Clears its queued flag and resumes operation
|
|
// (re-registering belt/tunnel/splitter tiles); discards deconstruction
|
|
// progress and credits no refund. No-op if the id is not queued.
|
|
void cancelDeconstruction(BuildingId id);
|
|
|
|
// True if the building is currently in the deconstruction queue.
|
|
|
|
// 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);
|
|
|
|
// Splitter filter configuration for a queued/under-construction Splitter
|
|
// site (REQ-BLD-SITE-CONFIG). Operational splitters are configured through
|
|
// BeltSystem by tile; these mirror that for sites, which are not yet
|
|
// registered with BeltSystem. getSiteSplitterInfo returns the site's two
|
|
// output directions (derived from its surface mask) and stored filters, or
|
|
// nullopt if the id is not a Splitter site. The stored filters are applied
|
|
// to BeltSystem when the splitter finishes building (tickConstruction).
|
|
void setSiteSplitterFilters(BuildingId id,
|
|
const std::vector<ItemType>& filterA,
|
|
const std::vector<ItemType>& filterB);
|
|
|
|
// -- Tick hooks (called from Simulation::tick in the documented order) ---
|
|
void tickConstruction(Tick currentTick);
|
|
// Advances the deconstruction queue (REQ-BLD-DECON-QUEUE): one building at a
|
|
// time, in parallel with tickConstruction. Removes the front building and
|
|
// credits its refund when its timer elapses.
|
|
void tickDeconstruction(Tick currentTick);
|
|
void tickBeltPull();
|
|
void tickProduction(Tick currentTick);
|
|
void tickShipyardProduction(Tick currentTick);
|
|
// Advances each building's virtual output belts, hands finished items off onto
|
|
// the adjacent real belt, and feeds new buffered items into them
|
|
// (REQ-MAT-OUTPUT-EMERGE).
|
|
void tickOutputBelts();
|
|
|
|
// -- Queries -------------------------------------------------------------
|
|
|
|
|
|
// REQ-UI-DEBUG-OVERLAY "Max Factory Production": count of completed
|
|
// (operational) Miner/Smelter/Assembler/ReprocessingPlant/Shipyard buildings.
|
|
|
|
// REQ-UI-DEBUG-OVERLAY "Current Factory Production": subset of the above
|
|
// that currently has an active production cycle.
|
|
|
|
// Production state for the UI status light (REQ-UI-STATUS-LIGHT). Returns
|
|
// nullopt for building types that show no light (belts, splitters, tunnels,
|
|
// HQ, defence stations). The Salvage Bay is a two-state special case:
|
|
// Producing while its output buffer holds scrap, Starved when empty.
|
|
|
|
// Visits every item currently emerging from a building output port on its
|
|
// virtual output belt (REQ-MAT-OUTPUT-EMERGE), passing the item type and its
|
|
// world-space centre (in tile units). Least-progressed first (drawn bottom) so
|
|
// callers can paint in visit order (REQ-GW-TILE-SIZE ordering).
|
|
void forEachEmergingItem(
|
|
const std::function<void(const ItemType&, QPointF)>& visit) const;
|
|
|
|
// Visits every item currently travelling inward on a building input port's
|
|
// virtual input belt (REQ-MAT-INPUT-INTAKE), passing the item type and its
|
|
// world-space centre (in tile units). Least-progressed first (drawn bottom).
|
|
void forEachIncomingItem(
|
|
const std::function<void(const ItemType&, QPointF)>& visit) 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);
|
|
|
|
|
|
// Input-capable adjacent tiles for a building or construction site
|
|
// (REQ-BLD-BELT-DRAG, REQ-MAT-INPUT-PORTS): each returned Port.tile is the
|
|
// outside adjacent tile and Port.direction is the belt facing that points into
|
|
// the target. Output-port edges are excluded. Empty for an unknown id.
|
|
|
|
// Register / unregister tile occupancy for ECS station entities.
|
|
void registerTileOccupancy(const std::vector<QPoint>& cells, BuildingId ownerPlaceholder);
|
|
void unregisterTileOccupancy(const std::vector<QPoint>& cells);
|
|
|
|
// Place one "scrap" item into a SalvageBay's output buffer.
|
|
// Returns false if bay not found, wrong type, or output buffer is full.
|
|
|
|
// 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<std::string>& 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<void(Building&)> fn);
|
|
|
|
// -- Determinism ---------------------------------------------------------
|
|
// Folds all building, construction-site, and tile-occupancy state into the
|
|
// hasher in deterministic order (see docs/replay_design.md).
|
|
void appendChecksum(Hasher& hasher) const;
|
|
|
|
private:
|
|
// Starts the front deconstruction-queue entry's timer if not yet started
|
|
// (mirrors how tickConstruction starts a queued construction site).
|
|
void startFrontDeconstruction(Tick currentTick);
|
|
|
|
// Registers a belt/splitter/tunnel building's tile with the belt subsystem
|
|
// (on construction completion, or when un-queuing a deconstruction). No-op for
|
|
// non-belt-subsystem types. Splitter filters are (re)applied after placement.
|
|
void reregisterBeltTile(const Building& building,
|
|
const std::vector<ItemType>& splitterFilterA,
|
|
const std::vector<ItemType>& splitterFilterB);
|
|
|
|
// True if the consumer would accept `type` at the given input port right now:
|
|
// it is a required input (or a building block for the HQ), the reservation-aware
|
|
// buffer has room, and the input belt entry is free (REQ-MAT-INPUT-INTAKE).
|
|
bool canAcceptInput(const Building& consumer,
|
|
std::size_t inputPortIndex,
|
|
const ItemType& type) const;
|
|
// Places an accepted item onto the consumer's input belt at progress 0.0,
|
|
// reserving a per-material buffer slot (REQ-MAT-INPUT-INTAKE).
|
|
void depositToInputBelt(Building& consumer,
|
|
std::size_t inputPortIndex,
|
|
const Item& item);
|
|
// Attempts to hand an emerging output item straight into a directly adjacent
|
|
// building whose input edge meets the producer's output port (REQ-MAT-DIRECT-COUPLE).
|
|
// Returns true if the item was accepted onto the consumer's input belt.
|
|
bool tryDirectCoupleDeposit(BuildingId producerId,
|
|
const Port& outputPort,
|
|
const Item& item);
|
|
|
|
// Candidate recipes an idle building would try this tick: an auto-recipe
|
|
// building (Smelter, Reprocessing Plant) offers every recipe of its type with
|
|
// inputs; other buildings offer only their selected recipe. Shared by
|
|
// tickProduction and the status classifier (REQ-MAT-CYCLE, REQ-UI-STATUS-LIGHT).
|
|
// True if every input of `recipe` is present in `b`'s input buffers in the
|
|
// required per-cycle amount (REQ-MAT-CYCLE input check).
|
|
// Combined base + module materials a shipyard needs per ship (REQ-BLD-SHIPYARD).
|
|
// True if the building currently has all inputs/materials to start a cycle
|
|
// (ignoring output-buffer space); drives the Starved/Blocked distinction of
|
|
// the status light (REQ-UI-STATUS-LIGHT).
|
|
|
|
void initBuffers(Building& b, const RecipeDef& recipe) const;
|
|
// Buffers for an auto-recipe building (Smelter, Reprocessing Plant): input
|
|
// caps span the union of every recipe of the building's type; no player
|
|
// recipe is selected (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING).
|
|
void initAutoBuffers(Building& b) const;
|
|
void initShipyardBuffers(Building& b) const;
|
|
void initSalvageBayBuffer(Building& b) const;
|
|
// Core input-edge scan shared by operational buildings and construction sites.
|
|
std::vector<Item> rollReprocessingOutput(const RecipeDef& recipe);
|
|
|
|
const GameConfig& m_config;
|
|
|
|
// The factory's world data — buildings, queued work, tile ownership. Owned by
|
|
// Simulation, not by this system (see FactoryState.h).
|
|
FactoryState& m_state;
|
|
|
|
BeltSystem& m_belts;
|
|
std::function<BuildingId()> m_allocateBuildingId;
|
|
std::function<void(int)> m_addBuildingBlocks;
|
|
std::function<void(const std::string&, QVector2D,
|
|
const std::optional<ShipLayoutConfig>&)> m_spawnShip;
|
|
std::function<bool(const std::string&)> m_isItemUnlocked;
|
|
std::mt19937& m_rng;
|
|
};
|