#pragma once #include #include #include #include "BeltDragPath.h" #include "BuildingId.h" #include "BuildingType.h" #include "FactoryState.h" #include "GameConfig.h" #include "Rotation.h" // Where a building may be placed, and what is already sitting on those tiles. // Free functions over the factory state and the config — they read no other // system state, so they do not belong to BuildingSystem. // True if every body cell lies inside the world: 0 <= y < world.height_tiles and // x >= the current asteroid left edge (REQ-BLD-PLACE-VALID). Terrain type is not // checked — see isPlacementValid for the full rule. bool bodyCellsWithinWorldBounds(const FactoryState& state, const GameConfig& config, const std::vector& bodyCells, QPoint anchor); // 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. bool isPlacementValid(const FactoryState& state, const GameConfig& config, BuildingType type, QPoint anchor, Rotation rotation); // An existing building or site whose footprint a ghost exactly covers. struct CoincidingBuilding { BuildingId id; Rotation rotation; }; // The building or site whose footprint a ghost of the given type/anchor/rotation // exactly covers: same type, same body cells, a single owner. Operational buildings // and construction sites alike. Rotation is not part of the test -- the target's own // facing is reported so each caller can apply its own rule to it. std::optional findCoincidingSameTypeBuilding(const FactoryState& state, const GameConfig& config, BuildingType type, QPoint anchor, Rotation rot); // The building or site that a ghost of the given type/anchor/rotation would // rotate in place rather than replace: same type, same body cells, one owner // (REQ-BLD-ROTATE-IN-PLACE). Tunnels never qualify. Builder mode only; blueprint // placement mode never rotates anything -- see resolveBlueprintGhost. std::optional findRotateInPlaceTarget(const FactoryState& state, const GameConfig& config, BuildingType type, QPoint anchor, Rotation rot); // True if placing here would actually do something: the terrain and bounds rules of // isPlacementValid hold, and the body cells are either all free or occupied only by // a building this placement would rotate in place. This is the question the ghost // asks to colour itself and the click path asks before enqueuing a command // (REQ-BLD-GHOST, REQ-BLD-PLACE-VALID, REQ-BLD-ROTATE-IN-PLACE). bool canPlaceBuilding(const FactoryState& state, const GameConfig& config, BuildingType type, QPoint anchor, Rotation rotation); // What one ghost of a blueprint would do at its resolved tile. enum class BlueprintGhostAction { PlaceNew, // free, valid cells: a new construction site, charged for CompatibleOverlap, // the same building is already there: left untouched, free // (REQ-UI-BLUEPRINT-OVERLAP) Transfer, // hand this blueprint's settings to the building already there, // free (REQ-UI-BLUEPRINT-TRANSFER) Invalid // terrain, bounds, or an overlap that is neither of the above }; struct BlueprintGhostResolved { BlueprintGhostAction action; std::optional targetId; // set for CompatibleOverlap and Transfer // Where the ghost belongs on screen. The queried anchor and rotation, except at a // hovered transfer target, where the ghost snaps onto the target so it shows what the // click will act on (REQ-UI-BLUEPRINT-TRANSFER). QPoint ghostAnchor; Rotation ghostRotation; }; // Classifies one ghost of a blueprint against the current factory state // (REQ-UI-BLUEPRINT-OVERLAP, REQ-UI-BLUEPRINT-TRANSFER). // // `hoverTile` is set only for a blueprint holding exactly one building, and is then the // tile under the cursor. That gesture is a copying tool rather than a layout, so it finds // its transfer target by hit-testing the cursor instead of by footprint coincidence -- // without which a Shipyard could never be targeted at a different facing, its 4x2 // footprint covering entirely different tiles once rotated. Pass nullopt for a // constellation, whose ghosts are judged purely by where the blueprint puts them. The // size is read from the blueprint as stored, before locked types are dropped, so the // gesture does not change behavior as the player unlocks things. // // Shared by the ghost coloring and the click path so a preview cannot disagree with what // the click then does -- the same reason resolveBeltDragPath is shared. BlueprintGhostResolved resolveBlueprintGhost(const FactoryState& state, const GameConfig& config, BuildingType type, QPoint anchor, Rotation rotation, std::optional hoverTile); // What a belt drag would do to one tile of its path (REQ-BLD-BELT-DRAG). enum class BeltTileAction { PlaceNew, // empty, valid cell: a new belt, subject to affordability RotateInPlace, // already a belt (or belt site): re-oriented, free Invalid // occupied by something else, or invalid terrain }; struct BeltDragResolved { BeltTileAction action; bool affordable; // meaningful only for PlaceNew std::optional rotateId; // set only for RotateInPlace }; // Classifies every tile of a belt drag path against the current factory state, // spending `buildingBlocksStock` cumulatively across the PlaceNew tiles so a path // longer than the player can afford is only partly buildable (REQ-BLD-BELT-DRAG). // Shared so the previewed ghosts and the placement on release cannot disagree. std::vector resolveBeltDragPath(const std::vector& path, const FactoryState& state, const GameConfig& config, int buildingBlocksStock);