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);
}
}
}