using System; using System.Linq; using System.Collections; using System.Collections.Generic; using UnityEngine; using Sirenix.OdinInspector; using UniRx; using KairoEngine.Core; namespace KairoEngine.Core.GameActions { [System.Serializable] public class GameActionsController : ISerializationCallbackReceiver { /// A context object that holds a list of GameActionContextVariables to be accessible for GameActions to use. [InlineProperty, HideLabel, ShowIf("showContext")] public GameActionContext context = new GameActionContext(); [HideInInspector] public bool showContext = false; /// A list of GameActions to be executed in order. [ListDrawerSettings(HideAddButton = true, HideRemoveButton = false, DraggableItems = true, Expanded = false, ShowPaging = false, ShowItemCount = true)] [ShowInInspector, NonSerialized, OnInspectorInit("InitializeActionList")] [Space(4)] public List actions = new List(); [NonSerialized] private CompositeDisposable cancel; private int actionIndex = 0; public bool started => _started; public bool isDone => _done; private bool _started = false; private bool _done = false; [HideInInspector] public int depth = 0; #region FlowControl /// Run GameActions on this controller. Each action will be fired after the previous action ended. public void Start() { actionIndex = 0; _done = false; _started = true; var observable = Observable.EveryUpdate(); cancel = new CompositeDisposable(); observable.Subscribe(xs => ExecuteActions()).AddTo(cancel); } /// Executes GameActions until it has to wait for an Update or reach the end of the action list. private void ExecuteActions() { if(actionIndex >= actions.Count) { // done _done = true; cancel.Dispose(); return; } if(actions[actionIndex].done) { actionIndex += 1; ExecuteActions(); } else { if(actions[actionIndex].started == false) { actions[actionIndex].Start(); ExecuteActions(); } else actions[actionIndex].Update(); } } /// Stop executing GameActions on this controller. public void Stop() { if(cancel != null) cancel.Dispose(); } public void Restart() { actionIndex = 0; _started = false; _done = false; foreach (var action in actions) action.Restart(); } #endregion #region Serialization [SerializeField, HideInInspector] private List serializedActions = new List(); [SerializeField, HideInInspector] public GameActionObjectSerializer objectSerializer = new GameActionObjectSerializer(); private void SerializeActions() { if(actions == null) return; serializedActions = new List(); objectSerializer.ClearGameObjects(); objectSerializer.ClearScriptableObjects(); for (int i = 0; i < actions.Count; i++) { actions[i].OnBeforeSerialize(objectSerializer, i, depth); string data = JsonUtility.ToJson(actions[i]); serializedActions.Add(data); } } private void DeserializeActions() { if(serializedActions == null) return; if(actions == null) actions = new List(); if(actions.Count > 0) actions.Clear(); for (int i = 0; i < serializedActions.Count; i++) { GameAction action = JsonUtility.FromJson(serializedActions[i]); action = GameAction.InvokeStringMethod(action.className, action.typeName, serializedActions[i]); action.controller = this; action.OnBeforeDeserialize(objectSerializer, i, depth); actions.Add(action); // Type t = Type.GetType(action.typeName); // dynamic obj = Convert.ChangeType(action, t); // if(obj != null) actions.Add(obj.Duplicate()); // else if(action != null) actions.Add(action); } for (int i = 0; i < actions.Count; i++) { actions[i].Initialize(objectSerializer); } objectSerializer.ClearGameObjects(); objectSerializer.ClearScriptableObjects(); //serializedActions = new List(); } public void OnBeforeSerialize() => SerializeActions(); public void OnAfterDeserialize() => DeserializeActions(); #endregion #region NewAction [OnInspectorInit("GetCompatibleVariablenames"), OnValueChanged("AddNewAction")] [ValueDropdown("possibleActions", IsUniqueList = false)] [LabelText("Add New Action")] [NonSerialized] public GameAction newAction; [NonSerialized] private IEnumerable possibleActions = new ValueDropdownList(); private void GetCompatibleVariablenames() { possibleActions = ReflectiveEnumerator.GetEnumerableOfType(this) .Where(x => x.GetTypeName() != "GameActionBase") .Where(x => x.GetTypeName() != "GameAction") .Select(x => new ValueDropdownItem(x.GetActionName(), x)); } private void AddNewAction() { if(newAction != null) { newAction.controller = this; newAction.className = newAction.GetType().AssemblyQualifiedName; actions.Add(newAction); newAction = null; } } #endregion #region Utilities private void InitializeActionList() { if(actions == null) actions = new List(); } public GameActionsController Duplicate() { GameActionsController newController = new GameActionsController(); newController.actions = new List(); var newSerializedActions = new List(); objectSerializer.ClearGameObjects(); for (int i = 0; i < actions.Count; i++) { actions[i].OnBeforeSerialize(objectSerializer, i, depth); string data = JsonUtility.ToJson(actions[i]); newSerializedActions.Add(data); } newController.objectSerializer = objectSerializer; objectSerializer = new GameActionObjectSerializer(); newController.context = this.context.Duplicate(); for (int i = 0; i < newSerializedActions.Count; i++) { //Debug.Log(newSerializedActions[i]); GameAction action = JsonUtility.FromJson(newSerializedActions[i]); if(action == null) continue; action = GameAction.InvokeStringMethod(action.className, action.typeName, newSerializedActions[i]); action.controller = newController; action.OnBeforeDeserialize(newController.objectSerializer, i, depth); newController.actions.Add(action); } newController.objectSerializer.ClearGameObjects(); return newController; } #endregion } }