6.5 KiB
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
# 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
# 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:
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):
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:
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.