using System; using System.Collections; using Core; using Input; using Interactions; using UnityEngine; using UnityEngine.Events; namespace Items { /// /// Saveable data for ControllerSwitchItem state /// [Serializable] public class ControllerSwitchItemSaveData { public bool hasBeenUsed; } /// /// Base interactable item that switches control from one character controller to another. /// Level-agnostic - handles only controller switching logic. /// Derive from this class for level-specific behavior (camera switching, teleportation, etc.) /// public class ControllerSwitchItem : SaveableInteractable { [Header("Controller Switch Settings")] [Tooltip("Name of the controller to switch to (must match registration name in InputManager)")] [SerializeField] protected string targetControllerName; [Header("Visual Feedback")] [Tooltip("Visual representation to hide after use (optional)")] [SerializeField] protected GameObject visualRepresentation; public UnityEvent OnCharacterSwitch; // State protected bool _hasBeenUsed; protected 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; Logging.Debug("[ControllerSwitchItem] Starting controller switch sequence"); StartCoroutine(SwitchControllerSequence()); return true; } protected virtual IEnumerator SwitchControllerSequence() { _isSwitching = true; // Step 1: Get controllers var currentController = InputManager.Instance.GetActiveController(); var targetController = InputManager.Instance.GetController(targetControllerName); if (currentController == null || targetController == null) { Debug.LogError($"[ControllerSwitchItem] Failed to get controllers! Current: {currentController}, Target: {targetController} (name: {targetControllerName})"); _isSwitching = false; yield break; } GameObject currentGameObject = (currentController as MonoBehaviour)?.gameObject; GameObject targetGameObject = (targetController as MonoBehaviour)?.gameObject; Logging.Debug($"[ControllerSwitchItem] Switching from {currentGameObject?.name} to {targetGameObject?.name}"); // Step 2: Deactivate current controller DeactivateCurrentController(currentController, currentGameObject); // Step 3: Activate target controller ActivateTargetController(targetController, targetGameObject); // Step 4: Switch InputManager to target controller bool switchSuccess = InputManager.Instance.SwitchToController(targetControllerName); if (switchSuccess) { Logging.Debug($"[ControllerSwitchItem] Successfully switched input to controller: {targetControllerName}"); OnCharacterSwitch.Invoke(); } else { Debug.LogError($"[ControllerSwitchItem] Failed to switch to controller: {targetControllerName}"); } // Step 5: Mark as used if one-time use if (isOneTime) { _hasBeenUsed = true; DisableVisual(); } _isSwitching = false; } protected virtual void DeactivateCurrentController(ITouchInputConsumer currentController, GameObject currentGameObject) { // If current is a player controller, deactivate it if (currentController is BasePlayerMovementController currentPlayerController) { currentPlayerController.DeactivateController(); } // If switching FROM follower mode, deactivate follower if (currentGameObject != null) { var currentFollower = currentGameObject.GetComponent(); if (currentFollower != null && currentFollower.IsFollowerActive) { currentFollower.DeactivateFollower(); } } } protected virtual void ActivateTargetController(ITouchInputConsumer targetController, GameObject targetGameObject) { // Check if target GameObject has FollowerController component FollowerController targetFollower = null; if (targetGameObject != null) { targetFollower = targetGameObject.GetComponent(); } // If switching TO a GameObject with FollowerController, we need special handling if (targetFollower != null) { // Switching TO Pulver player control (Pulver has both FollowerController and PulverController) // Deactivate follower mode, activate player control targetFollower.DeactivateFollower(); if (targetController is BasePlayerMovementController targetPlayerController) { targetPlayerController.ActivateController(); } } else { // Switching TO Trafalgar (no FollowerController on Trafalgar) // If there's a Pulver in the scene, activate its follower mode var pulverFollower = FindFirstObjectByType(); if (pulverFollower != null) { pulverFollower.ActivateFollower(); } // Activate the target player controller if (targetController is BasePlayerMovementController targetPlayerController) { targetPlayerController.ActivateController(); } } } protected 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 } }