# Input Centralization Design ## Problem Input handling is scattered across `PlayerController`, `CameraController`, and `TileHighlight`, each independently reading mouse state. There's no way to globally disable gameplay input during overlays or animations. ## Design ### PlayerController — single input authority `PlayerController` becomes the only script that reads raw input. All other gameplay scripts react to its signals. **Signals:** | Signal | Emitted when | |--------|-------------| | `mouse_grid_changed(coords: Vector2i)` | Hovered grid cell changes (every frame check in `_process()`) | | `camera_drag(delta: Vector2)` | Mouse is dragged (left-drag past 8px threshold, or any middle-drag) | | `combat_requested(attacker, defender)` | Existing — click on enemy unit while friendly selected | **Properties:** | Property | Purpose | |----------|---------| | `input_disabled: bool` | When true, all input processing stops — no signals emitted, no click handling, no movement | **Input handling (`_unhandled_input`):** - Left click release (no drag): unit selection or movement target (existing logic) - Left click release on enemy unit while friendly selected: emit `combat_requested` (existing logic) - Left mouse drag: track press position, once delta exceeds 8px threshold, enter drag mode and emit `camera_drag(delta)` on each `InputEventMouseMotion` - Middle mouse drag: immediately enter drag mode, emit `camera_drag(delta)` on each motion - All of the above gated by `if input_disabled: return` at the top **Mouse grid tracking (`_process`):** - Read `get_global_mouse_position()`, snap to grid, convert to `Vector2i` - If coords changed from previous frame, emit `mouse_grid_changed(coords)` - Gated by `input_disabled` **Left-drag vs left-click disambiguation:** - On left button press: record press position, set `_drag_candidate = true` - On mouse motion while `_drag_candidate`: if distance from press > 8px, enter drag mode (`_dragging = true`), stop treating this as a click - On left button release: if `_dragging`, end drag and reset state; if not, treat as click (existing selection/movement logic) - This matches the existing `CameraController` behavior ### CameraController — reactive - Remove `_unhandled_input()` entirely - Remove all drag detection state (`_dragging`, `_drag_start`, etc.) - Expose a method (e.g., `apply_drag(delta: Vector2)`) or connect directly to `camera_drag` signal - Apply delta to camera position, respecting any existing bounds/smoothing ### TileHighlight — reactive - Remove `_process()` mouse tracking and `get_global_mouse_position()` calls - Keep the pulse animation (can run in its own `_process` gated on visibility) - Expose a method like `set_grid_coords(coords: Vector2i)` that positions the highlight - Hide when input is disabled (connected to a signal or called directly) - Keep `_notification` for `WM_MOUSE_EXIT` / `WM_MOUSE_ENTER` if still relevant, or remove if PlayerController handles this ### strategy_phase.gd — wiring Connects signals in `_ready()`: ``` player_controller.mouse_grid_changed → tile_highlight.set_grid_coords (or similar) player_controller.camera_drag → camera_controller.apply_drag ``` Overlays toggle input: ``` # When showing overlay: player_controller.input_disabled = true # When hiding overlay: player_controller.input_disabled = false ``` ## What doesn't change - `CombatUI._unhandled_input()` for right-click dismiss — this is UI-layer input, not gameplay input - Unit signals (`unit_died`, `unit_selected_changed`) - CombatSystem logic - All existing click behavior (selection, movement, combat requests) — just consolidated under the `input_disabled` gate ## Migration notes - `CameraController` drag state variables and `_unhandled_input` are deleted, not left as dead code - `TileHighlight._process` mouse reading is deleted - `TileHighlight` keeps its `tile_size` export and grid-snapping math (used by `set_grid_coords`) - PlayerController needs `dl_map` reference for grid coordinate conversion (already has it)