using System.Collections.Generic; using Sirenix.OdinInspector; using UnityEngine; namespace DDD { public class RestaurantCharacterInteraction : MonoBehaviour, IInteractor, IEventHandler { [SerializeField, ReadOnly] protected Collider[] _nearColliders = new Collider[10]; protected IInteractable _nearestInteractable; protected IInteractable _previousInteractable; protected IInteractable _interactingTarget; protected float _interactHeldTime; protected bool _isInteracting; protected float _interactionRadius = 1f; protected LayerMask _interactionLayerMask = (LayerMask)(-1); private Dictionary _cachedSolvers = new(); protected virtual void Start() { } protected virtual void Update() { _nearestInteractable = GetNearestInteractable(); if (_nearestInteractable != _previousInteractable) { _previousInteractable = _nearestInteractable; OnNearestInteractableChanged(_nearestInteractable); } if (_isInteracting) { if (_nearestInteractable != _interactingTarget || CanInteractTo(_interactingTarget) == false) { ResetInteractionState(); return; } _interactHeldTime += Time.deltaTime; float requiredHoldTime = _interactingTarget.GetRequiredHoldTime(); float ratio = Mathf.Clamp01(_interactHeldTime / requiredHoldTime); if (_interactHeldTime >= requiredHoldTime) { OnInteractionHoldProgress(1f); _isInteracting = false; _interactingTarget.OnInteracted(this); _interactingTarget = null; OnInteractionCompleted(); } OnInteractionHoldProgress(ratio); } } protected virtual void OnDestroy() { } protected virtual void OnNearestInteractableChanged(IInteractable newTarget) { } protected virtual void OnInteractionHoldProgress(float ratio) { } protected virtual void OnInteractionCompleted() { } protected void ResetInteractionState() { _isInteracting = false; _interactingTarget = null; _interactHeldTime = 0f; OnInteractionHoldProgress(0f); } protected IInteractable GetNearestInteractable() { int colliderCount = Physics.OverlapSphereNonAlloc(transform.position, _interactionRadius, _nearColliders, _interactionLayerMask, QueryTriggerInteraction.Collide); float closestDistance = float.MaxValue; IInteractable closest = null; for (int i = 0; i < colliderCount; i++) { var col = _nearColliders[i]; if (col.TryGetComponent(out var interactable) == false) continue; var type = interactable.GetInteractionType(); if (interactable.IsInteractionHidden()) continue; if (CanSolveInteractionType(type) == false) continue; float distance = Vector3.Distance(transform.position, col.transform.position); if (distance < closestDistance) { closestDistance = distance; closest = interactable; } } return closest; } public virtual void Invoke(RestaurantInteractionEvent evt) { } public GameObject GetInteractorGameObject() => gameObject; public IInteractionSolver GetInteractionSolver(InteractionType interactionType) { TryGetSolverForType(interactionType, out var solver); return solver; } public IInteractable GetFocusedInteractable() { return _nearestInteractable; } private bool TryGetSolverFor(IInteractable interactable, out IInteractionSolver solver) { solver = null; if (interactable == null) return false; return TryGetSolverForType(interactable.GetInteractionType(), out solver); } private bool TryGetSolverForType(InteractionType type, out IInteractionSolver solver) { if (_cachedSolvers.TryGetValue(type, out solver)) return solver != null; solver = null; if (RestaurantInteractionEventSolvers.TypeToSolver.TryGetValue(type, out var solverType) == false) return false; if (transform.TryGetComponent(solverType, out var component) == false) return false; solver = component as IInteractionSolver; _cachedSolvers[type] = solver; return solver != null; } public bool CanSolveInteractionType(InteractionType type) { if (_cachedSolvers.TryGetValue(type, out var cachedSolver)) return cachedSolver != null; if (RestaurantInteractionEventSolvers.TypeToSolver.TryGetValue(type, out var solverType) == false) return false; if (transform.TryGetComponent(solverType, out var component) == false) return false; var solver = component as IInteractionSolver; _cachedSolvers[type] = solver; return solver != null; } public bool CanInteractTo(IInteractable interactable, ScriptableObject payloadSo = null) { if (interactable == null) return false; if (interactable.CanInteract() == false) return false; if (TryGetSolverFor(interactable, out var solver) == false) return false; return solver.CanExecuteInteraction(this, interactable, payloadSo); } } }