#pragma once #include #include #include #include #include #include #include #include #include #include #include #include "Blueprint.h" #include "BuildingConfig.h" #include "BlueprintModeExitedEvent.h" #include "BlueprintPlacementRequestedEvent.h" #include "BuilderModeExitedEvent.h" #include "BuildingType.h" #include "BuildingTypeSelectedEvent.h" #include "BuildingId.h" #include "DemolishModeChangedEvent.h" #include "DemolishModeToggleRequestedEvent.h" #include "EventHandler.h" #include "ExitBlueprintModeRequestedEvent.h" #include "ExitBuilderModeRequestedEvent.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 "CommandManager.h" #include "EntitySelectedEvent.h" #include "GameConfig.h" #include "Rotation.h" #include "Tick.h" #include "TickDriver.h" #include "VisualsConfig.h" struct Command; struct ParsedReplay; class ReplayPlayer; class Simulation; class QPainter; struct QPointCompare { bool operator()(const QPoint& a, const QPoint& b) const { if (a.x() != b.x()) { return a.x() < b.x(); } return a.y() < b.y(); } }; class GameWorldView : public QOpenGLWidget, public CombinedEventHandler { Q_OBJECT public: GameWorldView(Simulation* sim, const GameConfig* config, const VisualsConfig* visuals, const std::string& configDir, 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; void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* 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 event) override; void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; // Enqueue a sim command onto the CommandManager (the single mutation path). void enqueueCommand(std::shared_ptr 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; void drawTiles(QPainter& painter); void drawPortItems(QPainter& painter); void drawBuildings(QPainter& painter); void drawSelectionHighlights(QPainter& painter); void drawCopyConfigFeedback(QPainter& painter); void drawStations(QPainter& painter); void drawBeltItems(QPainter& painter); void drawScrap(QPainter& painter); void drawShips(QPainter& painter); void drawHpBar(QPainter& painter, qreal left, qreal top, qreal width, float fraction, bool isEnemy); void drawDebugSensorRanges(QPainter& painter); void drawDebugTargetLines(QPainter& painter); void drawDebugOverlay(QPainter& painter); void drawBeams(QPainter& painter); void drawOverlays(QPainter& painter); void drawScreenSpace(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 demolish mode is active (REQ-UI-DEMOLISH-BORDER), // tinted with the demolish overlay color (including its configured alpha) from // visuals.toml instead of black. void drawDemolishBorder(QPainter& painter); // Shared drawing for the pause/demolish 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); float getTilePx() const; float getViewportWidthTiles() const; // World-X (tiles) at the left edge of the viewport. m_scrollXTiles stores the // view center; this derives the left edge the world<->widget conversions need. float getViewLeftTiles() const; QPointF worldToWidget(QVector2D worldPos) const; QPointF tileToWidget(QPoint tile) const; QPoint widgetToTile(QPoint widgetPt) const; QRectF tileRect(QPoint tile) const; QRect getViewportRect() const; // Widget-space rectangle covering a building or construction site's footprint, // or nullopt if the id resolves to neither. Shared by the selection highlight // and the copy-settings feedback (REQ-BLD-COPY-CONFIG-FEEDBACK). std::optional footprintWidgetRect(BuildingId id) const; float getAsteroidLeftEdge() const; float getEnemyStationRightEdge() const; // Horizontal pan speed at a given view-center X, in tiles/s (REQ-UI-SCROLL-SPEED). float panSpeedTilesPerSecondAt(float viewCenterXTiles) const; void clampScroll(); bool isValidPlacement(BuildingType type, QPoint anchor, Rotation rot) const; const BuildingDef* findBuildingDef(BuildingType type) const; std::optional buildingAtTile(QPoint tile) const; std::optional siteAtTile(QPoint tile) const; // Ids of all buildings and construction sites whose footprint intersects // the tile box spanned by the two (unordered) corner tiles. std::vector buildingsInBox(QPoint cornerA, QPoint cornerB) const; QVector2D widgetToWorld(QPoint widgetPt) const; void drawPortGlyph(QPainter& painter, QPoint tile, Rotation direction, const QColor& color, bool centered); void drawBuildingGhost(QPainter& painter, BuildingType type, QPoint anchorTile, Rotation rotation, bool valid, bool showPortTargetGlyphs); void placeBlueprintAtTile(QPoint center); std::optional entityPosition(entt::entity entity) const; // Clears the scrap selection, emitting an empty ScrapSelectionChangedEvent when // it was non-empty (REQ-UI-SCRAP-CLICK-SELECT). Used when another selection // category takes over. void clearScrapSelection(); // Drops despawned or fully-collected piles from the scrap selection and re-emits // when it changed (REQ-UI-SCRAP-CLICK-SELECT). Called each frame from onFrame(). void pruneDespawnedScrap(); void stepSpeed(int delta); void placeAtTile(QPoint tile); // Copy-settings gesture (REQ-BLD-COPY-CONFIG): Shift+right-click copies a // building's configuration into m_copiedConfig; Shift+left-click applies it to // another building of the same type via the existing configuration commands. void copyConfigFrom(BuildingId id); void pasteConfigTo(BuildingId id); void enterBuilderMode(BuildingType type); void exitBuilderMode(); void enterBlueprintMode(Blueprint blueprint); void exitBlueprintMode(); void toggleDemolishMode(); void rotateGhost(bool clockwise); struct ActiveBeam { BeamFiredEvent event; QVector2D targetOffset; }; // 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 m_replayPlayer; TickDriver m_tickDriver; QElapsedTimer m_frameTimer; std::mt19937 m_rng; double m_gameSpeedMultiplier; double m_prevNonZeroSpeed; // World-X (tiles) at the center of the viewport (see getViewLeftTiles()). float m_scrollXTiles; QTimer* m_renderTimer; std::vector m_activeBeams; std::optional m_builderType; Rotation m_ghostRotation; QPoint m_ghostTile; bool m_ghostValid; std::set m_beltDragTiles; bool m_dragging; std::optional m_blueprintMode; QPoint m_blueprintGhostTile; // Temporary cache for the copy-settings gesture (REQ-BLD-COPY-CONFIG); held // only while Shift is down and cleared on Shift release. std::optional m_copiedConfig; // Brief outline flash shown on a building when settings are copied from it or // pasted onto it (REQ-BLD-COPY-CONFIG-FEEDBACK). remainingMs counts down in // wall-clock time so the flash plays at a fixed length regardless of game speed // (and while paused). struct CopyConfigFlash { BuildingId id; qint64 remainingMs; }; std::vector m_copyConfigFlashes; static constexpr qint64 kCopyFlashDurationMs = 300; bool m_demolishMode; std::optional m_demolishHoverBuildingId; bool m_debugDraw; std::vector m_selectedBuildingIds; std::optional m_selectedEntity; std::vector m_selectedScrap; bool m_boxSelecting; QPoint m_boxStartTile; QPoint m_boxCurrentTile; bool m_scrollLeft; bool m_scrollRight; 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; };