using System; using System.Collections; using Core; using Input; using Interactions; using Minigames.TrashMaze.Core; using Minigames.TrashMaze.Data; using Unity.Cinemachine; using UnityEngine; namespace Items { /// /// Saveable data for ControllerSwitchItem state /// [Serializable] public class ControllerSwitchItemSaveData { public bool hasBeenUsed; } /// /// Camera switching mode for controller switch items /// public enum CameraSwitchMode { /// /// No camera switching - controller switch only /// None, /// /// Use a direct reference to a Cinemachine camera /// DirectReference, /// /// Use TrashMazeCameraController state manager API /// TrashMazeCameraState } /// /// An interactable item that switches control from one character controller to another. /// When clicked: /// 1. The selected character moves to this item's position /// 2. Upon arrival, the current controller is disabled /// 3. Camera blends to the target camera (based on camera mode) /// 4. Once the blend completes, control switches to the target controller /// public class ControllerSwitchItem : SaveableInteractable { [Header("Controller Switch Settings")] [Tooltip("Name of the controller to switch to (must match GameObject name of the controller)")] [SerializeField] private string targetControllerName; [Header("Camera Settings")] [Tooltip("How to switch the camera when changing controllers")] [SerializeField] private CameraSwitchMode cameraSwitchMode = CameraSwitchMode.None; [Tooltip("Direct camera reference (only used if Camera Switch Mode is DirectReference)")] [SerializeField] private CinemachineCamera targetVirtualCamera; [Tooltip("Target camera state (only used if Camera Switch Mode is TrashMazeCameraState)")] [SerializeField] private TrashMazeCameraState targetCameraState; [Header("Visual Feedback")] [Tooltip("Visual representation to hide after use (optional)")] [SerializeField] private GameObject visualRepresentation; // State private bool _hasBeenUsed; private PlayerTouchController _currentPlayerController; private bool _isSwitching; public override string SaveId => $"{gameObject.scene.name}/ControllerSwitchItem/{gameObject.name}"; internal override void OnManagedAwake() { base.OnManagedAwake(); if (string.IsNullOrEmpty(targetControllerName)) { Debug.LogError($"[ControllerSwitchItem] {gameObject.name} has no target controller name specified!"); } } internal override void OnManagedStart() { base.OnManagedStart(); // Apply state after restoration if (_hasBeenUsed && isOneTime) { DisableVisual(); } } protected override bool CanBeClicked() { // Cannot be clicked if already used (one-time) or if currently switching if (_isSwitching) return false; if (isOneTime && _hasBeenUsed) return false; // Check if target controller is registered if (!InputManager.Instance.IsControllerRegistered(targetControllerName)) { Debug.LogWarning($"[ControllerSwitchItem] Target controller '{targetControllerName}' is not registered with InputManager."); return false; } return base.CanBeClicked(); } protected override bool DoInteraction() { if (_isSwitching) return false; // By the time this is called, the interacting character has already arrived at this item // We just need to perform the controller/camera switch Logging.Debug("[ControllerSwitchItem] Starting controller switch sequence"); // Start the async switch sequence (camera blend + controller switch) StartCoroutine(SwitchControllerSequence()); // Return true immediately - interaction is considered successful // The coroutine will handle the actual switching asynchronously return true; } private IEnumerator SwitchControllerSequence() { _isSwitching = true; // Step 1: Get current player controller (the one we're switching FROM) _currentPlayerController = FindFirstObjectByType(); if (_currentPlayerController == null) { Debug.LogError("[ControllerSwitchItem] Could not find PlayerTouchController in scene!"); _isSwitching = false; yield break; } Logging.Debug("[ControllerSwitchItem] Character has arrived, beginning switch"); // Step 2: Disable current player controller _currentPlayerController.enabled = false; Logging.Debug("[ControllerSwitchItem] Disabled current player controller"); // Step 3: Blend to target camera based on mode yield return SwitchCamera(); // Step 4: Switch to target controller ITouchInputConsumer targetController = InputManager.Instance.GetController(targetControllerName); if (targetController != null) { // Enable the target controller if it's a MonoBehaviour if (targetController is MonoBehaviour targetMono) { targetMono.enabled = true; Logging.Debug($"[ControllerSwitchItem] Enabled target controller: {targetControllerName}"); } // Switch input control to the target controller bool switchSuccess = InputManager.Instance.SwitchToController(targetControllerName); if (switchSuccess) { Logging.Debug($"[ControllerSwitchItem] Successfully switched input to controller: {targetControllerName}"); } else { Debug.LogError($"[ControllerSwitchItem] Failed to switch to controller: {targetControllerName}"); } } else { Debug.LogError($"[ControllerSwitchItem] Target controller '{targetControllerName}' not found!"); } // Step 5: Mark as used if one-time use if (isOneTime) { _hasBeenUsed = true; DisableVisual(); } _isSwitching = false; } private IEnumerator SwitchCamera() { switch (cameraSwitchMode) { case CameraSwitchMode.None: // No camera switching Logging.Debug("[ControllerSwitchItem] No camera switching configured"); break; case CameraSwitchMode.DirectReference: if (targetVirtualCamera != null) { Logging.Debug($"[ControllerSwitchItem] Blending to camera: {targetVirtualCamera.name}"); // Set the target camera as highest priority targetVirtualCamera.Priority = 100; // Wait for camera blend to complete yield return WaitForCameraBlend(); } else { Debug.LogWarning("[ControllerSwitchItem] DirectReference mode selected but no camera assigned!"); } break; case CameraSwitchMode.TrashMazeCameraState: if (TrashMazeCameraController.Instance != null) { Logging.Debug($"[ControllerSwitchItem] Switching to camera state: {targetCameraState}"); // Use the state manager API if (targetCameraState == TrashMazeCameraState.Gameplay) { TrashMazeCameraController.Instance.SwitchToGameplay(); } else if (targetCameraState == TrashMazeCameraState.Maze) { TrashMazeCameraController.Instance.SwitchToMaze(); } // Wait for camera blend to complete yield return WaitForCameraBlend(); } else { Debug.LogError("[ControllerSwitchItem] TrashMazeCameraController instance not found in scene!"); } break; } } private IEnumerator WaitForCameraBlend() { CinemachineBrain brain = Camera.main?.GetComponent(); if (brain != null) { // Wait until blend is not active while (brain.IsBlending) { yield return null; } Logging.Debug("[ControllerSwitchItem] Camera blend completed"); } else { // If no brain, just wait a brief moment yield return new WaitForSeconds(0.5f); } } private void DisableVisual() { if (visualRepresentation != null) { visualRepresentation.SetActive(false); } } #region Save/Load protected override object GetSerializableState() { return new ControllerSwitchItemSaveData { hasBeenUsed = _hasBeenUsed }; } protected override void ApplySerializableState(string serializedData) { try { var data = JsonUtility.FromJson(serializedData); _hasBeenUsed = data.hasBeenUsed; Logging.Debug($"[ControllerSwitchItem] Restored state: hasBeenUsed={_hasBeenUsed}"); } catch (Exception e) { Debug.LogError($"[ControllerSwitchItem] Failed to deserialize save data: {e.Message}"); } } #endregion #if UNITY_EDITOR private void OnValidate() { // Visual feedback in editor if (string.IsNullOrEmpty(targetControllerName)) { name = "ControllerSwitchItem (UNCONFIGURED)"; } else { name = $"ControllerSwitchItem_To_{targetControllerName}"; } } #endif } }