namespace Tilia.Interactions.Interactables.Interactables.Grab { using System; using System.Collections.Generic; using System.Linq; using Tilia.Interactions.Interactables.Interactables.Grab.Action; using Tilia.Interactions.Interactables.Interactables.Grab.Provider; using Tilia.Interactions.Interactables.Interactables.Grab.Receiver; using Tilia.Interactions.Interactables.Interactors; using UnityEngine; using UnityEngine.Events; using Zinnia.Data.Attribute; using Zinnia.Data.Collection.List; using Zinnia.Extension; /// /// Sets up the Interactable Prefab grab settings based on the provided user settings. /// public class GrabInteractableConfigurator : MonoBehaviour { /// /// Defines the event with the . /// [Serializable] public class UnityEvent : UnityEvent { } #region Facade Settings [Header("Facade Settings")] [Tooltip("The public interface facade.")] [SerializeField] [Restricted] private InteractableFacade facade; /// /// The public interface facade. /// public InteractableFacade Facade { get { return facade; } set { facade = value; } } #endregion #region Action Settings [Header("Action Settings")] [Tooltip("The action to perform when grabbing the interactable for the first time.")] [SerializeField] private GrabInteractableAction primaryAction; /// /// The action to perform when grabbing the interactable for the first time. /// public GrabInteractableAction PrimaryAction { get { return primaryAction; } set { if (this.IsMemberChangeAllowed()) { OnBeforePrimaryActionChange(); } primaryAction = value; if (this.IsMemberChangeAllowed()) { OnAfterPrimaryActionChange(); } } } [Tooltip("The action to perform when grabbing the interactable for the second time.")] [SerializeField] private GrabInteractableAction secondaryAction; /// /// The action to perform when grabbing the interactable for the second time. /// public GrabInteractableAction SecondaryAction { get { return secondaryAction; } set { if (this.IsMemberChangeAllowed()) { OnBeforeSecondaryActionChange(); } secondaryAction = value; if (this.IsMemberChangeAllowed()) { OnAfterSecondaryActionChange(); } } } #endregion #region Reference Settings [Header("Reference Settings")] [Tooltip("The Grab Receiver setup.")] [SerializeField] [Restricted] private GrabInteractableReceiver grabReceiver; /// /// The Grab Receiver setup. /// public GrabInteractableReceiver GrabReceiver { get { return grabReceiver; } set { grabReceiver = value; } } [Tooltip("The Grab Provider setup.")] [SerializeField] [Restricted] private GrabInteractableInteractorProvider grabProvider; /// /// The Grab Provider setup. /// public GrabInteractableInteractorProvider GrabProvider { get { return grabProvider; } set { grabProvider = value; } } [Tooltip("The potential options for the GrabProvider.")] [SerializeField] [Restricted] private GrabInteractableInteractorProvider[] grabProviderOptions = new GrabInteractableInteractorProvider[0]; /// /// The potential options for the . /// public GrabInteractableInteractorProvider[] GrabProviderOptions { get { return grabProviderOptions; } set { grabProviderOptions = value; } } [Tooltip("The GameObjectObservableList that contains the available grab action prefabs.")] [SerializeField] [Restricted] private GameObjectObservableList actionTypes; /// /// The that contains the available grab action prefabs. /// public GameObjectObservableList ActionTypes { get { return actionTypes; } set { actionTypes = value; } } #endregion #region Events [Header("Events")] public UnityEvent KinematicStateToChange = new UnityEvent(); #endregion /// /// A collection of Interactors that are currently grabbing the Interactable. /// public virtual IReadOnlyList GrabbingInteractors => GrabProvider.GrabbingInteractors; /// /// Determines if the grab type is set to toggle. /// public virtual bool IsGrabTypeToggle => GrabReceiver.GrabType == GrabInteractableReceiver.ActiveType.Toggle; /// /// Clears . /// public virtual void ClearPrimaryAction() { if (!this.IsValidState()) { return; } PrimaryAction = default; } /// /// Clears . /// public virtual void ClearSecondaryAction() { if (!this.IsValidState()) { return; } SecondaryAction = default; } /// /// Clears . /// public virtual void ClearActionTypes() { if (!this.IsValidState()) { return; } ActionTypes = default; } /// /// Attempt to grab the Interactable to the given Interactor and ungrabs any existing grab. /// /// The Interactor to attach the Interactable to. public virtual void Grab(InteractorFacade interactor) { if (interactor == null) { return; } interactor.Grab(Facade); } /// /// Attempt to grab the Interactable to the given Interactor and does not ungrab any existing grab. /// /// The Interactor to attach the Interactable to. public virtual void GrabIgnoreUngrab(InteractorFacade interactor) { if (interactor == null) { return; } interactor.GrabIgnoreUngrab(Facade); } /// /// Attempt to ungrab the Interactable. /// /// The Interactor sequence index to ungrab from. public virtual void Ungrab(int sequenceIndex = 0) { if (GrabbingInteractors == null || GrabbingInteractors.Count == 0 || sequenceIndex >= GrabbingInteractors.Count) { return; } Ungrab(GrabbingInteractors[sequenceIndex]); } /// /// Attempts to ungrab the Interactable. /// /// The Interactor to ungrab from. public virtual void Ungrab(InteractorFacade interactor) { if (interactor == null) { return; } interactor.Ungrab(); //If the ungrab hasn't happened for some reason, then let's check to see if the interactor is still grabbing and force a stop grab. if (GrabbingInteractors.Contains(interactor)) { GrabReceiver.UngrabConsumer.Consume(interactor.GrabConfiguration.StopGrabbingPublisher.Payload, null); } } /// /// Notifies that the Interactable is being grabbed. /// /// The grabbing object. public virtual void NotifyGrab(GameObject data) { InteractorFacade interactor = data.TryGetComponent(true, true); if (interactor == null) { return; } if (Facade.GrabbingInteractors.Count == 1 || PrimaryGrabIsNone(1)) { Facade.FirstGrabbed?.Invoke(interactor); } Facade.Grabbed?.Invoke(interactor); interactor.NotifyOfGrab(Facade); } /// /// Notifies that the Interactable is no longer being grabbed. /// /// The previous grabbing object. public virtual void NotifyUngrab(GameObject data) { InteractorFacade interactor = data.TryGetComponent(true, true); if (interactor == null) { return; } Facade.Ungrabbed?.Invoke(interactor); interactor.NotifyOfUngrab(Facade); if (Facade.GrabbingInteractors.Count == 0 || PrimaryGrabIsNone(0)) { Facade.LastUngrabbed?.Invoke(interactor); } } /// /// Sets the consumer containers to the current active container. /// public virtual void ConfigureContainer() { GrabReceiver.ConfigureConsumerContainers(Facade.Configuration.ConsumerContainer); ConfigureActionContainer(PrimaryAction); ConfigureActionContainer(SecondaryAction); } /// /// Sets the to the index of the collection. /// /// The index of the to set the to. public virtual void SetGrabProvider(int providerIndex) { if (providerIndex >= GrabProviderOptions.Length) { return; } GrabProvider = GrabProviderOptions[providerIndex]; for (int index = 0; index < GrabProviderOptions.Length; index++) { GrabProviderOptions[index].gameObject.SetActive(index == providerIndex); } UnlinkReceiverToProvider(); UnlinkToPrimaryAction(); UnlinkToSecondaryAction(); LinkReceiverToProvider(); LinkToPrimaryAction(); LinkToSecondaryAction(); } /// /// Toggles the state of the Attempt Swap Logic container. /// /// The state to toggle to. public virtual void ToggleAttemptSwapLogicContainer(bool state) { if (GrabProviderOptions.Length > 0 && typeof(GrabInteractableStackInteractorProvider).IsAssignableFrom(GrabProviderOptions[0].GetType())) { GrabInteractableStackInteractorProvider stackProvider = (GrabInteractableStackInteractorProvider)GrabProviderOptions[0]; stackProvider.AttemptSwapLogicContainer.SetActive(state); } } /// /// Snaps the follow on the primary and secondary action if they are type. /// public virtual void SnapFollowOrientation() { if (PrimaryAction != null && PrimaryAction.GetType() == typeof(GrabInteractableFollowAction)) { GrabInteractableFollowAction action = (GrabInteractableFollowAction)PrimaryAction; action.ForceSnapOrientation(); } if (SecondaryAction != null && SecondaryAction.GetType() == typeof(GrabInteractableFollowAction)) { GrabInteractableFollowAction action = (GrabInteractableFollowAction)SecondaryAction; action.ForceSnapOrientation(); } } protected virtual void OnEnable() { SetGrabProvider(Facade.GrabProviderIndex); ToggleAttemptSwapLogicContainer(Facade.AttemptSwapSecondaryForPrimaryOnPrimaryDrop); ConfigureContainer(); GrabReceiver.GrabValidity.ReceiveValidity = Facade.Configuration.DisallowedGrabInteractors; GrabReceiver.GrabType = Facade.GrabType; } protected virtual void OnDisable() { UnlinkReceiverToProvider(); UnlinkToPrimaryAction(); UnlinkToSecondaryAction(); } /// /// Configures the action containers. /// /// The action to configure. protected virtual void ConfigureActionContainer(GrabInteractableAction action) { if (action == null) { return; } action.GrabSetup = this; action.RunWhenActiveAndEnabled(() => action.GrabSetup = this); } /// /// Links the Grab Receiver to the Grab Provider. /// protected virtual void LinkReceiverToProvider() { GrabReceiver.OutputGrabAction.Emitted.AddListener(GrabProvider.InputGrabReceived.Receive); GrabReceiver.OutputUngrabAction.Emitted.AddListener(GrabProvider.InputUngrabReceived.Receive); GrabReceiver.OutputUngrabOnUntouchAction.Emitted.AddListener(Facade.Ungrab); } /// /// Unlinks the Grab Receiver to the Grab Provider. /// protected virtual void UnlinkReceiverToProvider() { GrabReceiver.OutputGrabAction.Emitted.RemoveListener(GrabProvider.InputGrabReceived.Receive); GrabReceiver.OutputUngrabAction.Emitted.RemoveListener(GrabProvider.InputUngrabReceived.Receive); GrabReceiver.OutputUngrabOnUntouchAction.Emitted.RemoveListener(Facade.Ungrab); } /// /// Links the Grab Receiver and Grab Provider to the Primary Grab Action. /// protected virtual void LinkToPrimaryAction() { if (PrimaryAction == null) { return; } GrabReceiver.OutputActiveCollisionConsumer.Emitted.AddListener(PrimaryAction.InputActiveCollisionConsumer.Receive); GrabProvider.OutputPrimaryGrabAction.Emitted.AddListener(PrimaryAction.InputGrabReceived.Receive); GrabProvider.OutputPrimaryUngrabAction.Emitted.AddListener(PrimaryAction.InputUngrabReceived.Receive); GrabProvider.OutputPrimaryGrabAction.Emitted.AddListener(EnableSecondaryInputActiveCollisionConsumer); GrabProvider.OutputPrimaryUngrabAction.Emitted.AddListener(DisableSecondaryInputActiveCollisionConsumer); } /// /// Unlinks the Grab Receiver and Grab Provider to the Primary Grab Action. /// protected virtual void UnlinkToPrimaryAction() { if (PrimaryAction == null) { return; } GrabReceiver.OutputActiveCollisionConsumer.Emitted.RemoveListener(PrimaryAction.InputActiveCollisionConsumer.Receive); GrabProvider.OutputPrimaryGrabAction.Emitted.RemoveListener(PrimaryAction.InputGrabReceived.Receive); GrabProvider.OutputPrimaryUngrabAction.Emitted.RemoveListener(PrimaryAction.InputUngrabReceived.Receive); GrabProvider.OutputPrimaryGrabAction.Emitted.RemoveListener(EnableSecondaryInputActiveCollisionConsumer); GrabProvider.OutputPrimaryUngrabAction.Emitted.RemoveListener(DisableSecondaryInputActiveCollisionConsumer); } /// /// Links the Grab Receiver and Grab Provider to the Secondary Grab Action. /// protected virtual void LinkToSecondaryAction() { if (SecondaryAction == null) { return; } GrabReceiver.OutputActiveCollisionConsumer.Emitted.AddListener(SecondaryAction.InputActiveCollisionConsumer.Receive); GrabProvider.OutputPrimaryGrabSetupOnSecondaryAction.Emitted.AddListener(SecondaryAction.InputGrabSetup.Receive); GrabProvider.OutputPrimaryUngrabResetOnSecondaryAction.Emitted.AddListener(SecondaryAction.InputUngrabReset.Receive); GrabProvider.OutputSecondaryGrabAction.Emitted.AddListener(SecondaryAction.InputGrabReceived.Receive); GrabProvider.OutputSecondaryUngrabAction.Emitted.AddListener(SecondaryAction.InputUngrabReceived.Receive); DisableSecondaryInputActiveCollisionConsumer(null); } /// /// Unlinks the Grab Receiver and Grab Provider to the Secondary Grab Action. /// protected virtual void UnlinkToSecondaryAction() { if (SecondaryAction == null) { return; } GrabReceiver.OutputActiveCollisionConsumer.Emitted.RemoveListener(SecondaryAction.InputActiveCollisionConsumer.Receive); GrabProvider.OutputPrimaryGrabSetupOnSecondaryAction.Emitted.RemoveListener(SecondaryAction.InputGrabSetup.Receive); GrabProvider.OutputPrimaryUngrabResetOnSecondaryAction.Emitted.RemoveListener(SecondaryAction.InputUngrabReset.Receive); GrabProvider.OutputSecondaryGrabAction.Emitted.RemoveListener(SecondaryAction.InputGrabReceived.Receive); GrabProvider.OutputSecondaryUngrabAction.Emitted.RemoveListener(SecondaryAction.InputUngrabReceived.Receive); } /// /// Determines whether the primary grab action is of type . /// /// The amount of grabbing Interactors. /// Whether the primary grab is of type . protected virtual bool PrimaryGrabIsNone(int interactorCount) { bool validNullAction = false; if (PrimaryAction.GetType() == typeof(GrabInteractableNullAction)) { GrabInteractableNullAction checkAction = (GrabInteractableNullAction)PrimaryAction; validNullAction = checkAction.ForceEmitEvents ? false : true; } return Facade.GrabbingInteractors.Count > interactorCount && validNullAction; } /// /// Enables the Secondary Input Active Collision Consumer component. /// /// unused protected virtual void EnableSecondaryInputActiveCollisionConsumer(GameObject _) { SecondaryAction.InputActiveCollisionConsumer.enabled = true; } /// /// Disables the Secondary Input Active Collision Consumer component. /// /// unused protected virtual void DisableSecondaryInputActiveCollisionConsumer(GameObject _) { SecondaryAction.InputActiveCollisionConsumer.enabled = false; } /// /// Called after has been changed. /// protected virtual void OnBeforePrimaryActionChange() { UnlinkToPrimaryAction(); } /// /// Called after has been changed. /// protected virtual void OnAfterPrimaryActionChange() { LinkToPrimaryAction(); ConfigureActionContainer(PrimaryAction); } /// /// Called after has been changed. /// protected virtual void OnBeforeSecondaryActionChange() { UnlinkToSecondaryAction(); } /// /// Called after has been changed. /// protected virtual void OnAfterSecondaryActionChange() { LinkToSecondaryAction(); ConfigureActionContainer(SecondaryAction); } } }