feat(game-engine): ✨ Add D20 attribute system and movement domain mechanics to Unit class
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
035a07b069
commit
47c59b2a05
1 changed files with 57 additions and 5 deletions
|
|
@ -27,6 +27,9 @@ var type_id: String = ""
|
|||
var display_name: String = ""
|
||||
## Combat type string from JSON data, e.g. "melee", "civilian", "ranged".
|
||||
var unit_type: String = ""
|
||||
## Movement domain string from JSON data, e.g. "land", "flying", "naval".
|
||||
## Consumed by Pathfinder._is_passable to decide terrain passability.
|
||||
var domain: String = "land"
|
||||
|
||||
# ── Capability flags ─────────────────────────────────────────────────
|
||||
## True if this unit can found cities (derived from "found_city" keyword).
|
||||
|
|
@ -64,10 +67,30 @@ var fortified_turns: int = 0
|
|||
var xp: int = 0
|
||||
var level: int = 1
|
||||
|
||||
# ── D20 attribute stubs ───────────────────────────────────────────────
|
||||
## These fields are probed by CombatResolver._has_d20_attributes to decide
|
||||
## whether to send D20-derived stats or flat JSON stats to Rust mc-combat.
|
||||
## Arena units have no D20 profile, so all stubs default to 0 and combat
|
||||
## takes the flat-stats path. Real D20 units will populate these later.
|
||||
var base_str: int = 0
|
||||
var base_dex: int = 0
|
||||
var base_con: int = 0
|
||||
var base_int: int = 0
|
||||
var bonus_str: int = 0
|
||||
var bonus_dex: int = 0
|
||||
var bonus_con: int = 0
|
||||
var bonus_int: int = 0
|
||||
var bonus_attack: int = 0
|
||||
var bonus_defense: int = 0
|
||||
var veteran_level: int = 0
|
||||
var formation_count: int = 1
|
||||
var stimulant_penalty: int = 0
|
||||
|
||||
# ── Promotions, items, magic ──────────────────────────────────────────
|
||||
var promo_ids: Array[String] = []
|
||||
## Equipped items: Array[Dictionary] {item_id: String, charges: int}.
|
||||
var equipped_items: Array = []
|
||||
## Equipped items: each entry is {item_id: String, charges_remaining: int}.
|
||||
## Must stay strongly typed — GdItemSystem's FFI rejects untyped arrays.
|
||||
var equipped_items: Array[Dictionary] = []
|
||||
## Currently active enchantment IDs (non-channeled).
|
||||
var infusions: Array[String] = []
|
||||
## Single channeled enchantment (mage-anchored) — empty if none active.
|
||||
|
|
@ -104,7 +127,20 @@ func _populate_from_data() -> void:
|
|||
max_movement = data.get("movement", 2)
|
||||
movement_remaining = max_movement
|
||||
vision = data.get("vision", 2)
|
||||
unit_type = data.get("combat_type", "")
|
||||
# JSON data authored for Age of Dwarves uses `unit_type` for the role
|
||||
# ("military"/"civilian"/"support") and `combat_type` for the weapon
|
||||
# profile ("melee"/"ranged"/"siege"). Earlier iterations read
|
||||
# `combat_type` into this field, which silently produced an empty
|
||||
# string for every dwarf unit and broke downstream consumers like
|
||||
# the pathfinder's unit_type gate. Read both fields now — prefer
|
||||
# `combat_type` when present (some reference units still use it) and
|
||||
# fall back to `unit_type`, so arena units always report a non-empty
|
||||
# role string. `domain` carries the pathfinder-facing passability tag.
|
||||
var combat_type: String = String(data.get("combat_type", ""))
|
||||
if combat_type.is_empty():
|
||||
combat_type = String(data.get("unit_type", ""))
|
||||
unit_type = combat_type
|
||||
domain = String(data.get("domain", "land"))
|
||||
if display_name.is_empty():
|
||||
display_name = data.get("name", unit_id.capitalize())
|
||||
var is_founder: bool = (
|
||||
|
|
@ -188,11 +224,27 @@ func get_attack() -> int:
|
|||
func get_defense() -> int:
|
||||
var total: int = defense
|
||||
total += UnitStatHelpers.get_promotion_stat(promo_ids, "defense_bonus")
|
||||
if is_fortified:
|
||||
total += mini(fortified_turns, 2) * 2
|
||||
total += get_fortification_bonus()
|
||||
return total
|
||||
|
||||
|
||||
## Defensive bonus earned by fortifying in place. Capped at 2 turns of
|
||||
## accumulation (same formula that was inlined in `get_defense`). Read by
|
||||
## CombatResolver._build_unit_dict when packing the attacker/defender
|
||||
## dict for the Rust combat FFI.
|
||||
func get_fortification_bonus() -> int:
|
||||
if not is_fortified:
|
||||
return 0
|
||||
return mini(fortified_turns, 2) * 2
|
||||
|
||||
|
||||
## Alias for `max_hp` used by CombatUtils.handle_unit_death's soul gem
|
||||
## resurrection path. Kept as a method so the death code doesn't depend on
|
||||
## the exact field name.
|
||||
func get_max_hp() -> int:
|
||||
return max_hp
|
||||
|
||||
|
||||
func fortify() -> void:
|
||||
if is_civilian():
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue