diff --git a/src/game/engine/src/modules/combat/combat_resolver.gd b/src/game/engine/src/modules/combat/combat_resolver.gd index 23c897e5..3abd0594 100644 --- a/src/game/engine/src/modules/combat/combat_resolver.gd +++ b/src/game/engine/src/modules/combat/combat_resolver.gd @@ -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) diff --git a/src/game/engine/src/modules/combat/combat_utils.gd b/src/game/engine/src/modules/combat/combat_utils.gd index 37e3eef4..04ee1d40 100644 --- a/src/game/engine/src/modules/combat/combat_utils.gd +++ b/src/game/engine/src/modules/combat/combat_utils.gd @@ -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)