using UnityEngine; using UnityEngine.InputSystem; using System.Collections.Generic; /// /// TeamControlManager - Manages player control switching between team members. /// Inspired by FRAG Pro Shooter's instant character switching system. /// public class TeamControlManager : MonoBehaviour { public static TeamControlManager Instance { get; private set; } [Header("Current Control")] [SerializeField] private GameObject currentControlledCharacter; [SerializeField] private int currentCharacterIndex = 0; [Header("Team References")] [SerializeField] private List playerTeamCharacters = new List(); [Header("Camera Settings")] [SerializeField] private bool autoFollowCamera = true; // Events public event System.Action OnControlSwitched; // (old, new) private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; } /// /// Register a character to the player team /// public void RegisterPlayerTeamCharacter(GameObject character, bool isPlayerControlled = false) { if (!playerTeamCharacters.Contains(character)) { playerTeamCharacters.Add(character); DevLog.Log($"[TeamControlManager] Registered {character.name} to player team"); if (isPlayerControlled) { SetControlledCharacter(character); } } } /// /// Remove a character from the team (e.g., on death) /// public void UnregisterCharacter(GameObject character) { if (playerTeamCharacters.Contains(character)) { playerTeamCharacters.Remove(character); DevLog.Log($"[TeamControlManager] Unregistered {character.name}"); // If this was the controlled character, switch to another if (currentControlledCharacter == character) { SwitchToNextAvailableCharacter(); } } } /// /// Set which character is player-controlled using the unified ControlType system /// public void SetControlledCharacter(GameObject newCharacter) { if (newCharacter == null || newCharacter == currentControlledCharacter) return; GameObject oldCharacter = currentControlledCharacter; // Get TeamMember components TeamMember oldMember = oldCharacter?.GetComponent(); TeamMember newMember = newCharacter.GetComponent(); // Switch control using ControlType system (handles all component config!) if (oldMember != null) { // Old character becomes PlayerAI (still on player team, but AI controlled) oldMember.SetControlType(TeamMember.ControlType.PlayerAI); DevLog.Log($"[TeamControlManager] {oldCharacter.name} -> PlayerAI"); } if (newMember != null) { // New character becomes HumanControlled newMember.SetControlType(TeamMember.ControlType.HumanControlled); DevLog.Log($"[TeamControlManager] {newCharacter.name} -> HumanControlled"); // CRITICAL: Bind input actions for the new controlled character BindPlayerInputActions(newCharacter); } else { // Fallback for characters without TeamMember (shouldn't happen) DevLog.LogWarning($"[TeamControlManager] {newCharacter.name} has no TeamMember component!"); EnablePlayerControl(newCharacter); } currentControlledCharacter = newCharacter; currentCharacterIndex = playerTeamCharacters.IndexOf(newCharacter); // Update camera using CameraManager (preferred) or legacy UpdateCameraTarget if (CameraManager.Instance != null) { CameraManager.Instance.SetTarget(newMember); } else if (autoFollowCamera) { UpdateCameraTarget(newCharacter); } DevLog.Log($"[TeamControlManager] Switched control to {newCharacter.name}"); OnControlSwitched?.Invoke(oldCharacter, newCharacter); } /// /// Switch to the next character in the team /// public void SwitchToNextCharacter() { if (playerTeamCharacters.Count <= 1) return; int nextIndex = (currentCharacterIndex + 1) % playerTeamCharacters.Count; // Find next alive character for (int i = 0; i < playerTeamCharacters.Count; i++) { int checkIndex = (currentCharacterIndex + 1 + i) % playerTeamCharacters.Count; GameObject candidate = playerTeamCharacters[checkIndex]; if (candidate != null && candidate.activeInHierarchy && IsCharacterAlive(candidate)) { SetControlledCharacter(candidate); return; } } DevLog.LogWarning("[TeamControlManager] No other alive characters to switch to!"); } /// /// Switch to a specific character by index /// public void SwitchToCharacter(int index) { if (index >= 0 && index < playerTeamCharacters.Count) { GameObject character = playerTeamCharacters[index]; if (character != null && character.activeInHierarchy && IsCharacterAlive(character)) { SetControlledCharacter(character); } } } /// /// Switch to next available character when current dies /// private void SwitchToNextAvailableCharacter() { foreach (var character in playerTeamCharacters) { if (character != null && character.activeInHierarchy && IsCharacterAlive(character)) { SetControlledCharacter(character); return; } } Debug.LogError("[TeamControlManager] No available characters to control!"); currentControlledCharacter = null; } private bool IsCharacterAlive(GameObject character) { // Check if character has health and is alive HealthNew health = character.GetComponent(); if (health != null) { return !health.isDead; } return true; // Assume alive if no health component } private void EnablePlayerControl(GameObject character) { // Update TeamMember TeamMember teamMember = character.GetComponent(); if (teamMember != null) { teamMember.IsPlayerControlled = true; } // Enable PlayerScript PlayerScript playerScript = character.GetComponent(); if (playerScript != null) { playerScript.enabled = true; playerScript.attack = true; } // Enable PlayerInput PlayerInput playerInput = character.GetComponent(); if (playerInput != null) { playerInput.enabled = true; try { playerInput.SwitchCurrentActionMap("Player Controls"); } catch (System.Exception) { } } // Update tag character.tag = "Player"; DevLog.Log($"[TeamControlManager] Enabled player control on {character.name}"); } private void DisablePlayerControl(GameObject character) { // Update TeamMember TeamMember teamMember = character.GetComponent(); if (teamMember != null) { teamMember.IsPlayerControlled = false; } // Disable PlayerScript (attacks, not movement animation) PlayerScript playerScript = character.GetComponent(); if (playerScript != null) { playerScript.attack = false; // Keep enabled for animation but disable input } // Disable PlayerInput PlayerInput playerInput = character.GetComponent(); if (playerInput != null) { playerInput.enabled = false; } DevLog.Log($"[TeamControlManager] Disabled player control on {character.name}"); } private void EnableTeamAI(GameObject character) { TeamAIController aiController = character.GetComponent(); if (aiController != null) { aiController.enabled = true; } // Also enable CashSystemAI if present CashSystemAI cashAI = character.GetComponent(); if (cashAI != null) { cashAI.enabled = true; } DevLog.Log($"[TeamControlManager] Enabled AI on {character.name}"); } private void DisableTeamAI(GameObject character) { TeamAIController aiController = character.GetComponent(); if (aiController != null) { aiController.enabled = false; } // Also disable CashSystemAI if present (player makes own decisions) CashSystemAI cashAI = character.GetComponent(); if (cashAI != null) { cashAI.enabled = false; } DevLog.Log($"[TeamControlManager] Disabled AI on {character.name}"); } private void UpdateCameraTarget(GameObject character) { // Correct separation: Follow = root (orbit center), LookAt = head (aim point) Transform followTarget = character.transform; Transform lookAtTarget = FindCharacterHead(character); // Update Cinemachine FreeLook with correct separation Cinemachine.CinemachineFreeLook[] freeLookCameras = FindObjectsOfType(); foreach (var cam in freeLookCameras) { if (cam.gameObject.activeInHierarchy) { cam.Follow = followTarget; cam.LookAt = lookAtTarget; } } // Update Cinemachine Virtual Cameras Cinemachine.CinemachineVirtualCamera[] virtualCameras = FindObjectsOfType(); foreach (var cam in virtualCameras) { if (cam.gameObject.activeInHierarchy) { cam.Follow = followTarget; cam.LookAt = lookAtTarget; } } DevLog.Log($"[TeamControlManager] Updated camera to follow {character.name} (Follow=root, LookAt=head)"); } private Transform FindCharacterHead(GameObject character) { string[] headNames = { "Head", "head", "Bip001 Head", "mixamorig:Head", "Bip01 Head" }; foreach (string headName in headNames) { Transform head = FindChildRecursive(character.transform, headName); if (head != null) return head; } // Fallback to character root return character.transform; } private Transform FindChildRecursive(Transform parent, string name) { foreach (Transform child in parent) { if (child.name == name) return child; Transform found = FindChildRecursive(child, name); if (found != null) return found; } return null; } /// /// Get the currently controlled character /// public GameObject GetCurrentControlledCharacter() => currentControlledCharacter; /// /// Get all player team characters /// public List GetPlayerTeamCharacters() => playerTeamCharacters; /// /// Clear all registered characters /// public void ClearTeam() { playerTeamCharacters.Clear(); currentControlledCharacter = null; currentCharacterIndex = 0; } /// /// Bind input action callbacks to PlayerScript methods. /// This mirrors what Practice mode does in SelectionOptions.SetUpPlayerInputAI. /// private void BindPlayerInputActions(GameObject character) { PlayerInput playerInput = character.GetComponent(); PlayerScript playerScript = character.GetComponent(); if (playerInput == null || playerScript == null) { DevLog.LogWarning($"[TeamControlManager] Cannot bind input - PlayerInput: {playerInput != null}, PlayerScript: {playerScript != null}"); return; } // Ensure PlayerInput is enabled playerInput.enabled = true; try { playerInput.SwitchCurrentActionMap("Player Controls"); } catch { } var actionMap = playerInput.currentActionMap; if (actionMap == null) { Debug.LogError("[TeamControlManager] No current action map found!"); return; } // CRITICAL: Bind the Move action! TryBindAction(actionMap, "Move", playerScript.OnMove); // Bind attack actions TryBindAction(actionMap, "Jab", playerScript.OnJab); TryBindAction(actionMap, "Right", playerScript.OnRight); TryBindAction(actionMap, "LeftHook", playerScript.OnLeftHook); TryBindAction(actionMap, "RightHook", playerScript.OnRightHook); TryBindAction(actionMap, "LeftElbow", playerScript.OnLeftElbow); TryBindAction(actionMap, "RightElbow", playerScript.OnRightElbow); TryBindAction(actionMap, "RightBody", playerScript.OnRightBody); TryBindAction(actionMap, "PowerPunchLeft", playerScript.OnPowerPunchLeft); TryBindAction(actionMap, "SupermanPunch", playerScript.OnSupermanPunch); // Kick actions TryBindAction(actionMap, "LegKickRight", playerScript.OnLegKickRight); TryBindAction(actionMap, "BodyKickRight", playerScript.OnBodyKickRight); TryBindAction(actionMap, "KneeRight", playerScript.OnKneeRight); TryBindAction(actionMap, "BackSideKick", playerScript.OnBackSideKick); TryBindAction(actionMap, "FrontKickRight", playerScript.OnFrontKickRight); TryBindAction(actionMap, "LegPowerKickRight", playerScript.OnLegPowerKickRight); // Dodge actions TryBindAction(actionMap, "JumpBack", playerScript.OnJumpBack); TryBindAction(actionMap, "JumpLeft", playerScript.OnJumpLeft); TryBindAction(actionMap, "JumpRight", playerScript.OnJumpRight); // Block actions TryBindAction(actionMap, "BlockStepBack", playerScript.OnBlockStepBack); TryBindAction(actionMap, "BlockBodyLeft", playerScript.OnBlockBodyLeft); TryBindAction(actionMap, "BlockLeg", playerScript.OnBlockLeg); // Special attacks TryBindAction(actionMap, "Chokeslam", playerScript.OnChokeslam); TryBindAction(actionMap, "Suplex", playerScript.OnSuplex); TryBindAction(actionMap, "GiantSwing", playerScript.OnGiantSwing); TryBindAction(actionMap, "DiamondCrusher", playerScript.OnDiamondCrusher); TryBindAction(actionMap, "SumoSlap", playerScript.OnSumoSlap); TryBindAction(actionMap, "JavelinTackle", playerScript.OnJavelinTackle); TryBindAction(actionMap, "RKO", playerScript.OnRKO); TryBindAction(actionMap, "Spear", playerScript.OnSpear); TryBindAction(actionMap, "RockBottom", playerScript.OnRockBottom); TryBindAction(actionMap, "F5", playerScript.OnF5); DevLog.Log($"[TeamControlManager] Input actions bound for {character.name}"); } /// /// Helper to safely bind an action /// private void TryBindAction(InputActionMap actionMap, string actionName, System.Action callback) { InputAction action = actionMap.FindAction(actionName); if (action != null) { action.performed += callback; if (actionName == "Move") action.canceled += callback; } } }