Initial project sync
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
class_name CombatResolver
|
||||
extends RefCounted
|
||||
|
||||
const StatResolverScript := preload("res://scripts/resolvers/stat_resolver.gd")
|
||||
const DefenseResolverScript := preload("res://scripts/resolvers/defense_resolver.gd")
|
||||
|
||||
|
||||
static func resolve_hit(emitter: Area2D, receiver: Area2D) -> Dictionary:
|
||||
var action: Resource = emitter.get("action_context") as Resource
|
||||
var judgement: Dictionary = emitter.get("judgement_context")
|
||||
var receiver_context := _receiver_context(receiver, emitter)
|
||||
var attacker_effects := _owner_effect_container(emitter)
|
||||
var defense := DefenseResolverScript.resolve_effective_defense(receiver_context, _action_tags(action))
|
||||
var raw_damage := StatResolverScript.resolve_damage(float(emitter.get("damage")), action, judgement, attacker_effects, null)
|
||||
var final_damage := int(round(raw_damage * float(defense.get("damage_mult", 1.0))))
|
||||
var attacker_interrupts := _attacker_interrupts(emitter)
|
||||
# 受击方免压制钩子(2026-07-05 定案):Boss 对不耗能量的攻击恒霸体——
|
||||
# 伤害照常结算,但打断与击退一并取消。
|
||||
if attacker_interrupts and _receiver_shrugs_off(receiver, action):
|
||||
attacker_interrupts = false
|
||||
var knockback := _resolved_knockback(emitter, action, judgement, attacker_effects, defense) if attacker_interrupts else Vector2.ZERO
|
||||
return {
|
||||
"damage": final_damage,
|
||||
"hit_type": StringName(str(emitter.get("hit_type"))),
|
||||
"action": action,
|
||||
"action_tags": _action_tags(action),
|
||||
"judgement": judgement,
|
||||
"from": emitter.global_position,
|
||||
"emitter": emitter,
|
||||
"receiver": receiver,
|
||||
"defense": defense,
|
||||
"interrupts": bool(defense.get("interrupts", true)) and attacker_interrupts,
|
||||
"knockback": knockback,
|
||||
}
|
||||
|
||||
|
||||
static func _attacker_interrupts(emitter: Area2D) -> bool:
|
||||
var value = emitter.get("attacker_interrupts")
|
||||
if value is bool:
|
||||
return value
|
||||
return true
|
||||
|
||||
|
||||
static func _receiver_shrugs_off(receiver: Area2D, action: Resource) -> bool:
|
||||
var owner := receiver.get_parent()
|
||||
if owner == null or not owner.has_method("shrugs_off_hit"):
|
||||
return false
|
||||
return bool(owner.call("shrugs_off_hit", action))
|
||||
|
||||
|
||||
static func _receiver_context(receiver: Area2D, emitter: Area2D = null) -> Dictionary:
|
||||
var owner := receiver.get_parent()
|
||||
if owner != null:
|
||||
var context := {}
|
||||
var state_machine := owner.get_node_or_null("StateMachine")
|
||||
if state_machine != null and state_machine.has_method("build_context"):
|
||||
context = state_machine.call("build_context")
|
||||
var effect_container := owner.get_node_or_null("EffectContainer")
|
||||
if effect_container != null:
|
||||
context["effect_container"] = effect_container
|
||||
var block_judgement := _receiver_block_judgement(owner)
|
||||
if not block_judgement.is_empty():
|
||||
context["block_judgement"] = block_judgement
|
||||
if emitter != null and owner is Node2D:
|
||||
context["attack_from_front"] = _attack_from_front(owner as Node2D, emitter)
|
||||
return context
|
||||
return {}
|
||||
|
||||
|
||||
static func _receiver_block_judgement(owner: Node) -> StringName:
|
||||
var action_controller := owner.get_node_or_null("ActionController")
|
||||
if action_controller == null:
|
||||
return &""
|
||||
var intent = action_controller.get("current_intent")
|
||||
if intent == null:
|
||||
return &""
|
||||
var judgement = intent.get("judgement")
|
||||
if judgement is Dictionary and judgement.has("label"):
|
||||
return StringName(str(judgement["label"]))
|
||||
return &""
|
||||
|
||||
|
||||
static func _attack_from_front(owner: Node2D, emitter: Area2D) -> bool:
|
||||
var heading = owner.get("heading")
|
||||
if not (heading is Vector2) or is_zero_approx((heading as Vector2).x):
|
||||
return true
|
||||
var to_attacker := emitter.global_position.x - owner.global_position.x
|
||||
if is_zero_approx(to_attacker):
|
||||
return true
|
||||
return signf(to_attacker) == signf((heading as Vector2).x)
|
||||
|
||||
|
||||
static func _owner_effect_container(area: Area2D) -> Node:
|
||||
var source = area.get("source_actor")
|
||||
if source is Node and is_instance_valid(source):
|
||||
return (source as Node).get_node_or_null("EffectContainer")
|
||||
var owner := area.get_parent()
|
||||
if owner == null:
|
||||
return null
|
||||
return owner.get_node_or_null("EffectContainer")
|
||||
|
||||
|
||||
static func _action_tags(action: Resource) -> Array[StringName]:
|
||||
var result: Array[StringName] = []
|
||||
if action != null and action.get("action_tags") is Array:
|
||||
for tag: Variant in action.get("action_tags"):
|
||||
result.append(StringName(str(tag)))
|
||||
return result
|
||||
|
||||
|
||||
static func _resolved_knockback(emitter: Area2D, action: Resource, judgement: Dictionary, attacker_effects: Variant, defense: Dictionary) -> Vector2:
|
||||
var defense_state := StringName(str(defense.get("defense_state", &"Vulnerable")))
|
||||
if defense_state == &"Invincible" or defense_state == &"SuperArmor":
|
||||
return Vector2.ZERO
|
||||
return StatResolverScript.resolve_knockback(action, judgement, _base_knockback(emitter), attacker_effects)
|
||||
|
||||
|
||||
static func _base_knockback(emitter: Area2D) -> Vector2:
|
||||
var value = emitter.get("base_knockback")
|
||||
if value is Vector2:
|
||||
return value
|
||||
return Vector2.ZERO
|
||||
Reference in New Issue
Block a user