# Combat Proposal UI Design ## Overview Add a centered combat forecast overlay to the existing CombatUI that displays a `CombatProposal` with attacker/defender stats and Fight/Cancel buttons. The system uses parent-wired signals — no node references outside parent-child relationships. ## Signal Flow When a player clicks a defender while an attacker is selected: 1. `PlayerController` emits `combat_requested(attacker, defender)` 2. Parent scene script catches it, calls `CombatSystem.create_proposal(attacker, defender)` 3. Parent calls `CombatUI.show_proposal(proposal)` 4. Player sees the combat forecast panel On Fight: 1. `CombatUI` emits `fight_confirmed(proposal)` 2. Parent catches it, calls `CombatSystem.apply_proposal(proposal)` 3. Panel closes On Cancel (Cancel button or right-click): 1. `CombatUI` emits `fight_cancelled` 2. Parent catches it (no action needed beyond the signal existing for future use) 3. Panel closes ## Architecture ### Encapsulation Rules - **CombatUI** knows only about its own children (UnitPanel, CombatProposalPanel, buttons). Exposes signals and public methods. - **PlayerController** knows nothing about CombatUI or CombatSystem. Emits `combat_requested`. - **CombatSystem** knows nothing about UI. Exposes `create_proposal()` and `apply_proposal()`. - **Parent scene script** (`strategy_phase.tscn` root node) is the only place that references sibling nodes via `@onready`. It wires signals in `_ready()` and contains the orchestration logic. ### Parent Scene Script A new script on the `strategy_phase.tscn` root node (~20 lines) that: - Holds `@onready` references to `PlayerController`, `CombatSystem`, and `CombatUI` - Connects `PlayerController.combat_requested` to a local method that creates a proposal and passes it to `CombatUI.show_proposal()` - Connects `CombatUI.fight_confirmed` to a local method that calls `CombatSystem.apply_proposal()` - Connects `CombatUI.fight_cancelled` (no-op for now, but wired for future use) This replaces the current direct signal connection from `PlayerController.combat_requested` to `CombatSystem.process_combat` in the scene editor. ## CombatUI Changes ### New Signals - `fight_confirmed(proposal: CombatProposal)` — emitted when Fight button pressed - `fight_cancelled` — emitted when Cancel button pressed or right-click detected ### New Public Methods - `show_proposal(proposal: CombatProposal)` — stores the proposal reference, populates all labels and progress bars from the proposal's `CombatantStats`, shows the CombatProposalPanel - `_hide_proposal()` — hides the panel, clears stored proposal reference ### Input Handling Right-click while the proposal panel is visible triggers `_hide_proposal()` and emits `fight_cancelled`. Handled via `_unhandled_input` on CombatUI. ### Existing Unit Panel The bottom-left unit info panel remains visible and unaffected. It continues to show the selected unit's info, which provides useful context alongside the forecast. ## CombatProposalPanel Layout New `PanelContainer` child of the CombatUI CanvasLayer, centered on screen via anchor presets. Hidden by default. ``` ┌─────────────────────────────────────────────┐ │ COMBAT FORECAST │ │ │ │ [Attacker] [Defender] │ │ Name Name │ │ HP ████████░░ HP ██████░░░░ │ │ ATK: 12 ATK: 8 │ │ DEF: 5 DEF: 6 │ │ HIT: 78% HIT: 65% │ │ SPD: 10 SPD: 7 │ │ │ │ [ Fight ] [ Cancel ] │ └─────────────────────────────────────────────┘ ``` ### Scene Structure ``` CombatUI (CanvasLayer) — combat_ui.gd ├── UnitPanel (PanelContainer) — existing, bottom-left │ └── MarginContainer │ └── VBoxContainer │ ├── NameLabel (Label) │ └── HPBar (ProgressBar) └── CombatProposalPanel (PanelContainer) — new, centered └── MarginContainer └── VBoxContainer ├── TitleLabel (Label) — "COMBAT FORECAST" ├── StatsContainer (HBoxContainer) │ ├── AttackerStats (VBoxContainer) │ │ ├── AttackerNameLabel (Label) │ │ ├── AttackerHPBar (ProgressBar) │ │ ├── AttackerATKLabel (Label) — "ATK: {value}" │ │ ├── AttackerDEFLabel (Label) — "DEF: {value}" │ │ ├── AttackerHITLabel (Label) — "HIT: {value}%" │ │ └── AttackerSPDLabel (Label) — "SPD: {value}" │ └── DefenderStats (VBoxContainer) │ ├── DefenderNameLabel (Label) │ ├── DefenderHPBar (ProgressBar) │ ├── DefenderATKLabel (Label) — "ATK: {value}" │ ├── DefenderDEFLabel (Label) — "DEF: {value}" │ ├── DefenderHITLabel (Label) — "HIT: {value}%" │ └── DefenderSPDLabel (Label) — "SPD: {value}" └── ButtonContainer (HBoxContainer) — centered ├── FightButton (Button) — "Fight" └── CancelButton (Button) — "Cancel" ``` ### Data Mapping All values come from `CombatProposal.CombatantStats`: | Panel Field | Source | |---|---| | Name | `combatant_stats.unit.info.name` | | HP bar max | `combatant_stats.max_hp` | | HP bar value | `combatant_stats.hp` | | ATK | `combatant_stats.atk` | | DEF | `combatant_stats.def` | | HIT | `combatant_stats.hit` (already adjusted: attacker hit - defender eva) | | SPD | `combatant_stats.spd` | ### CombatProposal Change `CombatantStats` in `resources/resource_definitions/combat_proposal.gd` needs a new `max_hp: int` field added so the HP bar can display a meaningful ratio. `CombatSystem._snapshot()` must populate it from `unit.current_stats.max_hp`. ### Styling - Uses the existing `main_ui_theme.tres` (Minecraft font) - Panel background: default theme PanelContainer style - Centered via `anchors_preset = CENTER` - No fixed pixel size — let the content determine width naturally with margin padding ## Button Wiring FightButton and CancelButton `pressed` signals connect to methods on `combat_ui.gd` within the scene (parent-child, no encapsulation issue): - `FightButton.pressed` → `_on_fight_pressed()` — emits `fight_confirmed(stored_proposal)`, calls `_hide_proposal()` - `CancelButton.pressed` → `_on_cancel_pressed()` — emits `fight_cancelled`, calls `_hide_proposal()` ## Files Changed | File | Change | |---|---| | `resources/resource_definitions/combat_proposal.gd` | Add `max_hp` field to `CombatantStats` | | `nodes/combat_system.gd` | Populate `max_hp` in `_snapshot()` | | `scripts/combat_ui.gd` | Add signals, `show_proposal()`, `_hide_proposal()`, `_unhandled_input` for right-click cancel, button handlers | | `prefabs/combat_ui.tscn` | Add CombatProposalPanel subtree, wire button signals | | `scenes/strategy_phase.tscn` | Add root script, remove direct PlayerController→CombatSystem signal connection | ## Files Added | File | Purpose | |---|---| | `nodes/strategy_phase.gd` | Parent orchestration script for strategy_phase.tscn root node |