using MalbersAnimations.Events; using MalbersAnimations.Scriptables; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; namespace MalbersAnimations.Controller { [System.Serializable] public class Mode { #region Public Variables /// Is this Mode Active? [SerializeField] private bool active = true; [SerializeField] private bool ignoreLowerModes = false; /// Animation Tag Hash of the Mode protected int ModeTagHash; /// Which Input Enables the Ability public string Input; /// ID of the Mode [SerializeField] public ModeID ID; /// Modifier that can be used when the Mode is Enabled/Disabled or Interrupted [CreateScriptableAsset] public ModeModifier modifier; /// Elapsed time to be interrupted by another Mode If 0 then the Mode cannot be interrupted by any other mode public FloatReference CoolDown = new FloatReference(0); /// List of Abilities public List Abilities; /// Active Ability index [SerializeField] private IntReference m_AbilityIndex = new IntReference(-99); public IntReference DefaultIndex = new IntReference(0); public IntEvent OnAbilityIndex = new IntEvent(); public bool ResetToDefault = false; [SerializeField] private bool allowRotation = false; [SerializeField] private bool allowMovement = false; public UnityEvent OnEnterMode = new UnityEvent(); public UnityEvent OnExitMode = new UnityEvent(); [Tooltip("Global Audio Source assigned to the Mode to Play Audio Clips")] public AudioSource m_Source; #endregion #region Properties /// Is THIS Mode Playing? public bool PlayingMode { get; set; } // public int EnterStateInfo { get; set; } /// Is the Mode In transition public bool IsInTransition { get; set; } /// Is the Mode Active public bool Active { get => active; set => active = value; } /// Priority of the Mode. Higher value more priority public int Priority { get; internal set; } /// Allows Additive rotation while the mode is playing public bool AllowRotation { get => allowRotation; set => allowRotation = value; } /// Allows Additive Speeds while the mode is playing public bool AllowMovement { get => allowMovement; set => allowMovement = value; } public string Name => ID != null ? ID.name : string.Empty; /// Does this Mode uses Cool Down? False if Cooldown = 0 public bool HasCoolDown => (CoolDown == 0) || InCoolDown; /// Is this mode in CoolDown? public bool InCoolDown { get; internal set; } //{ // get // { // Debug.Log($"{Animal.name} InCoolDown [{inCoolDown}]"); // return inCoolDown; // } // set // { // inCoolDown = value; // } //} //private bool inCoolDown; public float ActivationTime; /// If enabled, it will play this Mode even if a Lower Mode is Playing public bool IgnoreLowerModes { get => ignoreLowerModes; set => ignoreLowerModes = value; } /// Active Ability Index of the mode public int AbilityIndex { get => m_AbilityIndex; set { m_AbilityIndex.Value = value; OnAbilityIndex.Invoke(value); // Debug.Log($"{Animal.name} AbilityIndex [{m_AbilityIndex.Value}]"); } } public void SetAbilityIndex(int index) => AbilityIndex = index; public MAnimal Animal { get; private set; } /// Current Selected Ability to Play on the Mode public Ability ActiveAbility { get; private set; } //{ // get => m_ActiveAbility; // set // { // m_ActiveAbility = value; // Debug.Log($"{Animal.name} ActiveAbility [{m_ActiveAbility}]"); // } //} //private Ability m_ActiveAbility; /// Current Value of the Input if this mode was called by an Input public bool InputValue { get; internal set; } //{ // get => m_InputValue; // set // { // m_InputValue = value; // Debug.Log($"Mode [{ID}] Input: [{Input}] Value [{m_InputValue}]"); // } //} //private bool m_InputValue; #endregion internal void ConnectInput(IInputSource InputSource, bool connect) { if (!string.IsNullOrEmpty(Input)) //If a mode has an Input { var input = InputSource.GetInput(Input); if (input != null) { if (connect) input.InputChanged.AddListener(ActivatebyInput); else input.InputChanged.RemoveListener(ActivatebyInput); } } foreach (var ability in Abilities) { if (!string.IsNullOrEmpty(ability.Input)) //If a mode has an Input { var input = InputSource.GetInput(ability.Input); if (input != null) { if (ability.InputListener == null) ability.InputListener = (x) => ActivateAbilitybyInput(ability, x); //Very important to remove the same added listener. if (connect) input.InputChanged.AddListener(ability.InputListener); else input.InputChanged.RemoveListener(ability.InputListener); } } } } /// Set everyting up when the Animal Script Start public virtual void AwakeMode(MAnimal animal) { Animal = animal; //Cache the Animal OnAbilityIndex.Invoke(AbilityIndex); //Make the first invoke ActivationTime = -CoolDown * 2; InCoolDown = false; } /// Reset the current mode and ability public virtual void ResetMode() { if (Animal.ActiveMode == this) //if is the same Mode then set the AnimalPlaying mode to false { Animal.Set_State_Sleep_FromMode(false); //Restore all the States that are sleep from this mode } PlayingMode = false; modifier?.OnModeExit(this); if (ActiveAbility != null) { ActiveAbility.modifier?.OnModeExit(this); if (ActiveAbility.m_stopAudio) { if (ActiveAbility.audioSource != null) ActiveAbility.audioSource.Stop(); if (m_Source != null) m_Source.Stop(); } OnExitInvoke(); } if (ResetToDefault && !InputValue) //Important if the Input is still Active then Do not Reset to Default m_AbilityIndex.Value = DefaultIndex.Value; ActiveAbility = null; //Reset to the default //InCoolDown = false; // Debugging("ResetMode"); } /// Reset the current mode inside the Animal public virtual void ModeExit() { // Debugging("ModeExit"); Animal.ActiveMode = null; Animal.ModeTime = 0; //Reset Mode Time Animal.SetModeStatus(Animal.ModeAbility = 0); //Reset the Mode Ability to 0 } /// Resets the Ability Index on the animal to the default value public virtual void ResetAbilityIndex() { if (!Animal.Zone) SetAbilityIndex(DefaultIndex); //Dont reset it if you are on a zone... the Zone will do it automatically if you exit it } /// Returns True if a mode has an Ability Index public bool HasAbilityIndex(int index) => Abilities.Find(ab => ab.Index == index) != null; public void SetActive(bool value) => Active = value; public void ActivatebyInput(bool Input_Value) { if (!Active) return; if (Animal != null && !Animal.enabled) return; if (Animal.LockInput) return; //Do no Activate if is sleep or disable or lock input is Enable; if (InputValue != Input_Value) //Only Change if the Inputs are Different { InputValue = Input_Value; if (InputValue) { if (Animal.Zone && Animal.Zone.IsMode) //meaning the Zone its a Mode zone and it also changes the Status Animal.Zone.ActivateZone(Animal); else TryActivate(); } else { if (PlayingMode && CheckStatus(AbilityStatus.Charged)) //if this mode is playing && is set to Hold by Input & the Input was true { Animal.Mode_Interrupt(); Debugging($"[INTERRUPTED] Ability: [{ActiveAbility.Name}] " + $"Status: [Input Released]"); } } } } public void ActivateAbilitybyInput(Ability ability, bool Input_Value) { //Debug.Log(Name + "Input = " + Input_Value ); if (ability.InputValue != Input_Value) //Only Change if the Inputs are Different { ability.InputValue = Input_Value; if (!Active) return; if (!Animal.enabled) return; if (Animal.LockInput) return; //Do no Activate if is sleep or disable or lock input is Enable; if (ability.InputValue) { TryActivate(ability); } else { if (PlayingMode && ActiveAbility.Index == ability.Index && CheckStatus(AbilityStatus.Charged)) //if this mode is playing && is set to Hold by Input & the Input was true { Animal.Mode_Interrupt(); Debugging($"[INTERRUPTED] Ability: [{ActiveAbility.Name}] " + $"Status: [Input Released]"); } } } } /// Randomly Activates an Ability from this mode private void Activate(Ability newAbility, int modeStatus, string deb) { ActiveAbility = newAbility; Animal.SetModeParameters(this, modeStatus); ActiveAbility.modifier?.OnModeEnter(this); //Active Local Mode Modifier AudioSource source = ActiveAbility.audioSource != null ? ActiveAbility.audioSource : m_Source; if (source && source.isActiveAndEnabled) { if (!ActiveAbility.audioClip.NullOrEmpty()) source.clip = ActiveAbility.audioClip.GetValue(); if (source.isPlaying) source.Stop(); source.PlayDelayed(ActiveAbility.ClipDelay); } Debugging($"[PREPARED] Ability: [{ActiveAbility.Name}]. {deb}"); } public bool ForceActivate(int abilityIndex) { if (abilityIndex != 0) AbilityIndex = abilityIndex; if (!Animal.IsPreparingMode) { Debugging($"[FORCED ACTIVATE {AbilityIndex}]"); if (Animal.IsPlayingMode) { Animal.ActiveMode.ResetMode(); Animal.ActiveMode.ModeExit(); //This allows to Play a mode again } return TryActivate(); } return false; } public virtual bool TryActivate() => TryActivate(AbilityIndex); public virtual bool TryActivate(int index) => TryActivate(GetTryAbility(index)); public virtual bool TryActivate(int index, AbilityStatus status, float time = 0) { var TryNextAbility = GetTryAbility(index); if (TryNextAbility != null) { TryNextAbility.Status = status; if (status == AbilityStatus.PlayOneTime) TryNextAbility.AbilityTime = time; return TryActivate(TryNextAbility); } return false; } /// Checks if the ability can be activated public virtual bool TryActivate(Ability newAbility) { //Debug.Log($"TryActivate {(newAbility != null ? newAbility.Name : "NULL")}"); if (!Active) return false; int ModeStatus = 0; //Default Mode Status on the Mode .. This is changed if It can transition from an old ability to another string deb = ""; //Safe the Aproved Result if (newAbility == null) { // ModeExit(); Debugging($"Ability is [NULL]. FAILED TO PLAY"); ResetMode(); return false; } if (Animal.IsPreparingMode) { Debugging($"Its already preparing a Mode [Skip]"); return false; } if (!newAbility.Active) { Debugging($"[{newAbility.Name}] is Disabled. Mode Ignored"); return false; } if (StateCanInterrupt(Animal.ActiveState.ID, newAbility)) //Check if the States can block the mode { Debugging($"[{newAbility.Name}] cannot be Activated. The Active State [{Animal.ActiveStateID.name}] won't allow it"); return false; } if (StanceCanInterrupt(Animal.Stance, newAbility)) //Check if the States can block the mode { Debugging($"[{newAbility.Name}] cannot be Activated. The current Stance won't allow it"); return false; } //If this IS the mode that the animal is playing if (PlayingMode) { //if is set to Toggle then if is already playing this mode then stop it if (ActiveAbility.Index == newAbility.Index && CheckStatus(AbilityStatus.Toggle)) { InputValue = false; //Reset the Input Value to false of this mode Animal.Mode_Interrupt(); Debugging($"[INTERRUPTED] Ability: [{ActiveAbility.Name}] " + $"Status: [Toggle Off]"); return false; } //Means it can transition from one ability to another else if (newAbility.HasTransitionFrom && newAbility.Limits.TransitionFrom.Contains(ActiveAbility.Index)) { ModeStatus = ActiveAbility.Index; //This is used to Transition from an Older Mode Ability to a new one deb = ($"Last Ability [{ModeStatus}] is allowing it. "); ResetMode(); } //Means the Ability needs to finish its animation or Finish the Cooldown else if (HasCoolDown) { Debugging($"[Failed to play - {newAbility.Name}]. " + $"[{Name}] is in cooldown"); return false; } //Means the Ability was in cooldown but the coldown ended!! else if (!InCoolDown) { ResetMode(); ModeExit(); //This allows to Play a mode again INT ID = 0 to it can be available again deb = ($"No Longer in Cooldown [Same Mode]"); } } //If the Animal is playing a Different Mode else if (Animal.IsPlayingMode) { var ActiveMode = Animal.ActiveMode; //Store the Playing mode if (Priority > ActiveMode.Priority && IgnoreLowerModes && !InCoolDown) { ActiveMode.ResetMode(); ActiveMode.InputValue = false; //Set the Input to false so both modes don't overlap ActiveMode.ModeExit(); //This allows to Play a mode again deb = ($"Has Interrupted [{ActiveMode.ID.name}] Mode, because it had lower Priority"); } else { if (ActiveMode.HasCoolDown) { Debugging($"[Failed to Activate]. [{ActiveMode.ID.name}] needs to finish its animation"); return false; //Means that the Animations needs to finish first if the Active Mode Has no Cool Down so skip the code } else if (!ActiveMode.InCoolDown) //Means that the Active mode can be Interrupted since is no longer on cooldown { ActiveMode.ResetMode(); ActiveMode.ModeExit(); //This allows to Play a mode again INT ID = 0 to it can be available again deb = ($"No Longer in Cooldown [Different Mode]"); } } } else if (InCoolDown) { Debugging($"[Failed to play - {newAbility.Name}]. " + $"[Mode: {Name}] is still in cooldown"); return false; } Activate(newAbility, ModeStatus, deb); return true; } /// Called by the Mode Behaviour on Entering the Animation State. ///Done this way to check for Modes that are on other Layers besides the Base Layer public void AnimationTagEnter() { if (ActiveAbility != null && !PlayingMode) { PlayingMode = true; Animal.IsPreparingMode = false; Animal.ActiveMode = this; Animal.Set_State_Sleep_FromMode(true); //Put to sleep the states needed OnEnterInvoke(); //Invoke the ON ENTER Event ActivationTime = Time.time; //Store the time the Mode started if (!AllowMovement) Animal.InertiaPositionSpeed = Vector3.zero; //Remove Speeds if the Mode is Playing that does not allow Movement var AMode = ActiveAbility.Status; //Check if the Current Ability overrides the global properties var AModeName = AMode.ToString(); int ModeStatus = Int_ID.Loop; //That means the Ability is Loopable if (AMode == AbilityStatus.PlayOneTime) { ModeStatus = Int_ID.OneTime; //That means the Ability is OneTime } else if (AMode == AbilityStatus.ActiveByTime) { float HoldByTime = ActiveAbility.AbilityTime; Animal.StartCoroutine(Ability_By_Time(HoldByTime)); AModeName += ": " + HoldByTime; InputValue = false; } else if (AMode == AbilityStatus.Toggle) { AModeName += " On"; InputValue = false; } Debugging($"[ANIMATION ENTER] Ability: " + $"[{ActiveAbility.Name}] Status: [{AModeName}]"); SetCoolDown(); Animal.SetModeStatus(ModeStatus); } } internal void OnAnimatorMove(float deltaTime) { if (ActiveAbility.Status == AbilityStatus.Charged && ActiveAbility.AbilityTime > 0) { var currentTime = (Time.time - ActivationTime)/ActiveAbility.AbilityTime; var curve = ActiveAbility.ChargeCurve.Evaluate(currentTime); var Char_Value = curve * ActiveAbility.ChargeValue; Animal.Mode_SetPower(curve); ActiveAbility.OnCharged.Invoke(Char_Value); } } /// Called by the Mode Behaviour on Exiting the Animation State /// Done this way to check for Modes that are on other Layers besides the base one public void AnimationTagExit(Ability exitingAbility, int ExitTransitionAbility) { //Debug.Log("Active Ability = " + ActiveAbility.Index.Value); //Debug.Log("Exiting Avility = " + exitingAbility.Index.Value); //Debug.Log("ActiveMode = " + Animal.ActiveMode); //Means that we just exiting the same animation that we entered IMPORTANT string ExitTagLogic = "[Skip Exit Logic]"; if (Animal.ActiveMode == this && ActiveAbility != null && ActiveAbility.Index.Value == exitingAbility.Index.Value) { ExitTagLogic = $"[Mode Reseted] AcAb:[{ActiveAbility.Index.Value}- {ActiveAbility.Status.ToString()}] ExAb:[{exitingAbility.Index.Value}]"; if (ActiveAbility.Status != AbilityStatus.PlayOneTime) SetCoolDown(); //Set the cooldown after the mode has finish if is not set to Play one time ResetMode(); ModeExit(); if (ExitTransitionAbility != -1) //Meaning it will end in another mode { IsInTransition = false; //Reset that is in transition IMPORTANT if (TryActivate(ExitTransitionAbility)) { ExitTagLogic = "[Exit to another Ability]"; AnimationTagEnter(); //Do the animation Tag Enter since the next animation it may not be a entering mode animation } } else { if (!InCoolDown) //If the Mode is not in CoolDown { if (InputValue) //Check if the Input is still Active so the mode can be reactivated again. TryActivate(); else if (exitingAbility.InputValue) //Check if the Input is still Active on th Ability so the mode can be reactivated again. TryActivate(exitingAbility); } } } Debugging($"[ANIMATION EXIT] Ability: [{(exitingAbility?.Name)}] " + $"Status: {ExitTagLogic}"); } public virtual Ability GetTryAbility(int index) { if (!Active) return null; //If the mode is disabled: Ignore AbilityIndex = index; //Check first if there's a modifier on Enter. Some mdifiers it will change the ABILITY INDEX...IMPORTANT modifier?.OnModeEnter(this); if (AbilityIndex == 0) return null; //if the Index is 0 Ignore if (Abilities == null || Abilities.Count == 0) { Debugging("There's no Abilities Please set a list of Abilities"); return null; } //Set the Index of the Ability for the Mode, Check for Random if (AbilityIndex == -99) return GetAbility(Abilities[Random.Range(0, Abilities.Count)].Index.Value); return GetAbility(AbilityIndex); //Find the Ability } /// Returns an ability by its Index public virtual Ability GetAbility(int NewIndex) => Abilities.Find(item => item.Index == NewIndex); /// Returns an ability by its Name public virtual Ability GetAbility(string abilityName) => Abilities.Find(item => item.Name == abilityName); public virtual void OnModeStateMove(AnimatorStateInfo stateInfo, Animator anim, int Layer) { IsInTransition = anim.IsInTransition(Layer) && (anim.GetNextAnimatorStateInfo(Layer).fullPathHash != anim.GetCurrentAnimatorStateInfo(Layer).fullPathHash); if (Animal.ActiveMode == this) { Animal.ModeTime = stateInfo.normalizedTime; modifier?.OnModeMove(this, stateInfo, anim, Layer); ActiveAbility.modifier?.OnModeMove(this, stateInfo, anim, Layer); } } /// Check for Exiting the Mode, If the animal changed to a new state and the Affect list has some State public virtual bool StateCanInterrupt(StateID ID, Ability ability = null) { if (ability == null) ability = ActiveAbility; var properties = ability.Limits; if (properties.affect == AffectStates.None) return false; if (ability.HasAffectStates) { if (properties.affect == AffectStates.Exclude && HasState(properties, ID) //If the new state is on the Exclude State || (properties.affect == AffectStates.Include && !HasState(properties, ID))) //OR If the new state is not on the Include State { Debugging($"Current State [{ID.name}] is Blocking " + ability.Name + ""); return true; } } return false; } public virtual bool StanceCanInterrupt(StanceID ID, Ability ability = null) { if (ability == null) ability = ActiveAbility; var properties = ability.Limits; if (properties.affect_Stance == AffectStates.None) return false; if (ability.HasAffectStances) { if (properties.affect_Stance == AffectStates.Exclude && HasStance(properties, ID) //If the new state is on the Exclude State || (properties.affect_Stance == AffectStates.Include && !HasStance(properties, ID))) //OR If the new state is not on the Include State { Debugging($"Current Stance [{ID.name}] is Blocking " + ability.Name + ""); return true; } } return false; } /// Find if a State ID is on the Avoid/Include Override list protected static bool HasState(ModeProperties properties, StateID ID) => properties.affectStates.Exists(x => x.ID == ID.ID); protected static bool HasStance(ModeProperties properties, StanceID ID) => properties.Stances.Exists(x => x.ID == ID.ID); private void SetCoolDown() { if (CoolDown > 0) { if (I_CoolDown != null) Animal.StopCoroutine(I_CoolDown); Animal.StartCoroutine(I_CoolDown = C_SetCoolDown(CoolDown)); } } private IEnumerator I_CoolDown; public IEnumerator C_SetCoolDown(float time) { InCoolDown = true; yield return new WaitForSeconds(time); InCoolDown = false; //if (InputValue && ActiveAbility != null && ActiveAbility.Status == AbilityStatus.PlayOneTime) //Only Reset with PlayOneTime //{ // ResetMode(); // ModeExit(); // TryActivate(AbilityIndex); //Check if the Input is still Active when there's a cooldown //} } protected IEnumerator Ability_By_Time(float time) { yield return new WaitForSeconds(time); Animal.Mode_Interrupt(); Debugging($"[INTERRUPTED] Ability: [{ActiveAbility.Name}] " + $"Status: [Time elapsed]"); } private void OnExitInvoke() { ActiveAbility.OnExit.Invoke(); OnExitMode.Invoke(); } private void OnEnterInvoke() { ActiveAbility.OnEnter.Invoke(); OnEnterMode.Invoke(); } private bool CheckStatus(AbilityStatus status) { if (ActiveAbility == null) return false; return ActiveAbility.Status == status; } /// Disable the Mode. If the mode is playing it check the status and it disable it properly public virtual void Disable() { Active = false; InputValue = false; if (PlayingMode) { if (!CheckStatus(AbilityStatus.PlayOneTime)) { Animal.Mode_Interrupt(); } else { //Do nothing ... let the mode finish since is on AbilityStatus.PlayOneTime } } } public virtual void Enable() => Active = true; internal void Debugging(string deb) { #if UNITY_EDITOR if (Animal.debugModes) Debug.Log($"[{Animal.name}] - Mode [{ID.name}] - {deb}"); #endif } } /// Ability for the Modes [System.Serializable] public class Ability { /// Is the Ability Active public BoolReference active = new BoolReference(true); /// Name of the Ability (Visual Only) public string Name; /// index of the Ability public IntReference Index = new IntReference(0); [Tooltip("Unique Input to play for each Ability")] public StringReference Input; [Tooltip("Clip to play when the ability is played")] public AudioClipReference audioClip; [Tooltip("Clip Sound Delay")] public FloatReference ClipDelay = new FloatReference(0); [Tooltip("Local AudioSource for an specific Ability")] public AudioSource audioSource; [Tooltip("Stop the Audio sound on Ability Exit")] public bool m_stopAudio= true; [Tooltip("Local Mode Modifier to Add to the Ability")] [CreateScriptableAsset] public ModeModifier modifier; /// Overrides Properties on the mode [UnityEngine.Serialization.FormerlySerializedAs("Properties")] public ModeProperties Limits; /// The Ability can Stay Active until it finish the Animation, by Holding the Input Down, by x time [Tooltip("The Ability can Stay Active until it finish the Animation, by Holding the Input Down, by x time ")] public AbilityStatus Status = AbilityStatus.PlayOneTime; /// The Ability can Stay Active by x seconds [Tooltip("The Ability will be completely charged after x seconds. If the value is zero, the charge logic will be ignored")] public FloatReference abilityTime = new FloatReference(3); [Tooltip("Curve value for the charged ability")] public AnimationCurve ChargeCurve = new AnimationCurve(MTools.DefaultCurve); [Tooltip("Charge maximun value for the Charged ability")] public FloatReference ChargeValue = new FloatReference(1); /// Time value when the Status is set Time public float AbilityTime { get => abilityTime.Value ; set => abilityTime.Value = value; } /// It Has Affect states to check public bool HasAffectStates => Limits.affectStates != null && Limits.affectStates.Count > 0; /// It Has Affect stances to check public bool HasAffectStances => Limits.Stances != null && Limits.Stances.Count > 0; public bool HasTransitionFrom => Limits.TransitionFrom != null && Limits.TransitionFrom.Count > 0; public bool Active { get => active.Value; set => active.Value = value; } /// Internal Ability Input value public bool InputValue { get; internal set; } /// Used to connect the Inputs to the Abilities instead of the General Mode public UnityAction InputListener; //Store the Input Listener public UnityEvent OnEnter = new UnityEvent(); public UnityEvent OnExit = new UnityEvent(); public FloatEvent OnCharged = new FloatEvent(); } public enum AbilityStatus { /// The Ability is Enabled One time and Exit when the Animation is finished PlayOneTime = 0, /// The Ability can be charged Charged = 1, /// The Ability is On for an x ammount of time ActiveByTime = 2, /// The Ability is ON and OFF everytime the Activate method is called Toggle = 3, /// The Ability is Play forever until is Mode Interrupt is called Forever = 4, } public enum AffectStates { None, Include, Exclude, } [System.Serializable] public class ModeProperties { [Tooltip("Exclude: The mode will not be activated when is on a State of the List.\n" + "Include: The mode will only be actived when the Animal is on a State of the List")] public AffectStates affect = AffectStates.None; /// Include/Exclude the States on this list depending the Affect variable [Tooltip("Include/Exclude the States on this list depending the Affect variable")] public List affectStates = new List(); [Tooltip("Exlcude: The mode will not be activated when is on a Stance of the List.\n" + "Include: The mode will only be actived when the Animal is on a Stance of the List")] public AffectStates affect_Stance = AffectStates.None; /// Include/Exclude the Stances on this list depending the Affect variable [Tooltip("Include/Exclude the Stances on this list depending the Affect Stanes variable")] public List Stances = new List(); [Tooltip("Modes can transition from other abilities inside the same mode. E.g Seat -> Lie -> Sleep")] public List TransitionFrom = new List(); public ModeProperties(ModeProperties properties) { affect = properties.affect; affect_Stance = properties.affect_Stance; affectStates = new List(properties.affectStates); Stances = new List(properties.Stances); TransitionFrom = new List(); } } }