#pragma once #include #include #include #include "BeltDragPath.h" #include "Blueprint.h" #include "BuildingId.h" #include "BuildingType.h" #include "Rotation.h" // Which of the mutually exclusive world-interaction modes is active // (REQ-UI-HOTKEYS, REQ-BLD-GHOST, REQ-UI-BLUEPRINT-PLACE, REQ-BLD-DECONSTRUCT). enum class BuildMode { None, // plain selection Builder, // placing one building type, ghost following the cursor Blueprint, // placing a saved multi-building layout Deconstruct // marking buildings for demolition }; // The active build mode and the transient state that belongs to it. // // These modes were previously three independent flags, and every entry point // cleared the other two by hand — inconsistently, which is how entering builder // mode came to drop a blueprint without announcing it. Here exclusivity is // structural: one mode is active, and every transition runs through enterMode(), // which exits whatever was active first and publishes the same events regardless // of which way the player got there. // // Everything needing the simulation — placement validity, tunnel matching, belt // path building — stays with the caller, which computes and hands back the result // (setGhostValidity, setTunnelGhost, setBeltDragPath). That keeps this a plain // value that can be tested without a world. class BuildModeController { public: BuildMode getMode() const; bool isBuilderMode() const; bool isBlueprintMode() const; bool isDeconstructMode() const; // --- transitions ---------------------------------------------------------- // Each leaves the previously active mode with its proper exit event. void enterBuilderMode(BuildingType type); void enterBlueprintMode(Blueprint blueprint); // Leaves deconstruct mode if it is active, enters it otherwise // (REQ-BLD-DECONSTRUCT-CLICK). void toggleDeconstructMode(); void exitBuilderMode(); void exitBlueprintMode(); // Backs out of whichever mode is active, if any (the Q key and right-click). void exitCurrentMode(); // --- builder mode --------------------------------------------------------- // Only meaningful while isBuilderMode(). BuildingType getBuilderType() const; // True while the builder type is TunnelEntry, where the ghost resolves to an // entry or an exit by hovered position (REQ-BLD-TUNNEL-MODE). bool isTunnelMode() const; // The type the ghost currently represents: the position-resolved tunnel type in // tunnel mode, the plain builder type otherwise. BuildingType getEffectiveBuilderType() const; QPoint getGhostTile() const; Rotation getGhostRotation() const; bool isGhostValid() const; void setGhostTile(QPoint tile); void setGhostValidity(bool valid); // Turns the ghost one quarter turn. Validity is not rechecked here; the caller // does that and calls setGhostValidity, because only it can see the world. void rotateGhost(bool clockwise); BuildingType getTunnelGhostType() const; const std::optional& getTunnelPartnerTile() const; void setTunnelGhost(BuildingType resolvedType, std::optional partnerTile); // --- belt drag placement (REQ-BLD-BELT-DRAG) ------------------------------ bool isDraggingBelt() const; QPoint getBeltDragAnchor() const; const std::vector& getBeltDragPath() const; void beginBeltDrag(QPoint anchorTile); void setBeltDragPath(std::vector path); // Drops the drag without placing anything, staying in builder mode. void cancelBeltDrag(); // --- blueprint mode ------------------------------------------------------- // Only meaningful while isBlueprintMode(). const Blueprint& getBlueprint() const; // Mutable so the caller can rotate the layout in place; rotating a blueprint // needs building footprints from the config, which does not belong here. Blueprint& getMutableBlueprint(); QPoint getBlueprintGhostTile() const; void setBlueprintGhostTile(QPoint tile); // Whether the ghost under the cursor would hand its settings to the building // already there rather than place anything (REQ-UI-BLUEPRINT-TRANSFER). Classifying // it needs the factory state, so the caller resolves it and stores the answer here, // as with setGhostValidity. Kept here rather than recomputed per reader so the // click, the ghost's colour, and the controls panel cannot disagree about what the // cursor is over. bool isHoveredGhostTransfer() const; void setHoveredGhostTransfer(bool transfer); // --- deconstruct mode ----------------------------------------------------- const std::optional& getDeconstructHoverBuildingId() const; void setDeconstructHoverBuildingId(std::optional id); private: // The single transition point: leaves the active mode, then enters `mode`. void enterMode(BuildMode mode); BuildMode m_mode = BuildMode::None; BuildingType m_builderType = BuildingType::Belt; QPoint m_ghostTile; Rotation m_ghostRotation = Rotation::East; bool m_ghostValid = false; BuildingType m_tunnelGhostType = BuildingType::TunnelEntry; std::optional m_tunnelPartnerTile; bool m_draggingBelt = false; QPoint m_beltDragAnchor; std::vector m_beltDragPath; Blueprint m_blueprint; QPoint m_blueprintGhostTile; bool m_hoveredGhostIsTransfer = false; std::optional m_deconstructHoverBuildingId; };