using System; using UnityEngine; /// /// Handles the process of moving characters to interactables and orchestrating interactions. /// public class InteractionOrchestrator : MonoBehaviour { private static InteractionOrchestrator _instance; private static bool _isQuitting = false; // Singleton for easy access (optional, can be replaced with DI or scene reference) public static InteractionOrchestrator Instance { get { if (_instance == null && Application.isPlaying && !_isQuitting) { _instance = FindAnyObjectByType(); if (_instance == null) { var go = new GameObject("InteractionOrchestrator"); _instance = go.AddComponent(); // DontDestroyOnLoad(go); } } return _instance; } } void Awake() { _instance = this; // DontDestroyOnLoad(gameObject); } void OnApplicationQuit() { _isQuitting = true; } // Store pending interaction state private Interactable _pendingInteractable; private Input.PlayerTouchController _pendingPlayer; private FollowerController _pendingFollower; private bool _interactionInProgress; /// /// Request an interaction between a character and an interactable. /// public void RequestInteraction(Interactable interactable, Character character) { // Only support player-initiated interactions for now if (character is Input.PlayerTouchController player) { // Compute closest point on the interaction radius Vector3 interactablePos = interactable.transform.position; Vector3 playerPos = player.transform.position; float stopDistance = GameManager.Instance.PlayerStopDistance; Vector3 toPlayer = (playerPos - interactablePos).normalized; Vector3 stopPoint = interactablePos + toPlayer * stopDistance; // Unsubscribe previous to avoid duplicate calls player.OnArrivedAtTarget -= OnPlayerArrived; player.OnArrivedAtTarget += OnPlayerArrived; _pendingInteractable = interactable; _pendingPlayer = player; _pendingFollower = FindFollower(); _interactionInProgress = true; player.MoveToAndNotify(stopPoint); } else { // Fallback: immediately interact OnCharacterArrived(interactable, character); } } // Helper to find the follower in the scene private FollowerController FindFollower() { // Use the recommended Unity API for finding objects return GameObject.FindFirstObjectByType(); } private void OnPlayerArrived() { if (!_interactionInProgress || _pendingInteractable == null || _pendingPlayer == null) return; // Unsubscribe to avoid memory leaks _pendingPlayer.OnArrivedAtTarget -= OnPlayerArrived; // Now dispatch the follower to the interactable if (_pendingFollower != null) { _pendingFollower.OnPickupArrived -= OnFollowerArrived; _pendingFollower.OnPickupArrived += OnFollowerArrived; _pendingFollower.GoToPointAndReturn(_pendingInteractable.transform.position, _pendingPlayer.transform); } else { // No follower found, just interact as player OnCharacterArrived(_pendingInteractable, _pendingPlayer); _interactionInProgress = false; _pendingInteractable = null; _pendingPlayer = null; _pendingFollower = null; } } private void OnFollowerArrived() { if (!_interactionInProgress || _pendingInteractable == null || _pendingFollower == null) return; _pendingFollower.OnPickupArrived -= OnFollowerArrived; // Now check requirements and interact as follower OnCharacterArrived(_pendingInteractable, _pendingFollower); _interactionInProgress = false; _pendingInteractable = null; _pendingPlayer = null; _pendingFollower = null; } /// /// Called when the character has arrived at the interactable. /// private void OnCharacterArrived(Interactable interactable, Character character) { // Check all requirements var requirements = interactable.GetComponents(); bool allMet = true; foreach (var req in requirements) { // For now, only FollowerController is supported for requirements // (can be extended to support Player, etc.) if (character is FollowerController follower) { if (!req.TryInteract(follower)) { allMet = false; break; } } // Add more character types as needed } if (allMet) { interactable.OnInteract(character); } else { interactable.CompleteInteraction(false); } } // --- ITEM MANAGEMENT API --- public void PickupItem(FollowerController follower, GameObject item) { if (item == null || follower == null) return; item.SetActive(false); item.transform.SetParent(null); follower.SetHeldItemFromObject(item); // Optionally: fire event, update UI, etc. } public void DropItem(FollowerController follower, Vector3 position) { var item = follower.GetHeldPickupObject(); if (item == null) return; item.transform.position = position; item.transform.SetParent(null); item.SetActive(true); follower.ClearHeldItem(); // Optionally: fire event, update UI, etc. } public void SlotItem(SlotItemBehavior slot, GameObject item) { if (slot == null || item == null) return; item.SetActive(false); item.transform.SetParent(null); slot.SetSlottedObject(item); // Optionally: update visuals, fire event, etc. } public void SwapItems(FollowerController follower, SlotItemBehavior slot) { var heldObj = follower.GetHeldPickupObject(); var slotObj = slot.GetSlottedObject(); // 1. Slot the follower's held object SlotItem(slot, heldObj); // 2. Give the slot's object to the follower PickupItem(follower, slotObj); } public GameObject CombineItems(GameObject itemA, GameObject itemB) { if (itemA == null || itemB == null) return null; var pickupA = itemA.GetComponent(); var pickupB = itemB.GetComponent(); if (pickupA == null || pickupB == null) { if (itemA != null) Destroy(itemA); if (itemB != null) Destroy(itemB); return null; } var rule = GameManager.Instance.GetCombinationRule(pickupA.itemData, pickupB.itemData); Vector3 spawnPos = itemA.transform.position; if (rule != null && rule.resultPrefab != null) { var newItem = Instantiate(rule.resultPrefab, spawnPos, Quaternion.identity); Destroy(itemA); Destroy(itemB); return newItem; } // If no combination found, just destroy both Destroy(itemA); Destroy(itemB); return null; } }