using System;
using NightclubArcadia.Interaction;
using NightclubArcadia.UI;
using UnityEngine;
using UnityEngine.EventSystems;
namespace NightclubArcadia.Player
{
///
/// Converts pointer clicks into click-to-move destinations or interactable
/// approach targets.
///
public sealed class PointerNavigationInput : MonoBehaviour
{
const float DoubleClickWindow = 0.35f;
const float DoubleClickPixelThreshold = 40f;
[SerializeField] Camera brainCamera;
[SerializeField] LayerMask clickMask = ~0;
[SerializeField] PlayerPointerInput pointerInput;
[SerializeField] ClickToMoveController clickToMove;
[SerializeField] PlayerMotionArbiter motionArbiter;
float lastClickTime = -1f;
Vector2 lastClickPosition;
bool pendingClick;
Vector2 pendingClickPosition;
void Awake()
{
if (pointerInput == null)
{
pointerInput = GetComponent();
}
if (clickToMove == null)
{
clickToMove = GetComponent();
}
if (motionArbiter == null)
{
motionArbiter = GetComponent();
}
if (brainCamera == null)
{
brainCamera = Camera.main;
}
if (clickMask.value == ~0)
{
clickMask = LayerMask.GetMask("Default", "Ground", "Interactable");
}
}
void OnEnable()
{
if (pointerInput != null)
{
pointerInput.Clicked += OnClicked;
}
}
void OnDisable()
{
if (pointerInput != null)
{
pointerInput.Clicked -= OnClicked;
}
pendingClick = false;
}
// Input System callbacks fire during event processing, where
// IsPointerOverGameObject() is invalid (queries last frame's UI).
// Queue the click and resolve it in Update instead.
void OnClicked()
{
if (pointerInput == null)
{
return;
}
pendingClick = true;
pendingClickPosition = pointerInput.ScreenPosition;
}
void Update()
{
if (!pendingClick)
{
return;
}
pendingClick = false;
ProcessClick(pendingClickPosition);
}
void ProcessClick(Vector2 screenPos)
{
if (!CanNavigate())
{
return;
}
if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject())
{
return;
}
if (brainCamera == null)
{
brainCamera = Camera.main;
}
if (brainCamera == null || clickToMove == null)
{
return;
}
var ray = brainCamera.ScreenPointToRay(screenPos);
var hits = Physics.RaycastAll(ray, 200f, clickMask, QueryTriggerInteraction.Collide);
Array.Sort(hits, (a, b) => a.distance.CompareTo(b.distance));
var isDoubleClick = IsDoubleClick(screenPos);
lastClickTime = Time.unscaledTime;
lastClickPosition = screenPos;
foreach (var hit in hits)
{
var interactable = FindInteractable(hit.collider);
if (interactable != null)
{
if (isDoubleClick)
{
clickToMove.RequestRun();
}
if (clickToMove.SetInteractionTarget(interactable))
{
return;
}
}
}
foreach (var hit in hits)
{
if (isDoubleClick)
{
clickToMove.RequestRun();
}
if (clickToMove.SetDestination(hit.point))
{
return;
}
}
}
bool CanNavigate()
{
if (GetComponent() is { IsLocked: true })
{
return false;
}
if (UIStateController.Instance == null)
{
return true;
}
return !UIStateController.Instance.IsDialogueActive
&& !UIStateController.Instance.IsMenuOpen;
}
bool IsDoubleClick(Vector2 screenPos)
{
if (lastClickTime < 0f)
{
return false;
}
var withinTime = Time.unscaledTime - lastClickTime <= DoubleClickWindow;
var withinDistance = Vector2.Distance(screenPos, lastClickPosition) <= DoubleClickPixelThreshold;
return withinTime && withinDistance;
}
static IClickInteractable FindInteractable(Collider collider)
{
var behaviours = collider.GetComponentsInParent();
foreach (var behaviour in behaviours)
{
if (behaviour is IClickInteractable interactable)
{
return interactable;
}
}
return null;
}
}
}