Files
dota_factory/src/ui/GameWorldView.h
Malte Langkabel 648241ba2a remove the Shift copy-building-settings gesture
REQ-BLD-COPY-CONFIG is dropped: Shift+right-click no longer caches a
building's recipe / schematic+layout / splitter filters, Shift+left-click
no longer stamps them onto same-type buildings, and the cyan eligible-
target tint and copy/paste flashes go with them. Shift+left-click now
falls through to normal selection, which needs no code of its own -- only
Ctrl is meaningful to selection (REQ-UI-MULTI-SELECT), so dropping the
branch that consumed the click is enough.

readBuildingConfig stays. It had two callers, and the other one is
captureBlueprintFromSelection, which needs it to record each blueprint
building's configuration (REQ-UI-BLUEPRINT-STORAGE). Its five tests stay
too, retagged [blueprint] and rewritten to name the caller that is left.

The visuals.toml copy_config colour is removed together with its
VisualsConfig field and its loader line: overlay keys are mandatory, so
those three have to move as one or startup aborts.

REQ-BLD-COPY-CONFIG-FEEDBACK, cited by four of these files and by the TOML
comment, never existed in requirements.md; deleting the citations retires
a dangling id.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-06 16:21:53 +02:00

299 lines
14 KiB
C++

#pragma once
#include <map>
#include <memory>
#include <optional>
#include <random>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <QColor>
#include <QElapsedTimer>
#include <QOpenGLWidget>
#include <QPoint>
#include <QPointF>
#include <QRectF>
#include <QTimer>
#include <QVector2D>
#include "Blueprint.h"
#include "BuildModeController.h"
#include "BlueprintModeExitedEvent.h"
#include "BlueprintPlacementRequestedEvent.h"
#include "BuilderModeExitedEvent.h"
#include "BuildingType.h"
#include "BuildingTypeSelectedEvent.h"
#include "BuildingId.h"
#include "DeconstructModeChangedEvent.h"
#include "DeconstructModeToggleRequestedEvent.h"
#include "EventHandler.h"
#include "ExitBlueprintModeRequestedEvent.h"
#include "ExitBuilderModeRequestedEvent.h"
#include "DebugDrawToggleRequestedEvent.h"
#include "GhostRotationRequestedEvent.h"
#include "InputMapper.h"
#include "ModeCancelRequestedEvent.h"
#include "PanDirectionChangedEvent.h"
#include "PauseToggleRequestedEvent.h"
#include "SpeedStepRequestedEvent.h"
#include "DebugDrawToggledEvent.h"
#include "ArtifactCountChangedEvent.h"
#include "BeamFiredEvent.h"
#include "CommandRequestedEvent.h"
#include "SchematicChoiceOption.h"
#include "WinEvent.h"
#include "SpeedChangeRequestedEvent.h"
#include "entt/entity/entity.hpp"
#include "BeltDragPath.h"
#include "CommandManager.h"
#include "EntitySelectionChangedEvent.h"
#include "GameConfig.h"
#include "PlacementRules.h"
#include "Rotation.h"
#include "SelectionController.h"
#include "Tick.h"
#include "TickDriver.h"
#include "TunnelCompletion.h"
#include "VisualsConfig.h"
#include "WorldCamera.h"
#include "WorldCoordinates.h"
#include "WorldRenderer.h"
struct Command;
struct ParsedReplay;
class ItemIconCache;
class ReplayPlayer;
class Simulation;
class QPainter;
class GameWorldView : public QOpenGLWidget,
public CombinedEventHandler<BeamFiredEvent,
BuildingTypeSelectedEvent,
ExitBuilderModeRequestedEvent,
DeconstructModeToggleRequestedEvent,
BlueprintPlacementRequestedEvent,
ExitBlueprintModeRequestedEvent,
SpeedChangeRequestedEvent,
PanDirectionChangedEvent,
PauseToggleRequestedEvent,
SpeedStepRequestedEvent,
GhostRotationRequestedEvent,
ModeCancelRequestedEvent,
DebugDrawToggleRequestedEvent,
CommandRequestedEvent>
{
Q_OBJECT
public:
// itemIcons is the window-wide per-item icon cache (REQ-UI-ITEM-ICON); not
// owned, must outlive this widget.
GameWorldView(Simulation* sim, const GameConfig* config,
const VisualsConfig* visuals, const std::string& configDir,
ItemIconCache* itemIcons, const ParsedReplay* replay,
QWidget* parent = nullptr);
~GameWorldView() override;
double getGameSpeed() const;
bool isDebugDrawEnabled() const;
void resetFrameTimer();
void setGameSpeed(double multiplier);
void resetForNewGame();
protected:
void initializeGL() override;
void paintGL() override;
// Only forwards to the input mapper; every key this widget acts on reaches it
// as a published action instead (REQ-UI-HOTKEYS).
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
// Key-up never arrives for a key that was still held when focus moved away —
// to a modal dialog, another widget, or another window. Any held action would
// otherwise stay held forever, so focus loss drops all of them.
void focusOutEvent(QFocusEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
private slots:
void onFrame();
private:
void handleEvent(std::shared_ptr<const BeamFiredEvent> event) override;
void handleEvent(std::shared_ptr<const BuildingTypeSelectedEvent> event) override;
void handleEvent(std::shared_ptr<const ExitBuilderModeRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const DeconstructModeToggleRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const BlueprintPlacementRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const ExitBlueprintModeRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const SpeedChangeRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const PanDirectionChangedEvent> event) override;
void handleEvent(std::shared_ptr<const PauseToggleRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const SpeedStepRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const GhostRotationRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const ModeCancelRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const DebugDrawToggleRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const CommandRequestedEvent> event) override;
// Enqueue a sim command onto the CommandManager (the single mutation path).
void enqueueCommand(std::shared_ptr<const Command> command);
// Enqueue a plain (unconfigured) building placement.
void enqueuePlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
// True if the player can currently afford to place one building of `type`.
// Used to pre-validate placements whose UI follow-up depends on success.
bool canAfford(BuildingType type) const;
// Screen-anchored chrome, drawn after the world (see WorldRenderer): these
// need no world transform, which is exactly why they stayed here.
void drawScreenSpace(QPainter& painter);
// Threat and production readout shown while debug draw is on (F3). Positioned
// in pixels at the top-left corner, so it belongs with the chrome rather than
// with the world.
void drawDebugOverlay(QPainter& painter);
// Vignette-style border shown while the game is paused (speed 0x, REQ-UI-PAUSE-BORDER)
// to make the paused state hard to miss: a black frame whose alpha fades from 50% at
// the viewport edges to 0% toward the center.
void drawPauseBorder(QPainter& painter);
// Vignette-style border shown while deconstruct mode is active (REQ-UI-DECONSTRUCT-BORDER),
// tinted with the deconstruct overlay color (including its configured alpha) from
// visuals.toml instead of black.
void drawDeconstructBorder(QPainter& painter);
// Shared drawing for the pause/deconstruct vignettes: a mitred picture-frame border
// whose alpha fades from `edgeColor` (with its own alpha) at the viewport edge to
// fully transparent toward the center.
void drawVignetteBorder(QPainter& painter, const QColor& edgeColor);
void drawReplayOverlay(QPainter& painter);
// Gathers the interaction state the renderer needs for this frame.
WorldRenderFrame makeRenderFrame() const;
// The world <-> widget transform for the current viewport size and scroll
// position. Cheap to build and deliberately not cached: it is a snapshot that
// a resize or a scroll invalidates, so every user takes a fresh one.
WorldCoordinates getCoordinates() const;
float getAsteroidLeftEdge() const;
float getEnemyStationRightEdge() const;
// The camera's current pan limits, read fresh from the simulation each frame:
// both edges move with asteroid expansion and with pushes (REQ-GW-SCROLL-LIMIT).
ScrollBounds getScrollBounds() const;
// canPlaceBuilding (PlacementRules) against this view's simulation.
bool canPlaceBuildingHere(BuildingType type, QPoint anchor, Rotation rot) const;
std::optional<BuildingId> buildingAtTile(QPoint tile) const;
std::optional<BuildingId> siteAtTile(QPoint tile) const;
void placeBlueprintAtTile(QPoint center);
// Drops despawned or fully-collected debris from the selection
// (REQ-UI-DEBRIS-CLICK-SELECT). Called each frame from onFrame().
void pruneDespawnedDebris();
// Drops despawned or dead actors from the selection (REQ-UI-ENTITY-CLICK-SELECT).
// Called each frame from onFrame().
void pruneDespawnedActors();
// Resolves a click into the selection it should produce, applying the category
// precedence of REQ-UI-SELECTION-CATEGORIES; the controller owns what that then
// does to the existing selection. Returns false when the click hit nothing,
// which is the only case that goes on to start a box drag.
bool selectAtPoint(QPoint tile, QVector2D worldPos, bool additive);
void selectInBox(bool additive);
void stepSpeed(int delta);
void placeAtTile(QPoint tile);
// Re-resolves the tunnel ghost type and completion partner from the current
// ghost tile, rotation, and sub-tile cursor position, storing both on the build
// mode controller. Only meaningful in tunnel mode (REQ-BLD-TUNNEL-MODE).
void updateTunnelGhost();
// Belt drag placement (REQ-BLD-BELT-DRAG).
// Recomputes the drag path from its anchor to cursorTile using the current ghost
// orientation, and stores it on the build mode controller.
void recomputeBeltDragPath(QPoint cursorTile);
// resolveBeltDragPath (PlacementRules) against this view's simulation.
std::vector<BeltDragResolved> resolveBeltDragPath() const;
// Enqueues placements and rotate-in-place commands for the resolved path.
void applyBeltDragPath();
// Turns the ghost and refreshes everything that depends on its facing: placement
// validity, the tunnel completion match, and an in-progress belt drag's path.
// The mode transitions themselves live on m_buildMode.
void rotateGhost(bool clockwise);
// Beam lifetime in game ticks so beams freeze with the simulation when
// paused or slowed, instead of fading on wall-clock time (REQ-SHP-FIRING-BEAM).
static constexpr Tick kBeamLifetimeTicks = secondsToTicks(0.3);
Simulation* m_sim;
const GameConfig* m_config;
const VisualsConfig* m_visuals;
// Funnels all player input into the single Simulation::apply chokepoint.
CommandManager m_commandManager;
// A Reset command was enqueued; reset the view after the next drain applies it.
bool m_viewResetPending = false;
// Non-null => view-only playback: ticks are driven by the recorded stream and
// live input is ignored (the CommandManager is in replay mode).
std::unique_ptr<ReplayPlayer> m_replayPlayer;
// Draws the world; this widget supplies the interaction state each frame and
// keeps only the screen-anchored chrome for itself.
std::unique_ptr<WorldRenderer> m_renderer;
TickDriver m_tickDriver;
QElapsedTimer m_frameTimer;
std::mt19937 m_rng;
double m_gameSpeedMultiplier;
double m_prevNonZeroSpeed;
// Horizontal view position (REQ-UI-SCROLL). Owns the scroll position itself;
// this widget only supplies the pan intent and the simulation-derived bounds.
WorldCamera m_camera;
QTimer* m_renderTimer;
std::vector<ActiveBeam> m_activeBeams;
// The active build mode (builder / blueprint / deconstruct, or none) and the
// ghost, tunnel and belt-drag state belonging to it. Owns the transitions
// between them; this widget supplies the parts that need the simulation.
BuildModeController m_buildMode;
// Last known cursor position in world (tile) units; used to pick the belt-drag
// end tile closest to the cursor when snapping to a building (REQ-BLD-BELT-DRAG)
// and to resolve the tunnel ghost sub-tile (REQ-BLD-TUNNEL-MODE).
QVector2D m_cursorWorldPos;
bool m_debugDraw;
// Owns the selection across all three categories and the rules for moving
// between them (REQ-UI-SELECTION-CATEGORIES), including publishing the change
// events. This widget only resolves what was hit.
SelectionController m_selection;
bool m_boxSelecting;
QPoint m_boxStartTile;
QPoint m_boxCurrentTile;
// Interprets this widget's key events into semantic actions and publishes them
// (REQ-UI-HOTKEYS). Owned here for now because this is the widget that holds
// focus; everything it produces travels by event, so it can move to a
// window-wide owner later without touching its consumers.
InputMapper m_inputMapper;
// Latest pan direction published by the input mapper (REQ-UI-SCROLL), fed to
// the camera each frame. Cached from the event payload rather than re-read:
// input has no other source of truth (see PanDirectionChangedEvent).
PanDirection m_panDirection = PanDirection::None;
bool m_gameOverShown;
bool m_winShown;
bool m_schematicChoiceShown;
Tick m_lastTick = Tick(-1);
int m_lastBlocks = -1;
int m_lastExpansionCost = -1;
int m_lastBossCounter = -1;
Tick m_lastBossCountdown = Tick(-1);
int m_lastArtifactCount = -1;
int m_lastUnlockedBuildingCount = -1;
};