SkillComponent.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. using FL.Battle.Skills;
  2. using System;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using XGame;
  6. using XGame.Database;
  7. using XGame.Framework;
  8. using XGame.Framework.Components;
  9. using XGame.Framework.Interfaces;
  10. using XGame.Framework.Time;
  11. namespace FL.Battle.Components
  12. {
  13. public interface ISkillContext
  14. {
  15. IEntity Entity { get; }
  16. Transform ActionRoot { get; }
  17. Vector3 Position { get; }
  18. ITimeModule Time { get; }
  19. IAnimator Animator { get; }
  20. VfxComponent Vfx { get; }
  21. ITargetSelector Selector { get; }
  22. void FindTargetAsync(Action<ITarget> onSelected);
  23. void OnSkillStart(int skillId);
  24. void OnSkillStop(int skillId);
  25. }
  26. public class SkillComponent : Component<ISkillContext>, IReset
  27. {
  28. private const int NO_SKILL = -1;
  29. private Dictionary<int, ISkillActor> _usingActors;
  30. public ITarget Target { get; private set; }
  31. private int _playingSkill = NO_SKILL;
  32. /// <summary>
  33. /// 技能冷却定时器
  34. /// key: skillId
  35. /// value: ITimer
  36. /// </summary>
  37. private Dictionary<int, ITimer> _skillCDTimers = new Dictionary<int, ITimer>();
  38. /// <summary>
  39. /// 技能队列
  40. /// 冷却时间到的技能进入队列等待播放
  41. /// </summary>
  42. private List<int> _skillQueue = new List<int>();
  43. private Dictionary<int, long> _buffSkillInfos;
  44. protected override void OnDisable()
  45. {
  46. Stop(false);
  47. }
  48. protected override void OnDispose()
  49. {
  50. Stop(true);
  51. }
  52. void IReset.Reset()
  53. {
  54. Stop(false);
  55. }
  56. #region 目标选择
  57. ///// <summary>
  58. ///// 默认的目标类型
  59. ///// </summary>
  60. //private EEntityType DefaultTargetType => Context.Entity.EntityType == EEntityType.Monster ? EEntityType.Player : EEntityType.Monster;
  61. //public void FindTargetAsync(int skillId = 0)
  62. //{
  63. // Context.Selector.FindAsync(this, DefaultTargetType, (target) =>
  64. // {
  65. // Context.Entity.State = EEntityState.Battle;
  66. // Target = target;
  67. // EnqueueSkill(skillId);
  68. // StartSkill();
  69. // });
  70. //}
  71. private void OnSelectedTarget(ITarget target)
  72. {
  73. Context.Entity.AddState(EEntityState.Battle);
  74. Target = target;
  75. StartSkill();
  76. }
  77. public void Prepare()
  78. {
  79. //初始化技能
  80. foreach (var skillId in Context.Entity.ActiveSkillIds)
  81. {
  82. EnqueueSkill(skillId);
  83. }
  84. //选择目标
  85. Context.FindTargetAsync(OnSelectedTarget);
  86. }
  87. #endregion
  88. public bool FindSkillByElementType(EElementType elementType, out int skillId)
  89. {
  90. foreach (var item in Context.Entity.ActiveSkillIds)
  91. {
  92. var table = SkillTableRepo.Get(item);
  93. if (table.Element == elementType)
  94. {
  95. skillId = item;
  96. return true;
  97. }
  98. }
  99. skillId = 0;
  100. return false;
  101. }
  102. /// <summary>
  103. /// 返回技能的最小剩余时间
  104. /// </summary>
  105. /// <returns></returns>
  106. public int GetMinCooldown()
  107. {
  108. if (_skillCDTimers.Count == 0)
  109. return 0;
  110. var remain = int.MaxValue;
  111. foreach (var skillId in Context.Entity.ActiveSkillIds)
  112. { // 有主动技能在队列里
  113. if (_skillQueue.Contains(skillId))
  114. return 0;
  115. if (_skillCDTimers.TryGetValue(skillId, out var timer))
  116. {
  117. var temp = (timer as Timer).RemainTime;
  118. if (temp < remain)
  119. remain = temp;
  120. }
  121. }
  122. return remain;
  123. }
  124. /// <summary>
  125. /// 冷却时间到的技能进入队列等待播放
  126. /// </summary>
  127. /// <param name="skillId"></param>
  128. private bool EnqueueSkill(int skillId)
  129. {
  130. if (Context.Entity.IsDead)
  131. return false;
  132. //var entityName = Context.Entity.Attr.Name;
  133. if (_playingSkill == skillId)
  134. {
  135. //Log.Debug($"技能正在施法. Entity:{entityName} Skill:{skillId}");
  136. return false;
  137. }
  138. if (_skillCDTimers.ContainsKey(skillId))
  139. { // 技能还在cd中
  140. //Log.Debug($"技能还在冷却中. Entity:{entityName} Skill:{skillId}");
  141. return false;
  142. }
  143. if (_skillQueue.Contains(skillId))
  144. {
  145. //Log.Debug($"技能队列重复. Entity:{entityName} Skill:{skillId}");
  146. return false;
  147. }
  148. //Log.Debug($"EnqueueSkill Entity:{entityName} Skill:{skillId} _playingSkill:{_playingSkill}");
  149. _skillQueue.Add(skillId);
  150. return true;
  151. }
  152. private void StartSkill()
  153. {
  154. if (_playingSkill != -1)
  155. return;
  156. // 没有技能 || 已死亡 || 移动中
  157. if (_skillQueue.Count == 0 || (Context.Entity.IsState(EEntityState.SkillBroken | EEntityState.Moving)))
  158. return;
  159. var skillId = _skillQueue[0];
  160. var isNoTarget = IsNoTarget(skillId);
  161. // 召唤类技能,不需要选目标
  162. if (isNoTarget == false && (Target == null || Target.IsDead))
  163. { // 没有目标或目标已死亡
  164. Context.FindTargetAsync(OnSelectedTarget);
  165. return;
  166. }
  167. _skillQueue.RemoveAt(0);
  168. _playingSkill = skillId;
  169. //Log.Debug($"StartSkill Entity:{Entity.Attr.Name} Skill:{skillId}");
  170. Context.OnSkillStart(skillId);
  171. GetActor(skillId).Play(skillId, isNoTarget ? null : Target, OnSkillCompleted);
  172. }
  173. private void OnSkillCompleted(int skillId, int skillCD)
  174. {
  175. RemoveActor(skillId);
  176. Context.OnSkillStop(skillId);
  177. //var uid = UIDDefine.New();
  178. //Log.Debug($"OnSkillCompleted Entity:{Entity.Attr.Name} Skill:{skillId} CD:{skillCD} UID:{uid} start");
  179. if (skillCD > 0)
  180. {
  181. var cdTimer = Context.Time.AddDelayTimer(skillCD, () =>
  182. {
  183. //Log.Debug($"OnSkillCompleted Entity:{Entity.Attr.Name} Skill:{skillId} CD:{skillCD} UID:{uid} remove");
  184. _skillCDTimers.Remove(skillId);
  185. if (RemoveTickSkill(skillId) == false)
  186. { // 非触发类技能,加入技能队列
  187. EnqueueSkill(skillId);
  188. }
  189. StartSkill();
  190. });
  191. _skillCDTimers.Add(skillId, cdTimer);
  192. }
  193. _playingSkill = NO_SKILL;
  194. StartSkill();
  195. }
  196. public void Stop(bool isDispose)
  197. {
  198. if (_usingActors != null)
  199. {
  200. foreach (var actor in _usingActors.Values)
  201. {
  202. ObjectPool.Recycle(actor);
  203. }
  204. _usingActors.Clear();
  205. }
  206. _buffSkillInfos?.Clear();
  207. _skillQueue.Clear();
  208. _playingSkill = NO_SKILL;
  209. foreach (var item in _skillCDTimers)
  210. {
  211. item.Value.Cancel();
  212. }
  213. _skillCDTimers.Clear();
  214. Target = null;
  215. }
  216. /// <summary>
  217. /// 技能是否不需要目标
  218. /// </summary>
  219. /// <param name="skillId"></param>
  220. /// <returns></returns>
  221. private bool IsNoTarget(int skillId)
  222. {
  223. var skillTable = SkillTableRepo.Get(skillId);
  224. if ((ESkillVfxType)skillTable.SkillVfxType is ESkillVfxType.BulletMortar or ESkillVfxType.Trap or ESkillVfxType.AOE or ESkillVfxType.AOENoMove)
  225. return true;
  226. return false;
  227. }
  228. private ISkillActor GetActor(int skillId)
  229. {
  230. //if (skillId == 0)
  231. //{
  232. // return _attackActor ??= new AttackActor() { Context = Context };
  233. //}
  234. //return _skillActor ??= new SimpleSkillActor() { Context = Context };
  235. if (_usingActors == null)
  236. {
  237. _usingActors = new Dictionary<int, ISkillActor>();
  238. }
  239. var actor = ObjectPool.Acquire<SimpleSkillActor>();
  240. actor.Context = Context;
  241. _usingActors.Add(skillId, actor);
  242. return actor;
  243. }
  244. private void RemoveActor(int skillId)
  245. {
  246. if (_usingActors?.TryGetValue(skillId, out var actor) ?? false)
  247. {
  248. _usingActors.Remove(skillId);
  249. ObjectPool.Recycle(actor);
  250. }
  251. }
  252. #region 触发类技能
  253. public void TryTickSkill(int skillId, long targetId, int probability = 10000)
  254. {
  255. if (_buffSkillInfos == null)
  256. _buffSkillInfos = new Dictionary<int, long>();
  257. if (_buffSkillInfos.ContainsKey(skillId))
  258. { // 已经触发 或 还在cd中
  259. return;
  260. }
  261. if (MathUtils.RandomTick(probability) == false)
  262. {
  263. return;
  264. }
  265. // 触发技能
  266. var skillVfxs = SkillVfxsTableRepo.Get(skillId);
  267. if (skillVfxs.AniName >= EAnimationName.attack)
  268. { // 有动作,昏迷等状态不触发
  269. if (Context.Entity.IsState(EEntityState.SkillBroken))
  270. {
  271. return;
  272. }
  273. // 加到技能播放队列
  274. if (EnqueueSkill(skillId))
  275. {
  276. _buffSkillInfos.Add(skillId, targetId);
  277. }
  278. }
  279. else
  280. { // 无动作,直接播放
  281. var target = Context.Selector.GetTarget(targetId);
  282. var isNoTarget = IsNoTarget(skillId);
  283. // 召唤类技能,不需要选目标
  284. if (isNoTarget == false && (target == null || target.IsDead))
  285. { // 没有目标或目标已死亡
  286. return;
  287. }
  288. Log.Debug($"触发技能 SkillId:{skillId} entity:{Context.Entity.EntityId} targetId:{targetId}");
  289. _buffSkillInfos.Add(skillId, targetId);
  290. GetActor(skillId).Play(skillId, isNoTarget ? null : target, OnTickSkillCompleted);
  291. }
  292. }
  293. private void OnTickSkillCompleted(int skillId, int skillCD)
  294. {
  295. RemoveActor(skillId);
  296. //var uid = UIDDefine.New();
  297. //Log.Debug($"OnSkillCompleted Entity:{Entity.Attr.Name} Skill:{skillId} CD:{skillCD} UID:{uid} start");
  298. if (skillCD > 0)
  299. {
  300. var cdTimer = Context.Time.AddDelayTimer(skillCD, () =>
  301. {
  302. //Log.Debug($"OnSkillCompleted Entity:{Entity.Attr.Name} Skill:{skillId} CD:{skillCD} UID:{uid} remove");
  303. _skillCDTimers.Remove(skillId);
  304. RemoveTickSkill(skillId);
  305. });
  306. _skillCDTimers.Add(skillId, cdTimer);
  307. }
  308. }
  309. /// <summary>
  310. /// 移除触发类技能
  311. /// </summary>
  312. /// <param name="skillId"></param>
  313. /// <returns></returns>
  314. private bool RemoveTickSkill(int skillId)
  315. {
  316. return _buffSkillInfos?.Remove(skillId) ?? false;
  317. }
  318. #endregion
  319. #region 被动技能
  320. //public void TryTickPassiveSkills(Func<SkillTable, bool> extenCondition)
  321. //{
  322. // if (Context.Entity.PassiveSkillIds == null) return;
  323. // foreach(var skillId in Context.Entity.PassiveSkillIds)
  324. // {
  325. // var skill = SkillTableRepo.Get(skillId);
  326. // if (skill == null) continue;
  327. // if (!extenCondition(skill)) continue;
  328. // TryTickSkill(skillId, Context.Entity.EntityId);
  329. // }
  330. //}
  331. #endregion
  332. #region IPauseable 实现
  333. public void Pause(EEntityState state)
  334. {
  335. if (state is EEntityState.Palsy)
  336. {
  337. foreach (var item in _skillCDTimers.Values)
  338. { //TODO 冷却中的技能cd回退到80%
  339. var timer = item as Timer;
  340. timer.ResetForAxis(Mathf.RoundToInt(timer.TimeAxis * KeyValue.ParalysisDelayFac.ToRealFloat()));
  341. }
  342. }
  343. if (_playingSkill != NO_SKILL)
  344. { // 有技能正在施法,直接进入cd
  345. var skillId = _playingSkill;
  346. var skill = SkillTableRepo.Get(skillId);
  347. OnSkillCompleted(skillId, skill.RestTime);
  348. }
  349. if (_buffSkillInfos != null)
  350. { // 移除队列里的触发类技能
  351. for (var i = _skillQueue.Count - 1; i >= 0; i--)
  352. {
  353. var skillId = _skillQueue[i];
  354. if (_buffSkillInfos.ContainsKey(skillId))
  355. {
  356. _buffSkillInfos.Remove(skillId);
  357. _skillQueue.RemoveAt(i);
  358. }
  359. }
  360. }
  361. }
  362. public void Resume()
  363. {
  364. StartSkill();
  365. }
  366. #endregion
  367. }
  368. }