You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
8.3 KiB
GDScript

extends AnimationPlayer
class_name Skill
@onready var character: Character = (get_owner() as Character)
@onready var view: View = (%View as View)
@onready var status: Status = (%Status as Status)
@onready var effect: Effect = (%Effect as Effect)
@onready var buff: Buff = (%Buff as Buff)
@onready var move: Move = (%Move as Move)
@onready var battle: Battle = (%Battle as Battle)
@onready var battle_attack_area: BattleAttackArea = (%BattleAttackArea as BattleAttackArea)
var skill_dict: Dictionary = {} # name -> skill
var skill_map: Dictionary = {} # input -> skillCfg[]
func init():
animation_finished.connect(_on_animation_finished)
cancel_skill()
func add_skill(action: String, skillCfg: SkillCfg):
if not action in skill_map:
skill_map[action] = []
skill_map[action].append(skillCfg)
skill_dict[skillCfg.get_res_name()] = skillCfg
func remove_skill(action: String, skillCfg: SkillCfg) -> void:
if not action in skill_map:
return
skill_map[action].filter(func(cfg): return cfg != skillCfg)
skill_dict.erase(skillCfg.get_res_name())
func get_skill_by_name(skill_name: String) -> SkillCfg:
return skill_dict.get(skill_name, null)
func _process(_delta):
if status.is_skill_running and status.is_pause == is_playing():
if status.is_pause: pause();
else: play();
func _on_animation_finished(_anim_name):
cancel_skill()
func cast_skill_mp_cost_check(cfg: SkillCfg) -> SkillCfg:
if status.mp < cfg.mp_cost:
#todo mp不足
return null
if status.mp_sub == 0 and cfg.mp_sub_cost:
return null
return cfg
func cast_skill_check(cfg: SkillCfg, break_level: Enum.EBreakLevel = Enum.EBreakLevel.Break) -> bool:
var animation_name: String = "animation_library/%s" % cfg.get_res_name()
if not has_animation(animation_name):
print("技能animation不存在", animation_name)
return false
if current_animation:
var pos_offset: float = fmod(current_animation_position, 0.1)
if pos_offset < 0.02 or pos_offset > 0.08:
return false
#检查打断级别
if cfg.break_level > break_level:
return false
#检查姿态
var stance_from: int = cfg.stance_from
var check_any_ground: bool = (stance_from == Enum.EStance.GroundAny) and status.is_on_floor
var check_any_air: bool = (stance_from == Enum.EStance.AirAny) and not status.is_on_floor
var check_any: bool = (stance_from == Enum.EStance.Any) or check_any_ground or check_any_air
if (stance_from != int(status.stance)) and not check_any:
return false
#检查落地姿态
var is_ground_stance: bool = (stance_from == Enum.EStance.GroundAny) or (stance_from == Enum.EStance.GroundIdle)
if is_ground_stance and not status.is_on_floor:
return false
#检查武器限制
if cfg is SkillWeaponCfg:
if cfg.weapon:
if cfg.weapon != status.weapon_list[status.weapon_index]:
return false
return true
func cast_skill_mp_cost(cfg: SkillCfg) -> bool:
if status.mp < cfg.mp_cost:
#todo mp不足
return false
if status.mp_sub == 0 and cfg.mp_sub_cost:
return false
if cfg.mp_cost:
character.cost_mp(cfg.mp_cost)
if cfg.mp_sub_cost:
character.cost_mp_sub()
return true
func cast_skill_by_name(skill_name: String, cast_dir: Vector2) -> void:
var cfg: SkillCfg = skill_dict.get(skill_name)
if not cfg:
return
if not cast_skill_mp_cost(cfg):
return
cast_skill(cfg, cast_dir)
func cast_skill(cfg: SkillCfg, cast_dir: Vector2, action_key: String = "") -> void:
var is_no_dir: bool = cast_dir.length() == 0
if is_no_dir:
cast_dir = Vector2.RIGHT if status.is_right else Vector2.LEFT
var is_cast_to_target: bool = status.target and (status.is_lock or is_no_dir)
if !cfg.free_lock and is_cast_to_target:
var target: Character = Global.character_mgr.get_character(status.target)
if target:
cast_dir = character.pos2D().direction_to(target.pos2D()).normalized()
var skill_move_dir: Vector2 = cast_dir
if cfg.is_lock_x:
cast_dir.y = 0
cast_dir = cast_dir.normalized()
if cast_dir.length() == 0:
cast_dir = Vector2.RIGHT if status.is_right else Vector2.LEFT
if cfg.is_lock_x_move:
skill_move_dir.y = 0
skill_move_dir = skill_move_dir.normalized()
if skill_move_dir.length() == 0:
skill_move_dir = Vector2.RIGHT if status.is_right else Vector2.LEFT
elif cfg.is_lock_x_move_sub:
#限定释放方向为左右45度以内
var angle: float = skill_move_dir.angle()
if angle > PI / 4 and angle < PI * 3 / 4:
angle = PI / 4 if angle < PI / 2 else PI * 3 / 4
elif angle < -PI / 4 and angle > -PI * 3 / 4:
angle = -PI / 4 if angle > -PI / 2 else -PI * 3 / 4
skill_move_dir = Vector2(cos(angle), sin(angle))
#技能重复次数
if status.skill_cfg and status.skill_cfg == cfg:
status.set_status("skill_repeat_count", 1)
else:
status.set_status("skill_repeat_count", 0)
break_skill()
status.set_status("speed_up_rate", -1)
status.set_status("is_free_control", false)
status.set_status("is_free_turn", false)
status.set_status("is_skill_running", true)
status.set_status("skill_cfg", cfg)
status.set_status("skill_dir", cast_dir.normalized())
status.set_status("skill_move_dir", skill_move_dir)
status.set_status("break_level", Enum.EBreakLevel.None)
status.set_status("stance", cfg.stance_to)
status.set_status("is_charging", cfg.is_charging)
status.set_status("skill_action_key", action_key)
status.set_skill_break_level_add(cfg.mp_cost) # todo
character.set_body_scale(cfg.get_owner())
if cfg.is_charging:
buff.add_buff("charging", -1)
if cast_dir.x != 0:
status.set_status("is_right", cast_dir.x > 0)
#预警特效
match cfg.warn_type:
Enum.ESkillWarnType.Normal: character.cast_particle(ResourceManager.particle_warn_normal, true)
Enum.ESkillWarnType.Mid: character.cast_particle(ResourceManager.particle_warn_mid, true)
Enum.ESkillWarnType.Heavy: character.cast_particle(ResourceManager.particle_warn_heavy, true)
_: pass
var animation_name: String = "animation_library/%s" % cfg.get_res_name()
play(animation_name, -1, Setting.animation_speed_scale)
seek(0.05, true, true)
battle.on_skill_cast(cfg.get_res_name())
func break_skill():
stop()
status.set_status("speed_up_rate", 0)
status.set_status("skill_move_speed", 0)
status.set_status("skill_float_speed", 0)
status.set_status("is_free_control", true)
status.set_status("is_free_turn", true)
status.set_status("is_skill_running", false)
status.set_status("skill_cfg", null)
status.set_status("break_level", Enum.EBreakLevel.Walk)
status.set_status("speed_down_push_rate", 0)
status.set_status("skill_move_stop", false)
status.set_status("is_speed_y_freeze", false)
status.set_status("is_charging", false)
status.set_status("charging_level", 0)
status.set_status("skill_action_key", "")
status.set_status("is_hit_character", false)
status.set_status("is_hit_wall", false)
status.set_skill_break_level_add(0)
buff.remove_buff("charging")
buff.remove_buff("pause")
battle_attack_area.set_active(false)
if status.throw_target != 0:
var character_to: Character = Global.character_mgr.get_character(status.throw_target)
if character_to:
character_to.set_status("is_be_throw", false)
status.set_status("throw_target", 0)
effect.release_effect()
func cancel_skill():
break_skill()
character.set_body_scale(status.cfg)
view.reset()
func on_hold() -> void:
_frame_back(1)
func on_check_ground(frame_offset: int) -> void:
if status.is_on_floor:
# 落地检测成功时跳帧
_frame_forward()
else:
_frame_back(frame_offset)
func on_check_charging(charging_level: int) -> void:
if status.charging_level >= charging_level:
_frame_forward()
return
if status.mp >= charging_level:
status.set_status("charging_level", status.charging_level + 1)
_frame_forward()
return
_frame_back(1)
func _frame_forward() -> void:
if not current_animation:
return
advance(Setting.animation_frame_rate)
func _frame_back(frame_offset: int) -> void:
if not current_animation:
return
var frame: int = int((current_animation_position + 0.001) / Setting.animation_frame_rate) - frame_offset
frame = max(0, frame)
var frame_pos: float = frame * Setting.animation_frame_rate
seek(frame_pos - Setting.animation_frame_rate * 0.8, true, true)
func on_cast_sub_character() -> void:
var cfg: SkillCfg = status.skill_cfg
if not cfg or not cfg.sub_character:
return
var pos: Vector3 = character.pos()
var dir: Vector2 = status.skill_dir
var sub: Character = Global.character_mgr.create_character(cfg.sub_character, status.team, pos, dir, status.id)
if sub:
sub.set_status("target", status.target)