# Combat System 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:** Create a stateless combat system that produces pre-combat proposals and resolves attacks between two units. **Architecture:** A `CombatProposal` resource holds snapshot stats for attacker and defender. A `CombatSystem` node exposes `create_proposal()` to build proposals and `apply_proposal()` to resolve combat, modifying unit HP directly. The system is added to the strategy phase scene tree. **Tech Stack:** Godot 4.6, GDScript --- ## File Structure | File | Responsibility | |------|---------------| | `resources/resource_definitions/combat_proposal.gd` | CombatProposal resource with CombatantStats inner class — holds unit references and snapshot stats | | `nodes/combat_system.gd` | CombatSystem node — `create_proposal()` and `apply_proposal()` methods | | `scenes/strategy_phase.tscn` | Scene tree — add CombatSystem node | --- ### Task 1: Create the CombatProposal resource **Files:** - Create: `resources/resource_definitions/combat_proposal.gd` - [ ] **Step 1: Create the CombatantStats inner class and CombatProposal resource** ```gdscript # resources/resource_definitions/combat_proposal.gd class_name CombatProposal extends Resource class CombatantStats: var unit: Unit var hp: int var sp: int var hit: int var atk: int var def: int var spd: int var attacker: CombatantStats var defender: CombatantStats ``` - [ ] **Step 2: Verify the file loads without errors** Open the Godot editor and check the Output panel for parse errors. Confirm `CombatProposal` appears as a recognized class by typing it in any script's autocomplete. --- ### Task 2: Create the CombatSystem node **Files:** - Create: `nodes/combat_system.gd` - Reference: `resources/resource_definitions/combat_proposal.gd` - Reference: `resources/resource_definitions/unit_stats.gd` - Reference: `nodes/unit.gd` - [ ] **Step 1: Create combat_system.gd with create_proposal** ```gdscript # nodes/combat_system.gd class_name CombatSystem extends Node func create_proposal(attacker: Unit, defender: Unit) -> CombatProposal: var proposal := CombatProposal.new() proposal.attacker = _snapshot(attacker, defender) proposal.defender = _snapshot(defender, attacker) return proposal func _snapshot(unit: Unit, opponent: Unit) -> CombatProposal.CombatantStats: var stats := CombatProposal.CombatantStats.new() stats.unit = unit 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 2: Add apply_proposal method** Append to `nodes/combat_system.gd`: ```gdscript func apply_proposal(proposal: CombatProposal) -> void: var atk_stats := proposal.attacker var def_stats := proposal.defender var atk_unit := atk_stats.unit var def_unit := def_stats.unit # Attacker strikes var atk_roll := randi_range(1, 100) if atk_roll <= atk_stats.hit: var damage := maxi(atk_stats.atk - def_stats.def, 0) def_unit.current_stats.current_hp -= damage # Counterattack if defender survives if def_unit.current_stats.current_hp > 0: var def_roll := randi_range(1, 100) if def_roll <= def_stats.hit: var damage := maxi(def_stats.atk - atk_stats.def, 0) atk_unit.current_stats.current_hp -= damage ``` - [ ] **Step 3: Verify the file loads without errors** Open the Godot editor and check the Output panel for parse errors on `combat_system.gd`. --- ### Task 3: Add CombatSystem to the strategy phase scene **Files:** - Modify: `scenes/strategy_phase.tscn` - [ ] **Step 1: Add CombatSystem node and script reference to the scene** Add a new `ext_resource` entry for the combat_system.gd script and a new node to the scene file. Append after the existing ext_resources and add the node as a child of the root `CombatTest` node: New ext_resource (use the next available id, `6_xxxxx` — the exact suffix will be assigned): ``` [ext_resource type="Script" path="res://nodes/combat_system.gd" id="6_combat"] ``` New node (add after the PlayerController node block): ``` [node name="CombatSystem" type="Node" parent="."] script = ExtResource("6_combat") ``` The resulting scene tree order: ``` CombatTest (Node2D) ├─ CombatUI ├─ CombatMap ├─ PlayerController ├─ CombatSystem <-- new ├─ Camera2D └─ AudioStreamPlayer ``` - [ ] **Step 2: Verify in the Godot editor** Open `scenes/strategy_phase.tscn` in the editor. Confirm: - CombatSystem node appears in the scene tree - No errors in the Output panel - The scene runs without crashes --- ### Task 4: Manual integration verification - [ ] **Step 1: Verify create_proposal works** Temporarily add the following to `CombatSystem._ready()` to test proposal creation (remove after verification): ```gdscript func _ready() -> void: # Temporary test — remove after verification var units := get_tree().get_nodes_in_group("units") if units.size() >= 2: var proposal := create_proposal(units[0], units[1]) print("Proposal created:") print(" Attacker: ", proposal.attacker.unit.current_info.name, " HP=", proposal.attacker.hp, " ATK=", proposal.attacker.atk, " DEF=", proposal.attacker.def, " HIT=", proposal.attacker.hit) print(" Defender: ", proposal.defender.unit.current_info.name, " HP=", proposal.defender.hp, " ATK=", proposal.defender.atk, " DEF=", proposal.defender.def, " HIT=", proposal.defender.hit) ``` Run the scene with at least 2 units deployed. Check the Output panel for correct stat snapshots. - [ ] **Step 2: Verify apply_proposal works** Extend the temporary `_ready()` to also apply the proposal: ```gdscript print("Before combat: Attacker HP=", proposal.attacker.unit.current_stats.current_hp, " Defender HP=", proposal.defender.unit.current_stats.current_hp) apply_proposal(proposal) print("After combat: Attacker HP=", proposal.attacker.unit.current_stats.current_hp, " Defender HP=", proposal.defender.unit.current_stats.current_hp) ``` Run multiple times (results will vary due to hit rolls). Confirm: - Defender HP decreases when attacker hits - Attacker HP decreases when defender counterattacks - HP never increases (no negative damage) - If defender dies (HP <= 0), no counterattack occurs - [ ] **Step 3: Remove temporary test code** Delete the entire `_ready()` method from `combat_system.gd`.