extends Node const StatResolverScript := preload("res://scripts/resolvers/stat_resolver.gd") const CombatResolverScript := preload("res://scripts/resolvers/combat_resolver.gd") var _resolved_pairs: Dictionary = {} func _physics_process(_delta: float) -> void: _resolved_pairs.clear() func resolve_damage(base_attack: float, action: Resource, judgement: Dictionary, buffs: Variant = null, burst: Variant = null) -> float: return StatResolverScript.resolve_damage(base_attack, action, judgement, buffs, burst) func resolve_cost(action: Resource, judgement: Variant = {}, effects: Variant = null) -> float: return StatResolverScript.resolve_cost(action, judgement, effects) func resolve_reward(action: Resource, judgement: Dictionary, effects: Variant = null) -> float: return StatResolverScript.resolve_reward(action, judgement, effects) func resolve_move(action: Resource, judgement: Dictionary, burst: Variant = null) -> Vector2: return StatResolverScript.resolve_move(action, judgement, burst) func resolve_action_snapshot(action: Resource, judgement: Dictionary = {}, effects: Variant = null) -> Dictionary: return StatResolverScript.resolve_action_snapshot(action, judgement, effects) func resolve_hit(emitter: Area2D, receiver: Area2D) -> Dictionary: if emitter == null or receiver == null: return {} if _receiver_is_dead(receiver): return {} var key := "%s:%s" % [emitter.get_instance_id(), receiver.get_instance_id()] if _resolved_pairs.has(key): return _resolved_pairs[key] var result := CombatResolverScript.resolve_hit(emitter, receiver) _resolved_pairs[key] = result _apply_interrupt(receiver, result) _apply_target_health(receiver, result) _apply_knockback(receiver, result) if receiver.has_method("receive_hit"): receiver.call("receive_hit", result) _apply_action_on_hit_effects(receiver, result) _dispatch_effect_events(emitter, receiver, result) var bus := _event_bus_or_null() if bus != null and bus.has_signal("hit_confirmed"): bus.emit_signal("hit_confirmed", result) return result func _apply_target_health(receiver: Area2D, result: Dictionary) -> void: var target := receiver.get_parent() if target == null: return var health_component := target.get_node_or_null("HealthComponent") if health_component != null and health_component.has_method("receive_hit"): health_component.call("receive_hit", result) func _apply_interrupt(receiver: Area2D, result: Dictionary) -> void: if not bool(result.get("interrupts", true)): return var target := receiver.get_parent() if target == null: return var action_controller := target.get_node_or_null("ActionController") if action_controller != null and action_controller.has_method("cancel_current"): action_controller.call("cancel_current", _cancel_reason_for_hit(target, result)) func _cancel_reason_for_hit(target: Node, result: Dictionary) -> StringName: var health_component := target.get_node_or_null("HealthComponent") if health_component != null and int(result.get("damage", 0)) > 0: var current := int(health_component.get("current")) if current > 0 and int(result.get("damage", 0)) >= current: return &"death" return &"interrupt" func _apply_knockback(receiver: Area2D, result: Dictionary) -> void: var knockback := result.get("knockback", Vector2.ZERO) as Vector2 if knockback == Vector2.ZERO: return var target := receiver.get_parent() if target == null: return knockback.x = _knockback_x_away_from_attacker(target, result, knockback.x) knockback.x *= _knockback_velocity_scale_for_receiver(target) var movement_motor := target.get_node_or_null("MovementMotor") if movement_motor != null and movement_motor.has_method("apply_knockback"): movement_motor.call("apply_knockback", knockback) ## 受击方可选钩子(鸭子类型,同 shrugs_off_hit):knockback_distance_taken_mult() ## 返回"水平滑行距离"倍数。线性摩擦下距离 ∝ 初速²,距离 ×N 需初速 ×√N。 func _knockback_velocity_scale_for_receiver(target: Node) -> float: if not target.has_method("knockback_distance_taken_mult"): return 1.0 return sqrt(maxf(float(target.call("knockback_distance_taken_mult")), 0.0)) func _knockback_x_away_from_attacker(target: Node, result: Dictionary, knockback_x: float) -> float: # resolve_knockback 输出无符号水平分量(数值契约,禁改);施加时按受击者 # 相对攻击者的位置定向为"背离攻击者"。dx 恰好为 0 时沿用历史 +X 语义。 if not (target is Node2D): return knockback_x var from = result.get("from") if not (from is Vector2): return knockback_x var dx := (target as Node2D).global_position.x - (from as Vector2).x if dx == 0.0: return knockback_x return signf(dx) * absf(knockback_x) func _receiver_is_dead(receiver: Area2D) -> bool: var target := receiver.get_parent() if target == null: return false var state_machine := target.get_node_or_null("StateMachine") if state_machine == null or not state_machine.has_method("build_context"): return false return StringName(str(state_machine.call("build_context").get("life_state", &"Alive"))) == &"Dead" func _dispatch_effect_events(emitter: Area2D, receiver: Area2D, result: Dictionary) -> void: _dispatch_actor_effect_event(emitter.get_parent(), &"on_hit", result) _dispatch_actor_effect_event(receiver.get_parent(), &"on_hurt", result) if _result_defense_state(result) == &"Parrying": _dispatch_actor_effect_event(receiver.get_parent(), &"on_parry_success", result) if int(result.get("damage", 0)) > 0 and _receiver_is_dead(receiver): _dispatch_actor_effect_event(emitter.get_parent(), &"on_kill", result) func _apply_action_on_hit_effects(receiver: Area2D, result: Dictionary) -> void: var target := receiver.get_parent() if target == null: return var effect_container := target.get_node_or_null("EffectContainer") if effect_container == null or not effect_container.has_method("add_effect"): return var action: Resource = result.get("action", null) as Resource if action == null: return var effects = action.get("on_hit_effects") if not effects is Array: return for definition: Resource in effects: if definition != null: effect_container.call("add_effect", definition, StringName(str(action.get("id")))) func _dispatch_actor_effect_event(actor: Node, event_name: StringName, result: Dictionary) -> void: if actor == null: return var effect_container := actor.get_node_or_null("EffectContainer") if effect_container != null and effect_container.has_method("dispatch_event"): effect_container.call("dispatch_event", event_name, {"result": result}) func _result_defense_state(result: Dictionary) -> StringName: var defense = result.get("defense", {}) if defense is Dictionary: return StringName(str(defense.get("defense_state", &"Vulnerable"))) return &"Vulnerable" func _event_bus_or_null() -> Node: if not is_inside_tree(): return null return get_tree().root.get_node_or_null("EventBus")