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", 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, 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)