Combat panel, mouse movement still passes through
This commit is contained in:
367
docs/superpowers/plans/2026-04-02-combat-proposal-ui.md
Normal file
367
docs/superpowers/plans/2026-04-02-combat-proposal-ui.md
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
# Combat Proposal UI Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Add a centered combat forecast overlay to CombatUI that displays a CombatProposal with stats and Fight/Cancel buttons, using parent-wired signals for encapsulation.
|
||||||
|
|
||||||
|
**Architecture:** CombatUI gains a CombatProposalPanel child and exposes `show_proposal()`, `fight_confirmed`, and `fight_cancelled`. A new parent script on the strategy_phase root node orchestrates the flow between PlayerController, CombatSystem, and CombatUI — the only place sibling references exist.
|
||||||
|
|
||||||
|
**Tech Stack:** Godot 4.6, GDScript, `.tscn` scene files
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|---|---|
|
||||||
|
| `resources/resource_definitions/combat_proposal.gd` | **Modify** — add `max_hp` field to `CombatantStats` |
|
||||||
|
| `nodes/combat_system.gd` | **Modify** — populate `max_hp` in `_snapshot()` |
|
||||||
|
| `scripts/combat_ui.gd` | **Modify** — add proposal panel logic, signals, input handling |
|
||||||
|
| `prefabs/combat_ui.tscn` | **Modify** — add CombatProposalPanel node subtree |
|
||||||
|
| `nodes/strategy_phase.gd` | **Create** — parent orchestration script |
|
||||||
|
| `scenes/strategy_phase.tscn` | **Modify** — attach root script, rewire signals |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Add `max_hp` to CombatProposal and CombatSystem
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `resources/resource_definitions/combat_proposal.gd`
|
||||||
|
- Modify: `nodes/combat_system.gd`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add `max_hp` field to `CombatantStats`**
|
||||||
|
|
||||||
|
In `resources/resource_definitions/combat_proposal.gd`, add `max_hp` to the inner class:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
class CombatantStats:
|
||||||
|
var unit: Unit
|
||||||
|
var max_hp: int
|
||||||
|
var hp: int
|
||||||
|
var sp: int
|
||||||
|
var hit: int
|
||||||
|
var atk: int
|
||||||
|
var def: int
|
||||||
|
var spd: int
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Populate `max_hp` in `_snapshot()`**
|
||||||
|
|
||||||
|
In `nodes/combat_system.gd`, add the `max_hp` line to `_snapshot()`:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
func _snapshot(unit: Unit, opponent: Unit) -> CombatProposal.CombatantStats:
|
||||||
|
var stats := CombatProposal.CombatantStats.new()
|
||||||
|
stats.unit = unit
|
||||||
|
stats.max_hp = unit.current_stats.max_hp
|
||||||
|
stats.hp = unit.current_stats.current_hp
|
||||||
|
stats.sp = unit.current_stats.current_sp
|
||||||
|
stats.hit = unit.current_stats.hit - opponent.current_stats.eva
|
||||||
|
stats.atk = unit.current_stats.phys_atk
|
||||||
|
stats.def = unit.current_stats.phys_def
|
||||||
|
stats.spd = unit.current_stats.spd
|
||||||
|
return stats
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify in editor**
|
||||||
|
|
||||||
|
Open the project in the Godot editor. Confirm no parse errors in the Output panel. The game should still run and the existing `process_combat` flow should work unchanged.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add resources/resource_definitions/combat_proposal.gd nodes/combat_system.gd
|
||||||
|
git commit -m "feat: add max_hp to CombatProposal.CombatantStats"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Add CombatProposalPanel to `combat_ui.tscn`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `prefabs/combat_ui.tscn`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add the CombatProposalPanel subtree**
|
||||||
|
|
||||||
|
Open `prefabs/combat_ui.tscn` in the Godot editor. Add the following node tree as a child of the root `CombatUI` CanvasLayer node:
|
||||||
|
|
||||||
|
```
|
||||||
|
CombatProposalPanel (PanelContainer) — unique name, centered, hidden by default
|
||||||
|
└── MarginContainer (margin 12 all sides)
|
||||||
|
└── VBoxContainer
|
||||||
|
├── TitleLabel (Label) — text: "COMBAT FORECAST", horizontal alignment: center
|
||||||
|
├── StatsContainer (HBoxContainer, separation: 24)
|
||||||
|
│ ├── AttackerStats (VBoxContainer)
|
||||||
|
│ │ ├── AttackerNameLabel (Label) — unique name, text: "Attacker"
|
||||||
|
│ │ ├── AttackerHPBar (ProgressBar) — unique name, show_percentage: false
|
||||||
|
│ │ ├── AttackerATKLabel (Label) — unique name, text: "ATK: 0"
|
||||||
|
│ │ ├── AttackerDEFLabel (Label) — unique name, text: "DEF: 0"
|
||||||
|
│ │ ├── AttackerHITLabel (Label) — unique name, text: "HIT: 0%"
|
||||||
|
│ │ └── AttackerSPDLabel (Label) — unique name, text: "SPD: 0"
|
||||||
|
│ └── DefenderStats (VBoxContainer)
|
||||||
|
│ ├── DefenderNameLabel (Label) — unique name, text: "Defender"
|
||||||
|
│ ├── DefenderHPBar (ProgressBar) — unique name, show_percentage: false
|
||||||
|
│ ├── DefenderATKLabel (Label) — unique name, text: "ATK: 0"
|
||||||
|
│ ├── DefenderDEFLabel (Label) — unique name, text: "DEF: 0"
|
||||||
|
│ ├── DefenderHITLabel (Label) — unique name, text: "HIT: 0%"
|
||||||
|
│ └── DefenderSPDLabel (Label) — unique name, text: "SPD: 0"
|
||||||
|
└── ButtonContainer (HBoxContainer, horizontal alignment: center)
|
||||||
|
├── FightButton (Button) — text: "Fight"
|
||||||
|
└── CancelButton (Button) — text: "Cancel"
|
||||||
|
```
|
||||||
|
|
||||||
|
**CombatProposalPanel settings:**
|
||||||
|
- `visible = false`
|
||||||
|
- `anchors_preset = 8` (Center)
|
||||||
|
- `grow_horizontal = 2` (Both)
|
||||||
|
- `grow_vertical = 2` (Both)
|
||||||
|
- `theme = main_ui_theme.tres` (same as UnitPanel)
|
||||||
|
|
||||||
|
All Label, ProgressBar, and Button nodes listed above must have "unique name in owner" checked so they can be referenced via `%NodeName` in script.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Verify in editor**
|
||||||
|
|
||||||
|
Run the scene. The proposal panel should not be visible. The existing UnitPanel should still work as before (selecting a unit shows name + HP).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add prefabs/combat_ui.tscn
|
||||||
|
git commit -m "feat: add CombatProposalPanel to combat_ui scene"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Add proposal logic to `combat_ui.gd`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `scripts/combat_ui.gd`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add signals, onready vars, and proposal state**
|
||||||
|
|
||||||
|
Add the new signals, `@onready` references for the proposal panel nodes, and a `_current_proposal` variable. The full updated script:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
class_name CombatUI extends CanvasLayer
|
||||||
|
|
||||||
|
signal fight_confirmed(proposal: CombatProposal)
|
||||||
|
signal fight_cancelled
|
||||||
|
|
||||||
|
@onready var unit_panel: PanelContainer = %UnitPanel
|
||||||
|
@onready var name_label: Label = %NameLabel
|
||||||
|
@onready var hp_bar: ProgressBar = %HPBar
|
||||||
|
|
||||||
|
@onready var proposal_panel: PanelContainer = %CombatProposalPanel
|
||||||
|
@onready var atk_name_label: Label = %AttackerNameLabel
|
||||||
|
@onready var atk_hp_bar: ProgressBar = %AttackerHPBar
|
||||||
|
@onready var atk_atk_label: Label = %AttackerATKLabel
|
||||||
|
@onready var atk_def_label: Label = %AttackerDEFLabel
|
||||||
|
@onready var atk_hit_label: Label = %AttackerHITLabel
|
||||||
|
@onready var atk_spd_label: Label = %AttackerSPDLabel
|
||||||
|
@onready var def_name_label: Label = %DefenderNameLabel
|
||||||
|
@onready var def_hp_bar: ProgressBar = %DefenderHPBar
|
||||||
|
@onready var def_atk_label: Label = %DefenderATKLabel
|
||||||
|
@onready var def_def_label: Label = %DefenderDEFLabel
|
||||||
|
@onready var def_hit_label: Label = %DefenderHITLabel
|
||||||
|
@onready var def_spd_label: Label = %DefenderSPDLabel
|
||||||
|
@onready var fight_button: Button = %FightButton
|
||||||
|
@onready var cancel_button: Button = %CancelButton
|
||||||
|
|
||||||
|
var _selected_unit: Unit
|
||||||
|
var _current_proposal: CombatProposal
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
unit_panel.visible = false
|
||||||
|
proposal_panel.visible = false
|
||||||
|
fight_button.pressed.connect(_on_fight_pressed)
|
||||||
|
cancel_button.pressed.connect(_on_cancel_pressed)
|
||||||
|
for unit: Unit in get_tree().get_nodes_in_group("units"):
|
||||||
|
unit.unit_selected_changed.connect(_on_unit_selected_changed)
|
||||||
|
unit.unit_died.connect(_on_unit_died)
|
||||||
|
get_tree().node_added.connect(_on_node_added)
|
||||||
|
|
||||||
|
func _on_node_added(node: Node) -> void:
|
||||||
|
if node is Unit and node.is_in_group("units"):
|
||||||
|
if not node.unit_selected_changed.is_connected(_on_unit_selected_changed):
|
||||||
|
node.unit_selected_changed.connect(_on_unit_selected_changed)
|
||||||
|
if not node.unit_died.is_connected(_on_unit_died):
|
||||||
|
node.unit_died.connect(_on_unit_died)
|
||||||
|
|
||||||
|
func _on_unit_died(unit: Unit) -> void:
|
||||||
|
if _selected_unit == unit:
|
||||||
|
_selected_unit = null
|
||||||
|
unit_panel.visible = false
|
||||||
|
if _current_proposal:
|
||||||
|
if _current_proposal.attacker.unit == unit or _current_proposal.defender.unit == unit:
|
||||||
|
_hide_proposal()
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
if _selected_unit and is_instance_valid(_selected_unit):
|
||||||
|
hp_bar.max_value = _selected_unit.current_stats.max_hp
|
||||||
|
hp_bar.value = _selected_unit.current_stats.current_hp
|
||||||
|
|
||||||
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
if proposal_panel.visible and event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
|
||||||
|
_hide_proposal()
|
||||||
|
fight_cancelled.emit()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
func _on_unit_selected_changed(unit: Unit, selected: bool) -> void:
|
||||||
|
if selected:
|
||||||
|
_selected_unit = unit
|
||||||
|
name_label.text = unit.current_info.name
|
||||||
|
hp_bar.max_value = unit.current_stats.max_hp
|
||||||
|
hp_bar.value = unit.current_stats.current_hp
|
||||||
|
unit_panel.visible = true
|
||||||
|
else:
|
||||||
|
_selected_unit = null
|
||||||
|
unit_panel.visible = false
|
||||||
|
|
||||||
|
func show_proposal(proposal: CombatProposal) -> void:
|
||||||
|
_current_proposal = proposal
|
||||||
|
var atk := proposal.attacker
|
||||||
|
var def := proposal.defender
|
||||||
|
atk_name_label.text = atk.unit.current_info.name
|
||||||
|
atk_hp_bar.max_value = atk.max_hp
|
||||||
|
atk_hp_bar.value = atk.hp
|
||||||
|
atk_atk_label.text = "ATK: %d" % atk.atk
|
||||||
|
atk_def_label.text = "DEF: %d" % atk.def
|
||||||
|
atk_hit_label.text = "HIT: %d%%" % atk.hit
|
||||||
|
atk_spd_label.text = "SPD: %d" % atk.spd
|
||||||
|
def_name_label.text = def.unit.current_info.name
|
||||||
|
def_hp_bar.max_value = def.max_hp
|
||||||
|
def_hp_bar.value = def.hp
|
||||||
|
def_atk_label.text = "ATK: %d" % def.atk
|
||||||
|
def_def_label.text = "DEF: %d" % def.def
|
||||||
|
def_hit_label.text = "HIT: %d%%" % def.hit
|
||||||
|
def_spd_label.text = "SPD: %d" % def.spd
|
||||||
|
proposal_panel.visible = true
|
||||||
|
|
||||||
|
func _hide_proposal() -> void:
|
||||||
|
proposal_panel.visible = false
|
||||||
|
_current_proposal = null
|
||||||
|
|
||||||
|
func _on_fight_pressed() -> void:
|
||||||
|
if _current_proposal:
|
||||||
|
var proposal := _current_proposal
|
||||||
|
_hide_proposal()
|
||||||
|
fight_confirmed.emit(proposal)
|
||||||
|
|
||||||
|
func _on_cancel_pressed() -> void:
|
||||||
|
_hide_proposal()
|
||||||
|
fight_cancelled.emit()
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Verify in editor**
|
||||||
|
|
||||||
|
Open the project. Confirm no parse errors. Run the game — the proposal panel should remain hidden. Selecting units should still show the UnitPanel as before.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add scripts/combat_ui.gd
|
||||||
|
git commit -m "feat: add combat proposal panel logic to CombatUI"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Create parent orchestration script and rewire signals
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `nodes/strategy_phase.gd`
|
||||||
|
- Modify: `scenes/strategy_phase.tscn`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create `nodes/strategy_phase.gd`**
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
class_name StrategyPhase extends Node2D
|
||||||
|
|
||||||
|
@onready var player_controller: PlayerController = $PlayerController
|
||||||
|
@onready var combat_system: CombatSystem = $CombatSystem
|
||||||
|
@onready var combat_ui: CombatUI = $CombatUI
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
player_controller.combat_requested.connect(_on_combat_requested)
|
||||||
|
combat_ui.fight_confirmed.connect(_on_fight_confirmed)
|
||||||
|
|
||||||
|
func _on_combat_requested(attacker: Unit, defender: Unit) -> void:
|
||||||
|
var proposal := combat_system.create_proposal(attacker, defender)
|
||||||
|
combat_ui.show_proposal(proposal)
|
||||||
|
|
||||||
|
func _on_fight_confirmed(proposal: CombatProposal) -> void:
|
||||||
|
combat_system.apply_proposal(proposal)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Attach script and remove old signal connection in `strategy_phase.tscn`**
|
||||||
|
|
||||||
|
Open `scenes/strategy_phase.tscn` in the Godot editor:
|
||||||
|
|
||||||
|
1. Select the root node `CombatTest`
|
||||||
|
2. Attach the script `res://nodes/strategy_phase.gd` to it
|
||||||
|
3. Go to the Node > Signals panel on `PlayerController`
|
||||||
|
4. Disconnect the existing `combat_requested` → `CombatSystem.process_combat` connection
|
||||||
|
|
||||||
|
If editing the `.tscn` file directly, the changes are:
|
||||||
|
- Add `[ext_resource type="Script" path="res://nodes/strategy_phase.gd" id="7_strat"]` to the ext_resources
|
||||||
|
- Add `script = ExtResource("7_strat")` to the root `CombatTest` node
|
||||||
|
- Remove the line `[connection signal="combat_requested" from="PlayerController" to="CombatSystem" method="process_combat"]`
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify full flow**
|
||||||
|
|
||||||
|
Run the game from the main menu:
|
||||||
|
1. Click Start — strategy phase loads with two units
|
||||||
|
2. Click the player unit (Putit) — UnitPanel appears bottom-left with name and HP
|
||||||
|
3. Click the enemy unit — **CombatProposalPanel appears centered** showing both units' stats
|
||||||
|
4. Click Cancel or right-click — panel closes, nothing happens
|
||||||
|
5. Repeat step 3, click Fight — panel closes, combat resolves (check console for combat log from `process_combat`... wait, `process_combat` is no longer wired)
|
||||||
|
|
||||||
|
**Important:** The parent script calls `apply_proposal()` directly, not `process_combat()`. The console debug prints from `process_combat()` will no longer appear. This is correct — `process_combat()` was a convenience wrapper that created and immediately applied a proposal with debug logging. The new flow separates these steps. The debug prints can be added to the parent script if needed for development, but are not required.
|
||||||
|
|
||||||
|
Verify combat works by checking that clicking Fight causes the defender to take damage (HP bar in UnitPanel updates when you re-select the damaged unit).
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add nodes/strategy_phase.gd scenes/strategy_phase.tscn
|
||||||
|
git commit -m "feat: add parent orchestration script, wire combat proposal flow"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: Final verification and cleanup
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- None new — verification pass only
|
||||||
|
|
||||||
|
- [ ] **Step 1: Test the full happy path**
|
||||||
|
|
||||||
|
1. Launch game from main menu
|
||||||
|
2. Click Start
|
||||||
|
3. Select player unit → UnitPanel shows
|
||||||
|
4. Click enemy unit → CombatProposalPanel shows with correct stats for both units
|
||||||
|
5. Click Fight → panel closes, combat resolves
|
||||||
|
6. Select the damaged unit → UnitPanel HP bar reflects new HP
|
||||||
|
7. Repeat combat until a unit dies → unit disappears, panels hide correctly
|
||||||
|
|
||||||
|
- [ ] **Step 2: Test cancel paths**
|
||||||
|
|
||||||
|
1. Open proposal panel
|
||||||
|
2. Click Cancel → panel closes, no combat
|
||||||
|
3. Open proposal panel again
|
||||||
|
4. Right-click → panel closes, no combat
|
||||||
|
5. Verify units are unaffected after cancels
|
||||||
|
|
||||||
|
- [ ] **Step 3: Test edge case — unit dies during proposal**
|
||||||
|
|
||||||
|
This is unlikely in the current single-player flow but worth verifying the guard in `_on_unit_died`:
|
||||||
|
|
||||||
|
1. This would require a unit dying from another source while the panel is open. For now, confirm that the `_on_unit_died` check in `combat_ui.gd` exists and would hide the panel if the attacker or defender dies.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit any fixes**
|
||||||
|
|
||||||
|
If any issues were found and fixed during verification:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add -u
|
||||||
|
git commit -m "fix: address issues found during combat proposal UI verification"
|
||||||
|
```
|
||||||
165
docs/superpowers/specs/2026-04-02-combat-proposal-ui-design.md
Normal file
165
docs/superpowers/specs/2026-04-02-combat-proposal-ui-design.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# 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 |
|
||||||
@@ -11,6 +11,7 @@ func create_proposal(attacker: Unit, defender: Unit) -> CombatProposal:
|
|||||||
func _snapshot(unit: Unit, opponent: Unit) -> CombatProposal.CombatantStats:
|
func _snapshot(unit: Unit, opponent: Unit) -> CombatProposal.CombatantStats:
|
||||||
var stats := CombatProposal.CombatantStats.new()
|
var stats := CombatProposal.CombatantStats.new()
|
||||||
stats.unit = unit
|
stats.unit = unit
|
||||||
|
stats.max_hp = unit.current_stats.max_hp
|
||||||
stats.hp = unit.current_stats.current_hp
|
stats.hp = unit.current_stats.current_hp
|
||||||
stats.sp = unit.current_stats.current_sp
|
stats.sp = unit.current_stats.current_sp
|
||||||
stats.hit = unit.current_stats.hit - opponent.current_stats.eva
|
stats.hit = unit.current_stats.hit - opponent.current_stats.eva
|
||||||
|
|||||||
16
nodes/strategy_phase.gd
Normal file
16
nodes/strategy_phase.gd
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
class_name StrategyPhase extends Node2D
|
||||||
|
|
||||||
|
@onready var player_controller: PlayerController = $PlayerController
|
||||||
|
@onready var combat_system: CombatSystem = $CombatSystem
|
||||||
|
@onready var combat_ui: CombatUI = $CombatUI
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
player_controller.combat_requested.connect(_on_combat_requested)
|
||||||
|
combat_ui.fight_confirmed.connect(_on_fight_confirmed)
|
||||||
|
|
||||||
|
func _on_combat_requested(attacker: Unit, defender: Unit) -> void:
|
||||||
|
var proposal := combat_system.create_proposal(attacker, defender)
|
||||||
|
combat_ui.show_proposal(proposal)
|
||||||
|
|
||||||
|
func _on_fight_confirmed(proposal: CombatProposal) -> void:
|
||||||
|
combat_system.apply_proposal(proposal)
|
||||||
1
nodes/strategy_phase.gd.uid
Normal file
1
nodes/strategy_phase.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dnsqtsx4u2hx4
|
||||||
@@ -40,3 +40,125 @@ layout_mode = 2
|
|||||||
max_value = 100.0
|
max_value = 100.0
|
||||||
value = 100.0
|
value = 100.0
|
||||||
show_percentage = false
|
show_percentage = false
|
||||||
|
|
||||||
|
[node name="BackgroundTint" type="ColorRect" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 0
|
||||||
|
color = Color(0, 0, 0, 0.4)
|
||||||
|
|
||||||
|
[node name="CombatProposalPanel" type="PanelContainer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
anchors_preset = 8
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme = ExtResource("1_2ro41")
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="CombatProposalPanel"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/margin_left = 12
|
||||||
|
theme_override_constants/margin_top = 12
|
||||||
|
theme_override_constants/margin_right = 12
|
||||||
|
theme_override_constants/margin_bottom = 12
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="CombatProposalPanel/MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="TitleLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "COMBAT FORECAST"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="StatsContainer" type="HBoxContainer" parent="CombatProposalPanel/MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 24
|
||||||
|
|
||||||
|
[node name="AttackerStats" type="VBoxContainer" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="AttackerNameLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Attacker"
|
||||||
|
|
||||||
|
[node name="AttackerHPBar" type="ProgressBar" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
show_percentage = false
|
||||||
|
|
||||||
|
[node name="AttackerATKLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "ATK: 0"
|
||||||
|
|
||||||
|
[node name="AttackerDEFLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "DEF: 0"
|
||||||
|
|
||||||
|
[node name="AttackerHITLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "HIT: 0%"
|
||||||
|
|
||||||
|
[node name="AttackerSPDLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/AttackerStats"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "SPD: 0"
|
||||||
|
|
||||||
|
[node name="DefenderStats" type="VBoxContainer" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="DefenderNameLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Defender"
|
||||||
|
|
||||||
|
[node name="DefenderHPBar" type="ProgressBar" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
show_percentage = false
|
||||||
|
|
||||||
|
[node name="DefenderATKLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "ATK: 0"
|
||||||
|
|
||||||
|
[node name="DefenderDEFLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "DEF: 0"
|
||||||
|
|
||||||
|
[node name="DefenderHITLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "HIT: 0%"
|
||||||
|
|
||||||
|
[node name="DefenderSPDLabel" type="Label" parent="CombatProposalPanel/MarginContainer/VBoxContainer/StatsContainer/DefenderStats"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "SPD: 0"
|
||||||
|
|
||||||
|
[node name="ButtonContainer" type="HBoxContainer" parent="CombatProposalPanel/MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="FightButton" type="Button" parent="CombatProposalPanel/MarginContainer/VBoxContainer/ButtonContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Fight"
|
||||||
|
|
||||||
|
[node name="CancelButton" type="Button" parent="CombatProposalPanel/MarginContainer/VBoxContainer/ButtonContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Cancel"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ class_name CombatProposal extends Resource
|
|||||||
|
|
||||||
class CombatantStats:
|
class CombatantStats:
|
||||||
var unit: Unit
|
var unit: Unit
|
||||||
|
var max_hp: int
|
||||||
var hp: int
|
var hp: int
|
||||||
var sp: int
|
var sp: int
|
||||||
var hit: int
|
var hit: int
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
[ext_resource type="Script" uid="uid://csdcbi2gtwrly" path="res://scripts/camera_controller.gd" id="4_ww3c6"]
|
[ext_resource type="Script" uid="uid://csdcbi2gtwrly" path="res://scripts/camera_controller.gd" id="4_ww3c6"]
|
||||||
[ext_resource type="AudioStream" uid="uid://dsikulned64qt" path="res://assets/music/combat_bgm_01.OGG" id="5_ficdm"]
|
[ext_resource type="AudioStream" uid="uid://dsikulned64qt" path="res://assets/music/combat_bgm_01.OGG" id="5_ficdm"]
|
||||||
[ext_resource type="Script" uid="uid://cf4ivrcbky0s3" path="res://nodes/combat_system.gd" id="6_combat"]
|
[ext_resource type="Script" uid="uid://cf4ivrcbky0s3" path="res://nodes/combat_system.gd" id="6_combat"]
|
||||||
|
[ext_resource type="Script" path="res://nodes/strategy_phase.gd" id="7_strat"]
|
||||||
|
|
||||||
[node name="CombatTest" type="Node2D" unique_id=855645983]
|
[node name="CombatTest" type="Node2D" unique_id=855645983]
|
||||||
|
script = ExtResource("7_strat")
|
||||||
|
|
||||||
[node name="CombatUI" parent="." unique_id=329168107 instance=ExtResource("1_6gip4")]
|
[node name="CombatUI" parent="." unique_id=329168107 instance=ExtResource("1_6gip4")]
|
||||||
|
|
||||||
@@ -27,5 +29,3 @@ script = ExtResource("4_ww3c6")
|
|||||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1057500234]
|
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1057500234]
|
||||||
stream = ExtResource("5_ficdm")
|
stream = ExtResource("5_ficdm")
|
||||||
autoplay = true
|
autoplay = true
|
||||||
|
|
||||||
[connection signal="combat_requested" from="PlayerController" to="CombatSystem" method="process_combat"]
|
|
||||||
|
|||||||
@@ -1,13 +1,37 @@
|
|||||||
class_name CombatUI extends CanvasLayer
|
class_name CombatUI extends CanvasLayer
|
||||||
|
|
||||||
|
signal fight_confirmed(proposal: CombatProposal)
|
||||||
|
signal fight_cancelled
|
||||||
|
|
||||||
@onready var unit_panel: PanelContainer = %UnitPanel
|
@onready var unit_panel: PanelContainer = %UnitPanel
|
||||||
@onready var name_label: Label = %NameLabel
|
@onready var name_label: Label = %NameLabel
|
||||||
@onready var hp_bar: ProgressBar = %HPBar
|
@onready var hp_bar: ProgressBar = %HPBar
|
||||||
|
|
||||||
|
@onready var background_tint: ColorRect = %BackgroundTint
|
||||||
|
@onready var proposal_panel: PanelContainer = %CombatProposalPanel
|
||||||
|
@onready var atk_name_label: Label = %AttackerNameLabel
|
||||||
|
@onready var atk_hp_bar: ProgressBar = %AttackerHPBar
|
||||||
|
@onready var atk_atk_label: Label = %AttackerATKLabel
|
||||||
|
@onready var atk_def_label: Label = %AttackerDEFLabel
|
||||||
|
@onready var atk_hit_label: Label = %AttackerHITLabel
|
||||||
|
@onready var atk_spd_label: Label = %AttackerSPDLabel
|
||||||
|
@onready var def_name_label: Label = %DefenderNameLabel
|
||||||
|
@onready var def_hp_bar: ProgressBar = %DefenderHPBar
|
||||||
|
@onready var def_atk_label: Label = %DefenderATKLabel
|
||||||
|
@onready var def_def_label: Label = %DefenderDEFLabel
|
||||||
|
@onready var def_hit_label: Label = %DefenderHITLabel
|
||||||
|
@onready var def_spd_label: Label = %DefenderSPDLabel
|
||||||
|
@onready var fight_button: Button = %FightButton
|
||||||
|
@onready var cancel_button: Button = %CancelButton
|
||||||
|
|
||||||
var _selected_unit: Unit
|
var _selected_unit: Unit
|
||||||
|
var _current_proposal: CombatProposal
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
unit_panel.visible = false
|
unit_panel.visible = false
|
||||||
|
proposal_panel.visible = false
|
||||||
|
fight_button.pressed.connect(_on_fight_pressed)
|
||||||
|
cancel_button.pressed.connect(_on_cancel_pressed)
|
||||||
for unit: Unit in get_tree().get_nodes_in_group("units"):
|
for unit: Unit in get_tree().get_nodes_in_group("units"):
|
||||||
unit.unit_selected_changed.connect(_on_unit_selected_changed)
|
unit.unit_selected_changed.connect(_on_unit_selected_changed)
|
||||||
unit.unit_died.connect(_on_unit_died)
|
unit.unit_died.connect(_on_unit_died)
|
||||||
@@ -24,12 +48,21 @@ func _on_unit_died(unit: Unit) -> void:
|
|||||||
if _selected_unit == unit:
|
if _selected_unit == unit:
|
||||||
_selected_unit = null
|
_selected_unit = null
|
||||||
unit_panel.visible = false
|
unit_panel.visible = false
|
||||||
|
if _current_proposal:
|
||||||
|
if _current_proposal.attacker.unit == unit or _current_proposal.defender.unit == unit:
|
||||||
|
_hide_proposal()
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
if _selected_unit and is_instance_valid(_selected_unit):
|
if _selected_unit and is_instance_valid(_selected_unit):
|
||||||
hp_bar.max_value = _selected_unit.current_stats.max_hp
|
hp_bar.max_value = _selected_unit.current_stats.max_hp
|
||||||
hp_bar.value = _selected_unit.current_stats.current_hp
|
hp_bar.value = _selected_unit.current_stats.current_hp
|
||||||
|
|
||||||
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
if proposal_panel.visible and event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
|
||||||
|
_hide_proposal()
|
||||||
|
fight_cancelled.emit()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
func _on_unit_selected_changed(unit: Unit, selected: bool) -> void:
|
func _on_unit_selected_changed(unit: Unit, selected: bool) -> void:
|
||||||
if selected:
|
if selected:
|
||||||
_selected_unit = unit
|
_selected_unit = unit
|
||||||
@@ -40,3 +73,39 @@ func _on_unit_selected_changed(unit: Unit, selected: bool) -> void:
|
|||||||
else:
|
else:
|
||||||
_selected_unit = null
|
_selected_unit = null
|
||||||
unit_panel.visible = false
|
unit_panel.visible = false
|
||||||
|
|
||||||
|
func show_proposal(proposal: CombatProposal) -> void:
|
||||||
|
_current_proposal = proposal
|
||||||
|
var atk := proposal.attacker
|
||||||
|
var def := proposal.defender
|
||||||
|
atk_name_label.text = atk.unit.current_info.name
|
||||||
|
atk_hp_bar.max_value = atk.max_hp
|
||||||
|
atk_hp_bar.value = atk.hp
|
||||||
|
atk_atk_label.text = "ATK: %d" % atk.atk
|
||||||
|
atk_def_label.text = "DEF: %d" % atk.def
|
||||||
|
atk_hit_label.text = "HIT: %d%%" % atk.hit
|
||||||
|
atk_spd_label.text = "SPD: %d" % atk.spd
|
||||||
|
def_name_label.text = def.unit.current_info.name
|
||||||
|
def_hp_bar.max_value = def.max_hp
|
||||||
|
def_hp_bar.value = def.hp
|
||||||
|
def_atk_label.text = "ATK: %d" % def.atk
|
||||||
|
def_def_label.text = "DEF: %d" % def.def
|
||||||
|
def_hit_label.text = "HIT: %d%%" % def.hit
|
||||||
|
def_spd_label.text = "SPD: %d" % def.spd
|
||||||
|
background_tint.visible = true
|
||||||
|
proposal_panel.visible = true
|
||||||
|
|
||||||
|
func _hide_proposal() -> void:
|
||||||
|
background_tint.visible = false
|
||||||
|
proposal_panel.visible = false
|
||||||
|
_current_proposal = null
|
||||||
|
|
||||||
|
func _on_fight_pressed() -> void:
|
||||||
|
if _current_proposal:
|
||||||
|
var proposal := _current_proposal
|
||||||
|
_hide_proposal()
|
||||||
|
fight_confirmed.emit(proposal)
|
||||||
|
|
||||||
|
func _on_cancel_pressed() -> void:
|
||||||
|
_hide_proposal()
|
||||||
|
fight_cancelled.emit()
|
||||||
|
|||||||
Reference in New Issue
Block a user