|
|
|
|
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 HitInfo:
|
|
|
|
|
var from: int
|
|
|
|
|
var to: int
|
|
|
|
|
var dir: Vector2
|
|
|
|
|
var attack: AttackCfg
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 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)
|
|
|
|
|
var result: Array[Character] = 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
|
|
|
|
|
target.add_attack(character.id(), attack_dir, attack)
|
|
|
|
|
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, dir: Vector2, attack: AttackCfg):
|
|
|
|
|
var hit_info = HitInfo.new()
|
|
|
|
|
hit_info.from = from
|
|
|
|
|
hit_info.to = character.id()
|
|
|
|
|
hit_info.dir = dir
|
|
|
|
|
hit_info.attack = attack
|
|
|
|
|
settle(hit_info)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func settle(hit_info: HitInfo) -> bool:
|
|
|
|
|
var character_from: Character = Global.character_mgr.get_character(hit_info.from) as Character
|
|
|
|
|
var character_to: Character = Global.character_mgr.get_character(hit_info.to) as Character
|
|
|
|
|
if !character_from or !character_to:
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
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 attack: AttackCfg = hit_info.attack
|
|
|
|
|
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(damage * cfg_from.mp.add_rate_attack)
|
|
|
|
|
character_from.remove_buff("mp_recover")
|
|
|
|
|
character_from.add_buff("mp_recover_cd", status.cfg.mp.recover_cd)
|
|
|
|
|
character_to.add_mp(damage * cfg_to.mp.add_rate_hit)
|
|
|
|
|
character_to.remove_buff("mp_recover")
|
|
|
|
|
character_to.add_buff("mp_recover_cd", status.cfg.mp.recover_cd)
|
|
|
|
|
|
|
|
|
|
#硬直等级
|
|
|
|
|
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 hit_info.dir.x!=0:
|
|
|
|
|
character_to.set_status("is_right", hit_info.dir.x<0)
|
|
|
|
|
else:
|
|
|
|
|
character_to.add_buff("stagger", 1)
|
|
|
|
|
hit_up_speed = 0
|
|
|
|
|
if hit_back_limit_curve:
|
|
|
|
|
var dir: Vector2 = character_from.pos2D() - character_to.pos2D()
|
|
|
|
|
var dist = clamp(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(hit_info.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")
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func add_mp(value: float):
|
|
|
|
|
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)
|
|
|
|
|
if value == mp_sub_max-mp_sub:
|
|
|
|
|
var mp_add = min(mp_max-mp, 1)
|
|
|
|
|
if mp_add > 0:
|
|
|
|
|
character.set_status("mp", mp+mp_add)
|
|
|
|
|
character.set_status("mp_sub", 0)
|
|
|
|
|
else:
|
|
|
|
|
character.set_status("mp_sub", mp_sub_max)
|
|
|
|
|
character.remove_buff("mp_recover_cd")
|
|
|
|
|
character.remove_buff("mp_recover")
|
|
|
|
|
else:
|
|
|
|
|
character.set_status("mp_sub", mp_sub+value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 cast_sub_character():
|
|
|
|
|
skill.on_cast_sub_character()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func stop():
|
|
|
|
|
move.stop()
|