2025-09-11 13:00:26 +02:00
|
|
|
|
using Input;
|
|
|
|
|
|
using UnityEngine;
|
2025-09-01 16:14:21 +02:00
|
|
|
|
using System;
|
2025-10-07 10:57:11 +00:00
|
|
|
|
using System.Collections.Generic;
|
2025-09-11 13:00:26 +02:00
|
|
|
|
using UnityEngine.Events;
|
2025-10-07 10:57:11 +00:00
|
|
|
|
using System.Threading.Tasks;
|
2025-10-14 15:53:58 +02:00
|
|
|
|
using Core;
|
2025-09-01 16:14:21 +02:00
|
|
|
|
|
2025-09-11 13:00:26 +02:00
|
|
|
|
namespace Interactions
|
2025-09-01 16:14:21 +02:00
|
|
|
|
{
|
2025-09-11 13:00:26 +02:00
|
|
|
|
public enum CharacterToInteract
|
2025-09-01 16:14:21 +02:00
|
|
|
|
{
|
2025-10-07 10:57:11 +00:00
|
|
|
|
None,
|
2025-09-11 13:00:26 +02:00
|
|
|
|
Trafalgar,
|
2025-10-07 10:57:11 +00:00
|
|
|
|
Pulver,
|
|
|
|
|
|
Both
|
2025-09-01 16:14:21 +02:00
|
|
|
|
}
|
2025-10-07 10:57:11 +00:00
|
|
|
|
|
2025-09-05 15:03:52 +02:00
|
|
|
|
/// <summary>
|
2025-10-31 13:50:08 +01:00
|
|
|
|
/// Base class for interactable objects that can respond to tap input events.
|
|
|
|
|
|
/// Subclasses should override OnCharacterArrived() to implement interaction-specific logic.
|
2025-09-05 15:03:52 +02:00
|
|
|
|
/// </summary>
|
2025-10-31 13:50:08 +01:00
|
|
|
|
public abstract class InteractableBase : MonoBehaviour, ITouchInputConsumer
|
2025-09-04 13:08:14 +02:00
|
|
|
|
{
|
2025-09-11 13:00:26 +02:00
|
|
|
|
[Header("Interaction Settings")]
|
2025-10-07 10:57:11 +00:00
|
|
|
|
public bool isOneTime;
|
2025-09-11 13:00:26 +02:00
|
|
|
|
public float cooldown = -1f;
|
|
|
|
|
|
public CharacterToInteract characterToInteract = CharacterToInteract.Pulver;
|
|
|
|
|
|
|
|
|
|
|
|
[Header("Interaction Events")]
|
|
|
|
|
|
public UnityEvent<PlayerTouchController, FollowerController> interactionStarted;
|
|
|
|
|
|
public UnityEvent interactionInterrupted;
|
|
|
|
|
|
public UnityEvent characterArrived;
|
|
|
|
|
|
public UnityEvent<bool> interactionComplete;
|
2025-09-29 09:34:15 +00:00
|
|
|
|
|
2025-09-11 13:00:26 +02:00
|
|
|
|
// Helpers for managing interaction state
|
|
|
|
|
|
private bool _interactionInProgress;
|
2025-10-31 13:50:08 +01:00
|
|
|
|
protected PlayerTouchController _playerRef;
|
|
|
|
|
|
protected FollowerController _followerController;
|
2025-09-11 13:00:26 +02:00
|
|
|
|
private bool _isActive = true;
|
2025-10-07 10:57:11 +00:00
|
|
|
|
private InteractionEventType _currentEventType;
|
2025-09-11 13:00:26 +02:00
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
// Action component system
|
|
|
|
|
|
private List<InteractionActionBase> _registeredActions = new List<InteractionActionBase>();
|
|
|
|
|
|
|
2025-09-11 13:00:26 +02:00
|
|
|
|
private void Awake()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Subscribe to interactionComplete event
|
|
|
|
|
|
interactionComplete.AddListener(OnInteractionComplete);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Register an action component with this interactable
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void RegisterAction(InteractionActionBase action)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_registeredActions.Contains(action))
|
|
|
|
|
|
{
|
|
|
|
|
|
_registeredActions.Add(action);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Unregister an action component from this interactable
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void UnregisterAction(InteractionActionBase action)
|
|
|
|
|
|
{
|
|
|
|
|
|
_registeredActions.Remove(action);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Dispatch an interaction event to all registered actions and await their completion
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task DispatchEventAsync(InteractionEventType eventType)
|
|
|
|
|
|
{
|
|
|
|
|
|
_currentEventType = eventType;
|
|
|
|
|
|
|
|
|
|
|
|
// Collect all tasks from actions that want to respond
|
|
|
|
|
|
List<Task<bool>> tasks = new List<Task<bool>>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var action in _registeredActions)
|
|
|
|
|
|
{
|
|
|
|
|
|
Task<bool> task = action.OnInteractionEvent(eventType, _playerRef, _followerController);
|
|
|
|
|
|
if (task != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
tasks.Add(task);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (tasks.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Wait for all tasks to complete
|
|
|
|
|
|
await Task.WhenAll(tasks);
|
|
|
|
|
|
}
|
|
|
|
|
|
// If no tasks were added, the method will complete immediately (no need for await)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-11 13:00:26 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Handles tap input. Triggers interaction logic.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void OnTap(Vector2 worldPosition)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isActive)
|
|
|
|
|
|
{
|
2025-10-14 15:53:58 +02:00
|
|
|
|
Logging.Debug($"[Interactable] Is disabled!");
|
2025-09-11 13:00:26 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-14 15:53:58 +02:00
|
|
|
|
Logging.Debug($"[Interactable] OnTap at {worldPosition} on {gameObject.name}");
|
2025-10-07 10:57:11 +00:00
|
|
|
|
|
|
|
|
|
|
// Start the interaction process asynchronously
|
|
|
|
|
|
_ = TryInteractAsync();
|
2025-09-11 13:00:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
private async Task TryInteractAsync()
|
2025-09-11 13:00:26 +02:00
|
|
|
|
{
|
|
|
|
|
|
_interactionInProgress = true;
|
|
|
|
|
|
|
|
|
|
|
|
_playerRef = FindFirstObjectByType<PlayerTouchController>();
|
|
|
|
|
|
_followerController = FindFirstObjectByType<FollowerController>();
|
|
|
|
|
|
|
|
|
|
|
|
interactionStarted?.Invoke(_playerRef, _followerController);
|
|
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
// Dispatch the InteractionStarted event to action components
|
|
|
|
|
|
await DispatchEventAsync(InteractionEventType.InteractionStarted);
|
|
|
|
|
|
|
|
|
|
|
|
// After all InteractionStarted actions complete, proceed to player movement
|
|
|
|
|
|
await StartPlayerMovementAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task StartPlayerMovementAsync()
|
|
|
|
|
|
{
|
2025-09-11 13:00:26 +02:00
|
|
|
|
if (_playerRef == null)
|
|
|
|
|
|
{
|
2025-10-14 15:53:58 +02:00
|
|
|
|
Logging.Debug($"[Interactable] Player character could not be found. Aborting interaction.");
|
2025-09-11 13:00:26 +02:00
|
|
|
|
interactionInterrupted.Invoke();
|
2025-10-07 10:57:11 +00:00
|
|
|
|
await DispatchEventAsync(InteractionEventType.InteractionInterrupted);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If characterToInteract is None, immediately trigger the characterArrived event
|
|
|
|
|
|
if (characterToInteract == CharacterToInteract.None)
|
|
|
|
|
|
{
|
|
|
|
|
|
await BroadcastCharacterArrivedAsync();
|
2025-09-11 13:00:26 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
// Check for a CharacterMoveToTarget component for Trafalgar (player) or Both
|
|
|
|
|
|
Vector3 stopPoint;
|
|
|
|
|
|
bool customTargetFound = false;
|
|
|
|
|
|
|
|
|
|
|
|
CharacterMoveToTarget[] moveTargets = GetComponentsInChildren<CharacterMoveToTarget>();
|
|
|
|
|
|
foreach (var target in moveTargets)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Target is valid if it matches Trafalgar specifically or is set to Both
|
|
|
|
|
|
if (target.characterType == CharacterToInteract.Trafalgar || target.characterType == CharacterToInteract.Both)
|
|
|
|
|
|
{
|
|
|
|
|
|
stopPoint = target.GetTargetPosition();
|
|
|
|
|
|
customTargetFound = true;
|
|
|
|
|
|
|
|
|
|
|
|
// We need to wait for the player to arrive, so use a TaskCompletionSource
|
|
|
|
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
|
|
|
|
|
|
|
|
|
|
// Use local functions instead of circular lambda references
|
|
|
|
|
|
void OnPlayerArrivedLocal()
|
|
|
|
|
|
{
|
|
|
|
|
|
// First remove both event handlers to prevent memory leaks
|
|
|
|
|
|
if (_playerRef != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
|
|
|
|
|
|
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Then continue with the interaction flow
|
|
|
|
|
|
OnPlayerArrivedAsync().ContinueWith(_ => tcs.TrySetResult(true));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OnPlayerMoveCancelledLocal()
|
|
|
|
|
|
{
|
|
|
|
|
|
// First remove both event handlers to prevent memory leaks
|
|
|
|
|
|
if (_playerRef != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
|
|
|
|
|
|
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Then handle the cancellation
|
|
|
|
|
|
OnPlayerMoveCancelledAsync().ContinueWith(_ => tcs.TrySetResult(false));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Unsubscribe previous handlers (if any)
|
|
|
|
|
|
_playerRef.OnArrivedAtTarget -= OnPlayerArrived;
|
|
|
|
|
|
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelled;
|
|
|
|
|
|
|
|
|
|
|
|
// Subscribe our new handlers
|
|
|
|
|
|
_playerRef.OnArrivedAtTarget += OnPlayerArrivedLocal;
|
|
|
|
|
|
_playerRef.OnMoveToCancelled += OnPlayerMoveCancelledLocal;
|
|
|
|
|
|
|
|
|
|
|
|
// Start the player movement
|
|
|
|
|
|
_playerRef.MoveToAndNotify(stopPoint);
|
|
|
|
|
|
|
|
|
|
|
|
// Await player arrival
|
|
|
|
|
|
await tcs.Task;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If no custom target was found, use the default behavior
|
|
|
|
|
|
if (!customTargetFound)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Compute closest point on the interaction radius
|
|
|
|
|
|
Vector3 interactablePos = transform.position;
|
|
|
|
|
|
Vector3 playerPos = _playerRef.transform.position;
|
|
|
|
|
|
float stopDistance = characterToInteract == CharacterToInteract.Pulver
|
|
|
|
|
|
? GameManager.Instance.PlayerStopDistance
|
|
|
|
|
|
: GameManager.Instance.PlayerStopDistanceDirectInteraction;
|
|
|
|
|
|
Vector3 toPlayer = (playerPos - interactablePos).normalized;
|
|
|
|
|
|
stopPoint = interactablePos + toPlayer * stopDistance;
|
2025-09-11 13:00:26 +02:00
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
// We need to wait for the player to arrive, so use a TaskCompletionSource
|
|
|
|
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
|
|
|
|
|
|
|
|
|
|
// Use local functions instead of circular lambda references
|
|
|
|
|
|
void OnPlayerArrivedLocal()
|
|
|
|
|
|
{
|
|
|
|
|
|
// First remove both event handlers to prevent memory leaks
|
|
|
|
|
|
if (_playerRef != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
|
|
|
|
|
|
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Then continue with the interaction flow
|
|
|
|
|
|
OnPlayerArrivedAsync().ContinueWith(_ => tcs.TrySetResult(true));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OnPlayerMoveCancelledLocal()
|
|
|
|
|
|
{
|
|
|
|
|
|
// First remove both event handlers to prevent memory leaks
|
|
|
|
|
|
if (_playerRef != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
|
|
|
|
|
|
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Then handle the cancellation
|
|
|
|
|
|
OnPlayerMoveCancelledAsync().ContinueWith(_ => tcs.TrySetResult(false));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Unsubscribe previous handlers (if any)
|
|
|
|
|
|
_playerRef.OnArrivedAtTarget -= OnPlayerArrived;
|
|
|
|
|
|
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelled;
|
|
|
|
|
|
|
|
|
|
|
|
// Subscribe our new handlers
|
|
|
|
|
|
_playerRef.OnArrivedAtTarget += OnPlayerArrivedLocal;
|
|
|
|
|
|
_playerRef.OnMoveToCancelled += OnPlayerMoveCancelledLocal;
|
|
|
|
|
|
|
|
|
|
|
|
// Start the player movement
|
|
|
|
|
|
_playerRef.MoveToAndNotify(stopPoint);
|
|
|
|
|
|
|
|
|
|
|
|
// Await player arrival
|
|
|
|
|
|
await tcs.Task;
|
|
|
|
|
|
}
|
2025-09-11 13:00:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
private async Task OnPlayerMoveCancelledAsync()
|
2025-09-11 13:00:26 +02:00
|
|
|
|
{
|
|
|
|
|
|
_interactionInProgress = false;
|
|
|
|
|
|
interactionInterrupted?.Invoke();
|
2025-10-07 10:57:11 +00:00
|
|
|
|
await DispatchEventAsync(InteractionEventType.InteractionInterrupted);
|
2025-09-11 13:00:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
private async Task OnPlayerArrivedAsync()
|
2025-09-11 13:00:26 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (!_interactionInProgress)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
// Dispatch PlayerArrived event
|
|
|
|
|
|
await DispatchEventAsync(InteractionEventType.PlayerArrived);
|
2025-09-11 13:00:26 +02:00
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
// After all PlayerArrived actions complete, proceed to character interaction
|
|
|
|
|
|
await HandleCharacterInteractionAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task HandleCharacterInteractionAsync()
|
|
|
|
|
|
{
|
2025-09-11 13:00:26 +02:00
|
|
|
|
if (characterToInteract == CharacterToInteract.Pulver)
|
|
|
|
|
|
{
|
2025-10-07 10:57:11 +00:00
|
|
|
|
// We need to wait for the follower to arrive, so use a TaskCompletionSource
|
|
|
|
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
|
|
|
|
|
|
|
|
|
|
// Create a proper local function for the event handler
|
|
|
|
|
|
void OnFollowerArrivedLocal()
|
|
|
|
|
|
{
|
|
|
|
|
|
// First remove the event handler to prevent memory leaks
|
|
|
|
|
|
if (_followerController != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_followerController.OnPickupArrived -= OnFollowerArrivedLocal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Then continue with the interaction flow
|
|
|
|
|
|
OnFollowerArrivedAsync().ContinueWith(_ => tcs.TrySetResult(true));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Register our new local function handler
|
|
|
|
|
|
_followerController.OnPickupArrived += OnFollowerArrivedLocal;
|
|
|
|
|
|
|
|
|
|
|
|
// Check for a CharacterMoveToTarget component for Pulver or Both
|
|
|
|
|
|
Vector3 targetPosition = transform.position;
|
|
|
|
|
|
CharacterMoveToTarget[] moveTargets = GetComponentsInChildren<CharacterMoveToTarget>();
|
|
|
|
|
|
foreach (var target in moveTargets)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (target.characterType == CharacterToInteract.Pulver || target.characterType == CharacterToInteract.Both)
|
|
|
|
|
|
{
|
|
|
|
|
|
targetPosition = target.GetTargetPosition();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Use the new GoToPoint method instead of GoToPointAndReturn
|
|
|
|
|
|
_followerController.GoToPoint(targetPosition);
|
|
|
|
|
|
|
|
|
|
|
|
// Await follower arrival
|
|
|
|
|
|
await tcs.Task;
|
2025-09-11 13:00:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
else if (characterToInteract == CharacterToInteract.Trafalgar)
|
|
|
|
|
|
{
|
2025-10-07 10:57:11 +00:00
|
|
|
|
await BroadcastCharacterArrivedAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (characterToInteract == CharacterToInteract.Both)
|
|
|
|
|
|
{
|
|
|
|
|
|
// We need to wait for the follower to arrive, so use a TaskCompletionSource
|
|
|
|
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
|
|
|
|
|
|
|
|
|
|
// Create a proper local function for the event handler
|
|
|
|
|
|
void OnFollowerArrivedLocal()
|
|
|
|
|
|
{
|
|
|
|
|
|
// First remove the event handler to prevent memory leaks
|
|
|
|
|
|
if (_followerController != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_followerController.OnPickupArrived -= OnFollowerArrivedLocal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Then continue with the interaction flow
|
|
|
|
|
|
OnFollowerArrivedAsync().ContinueWith(_ => tcs.TrySetResult(true));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Register our new local function handler
|
|
|
|
|
|
_followerController.OnPickupArrived += OnFollowerArrivedLocal;
|
|
|
|
|
|
|
|
|
|
|
|
// Check for a CharacterMoveToTarget component for Pulver or Both
|
|
|
|
|
|
Vector3 targetPosition = transform.position;
|
|
|
|
|
|
CharacterMoveToTarget[] moveTargets = GetComponentsInChildren<CharacterMoveToTarget>();
|
|
|
|
|
|
foreach (var target in moveTargets)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (target.characterType == CharacterToInteract.Pulver || target.characterType == CharacterToInteract.Both)
|
|
|
|
|
|
{
|
|
|
|
|
|
targetPosition = target.GetTargetPosition();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Use the new GoToPoint method instead of GoToPointAndReturn
|
|
|
|
|
|
_followerController.GoToPoint(targetPosition);
|
|
|
|
|
|
|
|
|
|
|
|
// Await follower arrival
|
|
|
|
|
|
await tcs.Task;
|
2025-09-11 13:00:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
private async Task OnFollowerArrivedAsync()
|
2025-09-11 13:00:26 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (!_interactionInProgress)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
// Dispatch InteractingCharacterArrived event and WAIT for all actions to complete
|
|
|
|
|
|
// This ensures we wait for any timeline animations to finish before proceeding
|
2025-10-14 15:53:58 +02:00
|
|
|
|
Logging.Debug("[Interactable] Follower arrived, dispatching InteractingCharacterArrived event and waiting for completion");
|
2025-10-07 10:57:11 +00:00
|
|
|
|
await DispatchEventAsync(InteractionEventType.InteractingCharacterArrived);
|
2025-10-14 15:53:58 +02:00
|
|
|
|
Logging.Debug("[Interactable] All InteractingCharacterArrived actions completed, proceeding with interaction");
|
2025-10-07 10:57:11 +00:00
|
|
|
|
|
|
|
|
|
|
// Check if we have any components that might have paused the interaction flow
|
|
|
|
|
|
foreach (var action in _registeredActions)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (action is InteractionTimelineAction timelineAction &&
|
|
|
|
|
|
timelineAction.respondToEvents.Contains(InteractionEventType.InteractingCharacterArrived) &&
|
|
|
|
|
|
timelineAction.pauseInteractionFlow)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-11 13:00:26 +02:00
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
// Tell the follower to return to the player
|
|
|
|
|
|
if (_followerController != null && _playerRef != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_followerController.ReturnToPlayer(_playerRef.transform);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// After all InteractingCharacterArrived actions complete, proceed to character arrived
|
|
|
|
|
|
await BroadcastCharacterArrivedAsync();
|
2025-09-11 13:00:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
// Legacy non-async method to maintain compatibility with existing code
|
|
|
|
|
|
private void OnPlayerArrived()
|
|
|
|
|
|
{
|
|
|
|
|
|
// This is now just a wrapper for the async version
|
|
|
|
|
|
_ = OnPlayerArrivedAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Legacy non-async method to maintain compatibility with existing code
|
|
|
|
|
|
private void OnPlayerMoveCancelled()
|
|
|
|
|
|
{
|
|
|
|
|
|
// This is now just a wrapper for the async version
|
|
|
|
|
|
_ = OnPlayerMoveCancelledAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-28 14:31:17 +01:00
|
|
|
|
private Task BroadcastCharacterArrivedAsync()
|
2025-09-11 13:00:26 +02:00
|
|
|
|
{
|
|
|
|
|
|
// Check for ObjectiveStepBehaviour and lock state
|
|
|
|
|
|
var step = GetComponent<PuzzleS.ObjectiveStepBehaviour>();
|
2025-10-16 19:43:19 +02:00
|
|
|
|
var slot = GetComponent<ItemSlot>();
|
|
|
|
|
|
if (step != null && !step.IsStepUnlocked() && slot == null)
|
2025-09-11 13:00:26 +02:00
|
|
|
|
{
|
2025-09-12 12:26:44 +02:00
|
|
|
|
DebugUIMessage.Show("This step is locked!", Color.yellow);
|
2025-10-31 13:50:08 +01:00
|
|
|
|
CompleteInteraction(false);
|
2025-09-11 13:00:26 +02:00
|
|
|
|
// Reset variables for next time
|
|
|
|
|
|
_interactionInProgress = false;
|
|
|
|
|
|
_playerRef = null;
|
|
|
|
|
|
_followerController = null;
|
2025-10-28 14:31:17 +01:00
|
|
|
|
return Task.CompletedTask;
|
2025-09-11 13:00:26 +02:00
|
|
|
|
}
|
2025-10-07 10:57:11 +00:00
|
|
|
|
|
|
|
|
|
|
// Dispatch CharacterArrived event
|
|
|
|
|
|
// await DispatchEventAsync(InteractionEventType.InteractingCharacterArrived);
|
|
|
|
|
|
|
2025-09-11 13:00:26 +02:00
|
|
|
|
// Broadcast appropriate event
|
|
|
|
|
|
characterArrived?.Invoke();
|
2025-10-07 10:57:11 +00:00
|
|
|
|
|
2025-10-31 13:50:08 +01:00
|
|
|
|
// Call the virtual method for subclasses to override
|
|
|
|
|
|
OnCharacterArrived();
|
|
|
|
|
|
|
2025-09-11 13:00:26 +02:00
|
|
|
|
// Reset variables for next time
|
|
|
|
|
|
_interactionInProgress = false;
|
|
|
|
|
|
_playerRef = null;
|
|
|
|
|
|
_followerController = null;
|
2025-10-28 14:31:17 +01:00
|
|
|
|
return Task.CompletedTask;
|
2025-09-11 13:00:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 13:50:08 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Called when the character has arrived at the interaction point.
|
|
|
|
|
|
/// Subclasses should override this to implement interaction-specific logic
|
|
|
|
|
|
/// and call CompleteInteraction(bool success) when done.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected virtual void OnCharacterArrived()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Default implementation does nothing - subclasses should override
|
|
|
|
|
|
// and call CompleteInteraction when their logic is complete
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 10:57:11 +00:00
|
|
|
|
private async void OnInteractionComplete(bool success)
|
2025-09-11 13:00:26 +02:00
|
|
|
|
{
|
2025-10-07 10:57:11 +00:00
|
|
|
|
// Dispatch InteractionComplete event
|
|
|
|
|
|
await DispatchEventAsync(InteractionEventType.InteractionComplete);
|
|
|
|
|
|
|
2025-09-11 13:00:26 +02:00
|
|
|
|
if (success)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isOneTime)
|
|
|
|
|
|
{
|
|
|
|
|
|
_isActive = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (cooldown >= 0f)
|
|
|
|
|
|
{
|
|
|
|
|
|
StartCoroutine(HandleCooldown());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private System.Collections.IEnumerator HandleCooldown()
|
|
|
|
|
|
{
|
|
|
|
|
|
_isActive = false;
|
|
|
|
|
|
yield return new WaitForSeconds(cooldown);
|
|
|
|
|
|
_isActive = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void OnHoldStart(Vector2 position)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void OnHoldMove(Vector2 position)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void OnHoldEnd(Vector2 position)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 13:50:08 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Call this from subclasses to mark the interaction as complete.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="success">Whether the interaction was successful</param>
|
|
|
|
|
|
protected void CompleteInteraction(bool success)
|
2025-09-11 13:00:26 +02:00
|
|
|
|
{
|
|
|
|
|
|
interactionComplete?.Invoke(success);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 13:50:08 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Legacy method for backward compatibility. Use CompleteInteraction instead.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// TODO: Remove this method in future versions
|
|
|
|
|
|
[Obsolete("Use CompleteInteraction instead")]
|
|
|
|
|
|
public void BroadcastInteractionComplete(bool success)
|
|
|
|
|
|
{
|
|
|
|
|
|
CompleteInteraction(success);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-11 13:00:26 +02:00
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Draws gizmos for pickup interaction range in the editor.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
void OnDrawGizmos()
|
|
|
|
|
|
{
|
2025-09-24 13:33:43 +00:00
|
|
|
|
float playerStopDistance = characterToInteract == CharacterToInteract.Trafalgar
|
|
|
|
|
|
? AppleHills.SettingsAccess.GetPlayerStopDistanceDirectInteraction()
|
|
|
|
|
|
: AppleHills.SettingsAccess.GetPlayerStopDistance();
|
2025-09-11 13:00:26 +02:00
|
|
|
|
|
|
|
|
|
|
Gizmos.color = Color.yellow;
|
|
|
|
|
|
Gizmos.DrawWireSphere(transform.position, playerStopDistance);
|
|
|
|
|
|
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
|
|
|
|
|
|
if (playerObj != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Vector3 stopPoint = transform.position +
|
|
|
|
|
|
(playerObj.transform.position - transform.position).normalized * playerStopDistance;
|
|
|
|
|
|
Gizmos.color = Color.cyan;
|
|
|
|
|
|
Gizmos.DrawSphere(stopPoint, 0.15f);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2025-09-09 13:38:03 +02:00
|
|
|
|
}
|
2025-09-01 16:14:21 +02:00
|
|
|
|
}
|