namespace Zinnia.Action { using System; using UnityEngine; using UnityEngine.Events; using Zinnia.Action.Collection; using Zinnia.Data.Collection.List; using Zinnia.Extension; /// /// Allows actions to dynamically register listeners to other actions. /// public class ActionRegistrar : MonoBehaviour { /// /// A source action to register a listener against. /// [Serializable] public struct ActionSource { [Tooltip("Determines if the source can be used.")] [SerializeField] private bool enabled; /// /// Determines if the source can be used. /// public bool Enabled { get { return enabled; } set { enabled = value; } } [Tooltip("The main container of the action.")] [SerializeField] private GameObject container; /// /// The main container of the action. /// public GameObject Container { get { return container; } set { container = value; } } [Tooltip("The action to subscribe to.")] [SerializeField] private Action action; /// /// The action to subscribe to. /// public Action Action { get { return action; } set { action = value; } } } /// /// Defines the event with the . /// [Serializable] public class ActionUnityEvent : UnityEvent { } /// /// Defines the event with the . /// [Serializable] public class GameObjectUnityEvent : UnityEvent { } #region Resgistrar Settings [Header("Resgistrar Settings")] [Tooltip("The action that will have the sources populated by the given Sources.")] [SerializeField] private Action target; /// /// The action that will have the sources populated by the given . /// public Action Target { get { return target; } set { if (this.IsMemberChangeAllowed()) { OnBeforeTargetChange(); } target = value; if (this.IsMemberChangeAllowed()) { OnAfterTargetChange(); } } } [Tooltip("A list of ActionSources to populate the target sources list with.")] [SerializeField] private ActionRegistrarSourceObservableList sources; /// /// A list of s to populate the target sources list with. /// public ActionRegistrarSourceObservableList Sources { get { return sources; } set { if (this.IsMemberChangeAllowed()) { OnBeforeSourcesChange(); } sources = value; if (this.IsMemberChangeAllowed()) { OnAfterSourcesChange(); } } } [Tooltip("A list of GameObjects that are the limits of Sources by matching against ActionSource.Container.")] [SerializeField] private GameObjectObservableList sourceLimits; /// /// A list of s that are the limits of by matching against . /// public GameObjectObservableList SourceLimits { get { return sourceLimits; } set { if (this.IsMemberChangeAllowed()) { OnBeforeSourceLimitsChange(); } sourceLimits = value; if (this.IsMemberChangeAllowed()) { OnAfterSourceLimitsChange(); } } } #endregion #region Action Events /// /// Emitted when the Action is added to the target. /// [Header("Action Events")] public ActionUnityEvent ActionAdded = new ActionUnityEvent(); /// /// Emitted when the Action is removed from the target. /// public ActionUnityEvent ActionRemoved = new ActionUnityEvent(); #endregion #region Limit Events /// /// Emitted when the Action is registered against the given limit. /// [Header("Limit Events")] public GameObjectUnityEvent LimitRegistered = new GameObjectUnityEvent(); /// /// Emitted when the Action is unregistered from the given limit. /// public GameObjectUnityEvent LimitUnregistered = new GameObjectUnityEvent(); #endregion protected virtual void OnEnable() { AddSourcesListeners(); AddSourceLimitsListeners(); TryAddTargetSources(); } protected virtual void OnDisable() { RemoveSourcesListeners(); RemoveSourcesLimitsListeners(); ClearTargetSources(); } /// /// Subscribes to events of . /// protected virtual void AddSourcesListeners() { if (Sources == null) { return; } Sources.Added.AddListener(OnSourceAdded); Sources.Removed.AddListener(OnSourceRemoved); } /// /// Unsubscribes from events of . /// protected virtual void RemoveSourcesListeners() { if (Sources == null) { return; } Sources.Added.RemoveListener(OnSourceAdded); Sources.Removed.RemoveListener(OnSourceRemoved); } /// /// Subscribes to events of . /// protected virtual void AddSourceLimitsListeners() { if (SourceLimits == null) { return; } SourceLimits.Added.AddListener(OnSourceLimitAdded); SourceLimits.Removed.AddListener(OnSourceLimitRemoved); } /// /// Unsubscribes from events of . /// protected virtual void RemoveSourcesLimitsListeners() { if (SourceLimits == null) { return; } SourceLimits.Added.RemoveListener(OnSourceLimitAdded); SourceLimits.Removed.RemoveListener(OnSourceLimitRemoved); } /// /// Adds all action sources from if their matches any . /// protected virtual void TryAddTargetSources() { if (Sources == null || SourceLimits == null) { return; } // It is expected that we have less SourceLimits than we have Sources, so this order of nesting the loops is the preferred one. foreach (GameObject limit in SourceLimits.SubscribableElements) { foreach (ActionSource source in Sources.SubscribableElements) { TryAddTargetSource(source, limit); } } } /// /// Clears all sources on . /// protected virtual void ClearTargetSources() { if (Target != null) { Target.ClearSources(); } } /// /// Adds the given source if its matches the given limit. /// /// The source to try to add. /// The limit to try to match against any . protected virtual void TryAddTargetSource(ActionSource source, GameObject limit) { if (source.Enabled && (limit == null || limit == source.Container)) { Target.AddSource(source.Action); ActionAdded?.Invoke(source.Action); LimitRegistered?.Invoke(limit); } } /// /// Removes the given source if its matches the given limit. /// /// The source to try to remove. /// The limit to try to match against any . protected virtual void TryRemoveTargetSource(ActionSource source, GameObject limit) { if (limit == source.Container) { if (Target.RemoveSource(source.Action)) { ActionRemoved?.Invoke(source.Action); LimitUnregistered?.Invoke(limit); } } } /// /// Called after an element is added to . /// /// The element added to the collection. protected virtual void OnSourceAdded(ActionSource source) { if (!this.IsValidState()) { return; } foreach (GameObject limit in SourceLimits.SubscribableElements) { TryAddTargetSource(source, limit); } } /// /// Called after an element is removed from . /// /// The element removed from the collection. protected virtual void OnSourceRemoved(ActionSource source) { if (!this.IsValidState()) { return; } foreach (GameObject limit in SourceLimits.SubscribableElements) { TryRemoveTargetSource(source, limit); } } /// /// Called after an element is added to . /// /// The element added to the collection. protected virtual void OnSourceLimitAdded(GameObject limit) { if (!this.IsValidState()) { return; } foreach (ActionSource source in Sources.SubscribableElements) { TryAddTargetSource(source, limit); } } /// /// Called after an element is removed from . /// /// The element removed from the collection. protected virtual void OnSourceLimitRemoved(GameObject limit) { if (!this.IsValidState()) { return; } foreach (ActionSource source in Sources.SubscribableElements) { TryRemoveTargetSource(source, limit); } } /// /// Called before has been changed. /// protected virtual void OnBeforeTargetChange() { ClearTargetSources(); } /// /// Called after has been changed. /// protected virtual void OnAfterTargetChange() { if (Target != null) { TryAddTargetSources(); } } /// /// Called before has been changed. /// protected virtual void OnBeforeSourcesChange() { if (Sources == null) { return; } RemoveSourcesListeners(); foreach (ActionSource source in Sources.SubscribableElements) { OnSourceRemoved(source); } } /// /// Called after has been changed. /// protected virtual void OnAfterSourcesChange() { AddSourcesListeners(); TryAddTargetSources(); } /// /// Called before has been changed. /// protected virtual void OnBeforeSourceLimitsChange() { if (SourceLimits == null) { return; } RemoveSourcesLimitsListeners(); foreach (GameObject limit in SourceLimits.SubscribableElements) { OnSourceLimitRemoved(limit); } } /// /// Called after has been changed. /// protected virtual void OnAfterSourceLimitsChange() { AddSourceLimitsListeners(); TryAddTargetSources(); } } }