88 lines
3.4 KiB
Markdown
88 lines
3.4 KiB
Markdown
# Combat System
|
||
|
||
## Overview
|
||
|
||
A stateless CombatSystem node that creates pre-combat proposals and applies combat resolution between two units. The system encapsulates all combat stat calculation so other systems don't need to know proposal logic.
|
||
|
||
## CombatProposal Resource
|
||
|
||
`resources/resource_definitions/combat_proposal.gd`
|
||
|
||
A Resource containing two sides — attacker and defender. Each side holds:
|
||
|
||
- A reference to the `Unit` node
|
||
- Pre-combat snapshot stats via an inner class `CombatantStats`:
|
||
- `hp: int` — from `current_stats.current_hp`
|
||
- `sp: int` — from `current_stats.current_sp`
|
||
- `hit: int` — calculated as `own hit - opponent's eva`
|
||
- `atk: int` — from `current_stats.phys_atk`
|
||
- `def: int` — from `current_stats.phys_def`
|
||
- `spd: int` — from `current_stats.spd`
|
||
|
||
### Stat Calculation
|
||
|
||
For the **attacker** side:
|
||
- `atk` = attacker's `phys_atk`
|
||
- `def` = attacker's `phys_def`
|
||
- `hit` = attacker's `hit` - defender's `eva`
|
||
- `hp`, `sp`, `spd` copied directly from attacker's `current_stats`
|
||
|
||
For the **defender** side:
|
||
- `atk` = defender's `phys_atk`
|
||
- `def` = defender's `phys_def`
|
||
- `hit` = defender's `hit` - attacker's `eva`
|
||
- `hp`, `sp`, `spd` copied directly from defender's `current_stats`
|
||
|
||
In the future, different attacks may swap between physical and magic stats, but for now only physical stats are used.
|
||
|
||
## CombatSystem Node
|
||
|
||
`nodes/combat_system.gd`
|
||
|
||
A `Node` added to the strategy phase scene tree. Stateless — operates purely on units passed in.
|
||
|
||
### `create_proposal(attacker: Unit, defender: Unit) -> CombatProposal`
|
||
|
||
- Reads both units' `current_stats`
|
||
- Builds a `CombatProposal` with snapshot stats for each side
|
||
- Applies cross-calculations (hit - eva)
|
||
- Returns the proposal without modifying any unit state
|
||
|
||
### `apply_proposal(proposal: CombatProposal) -> void`
|
||
|
||
1. **Attacker strikes:** Roll random int 1–100. If roll <= attacker's calculated `hit`, apply `max(attacker.atk - defender.def, 0)` damage to the defender Unit's `current_stats.current_hp` (modifies the actual unit, not the snapshot).
|
||
2. **Counterattack:** If the defender Unit's `current_stats.current_hp > 0` after the attack, roll 1–100 for defender. If roll <= defender's calculated `hit`, apply `max(defender.atk - attacker.def, 0)` damage to the attacker Unit's `current_stats.current_hp`.
|
||
|
||
- Damage has a floor of 0 (no negative damage / healing).
|
||
- Range is ignored for now. In the future, counterattack will depend on whether the defender has skills at appropriate range (defaulting to a "defend" action if not).
|
||
|
||
## Scene Integration
|
||
|
||
The CombatSystem node is added to `scenes/strategy_phase.tscn` as a sibling of PlayerController, CombatMap, etc. No exports needed.
|
||
|
||
```
|
||
CombatTest (Node2D)
|
||
├─ CombatUI
|
||
├─ CombatMap
|
||
├─ PlayerController
|
||
├─ CombatSystem <-- new
|
||
├─ Camera2D
|
||
└─ AudioStreamPlayer
|
||
```
|
||
|
||
## Files Changed
|
||
|
||
| File | Change |
|
||
|------|--------|
|
||
| `resources/resource_definitions/combat_proposal.gd` | New — CombatProposal resource with CombatantStats inner class |
|
||
| `nodes/combat_system.gd` | New — CombatSystem node with `create_proposal` and `apply_proposal` |
|
||
| `scenes/strategy_phase.tscn` | Add CombatSystem node to scene tree |
|
||
|
||
## Out of Scope
|
||
|
||
- Magic attack/defense selection (future: different attack types swap stats)
|
||
- Range-based counterattack eligibility (future: defend action when no skills in range)
|
||
- Critical hits
|
||
- UI for displaying the combat proposal
|
||
- Animations or visual feedback for combat resolution
|