using FL.Battle.Skills; using System; using System.Collections.Generic; using UnityEngine; using XGame; using XGame.Database; using XGame.Framework; using XGame.Framework.Components; using XGame.Framework.Interfaces; using XGame.Framework.Time; namespace FL.Battle.Components { public interface ISkillContext { IEntity Entity { get; } Transform ActionRoot { get; } Vector3 Position { get; } ITimeModule Time { get; } IAnimator Animator { get; } VfxComponent Vfx { get; } ITargetSelector Selector { get; } void FindTargetAsync(Action onSelected); void OnSkillStart(int skillId); void OnSkillStop(int skillId); } public class SkillComponent : Component, IReset { private const int NO_SKILL = -1; private Dictionary _usingActors; public ITarget Target { get; private set; } private int _playingSkill = NO_SKILL; /// /// 技能冷却定时器 /// key: skillId /// value: ITimer /// private Dictionary _skillCDTimers = new Dictionary(); /// /// 技能队列 /// 冷却时间到的技能进入队列等待播放 /// private List _skillQueue = new List(); private Dictionary _buffSkillInfos; protected override void OnDisable() { Stop(false); } protected override void OnDispose() { Stop(true); } void IReset.Reset() { Stop(false); } #region 目标选择 ///// ///// 默认的目标类型 ///// //private EEntityType DefaultTargetType => Context.Entity.EntityType == EEntityType.Monster ? EEntityType.Player : EEntityType.Monster; //public void FindTargetAsync(int skillId = 0) //{ // Context.Selector.FindAsync(this, DefaultTargetType, (target) => // { // Context.Entity.State = EEntityState.Battle; // Target = target; // EnqueueSkill(skillId); // StartSkill(); // }); //} private void OnSelectedTarget(ITarget target) { Context.Entity.AddState(EEntityState.Battle); Target = target; StartSkill(); } public void Prepare() { //初始化技能 foreach (var skillId in Context.Entity.ActiveSkillIds) { EnqueueSkill(skillId); } //选择目标 Context.FindTargetAsync(OnSelectedTarget); } #endregion public bool FindSkillByElementType(EElementType elementType, out int skillId) { foreach (var item in Context.Entity.ActiveSkillIds) { var table = SkillTableRepo.Get(item); if (table.Element == elementType) { skillId = item; return true; } } skillId = 0; return false; } /// /// 返回技能的最小剩余时间 /// /// public int GetMinCooldown() { if (_skillCDTimers.Count == 0) return 0; var remain = int.MaxValue; foreach (var skillId in Context.Entity.ActiveSkillIds) { // 有主动技能在队列里 if (_skillQueue.Contains(skillId)) return 0; if (_skillCDTimers.TryGetValue(skillId, out var timer)) { var temp = (timer as Timer).RemainTime; if (temp < remain) remain = temp; } } return remain; } /// /// 冷却时间到的技能进入队列等待播放 /// /// private bool EnqueueSkill(int skillId) { if (Context.Entity.IsDead) return false; //var entityName = Context.Entity.Attr.Name; if (_playingSkill == skillId) { //Log.Debug($"技能正在施法. Entity:{entityName} Skill:{skillId}"); return false; } if (_skillCDTimers.ContainsKey(skillId)) { // 技能还在cd中 //Log.Debug($"技能还在冷却中. Entity:{entityName} Skill:{skillId}"); return false; } if (_skillQueue.Contains(skillId)) { //Log.Debug($"技能队列重复. Entity:{entityName} Skill:{skillId}"); return false; } //Log.Debug($"EnqueueSkill Entity:{entityName} Skill:{skillId} _playingSkill:{_playingSkill}"); _skillQueue.Add(skillId); return true; } private void StartSkill() { if (_playingSkill != -1) return; // 没有技能 || 已死亡 || 移动中 if (_skillQueue.Count == 0 || (Context.Entity.IsState(EEntityState.SkillBroken | EEntityState.Moving))) return; var skillId = _skillQueue[0]; var isNoTarget = IsNoTarget(skillId); // 召唤类技能,不需要选目标 if (isNoTarget == false && (Target == null || Target.IsDead)) { // 没有目标或目标已死亡 Context.FindTargetAsync(OnSelectedTarget); return; } _skillQueue.RemoveAt(0); _playingSkill = skillId; //Log.Debug($"StartSkill Entity:{Entity.Attr.Name} Skill:{skillId}"); Context.OnSkillStart(skillId); GetActor(skillId).Play(skillId, isNoTarget ? null : Target, OnSkillCompleted); } private void OnSkillCompleted(int skillId, int skillCD) { RemoveActor(skillId); Context.OnSkillStop(skillId); //var uid = UIDDefine.New(); //Log.Debug($"OnSkillCompleted Entity:{Entity.Attr.Name} Skill:{skillId} CD:{skillCD} UID:{uid} start"); if (skillCD > 0) { var cdTimer = Context.Time.AddDelayTimer(skillCD, () => { //Log.Debug($"OnSkillCompleted Entity:{Entity.Attr.Name} Skill:{skillId} CD:{skillCD} UID:{uid} remove"); _skillCDTimers.Remove(skillId); if (RemoveTickSkill(skillId) == false) { // 非触发类技能,加入技能队列 EnqueueSkill(skillId); } StartSkill(); }); _skillCDTimers.Add(skillId, cdTimer); } _playingSkill = NO_SKILL; StartSkill(); } public void Stop(bool isDispose) { if (_usingActors != null) { foreach (var actor in _usingActors.Values) { ObjectPool.Recycle(actor); } _usingActors.Clear(); } _buffSkillInfos?.Clear(); _skillQueue.Clear(); _playingSkill = NO_SKILL; foreach (var item in _skillCDTimers) { item.Value.Cancel(); } _skillCDTimers.Clear(); Target = null; } /// /// 技能是否不需要目标 /// /// /// private bool IsNoTarget(int skillId) { var skillTable = SkillTableRepo.Get(skillId); if ((ESkillVfxType)skillTable.SkillVfxType is ESkillVfxType.BulletMortar or ESkillVfxType.Trap or ESkillVfxType.AOE or ESkillVfxType.AOENoMove) return true; return false; } private ISkillActor GetActor(int skillId) { //if (skillId == 0) //{ // return _attackActor ??= new AttackActor() { Context = Context }; //} //return _skillActor ??= new SimpleSkillActor() { Context = Context }; if (_usingActors == null) { _usingActors = new Dictionary(); } var actor = ObjectPool.Acquire(); actor.Context = Context; _usingActors.Add(skillId, actor); return actor; } private void RemoveActor(int skillId) { if (_usingActors?.TryGetValue(skillId, out var actor) ?? false) { _usingActors.Remove(skillId); ObjectPool.Recycle(actor); } } #region 触发类技能 public void TryTickSkill(int skillId, long targetId, int probability = 10000) { if (_buffSkillInfos == null) _buffSkillInfos = new Dictionary(); if (_buffSkillInfos.ContainsKey(skillId)) { // 已经触发 或 还在cd中 return; } if (MathUtils.Random(probability) == false) { return; } // 触发技能 var skillVfxs = SkillVfxsTableRepo.Get(skillId); if (skillVfxs.AniName >= EAnimationName.attack) { // 有动作,昏迷等状态不触发 if (Context.Entity.IsState(EEntityState.SkillBroken)) { return; } // 加到技能播放队列 if (EnqueueSkill(skillId)) { _buffSkillInfos.Add(skillId, targetId); } } else { // 无动作,直接播放 var target = Context.Selector.GetTarget(targetId); var isNoTarget = IsNoTarget(skillId); // 召唤类技能,不需要选目标 if (isNoTarget == false && (target == null || target.IsDead)) { // 没有目标或目标已死亡 return; } Log.Debug($"触发技能 SkillId:{skillId} entity:{Context.Entity.EntityId} targetId:{targetId}"); _buffSkillInfos.Add(skillId, targetId); GetActor(skillId).Play(skillId, isNoTarget ? null : target, OnTickSkillCompleted); } } private void OnTickSkillCompleted(int skillId, int skillCD) { RemoveActor(skillId); //var uid = UIDDefine.New(); //Log.Debug($"OnSkillCompleted Entity:{Entity.Attr.Name} Skill:{skillId} CD:{skillCD} UID:{uid} start"); if (skillCD > 0) { var cdTimer = Context.Time.AddDelayTimer(skillCD, () => { //Log.Debug($"OnSkillCompleted Entity:{Entity.Attr.Name} Skill:{skillId} CD:{skillCD} UID:{uid} remove"); _skillCDTimers.Remove(skillId); RemoveTickSkill(skillId); }); _skillCDTimers.Add(skillId, cdTimer); } } /// /// 移除触发类技能 /// /// /// private bool RemoveTickSkill(int skillId) { return _buffSkillInfos?.Remove(skillId) ?? false; } #endregion #region 被动技能 //public void TryTickPassiveSkills(Func extenCondition) //{ // if (Context.Entity.PassiveSkillIds == null) return; // foreach(var skillId in Context.Entity.PassiveSkillIds) // { // var skill = SkillTableRepo.Get(skillId); // if (skill == null) continue; // if (!extenCondition(skill)) continue; // TryTickSkill(skillId, Context.Entity.EntityId); // } //} #endregion #region IPauseable 实现 public void Pause(EEntityState state) { if (state is EEntityState.Palsy) { foreach (var item in _skillCDTimers.Values) { //TODO 冷却中的技能cd回退到80% var timer = item as Timer; timer.ResetForAxis(Mathf.RoundToInt(timer.TimeAxis * KeyValue.ParalysisDelayFac.ToRealFloat())); } } if (_playingSkill != NO_SKILL) { // 有技能正在施法,直接进入cd var skillId = _playingSkill; var skill = SkillTableRepo.Get(skillId); OnSkillCompleted(skillId, skill.RestTime); } if (_buffSkillInfos != null) { // 移除队列里的触发类技能 for (var i = _skillQueue.Count - 1; i >= 0; i--) { var skillId = _skillQueue[i]; if (_buffSkillInfos.ContainsKey(skillId)) { _buffSkillInfos.Remove(skillId); _skillQueue.RemoveAt(i); } } } } public void Resume() { StartSkill(); } #endregion } }