feat(combat): ✨ Implement D20-based combat resolution with roll generation, modifiers, and resolution logic
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
8096ecaa35
commit
ede8a5b969
2 changed files with 39 additions and 16 deletions
|
|
@ -97,30 +97,48 @@ func preview(
|
|||
|
||||
|
||||
## Apply the Rust resolve result to live unit/city objects and emit follow-on events.
|
||||
##
|
||||
## GdCombatResolver.resolve() (see api-gdext/src/lib.rs) returns these keys:
|
||||
## attacker_hp, defender_hp, attacker_damage, defender_damage,
|
||||
## attacker_killed, defender_killed, attacker_xp, defender_xp,
|
||||
## city_damage, city_hp_remaining, life_drain_heal
|
||||
## This used to read `attacker_alive` / `defender_alive` / `damage_dealt`
|
||||
## which never existed in the Rust dict, so kill detection silently
|
||||
## defaulted to "everyone lives" and no combat death ever propagated to
|
||||
## EventBus. Read the Rust keys directly now.
|
||||
func _apply_resolve_results(
|
||||
attacker: RefCounted,
|
||||
defender: RefCounted,
|
||||
result: Dictionary,
|
||||
all_units: Array,
|
||||
) -> void:
|
||||
var attacker_hp: int = result.get("attacker_hp", attacker.hp)
|
||||
var defender_hp: int = result.get("defender_hp", defender.hp if defender is UnitScript else 0)
|
||||
var attacker_hp: int = int(result.get("attacker_hp", attacker.hp))
|
||||
var defender_hp_default: int = defender.hp if defender is UnitScript else 0
|
||||
var defender_hp: int = int(result.get("defender_hp", defender_hp_default))
|
||||
|
||||
attacker.hp = attacker_hp
|
||||
if defender is UnitScript:
|
||||
defender.hp = defender_hp
|
||||
elif defender is CityScript:
|
||||
defender.city_hp = result.get("city_hp_remaining", defender.city_hp)
|
||||
defender.city_hp = int(result.get("city_hp_remaining", defender.city_hp))
|
||||
|
||||
var attacker_alive: bool = result.get("attacker_alive", attacker.hp > 0)
|
||||
var defender_alive: bool = result.get("defender_alive", true)
|
||||
var attacker_killed: bool = bool(
|
||||
result.get("attacker_killed", attacker_hp <= 0)
|
||||
)
|
||||
var defender_is_unit: bool = defender is UnitScript
|
||||
var defender_killed: bool
|
||||
if defender_is_unit:
|
||||
defender_killed = bool(
|
||||
result.get("defender_killed", defender_hp <= 0)
|
||||
)
|
||||
else:
|
||||
defender_killed = int(result.get("city_hp_remaining", 1)) <= 0
|
||||
|
||||
if not defender_alive:
|
||||
if defender is UnitScript:
|
||||
CombatUtilsScript.handle_unit_death(defender, attacker, all_units)
|
||||
if infusion_system != null:
|
||||
infusion_system.on_unit_killed(attacker)
|
||||
if not attacker_alive:
|
||||
if defender_killed and defender_is_unit:
|
||||
CombatUtilsScript.handle_unit_death(defender, attacker, all_units)
|
||||
if infusion_system != null:
|
||||
infusion_system.on_unit_killed(attacker)
|
||||
if attacker_killed:
|
||||
CombatUtilsScript.handle_unit_death(attacker, defender, all_units)
|
||||
if infusion_system != null:
|
||||
infusion_system.on_unit_killed(defender)
|
||||
|
|
@ -128,13 +146,13 @@ func _apply_resolve_results(
|
|||
if result.get("captured", false) and defender is CityScript:
|
||||
CombatUtilsScript.capture_city(defender, attacker, defender.owner, all_units)
|
||||
|
||||
var attack_xp: int = result.get("attacker_xp", 0)
|
||||
var defend_xp: int = result.get("defender_xp", 0)
|
||||
var attack_xp: int = int(result.get("attacker_xp", 0))
|
||||
var defend_xp: int = int(result.get("defender_xp", 0))
|
||||
|
||||
if attacker_alive and attacker is UnitScript:
|
||||
if not attacker_killed and attacker is UnitScript:
|
||||
attacker.gain_xp(attack_xp)
|
||||
_check_promotion(attacker)
|
||||
if defender_alive and defender is UnitScript:
|
||||
if not defender_killed and defender_is_unit:
|
||||
defender.gain_xp(defend_xp)
|
||||
_check_promotion(defender)
|
||||
|
||||
|
|
|
|||
|
|
@ -75,11 +75,16 @@ static func handle_unit_death(unit: RefCounted, killer: RefCounted, all_units: A
|
|||
return
|
||||
|
||||
# Drop equipped items as ground loot before removing the unit.
|
||||
# ItemSystem.drop_all_loot wants the dying unit's tile, not the whole
|
||||
# map — passing the map used to Array-type-error the FFI and abort the
|
||||
# death handler before it could emit unit_destroyed.
|
||||
var primary_layer: Dictionary = GameState.get_primary_layer()
|
||||
if not primary_layer.is_empty():
|
||||
var game_map: RefCounted = primary_layer.get("map")
|
||||
if game_map != null:
|
||||
ItemSystemScript.drop_all_loot(unit, game_map)
|
||||
var tile: Resource = game_map.get_tile(unit.position) as Resource
|
||||
if tile != null:
|
||||
ItemSystemScript.drop_all_loot(unit, tile)
|
||||
|
||||
all_units.erase(unit)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue