using System; using System.Collections.Generic; using System.Linq; using Sirenix.OdinInspector; using Sirenix.Utilities; using UnityEngine; using VOID.Generic.Objects.Unit.Actions; using VOID.Generic.Objects.Unit.Actions.Exceptions; using VOID.Generic.Objects.Unit.Events; namespace VOID.Generic.Objects.Unit { [Serializable] public class UnitObjectActionQueue : ObjectComponent { #region Initialize protected override void SelfInitialize() { parent.basicEvents.onFixedUpdate.AddListener(CheckRunningAction); } #endregion // Getters public List actionQueue => _actionQueue; public AbstractAction actionCurrent => _actionCurrent; [ShowInInspector, ReadOnly] private readonly List _actionQueue = new(); [ShowInInspector, ReadOnly] private AbstractAction _actionCurrent; /// /// 1. Checks if the action can be executed outside the queue. /// 2. If allowed, executes the action and handles potential errors. /// public void ExecuteInstantly(AbstractAction action) { if (!action.CanSkipQueue()) { Debug.LogError($"Action: {action.GetType().Name} cannot be executed via {nameof(ExecuteInstantly)}"); return; } try { action.DoIt(); } catch (UnitActionErrorException e) { parent.unitEvents.onActionError.Invoke(new ActionErrorEvent { unit = parent, action = e.action, cause = e.unitActionErrorType }); } } /// /// 1. Adds given action to queue /// 2. Starts it if queue is empty /// public void AddAction(AbstractAction action) => AddActionChain(new List { action }); /// /// 1. Adds given actions to queue /// 2. Runs only when previous action is finished successfully /// 3. Starts it if queue is empty /// public void AddActionChain(List actions) { // Unit is currently not controllable (stunned? doing something unstoppable? magically frozen by dev?) if (!parent.unitState.isControllable) { parent.unitEvents.onActionError.Invoke(new ActionErrorEvent { unit = parent, action = actions.First(), cause = UnitActionErrorType.UnitStateIsNotControllable }); return; } actions.ForEach((action, actionIndex) => { if (actionIndex != 0) action.runAfterAction = actions[actionIndex - 1]; _actionQueue.Add(action); }); if (_actionCurrent == null) NextAction(); } /// /// 1. Clear whole action queue /// 2. Cancel current action IF specified by argument /// 3. Some actions cant be stopped once fired by normal means, BUT we can force stopping it /// public void StopQueue(bool stopCurrent = true, bool stopForced = false) { _actionQueue.Clear(); if (!stopCurrent) return; if (!stopForced && _actionCurrent != null && !_actionCurrent.CanBeStopped()) return; _actionCurrent?.CancelIt(); _actionCurrent = null; } /// /// 1. Gets next action to do /// 2. If there is no more actions - do nothing /// 3. Proceed to execute it /// private void NextAction() { _actionCurrent = _actionQueue.FirstOrDefault(); _actionQueue.Remove(_actionCurrent); if (_actionCurrent == null) return; try { _actionCurrent.DoIt(); } catch (UnitActionErrorException e) { parent.unitEvents.onActionError.Invoke(new ActionErrorEvent { unit = parent, action = e.action, cause = e.unitActionErrorType }); } if (_actionCurrent.state is ActionState.Ready) Debug.LogWarning($"Action {_actionCurrent.GetType().Name} is still ready after starting. Skipping."); if (_actionCurrent.state is not ActionState.Running) NextAction(); } private void CheckRunningAction() { if (_actionCurrent?.state is ActionState.Running) return; NextAction(); } } }