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
}
}