extends Node3D class_name Battle @export var hit_back_limit_curve: Curve @onready var character: Character = (get_owner() as Character) @onready var status: Status = (%Status as Status) @onready var skill: Skill = (%Skill as Skill) @onready var move: Move = (%Move as Move) class HitResult: var is_hit: bool var is_break: bool func attack1() -> void: if not status.skill_cfg: return var attack: AttackCfg = status.skill_cfg.get_attack1() var attack_box: AttackBoxCfg = status.skill_cfg.get_attack1_box() _attack(attack, attack_box) func attack2() -> void: if not status.skill_cfg: return var attack: AttackCfg = status.skill_cfg.get_attack2() var attack_box: AttackBoxCfg = status.skill_cfg.get_attack2_box() _attack(attack, attack_box) func character_attack1() -> bool: var attack: AttackCfg = status.cfg.get_attack1() var attack_box: AttackBoxCfg = status.cfg.get_attack1_box() return _attack(attack, attack_box) func character_attack2() -> bool: var attack: AttackCfg = status.cfg.get_attack2() var attack_box: AttackBoxCfg = status.cfg.get_attack2_box() return _attack(attack, attack_box) func _attack(attack: AttackCfg, attack_box: AttackBoxCfg) -> bool: if not attack or not attack_box: return false var pos: Vector3 = character.pos() var attack_dir: Vector2 = status.skill_dir.normalized() var result: Array[Character] if attack_box.is_throw: var target: Character = Global.character_mgr.get_character(status.throw_target) if target: result = [target] else: var offset_xz: Vector2 = attack_dir * attack_box.offset.x var offset_y: float = attack_box.offset.y var offset: Vector3 = Vector3(offset_xz.x, offset_y, offset_xz.y) var shape: Shape3D = attack_box.shape _debug_draw_attack_box(shape, offset) result = Util.raycast_character(shape, pos+offset, attack_dir) var is_hit: bool = false for target: Character in result: if target == null: is_hit = true continue if target.team() == character.team(): continue var hit_result: HitResult = add_attack(character.id(), target.id(), attack_dir, attack) on_attack_hit(hit_result) is_hit = true if !is_hit and !attack.is_force_pause: skill.on_attack_miss() return is_hit func _debug_draw_attack_box(shape: Shape3D, offset: Vector3) -> void: if not get_tree().debug_collisions_hint: return if shape is BoxShape3D: var scale: Vector3 = shape.size character.cast_particle(ResourceManager.particle_debug_box, false, offset, scale) elif shape is CylinderShape3D: var scale: Vector3 = Vector3(shape.radius, shape.height, shape.radius) character.cast_particle(ResourceManager.particle_debug_cylinder, false, offset, scale) return func add_attack(from: int, to: int, dir: Vector2, attack: AttackCfg) -> HitResult: var hit_result: HitResult = HitResult.new() var character_from: Character = Global.character_mgr.get_character(from) as Character var character_to: Character = Global.character_mgr.get_character(to) as Character if !character_from or !character_to: return hit_result var cfg_from: CharacterCfg = character_from.cfg() var cfg_to: CharacterCfg = character_to.cfg() var is_stun = character_to.get_status("is_stun") var hp = character_to.get_status("hp") var shield = character_to.get_status("shield") var has_shield = shield > 0 var is_floating: bool = attack.is_floating or not character_to.get_status("is_on_floor") var is_hit_down: bool = attack.is_hit_down and not character_to.get_status("is_on_floor") var is_rebound: bool = attack.is_rebound var is_break_shield: bool = false var is_break_stun: bool = false var is_block: bool = false var is_kill: bool = false var is_break_skill: bool = false var pause_time: float = attack.pause_time var is_remote: bool = character_from != character_from.get_character_owner() #造成伤害 var damage: float = attack.damage_rate * cfg_from.attack if has_shield: damage = min(shield, damage) character_to.set_status("shield", shield-damage) is_break_shield = damage == shield if is_break_shield: character_to.remove_buff("shield_recover_cd") character_to.remove_buff("shield_recover") has_shield = false else: character_to.remove_buff("shield_recover") character_to.add_buff("shield_recover_cd", cfg_to.shield.recover_cd) else: damage = min(hp, damage) character_to.set_status("hp", hp-damage) is_kill = damage == hp if is_kill: character_to.add_buff("die", 1) #眩晕值累加 if not is_stun: var stun_damage: float = attack.stun_attack var stun = character_to.get_status("stun") var stun_max = character_to.get_status("stun_max") stun_damage = min(stun_max-stun, stun_damage) character_to.set_status("stun", stun+stun_damage) is_break_stun = stun_damage == stun_max-stun if is_break_stun: is_stun = true character_to.set_status("is_stun", true) character_to.remove_buff("stun_recover_cd") character_to.remove_buff("stun_recover") character_to.add_buff("stun_recover_break_cd", cfg_to.stun.recover_break_cd) else: character_to.remove_buff("stun_recover") character_to.add_buff("stun_recover_cd", cfg_to.stun.recover_cd) #mp累加 if not is_remote: character_from.add_mp_sub(damage * cfg_from.mp.add_rate_attack, true) character_to.add_mp_sub(damage * cfg_to.mp.add_rate_hit, true) #投技检测 if attack.is_throw_check and not character_to.get_status("is_be_throw") and status.throw_target == 0: character_to.set_status("is_be_throw", true) status.throw_target = character_to.id() #投技结束 if attack.is_throw_end: character_to.set_status("is_be_throw", false) status.throw_target = 0 #硬直等级 var break_level_def: int = cfg_to.shield.break_level_on if has_shield else cfg_to.shield.break_level_off var break_level_sub: int = clampi(attack.break_level - break_level_def, 0, 3) is_break_skill = break_level_sub > 0 if is_break_skill: #取消技能 if character_to.get_status("is_skill_running"): character_to.cancel_skill() #停止移动 character_to.move_stop() #受击动画 var trigger_hit: String = "" if is_rebound: trigger_hit="rebound" elif is_floating: trigger_hit = "air_hit_down" if is_hit_down else "air_hit_up" elif is_stun: trigger_hit="stun_hit" elif break_level_sub == 3: trigger_hit="lhit" elif break_level_sub == 2: trigger_hit="mhit" elif break_level_sub == 1: trigger_hit="hit" character_to.set_view_trigger(trigger_hit) #浮空 击落 强制位移 var hit_up_speed: float = attack.hit_up_speed var hit_back_speed: float = attack.hit_back_speed if is_floating: character_to.add_buff("stagger", -1) character_to.add_buff("floating", -1) if dir.x!=0: character_to.set_status("is_right", dir.x<0) else: character_to.add_buff("stagger", 1) hit_up_speed = 0 if hit_back_limit_curve: var character_dir: Vector2 = character_from.pos2D() - character_to.pos2D() var dist = clamp(character_dir.length(), 0, 1) hit_back_speed = max(hit_back_limit_curve.sample(dist), hit_back_speed) if is_hit_down: character_to.add_buff("hit_down", -1) character_to.set_hit_move(dir, hit_back_speed, hit_up_speed) character_to.add_buff("hit_back", attack.hit_back_duration) character_to.add_buff("hit_up", attack.hit_up_duration) #停止自身位移 if attack.is_stop_self and not is_remote: character_from.move_stop() character_from.set_status("skill_move_stop", true) #受击特效 match attack.damage_type: Enum.EDamageType.Sharp: character_to.cast_particle(ResourceManager.particle_hit_sharp, false) Enum.EDamageType.Blunt: character_to.cast_particle(ResourceManager.particle_hit_blunt, false) Enum.EDamageType.Ground: character_to.cast_particle(ResourceManager.particle_hit_ground, false) _: pass #抖动 character_to.add_buff("shake_x", 0.2, true) #闪白 character_to.add_buff("flash_white", 0.1) #卡帧 if not is_remote: character_from.set_pause_time(pause_time) character_to.set_pause_time(pause_time) #全局特效 if not is_remote: Global.camera_mgr.effect(pause_time) #伤害跳字 character_to.show_hit_damage(damage) #状态跳字 if is_break_shield: character_to.show_hit_text("Break") elif is_break_stun: character_to.show_hit_text("Stun") elif not is_break_skill: character_to.show_hit_text("Block") hit_result.is_break = is_break_skill return hit_result func add_mp_sub(value: float, from_battle: bool): var mp = character.get_status("mp") var mp_max = character.get_status("mp_max") var mp_sub = character.get_status("mp_sub") var mp_sub_max = character.get_status("mp_sub_max") value = min(mp_sub_max-mp_sub, value) var mp_sub_full = value == mp_sub_max-mp_sub var mp_full = mp == mp_max if mp_sub_full: #mp_sub已满 if mp_full: #mp已满 character.set_status("mp_sub", mp_sub_max) character.remove_buff("mp_recover_cd") character.remove_buff("mp_recover") else: #mp未满 character.set_status("mp", mp+1) character.set_status("mp_sub", 0) else: character.set_status("mp_sub", max(mp_sub+value, 0)) if from_battle: character.remove_buff("mp_recover") character.add_buff("mp_recover_cd", status.cfg.mp.recover_cd) func cost_mp(value: int): var mp = character.get_status("mp") var mp_max = character.get_status("mp_max") var mp_new = mp-value mp_new = min(mp_new, mp_max) mp_new = max(mp_new, 0) character.set_status("mp", mp_new) character.remove_buff("mp_recover") character.add_buff("mp_recover_cd", status.cfg.mp.recover_cd) func check_ground(): skill.on_check_ground(0) func check_ground1(): skill.on_check_ground(1) func check_ground2(): skill.on_check_ground(2) func check_charging1(): skill.on_check_charging(1) func check_charging2(): skill.on_check_charging(2) func check_charging3(): skill.on_check_charging(3) func cast_sub_character(): skill.on_cast_sub_character() func hold(): skill.on_hold() func stop(): move.stop() func change_dir(): status.skill_dir = status.move_dir status.is_right = status.move_dir.x > 0 func on_attack_hit(hit_result: HitResult) -> void: if not status.skill_cfg: return var skill_name: String = status.skill_cfg.get_res_name() if hit_result.is_hit: on_attack_hit_trigger(skill_name, "hit") if hit_result.is_break: on_attack_hit_trigger(skill_name, "break") func on_attack_hit_trigger(skill_name: String, trigger_name: String) -> void: var func_name: String = "on_%s_%s" % [skill_name, trigger_name] if has_method(func_name): call(func_name) func on_skill_release_trigger(skill_name: String) -> void: var func_name: String = "on_%s_release" % skill_name if has_method(func_name): call(func_name) func on_hero01_fist_skill01_break(): var target: int = status.throw_target skill.cast_skill_by_name("hero01_fist_skill02", status.move_dir) status.throw_target = target func on_hero01_fist_skill_charging_release(): match status.charging_level: 0: skill.cast_skill_by_name("hero01_fist_skill_charging01", status.move_dir) _: skill.cast_skill_by_name("hero01_fist_skill_charging02", status.move_dir)