using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.InputSystem; using System.Collections; using System.Collections.Generic; /// /// CashSystemManager - Main controller for Cash System game mode. /// Handles team spawning, win conditions, and game flow. /// public class CashSystemManager : MonoBehaviour { [Header("Match Settings")] [SerializeField] private float matchDuration = 300f; // 5 minutes [SerializeField] private float targetCashToWin = 2000f; [SerializeField] private bool endOnThreshold = true; [Header("Team Spawn Points")] [SerializeField] private Transform[] playerTeamSpawnPoints; [SerializeField] private Transform[] enemyTeamSpawnPoints; [Header("Vault References")] [SerializeField] private TeamVault playerVault; [SerializeField] private TeamVault enemyVault; [Header("Cashout Stations (Extraction Mode)")] [SerializeField] private CashoutStation playerStation; [SerializeField] private CashoutStation enemyStation; [Header("Extraction Mode Settings")] [SerializeField] private float killReward = 200f; // $200 per kill [SerializeField] private float teamWipePenaltyPercent = 0.30f; // -30% cash on team wipe [SerializeField] private float respawnTime = 10f; // 10s respawn timer [SerializeField] private GameObject vaultSpawnerPrefab; // Optional prefab for vault spawner [Header("Character Switching")] [SerializeField] private KeyCode switchCharacterKey = KeyCode.Tab; [SerializeField] private float switchCooldown = 1f; [Header("UI")] [SerializeField] private GameObject victoryScreen; [SerializeField] private GameObject defeatScreen; [Header("Buzzer Beater")] [SerializeField] private float buzzerBeaterWindow = 15f; [SerializeField] private float buzzerSlowMotion = 0.6f; // Runtime state private List playerTeamCharacters = new List(); private List enemyTeamCharacters = new List(); private int currentControlledIndex = 0; private float matchTimer; private bool matchEnded = false; private float lastSwitchTime = 0f; private bool _buzzerActive; public float MatchTimer => matchTimer; public float MatchDuration => matchDuration; public bool MatchEnded => matchEnded; public GameObject CurrentControlledCharacter => currentControlledIndex < playerTeamCharacters.Count ? playerTeamCharacters[currentControlledIndex] : null; private static CashSystemManager instance; public static CashSystemManager Instance => instance; private void Awake() { if (instance != null && instance != this) { Destroy(gameObject); return; } instance = this; } private void OnEnable() { GameEvents.OnCashDeposited += OnCashDepositedEvent; GameEvents.OnVaultStolen += OnVaultStolenEvent; GameEvents.OnKill += OnKillEvent; GameEvents.OnCharacterDied += OnCharacterDiedEvent; } private void OnDisable() { GameEvents.OnCashDeposited -= OnCashDepositedEvent; GameEvents.OnVaultStolen -= OnVaultStolenEvent; GameEvents.OnKill -= OnKillEvent; GameEvents.OnCharacterDied -= OnCharacterDiedEvent; } private void Start() { // Only activate in Cash System mode if (SelectionOptions.Instance == null || !SelectionOptions.Instance.isCashSystemSelected) { DevLog.Log("[CashSystemManager] Not in Cash System mode, disabling."); enabled = false; return; } DevLog.Log("[CashSystemManager] Cash System mode active, initializing..."); // Ensure TeamControlManager exists if (TeamControlManager.Instance == null) { GameObject tcmObj = new GameObject("TeamControlManager"); tcmObj.AddComponent(); DevLog.Log("[CashSystemManager] Created TeamControlManager"); } // Ensure AudioListenerManager exists for sound to work if (AudioListenerManager.Instance == null) { GameObject almObj = new GameObject("AudioListenerManager"); almObj.AddComponent(); DevLog.Log("[CashSystemManager] Created AudioListenerManager"); } // Ensure TeamIndicatorManager exists for visual team differentiation if (TeamIndicatorManager.Instance == null) { GameObject timObj = new GameObject("TeamIndicatorManager"); timObj.AddComponent(); DevLog.Log("[CashSystemManager] Created TeamIndicatorManager"); } // Ensure MinimapMarkerManager exists for minimap team markers if (MinimapMarkerManager.Instance == null) { GameObject mmmObj = new GameObject("MinimapMarkerManager"); mmmObj.AddComponent(); DevLog.Log("[CashSystemManager] Created MinimapMarkerManager"); } // Find vaults if not assigned if (playerVault == null || enemyVault == null) { TeamVault[] vaults = FindObjectsOfType(); foreach (TeamVault vault in vaults) { if (vault.TeamTag == "Player") playerVault = vault; else if (vault.TeamTag == "Enemy") enemyVault = vault; } } // Find CashoutStations if not assigned (extraction mode) if (playerStation == null || enemyStation == null) { CashoutStation[] stations = FindObjectsOfType(); foreach (CashoutStation station in stations) { if (station.TeamTag == "Player") playerStation = station; else if (station.TeamTag == "Enemy") enemyStation = station; } } // Ensure VaultSpawner exists for extraction mode if (FindObjectOfType() == null) { GameObject spawnerObj = new GameObject("VaultSpawner"); spawnerObj.AddComponent(); DevLog.Log("[CashSystemManager] Created VaultSpawner"); } matchTimer = matchDuration; // Initialize UI with vault values StartCoroutine(InitializeUIDelayed()); // Ensure UI panels exist (activates inactive instances or auto-creates minimal ones) PreMatchIntroUI.EnsureExists(); MatchResultUI.EnsureExists(); // Show pre-match intro before spawning if (PreMatchIntroUI.Instance != null) { PreMatchIntroUI.Instance.Show(); DevLog.Log("[CashSystemManager] PreMatchIntroUI.Show() called"); } else { DevLog.LogWarning("[CashSystemManager] PreMatchIntroUI.Instance is STILL null after EnsureExists!"); } if (MatchResultUI.Instance != null) DevLog.Log("[CashSystemManager] MatchResultUI ready for end-of-match"); else DevLog.LogWarning("[CashSystemManager] MatchResultUI.Instance is STILL null after EnsureExists!"); StartCoroutine(SpawnTeams()); } private IEnumerator InitializeUIDelayed() { yield return null; yield return new WaitForSeconds(0.2f); // Fire events instead of FindObjectOfType — UI subscribes to these GameEvents.FireTimerUpdated(matchTimer); if (playerStation != null) GameEvents.FireScoreChanged("Player", playerStation.StoredCash); else if (playerVault != null) GameEvents.FireScoreChanged("Player", playerVault.StoredCash); if (enemyStation != null) GameEvents.FireScoreChanged("Enemy", enemyStation.StoredCash); else if (enemyVault != null) GameEvents.FireScoreChanged("Enemy", enemyVault.StoredCash); } // Throttle timer UI updates to once per displayed second (was 60Hz) private int _lastDisplayedSecond = -1; private void Update() { if (matchEnded) return; // Timer tick matchTimer -= Time.deltaTime; // Only fire timer event when the displayed second changes (60Hz → 1Hz) int displayedSecond = Mathf.CeilToInt(matchTimer); if (displayedSecond != _lastDisplayedSecond) { _lastDisplayedSecond = displayedSecond; GameEvents.FireTimerUpdated(matchTimer); } if (matchTimer <= 0) { matchTimer = 0; EndMatch(); return; } // Buzzer beater slow-motion if (!_buzzerActive && matchTimer <= buzzerBeaterWindow) { float playerScore = GetTeamTotalCash("Player"); float enemyScore = GetTeamTotalCash("Enemy"); float scoreDiff = Mathf.Abs(playerScore - enemyScore); if (scoreDiff < 500f) // Close game { _buzzerActive = true; Time.timeScale = buzzerSlowMotion; Time.fixedDeltaTime = 0.02f * buzzerSlowMotion; GameEvents.FirePopupMessage("BUZZER BEATER!", 2f); } } // Threshold win — check both TeamVaults and CashoutStations if (endOnThreshold) { float playerScore = GetTeamTotalCash("Player"); float enemyScore = GetTeamTotalCash("Enemy"); if (playerScore >= targetCashToWin) { EndMatch(true); return; } if (enemyScore >= targetCashToWin) { EndMatch(false); return; } } // Character switching if (Input.GetKeyDown(switchCharacterKey) && Time.time - lastSwitchTime > switchCooldown) { SwitchControlledCharacter(); lastSwitchTime = Time.time; } } private IEnumerator SpawnTeams() { yield return new WaitForSeconds(0.5f); // Wait for scene to settle // Get team data from SelectionOptions string[] playerTeam = SelectionOptions.Instance.playerTeam; string[] enemyTeam = SelectionOptions.Instance.enemyTeam; int captainIndex = SelectionOptions.Instance.captainIndex; DevLog.Log($"[CashSystemManager] Spawning teams - Captain index: {captainIndex}"); DevLog.Log($"[CashSystemManager] Player team: {string.Join(", ", playerTeam ?? new string[0])}"); DevLog.Log($"[CashSystemManager] Enemy team: {string.Join(", ", enemyTeam ?? new string[0])}"); // If teams not set, generate them if (playerTeam == null || playerTeam.Length == 0 || string.IsNullOrEmpty(playerTeam[0])) { DevLog.Log("[CashSystemManager] Generating default teams..."); GenerateDefaultTeams(); playerTeam = SelectionOptions.Instance.playerTeam; enemyTeam = SelectionOptions.Instance.enemyTeam; } // Get spawn positions from vaults if spawn points not assigned Vector3 playerVaultPos = playerVault != null ? playerVault.transform.position : new Vector3(-10f, 0f, 0f); Vector3 enemyVaultPos = enemyVault != null ? enemyVault.transform.position : new Vector3(10f, 0f, 0f); DevLog.Log($"[CashSystemManager] Player vault position: {playerVaultPos}"); DevLog.Log($"[CashSystemManager] Enemy vault position: {enemyVaultPos}"); DevLog.Log($"[CashSystemManager] Player spawn points assigned: {playerTeamSpawnPoints != null && playerTeamSpawnPoints.Length > 0}"); DevLog.Log($"[CashSystemManager] Enemy spawn points assigned: {enemyTeamSpawnPoints != null && enemyTeamSpawnPoints.Length > 0}"); // Spawn player team - use assigned spawn points if available, otherwise vault positions for (int i = 0; i < playerTeam.Length; i++) { if (string.IsNullOrEmpty(playerTeam[i])) continue; // First try to use assigned spawn points Vector3 spawnPos; if (playerTeamSpawnPoints != null && i < playerTeamSpawnPoints.Length && playerTeamSpawnPoints[i] != null) { spawnPos = playerTeamSpawnPoints[i].position; DevLog.Log($"[CashSystemManager] Using assigned spawn point for player {i}: {spawnPos}"); } else { spawnPos = GetSpawnPositionNearVault(playerVaultPos, i, true); DevLog.Log($"[CashSystemManager] Using vault-based spawn for player {i}: {spawnPos}"); } bool isPlayerControlled = (i == captainIndex); DevLog.Log($"[CashSystemManager] Spawning player {playerTeam[i]} at {spawnPos}, isPlayerControlled: {isPlayerControlled}"); GameObject character = SpawnCharacter(playerTeam[i], spawnPos, "Player", isPlayerControlled); if (character != null) { playerTeamCharacters.Add(character); // CashCarrier and AI components are now added in SpawnCharacter/ConfigureCharacterControllers DevLog.Log($"[CashSystemManager] Player {character.name} spawned successfully at {character.transform.position}"); } } // Spawn enemy team - use assigned spawn points if available, otherwise vault positions for (int i = 0; i < enemyTeam.Length; i++) { if (string.IsNullOrEmpty(enemyTeam[i])) continue; // First try to use assigned spawn points Vector3 spawnPos; if (enemyTeamSpawnPoints != null && i < enemyTeamSpawnPoints.Length && enemyTeamSpawnPoints[i] != null) { spawnPos = enemyTeamSpawnPoints[i].position; DevLog.Log($"[CashSystemManager] Using assigned spawn point for enemy {i}: {spawnPos}"); } else { spawnPos = GetSpawnPositionNearVault(enemyVaultPos, i, false); DevLog.Log($"[CashSystemManager] Using vault-based spawn for enemy {i}: {spawnPos}"); } DevLog.Log($"[CashSystemManager] Spawning enemy {enemyTeam[i]} at {spawnPos}"); GameObject character = SpawnCharacter(enemyTeam[i], spawnPos, "Enemy", false); if (character != null) { enemyTeamCharacters.Add(character); // CashCarrier, CashSystemAI, and EnemyAIController are now added in SpawnCharacter/ConfigureCharacterControllers DevLog.Log($"[CashSystemManager] Enemy {character.name} spawned successfully at {character.transform.position}"); } } // Note: CashSystemAI is now added in ConfigureCharacterControllers, no need to add again currentControlledIndex = captainIndex; DevLog.Log($"[CashSystemManager] Teams spawned: {playerTeamCharacters.Count} players, {enemyTeamCharacters.Count} enemies"); // Assign deterministic AI roles: 1 Defender + 2 Collectors per team // Must happen AFTER all CashSystemAI components are fully initialized yield return null; // Give CashSystemAI.Start() a frame to run CashSystemAI.AssignTeamRoles(); // Initialize team HUD with player team characters (HUD is CanvasGroup-hidden, Initialize shows it) if (TeamHUDPanel.Instance != null) TeamHUDPanel.Instance.Initialize(playerTeamCharacters, currentControlledIndex); // Wait for intro panel to finish (including its countdown: 3-2-1-FIGHT!) // instead of hiding it early. The intro sets IsComplete when done. if (PreMatchIntroUI.Instance != null) { DevLog.Log("[CashSystemManager] Waiting for intro to complete..."); while (!PreMatchIntroUI.Instance.IsComplete) yield return null; DevLog.Log("[CashSystemManager] Intro complete, enabling combat"); } // Show the Cash System HUD now that intro is done CashSystemUI cashUI = FindObjectOfType(); if (cashUI != null) cashUI.ShowHUD(); // Enable combat — either intro did the countdown, or we do it ourselves if (PreMatchIntroUI.Instance != null && PreMatchIntroUI.Instance.IsComplete) { // Intro already fired countdown ticks; just enable combat now GameEvents.FireCombatEnabled(true); foreach (var c in playerTeamCharacters) if (c != null) EnableCharacterCombat(c); foreach (var c in enemyTeamCharacters) if (c != null) EnableCharacterCombat(c); } else { // No intro panel present — use standalone countdown fallback StartCoroutine(MatchCountdown()); } } private void GenerateDefaultTeams() { // Use all 6 characters, split between teams string[] allCharacters = { "Windham", "Amira", "Bahman", "Ziggy", "Amon", "Imani" }; // Shuffle for (int i = allCharacters.Length - 1; i > 0; i--) { int j = Random.Range(0, i + 1); string temp = allCharacters[i]; allCharacters[i] = allCharacters[j]; allCharacters[j] = temp; } // Assign to teams SelectionOptions.Instance.playerTeam = new string[] { allCharacters[0], allCharacters[1], allCharacters[2] }; SelectionOptions.Instance.enemyTeam = new string[] { allCharacters[3], allCharacters[4], allCharacters[5] }; SelectionOptions.Instance.captainIndex = 0; DevLog.Log($"[CashSystemManager] Generated teams - Player: {string.Join(", ", SelectionOptions.Instance.playerTeam)}, Enemy: {string.Join(", ", SelectionOptions.Instance.enemyTeam)}"); } /// /// Get spawn position near a vault - spreads characters in a line /// private Vector3 GetSpawnPositionNearVault(Vector3 vaultPosition, int index, bool isPlayerTeam) { // Offset from vault position (spawn a bit away from vault) float spawnDistance = 3f; // Distance from vault center float spacing = 2f; // Horizontal spacing between characters // Center the team spawn with offset from vault float xOffset = (index - 1) * spacing; // -2, 0, +2 for 3 characters // Different direction for player vs enemy if (isPlayerTeam) { // Player spawns "forward" from vault (opposite to enemy) return new Vector3(vaultPosition.x + xOffset, vaultPosition.y, vaultPosition.z + spawnDistance); } else { // Enemy spawns "forward" from their vault (opposite to player) return new Vector3(vaultPosition.x + xOffset, vaultPosition.y, vaultPosition.z - spawnDistance); } } private Vector3 GetSpawnPosition(Transform[] spawnPoints, int index) { if (spawnPoints != null && index < spawnPoints.Length && spawnPoints[index] != null) { return spawnPoints[index].position; } // Default positions float xOffset = 3f * index; return new Vector3(xOffset, 0f, (spawnPoints == playerTeamSpawnPoints) ? 10f : -10f); } private GameObject SpawnCharacter(string characterName, Vector3 position, string teamTag, bool isPlayerControlled) { // IMPORTANT: Always load the PLAYER prefab - NOT the Enemy variant! // The old code loaded "Enemy" prefabs which caused confusion // Now we use ONE prefab per character and configure it based on team GameObject prefab = CharacterPrefabManager.Instance.GetCharacterPrefab(characterName, isAI: false); //bool isAI = !isPlayerControlled; //GameObject prefab = CharacterPrefabManager.Instance.GetCharacterPrefab(characterName, isAI); if (prefab == null) { Debug.LogError($"[CashSystemManager] Failed to load prefab for {characterName}"); return null; } DevLog.Log($"[CashSystemManager] Loading prefab: {prefab.name} for {characterName}"); GameObject character = Instantiate(prefab, position, Quaternion.identity); // Snap to NavMesh surface so characters don't spawn underground with new environments UnityEngine.AI.NavMeshHit navHit; if (UnityEngine.AI.NavMesh.SamplePosition(position, out navHit, 10f, UnityEngine.AI.NavMesh.AllAreas)) { character.transform.position = navHit.position; // Warp NavMeshAgent to the snapped position so it doesn't fight physics var agent = character.GetComponent(); if (agent != null) { agent.enabled = false; character.transform.position = navHit.position; agent.enabled = true; agent.Warp(navHit.position); } // For AI characters: make Rigidbody kinematic so NavMeshAgent controls position // For player-controlled: leave Rigidbody non-kinematic (PlayerMovementBehaviour uses MovePosition) if (!isPlayerControlled) { var rb = character.GetComponent(); if (rb != null && agent != null) { rb.isKinematic = true; } } else { // Player-controlled: DISABLE NavMeshAgent entirely after snapping. // PlayerMovementBehaviour uses Rigidbody.MovePosition — an enabled NavMeshAgent // creates internal velocity corrections that fight the Rigidbody and cause drag. var agent2 = character.GetComponent(); if (agent2 != null) { agent2.enabled = false; } } DevLog.Log($"[CashSystemManager] Snapped {characterName} to NavMesh at Y={navHit.position.y} (was Y={position.y})"); } else { DevLog.LogWarning($"[CashSystemManager] No NavMesh found near {position} for {characterName} — character may be underground!"); } // Add TeamMember component if not present TeamMember teamMember = character.GetComponent(); if (teamMember == null) { teamMember = character.AddComponent(); } // Set team based on tag bool isPlayerTeam = (teamTag == "Player"); teamMember.CurrentTeam = isPlayerTeam ? TeamMember.Team.PlayerTeam : TeamMember.Team.EnemyTeam1; // Set proper tag (TeamMember.UpdateLayerAndTag will handle this but we set it here too) character.tag = teamTag; // Use the new ControlType system - this handles ALL component configuration! TeamMember.ControlType controlType; if (isPlayerControlled) { controlType = TeamMember.ControlType.HumanControlled; } else if (isPlayerTeam) { controlType = TeamMember.ControlType.PlayerAI; } else { controlType = TeamMember.ControlType.EnemyAI; } teamMember.SetControlType(controlType); // CRITICAL: For player-controlled characters, bind input action callbacks // This is what Practice mode does in SelectionOptions.SetUpPlayerInputAI if (isPlayerControlled) { BindPlayerInputActions(character); } // Add CashCarrier for carrying cash //if (character.GetComponent() == null) //{ // character.AddComponent(); //} // Register with TeamControlManager for switching if (TeamControlManager.Instance != null && isPlayerTeam) { TeamControlManager.Instance.RegisterPlayerTeamCharacter(character, isPlayerControlled); } // Register with CameraManager AND directly set target if player-controlled if (CameraManager.Instance != null) { CameraManager.Instance.RegisterTeamMember(teamMember); // DIRECTLY set camera target for the captain if (isPlayerControlled) { CameraManager.Instance.SetTarget(teamMember); DevLog.Log($"[CashSystemManager] Set CameraManager target to captain: {character.name}"); } } // Disable attack until countdown finishes (combat control is independent of ControlType) DisableCharacterCombat(character); // Configure AI controller scripts (CashSystemAI, TeamAIController, etc.) if (!isPlayerControlled) { ConfigureCharacterControllers(character, isPlayerTeam, isPlayerControlled); } DevLog.Log($"[CashSystemManager] Spawned {characterName} as {controlType}, Team: {(isPlayerTeam ? "PLAYER" : "ENEMY")}"); return character; } /// /// Configure the appropriate controller scripts based on team and control mode /// private void ConfigureCharacterControllers(GameObject character, bool isPlayerTeam, bool isPlayerControlled) { // Get existing components PlayerScript playerScript = character.GetComponent(); CharacterAIController aiController = character.GetComponent(); if (isPlayerControlled) { // PLAYER CONTROLLED: Enable player input scripts, disable AI scripts if (playerScript != null) playerScript.enabled = true; if (aiController != null) aiController.enabled = false; // Enable new modular input handler if present CharacterInputHandler inputHandler = character.GetComponent(); if (inputHandler != null) inputHandler.enabled = true; // Disable CharacterAIController for player-controlled if (aiController != null) aiController.enabled = false; // Don't add AI controllers to player-controlled character } else if (isPlayerTeam) { // PLAYER TEAM AI: Enable navigation, use TeamAIController + CharacterAIController if (playerScript != null) playerScript.enabled = false; // Disable CharacterInputHandler on AI characters CharacterInputHandler inputHandler = character.GetComponent(); if (inputHandler != null) inputHandler.enabled = false; // Enable CharacterAIController if present, or add it if (aiController == null) { aiController = character.AddComponent(); } aiController.enabled = true; // CRITICAL: Disable PlayerInput on AI characters to prevent duplicate input PlayerInput playerInput = character.GetComponent(); if (playerInput != null) { playerInput.enabled = false; DevLog.Log($"[CashSystemManager] Disabled PlayerInput on AI teammate {character.name}"); } // Disable AudioListener on AI characters (only player should have one) AudioListener audioListener = character.GetComponent(); if (audioListener != null) { audioListener.enabled = false; } // CRITICAL: Disable ALL embedded cameras on AI characters // This prevents multiple cameras from being active in the scene Camera[] cameras = character.GetComponentsInChildren(true); foreach (Camera cam in cameras) { cam.enabled = false; DevLog.Log($"[CashSystemManager] Disabled embedded camera {cam.name} on AI teammate {character.name}"); } // Also disable any Cinemachine cameras on AI characters Cinemachine.CinemachineBrain[] brains = character.GetComponentsInChildren(true); foreach (var brain in brains) { brain.enabled = false; } // Add TeamAIController if not present TeamAIController teamAI = character.GetComponent(); if (teamAI == null) { teamAI = character.AddComponent(); } teamAI.enabled = true; // Add CashSystemAI for cash-related decisions CashSystemAI cashAI = character.GetComponent(); if (cashAI == null) { cashAI = character.AddComponent(); } cashAI.enabled = true; // Keep NavMeshAgent for movement UnityEngine.AI.NavMeshAgent navAgent = character.GetComponent(); if (navAgent != null) navAgent.enabled = true; // Disable CharacterMovement - AI uses NavMeshAgent, not joystick-based movement CharacterMovement charMovement = character.GetComponent(); if (charMovement != null) charMovement.enabled = false; DevLog.Log($"[CashSystemManager] Configured {character.name} as PLAYER TEAM AI"); } else { // ENEMY TEAM AI: Use EnemyAIController + CharacterAIController if (playerScript != null) playerScript.enabled = false; // Disable CharacterInputHandler on AI characters CharacterInputHandler inputHandler = character.GetComponent(); if (inputHandler != null) inputHandler.enabled = false; // Enable CharacterAIController if present, or add it //if (aiController == null) //{ // aiController = character.AddComponent(); //} //aiController.enabled = true; // CRITICAL: Disable PlayerInput on AI characters to prevent duplicate input PlayerInput playerInput = character.GetComponent(); if (playerInput != null) { playerInput.enabled = false; DevLog.Log($"[CashSystemManager] Disabled PlayerInput on enemy AI {character.name}"); } // Disable AudioListener on AI characters (only player should have one) AudioListener audioListener = character.GetComponent(); if (audioListener != null) { audioListener.enabled = false; } // CRITICAL: Disable ALL embedded cameras on AI characters // This prevents multiple cameras from being active in the scene Camera[] cameras = character.GetComponentsInChildren(true); foreach (Camera cam in cameras) { cam.enabled = false; DevLog.Log($"[CashSystemManager] Disabled embedded camera {cam.name} on enemy AI {character.name}"); } // Also disable any Cinemachine cameras on AI characters Cinemachine.CinemachineBrain[] brains = character.GetComponentsInChildren(true); foreach (var brain in brains) { brain.enabled = false; } // Add EnemyAIController if not present //EnemyAIController enemyAI = character.GetComponent(); //if (enemyAI == null) //{ // enemyAI = character.AddComponent(); //} //enemyAI.enabled = true; // Add CashSystemAI for cash-related decisions //CashSystemAI cashAI = character.GetComponent(); //if (cashAI == null) //{ // cashAI = character.AddComponent(); //} //cashAI.enabled = true; // Keep NavMeshAgent for movement UnityEngine.AI.NavMeshAgent navAgent = character.GetComponent(); if (navAgent != null) navAgent.enabled = true; // Disable CharacterMovement - AI uses NavMeshAgent, not joystick-based movement CharacterMovement charMovement = character.GetComponent(); if (charMovement != null) charMovement.enabled = false; DevLog.Log($"[CashSystemManager] Configured {character.name} as ENEMY TEAM AI"); } } /// /// Set up player control on the captain character /// private void SetupPlayerControl(GameObject character) { DevLog.Log($"[CashSystemManager] Setting up player control for {character.name}"); // Find head transform for camera tracking (like Practice mode) Transform targetTransform = FindCharacterHead(character); DevLog.Log($"[CashSystemManager] Camera target: {targetTransform?.name ?? "character root"}"); // Set up camera to follow this character via CameraManager (single authority) if (CameraManager.Instance != null) { TeamMember teamMember = character.GetComponent(); if (teamMember != null) { CameraManager.Instance.SetTarget(teamMember); } else { CameraManager.Instance.SetTarget(character); } DevLog.Log($"[CashSystemManager] Camera target set via CameraManager for {character.name}"); } else { // Fallback: set Cinemachine targets directly with correct Follow/LookAt separation Transform followTarget = character.transform; // Root = orbit center Transform lookAtTarget = FindCharacterHead(character); // Head = aim point Cinemachine.CinemachineFreeLook[] freeLookCameras = FindObjectsOfType(); foreach (var freeLookCam in freeLookCameras) { if (freeLookCam.gameObject.activeInHierarchy) { freeLookCam.Follow = followTarget; freeLookCam.LookAt = lookAtTarget; } } DevLog.Log($"[CashSystemManager] Camera targets set directly (fallback) for {character.name}"); } // Enable player input components PlayerScript playerScript = character.GetComponent(); if (playerScript != null) { playerScript.enabled = true; // NOTE: attack is left false here - EnableCharacterCombat will set it to true after countdown DevLog.Log("[CashSystemManager] Enabled PlayerScript (attack will be enabled after countdown)"); } // Enable and configure PlayerInput component (critical for receiving input!) UnityEngine.InputSystem.PlayerInput playerInput = character.GetComponent(); if (playerInput != null) { playerInput.enabled = true; // Switch to Player Controls action map try { playerInput.SwitchCurrentActionMap("Player Controls"); DevLog.Log("[CashSystemManager] Switched to Player Controls action map"); } catch (System.Exception e) { DevLog.LogWarning($"[CashSystemManager] Could not switch action map: {e.Message}"); } DevLog.Log($"[CashSystemManager] PlayerInput enabled, current map: {playerInput.currentActionMap?.name ?? "null"}"); } else { DevLog.LogWarning("[CashSystemManager] No PlayerInput component found on player character!"); } // Disable AI components if present CharacterAIController aiController = character.GetComponent(); if (aiController != null) { aiController.enabled = false; DevLog.Log("[CashSystemManager] Disabled CharacterAIController"); } // Disable any AI brain UnityEngine.AI.NavMeshAgent navMeshAgent = character.GetComponent(); if (navMeshAgent != null && navMeshAgent.enabled) { // NavMeshAgent should stay enabled for movement, but we'll let PlayerScript control it DevLog.Log("[CashSystemManager] NavMeshAgent present"); } // Disable CashSystemAI if added CashSystemAI cashAI = character.GetComponent(); if (cashAI != null) { Destroy(cashAI); DevLog.Log("[CashSystemManager] Removed CashSystemAI from player"); } } /// /// Bind input action callbacks to PlayerScript methods. /// This mirrors what Practice mode does in SelectionOptions.SetUpPlayerInputAI. /// Without this explicit binding, the PlayerInput component won't route actions to PlayerScript. /// private void BindPlayerInputActions(GameObject character) { PlayerInput playerInput = character.GetComponent(); PlayerScript playerScript = character.GetComponent(); if (playerInput == null || playerScript == null) { DevLog.LogWarning($"[CashSystemManager] Cannot bind input actions - PlayerInput: {playerInput != null}, PlayerScript: {playerScript != null}"); return; } // Ensure PlayerInput is enabled with the right action map playerInput.enabled = true; try { playerInput.SwitchCurrentActionMap("Player Controls"); } catch (System.Exception e) { DevLog.LogWarning($"[CashSystemManager] Could not switch action map: {e.Message}"); } var actionMap = playerInput.currentActionMap; if (actionMap == null) { Debug.LogError("[CashSystemManager] No current action map found!"); return; } DevLog.Log($"[CashSystemManager] Binding input actions on action map: {actionMap.name}"); // CRITICAL: Bind the Move action - this is what Practice mode does! TryBindActionPerformed(actionMap, "Move", playerScript.OnMove); // performed TryBindActionCanceled(actionMap, "Move", playerScript.OnMove); // canceled // Bind attack actions TryBindActionPerformed(actionMap, "Jab", playerScript.OnJab); TryBindActionPerformed(actionMap, "Right", playerScript.OnRight); TryBindActionPerformed(actionMap, "LeftHook", playerScript.OnLeftHook); TryBindActionPerformed(actionMap, "RightHook", playerScript.OnRightHook); TryBindActionPerformed(actionMap, "LeftElbow", playerScript.OnLeftElbow); TryBindActionPerformed(actionMap, "RightElbow", playerScript.OnRightElbow); TryBindActionPerformed(actionMap, "RightBody", playerScript.OnRightBody); TryBindActionPerformed(actionMap, "PowerPunchLeft", playerScript.OnPowerPunchLeft); TryBindActionPerformed(actionMap, "SupermanPunch", playerScript.OnSupermanPunch); // Kick actions TryBindActionPerformed(actionMap, "LegKickRight", playerScript.OnLegKickRight); TryBindActionPerformed(actionMap, "BodyKickRight", playerScript.OnBodyKickRight); TryBindActionPerformed(actionMap, "KneeRight", playerScript.OnKneeRight); TryBindActionPerformed(actionMap, "BackSideKick", playerScript.OnBackSideKick); TryBindActionPerformed(actionMap, "FrontKickRight", playerScript.OnFrontKickRight); TryBindActionPerformed(actionMap, "LegPowerKickRight", playerScript.OnLegPowerKickRight); // Dodge actions TryBindActionPerformed(actionMap, "JumpBack", playerScript.OnJumpBack); TryBindActionPerformed(actionMap, "JumpLeft", playerScript.OnJumpLeft); TryBindActionPerformed(actionMap, "JumpRight", playerScript.OnJumpRight); // Block actions TryBindActionPerformed(actionMap, "BlockStepBack", playerScript.OnBlockStepBack); TryBindActionPerformed(actionMap, "BlockBodyLeft", playerScript.OnBlockBodyLeft); TryBindActionPerformed(actionMap, "BlockLeg", playerScript.OnBlockLeg); // Special/Vicious attacks TryBindActionPerformed(actionMap, "Chokeslam", playerScript.OnChokeslam); TryBindActionPerformed(actionMap, "Suplex", playerScript.OnSuplex); TryBindActionPerformed(actionMap, "GiantSwing", playerScript.OnGiantSwing); TryBindActionPerformed(actionMap, "DiamondCrusher", playerScript.OnDiamondCrusher); TryBindActionPerformed(actionMap, "SumoSlap", playerScript.OnSumoSlap); TryBindActionPerformed(actionMap, "JavelinTackle", playerScript.OnJavelinTackle); TryBindActionPerformed(actionMap, "RKO", playerScript.OnRKO); TryBindActionPerformed(actionMap, "Spear", playerScript.OnSpear); TryBindActionPerformed(actionMap, "RockBottom", playerScript.OnRockBottom); TryBindActionPerformed(actionMap, "F5", playerScript.OnF5); DevLog.Log($"[CashSystemManager] Input actions bound successfully for {character.name}"); } /// /// Helper to safely bind an action's performed callback /// private void TryBindActionPerformed(InputActionMap actionMap, string actionName, System.Action callback) { InputAction action = actionMap.FindAction(actionName); if (action != null) { action.performed += callback; } } /// /// Helper to safely bind an action's canceled callback /// private void TryBindActionCanceled(InputActionMap actionMap, string actionName, System.Action callback) { InputAction action = actionMap.FindAction(actionName); if (action != null) { action.canceled += callback; } } /// /// Helper to safely bind an action (both performed and started) /// private void TryBindAction(InputActionMap actionMap, string actionName, System.Action startedCallback, System.Action performedCallback) { InputAction action = actionMap.FindAction(actionName); if (action != null) { if (startedCallback != null) action.started += startedCallback; if (performedCallback != null) action.performed += performedCallback; } } /// /// Find the character's head transform for camera targeting (matches Practice mode) /// private Transform FindCharacterHead(GameObject character) { Transform headTransform = null; try { // Common head bone names in character rigs string[] headNames = { "Head", "head", "Bip001 Head", "mixamorig:Head", "Bip01 Head" }; foreach (string headName in headNames) { headTransform = character.transform.Find(headName); if (headTransform != null) break; // Also search recursively headTransform = FindChildRecursive(character.transform, headName); if (headTransform != null) break; } // If no head found, try to find any transform with "head" in name if (headTransform == null) { headTransform = FindChildWithPartialName(character.transform, "head"); } } catch (System.Exception e) { DevLog.LogWarning($"[CashSystemManager] Error finding head for {character.name}: {e.Message}"); } return headTransform ?? 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; } private Transform FindChildWithPartialName(Transform parent, string partialName) { foreach (Transform child in parent) { if (child.name.ToLower().Contains(partialName.ToLower())) return child; Transform found = FindChildWithPartialName(child, partialName); if (found != null) return found; } return null; } /// /// Disable combat for a character (used before countdown) /// private void DisableCharacterCombat(GameObject character) { PlayerScript playerScript = character.GetComponent(); if (playerScript != null) { playerScript.attack = false; } CharacterAIController aiController = character.GetComponent(); if (aiController != null) { aiController.attack = false; } } private IEnumerator MatchCountdown() { DevLog.Log("[CashSystemManager] Starting match countdown..."); // Try to find existing countdown UI TMPro.TextMeshProUGUI countdownText = null; GameObject uiObject = GameObject.Find("UI"); if (uiObject != null) { Transform roundInfo = uiObject.transform.Find("Canvas/rounds info (TMP)"); if (roundInfo != null) countdownText = roundInfo.GetComponent(); } yield return new WaitForSecondsRealtime(1f); string[] steps = { "3", "2", "1", "FIGHT!" }; foreach (string step in steps) { if (countdownText != null) countdownText.text = step; GameEvents.FireCountdownTick(step); yield return new WaitForSecondsRealtime(1f); } // Clear if (countdownText != null) countdownText.text = ""; GameEvents.FireCountdownTick(""); // Enable combat GameEvents.FireCombatEnabled(true); foreach (var c in playerTeamCharacters) if (c != null) EnableCharacterCombat(c); foreach (var c in enemyTeamCharacters) if (c != null) EnableCharacterCombat(c); } private void EnableCharacterCombat(GameObject character) { // Enable attack for player characters PlayerScript playerScript = character.GetComponent(); if (playerScript != null) { playerScript.attack = true; } // Enable attack for AI characters CharacterAIController aiController = character.GetComponent(); if (aiController != null) { aiController.attack = true; } } private void SwitchControlledCharacter() { if (playerTeamCharacters.Count <= 1) return; // Use TeamControlManager if available if (TeamControlManager.Instance != null) { TeamControlManager.Instance.SwitchToNextCharacter(); // Update local tracking GameObject newControlled = TeamControlManager.Instance.GetCurrentControlledCharacter(); if (newControlled != null) { GameObject oldControlled = currentControlledIndex < playerTeamCharacters.Count ? playerTeamCharacters[currentControlledIndex] : null; currentControlledIndex = playerTeamCharacters.IndexOf(newControlled); SelectionOptions.Instance.currentControlledIndex = currentControlledIndex; GameEvents.FireCharacterSwitched(oldControlled, newControlled); } return; } // Fallback: Manual switching int previousIndex = currentControlledIndex; // Find next alive character for (int i = 1; i <= playerTeamCharacters.Count; i++) { int nextIndex = (currentControlledIndex + i) % playerTeamCharacters.Count; GameObject character = playerTeamCharacters[nextIndex]; if (character != null) { HealthNew health = character.GetComponent(); if (health == null || !health.isDead) { currentControlledIndex = nextIndex; break; } } } if (currentControlledIndex != previousIndex) { SelectionOptions.Instance.currentControlledIndex = currentControlledIndex; // Disable control on previous character GameObject previousCharacter = playerTeamCharacters[previousIndex]; if (previousCharacter != null) { PlayerScript prevPS = previousCharacter.GetComponent(); if (prevPS != null) prevPS.attack = false; TeamAIController prevAI = previousCharacter.GetComponent(); if (prevAI != null) prevAI.enabled = true; } // Enable control on new character GameObject newCharacter = playerTeamCharacters[currentControlledIndex]; if (newCharacter != null) { PlayerScript newPS = newCharacter.GetComponent(); if (newPS != null) { newPS.enabled = true; newPS.attack = true; } TeamAIController newAI = newCharacter.GetComponent(); if (newAI != null) newAI.enabled = false; // Update camera UpdateCameraToCharacter(newCharacter); } DevLog.Log($"[CashSystemManager] Switched control to {playerTeamCharacters[currentControlledIndex].name}"); } } /// /// Update camera to follow a specific character /// private void UpdateCameraToCharacter(GameObject character) { // Route through CameraManager (single authority) with correct Follow/LookAt separation if (CameraManager.Instance != null) { TeamMember teamMember = character.GetComponent(); if (teamMember != null) { CameraManager.Instance.SetTarget(teamMember); } else { CameraManager.Instance.SetTarget(character); } DevLog.Log($"[CashSystemManager] Updated camera via CameraManager to follow {character.name}"); return; } // Fallback: direct Cinemachine target update with correct separation Transform followTarget = character.transform; // Root = orbit center Transform lookAtTarget = FindCharacterHead(character); // Head = aim point Cinemachine.CinemachineFreeLook[] freeLookCameras = FindObjectsOfType(); foreach (var cam in freeLookCameras) { if (cam.gameObject.activeInHierarchy) { cam.Follow = followTarget; cam.LookAt = lookAtTarget; } } Cinemachine.CinemachineVirtualCamera[] virtualCameras = FindObjectsOfType(); foreach (var cam in virtualCameras) { if (cam.gameObject.activeInHierarchy) { cam.Follow = followTarget; cam.LookAt = lookAtTarget; } } DevLog.Log($"[CashSystemManager] Updated camera to follow {character.name} (direct fallback)"); } public void OnVaultUpdated(TeamVault vault) { // Legacy callback kept for backwards compat; real routing now via GameEvents GameEvents.FireScoreChanged(vault.TeamTag, vault.StoredCash); } // ─── Event Handlers ────────────────────────────────────── private void OnCashDepositedEvent(string teamTag, float value, int combo) { // UI updates itself via GameEvents — manager just tracks win conditions } private void OnVaultStolenEvent(TeamVault vault, float amount) { // UI updates itself via GameEvents — manager just tracks win conditions } // ─── Kill Reward System ($200 per kill) ────────────────── private void OnKillEvent(GameObject killer, GameObject victim) { if (matchEnded || killer == null || victim == null) return; string killerTeamTag = killer.tag; // Award $200 to killer's team AwardCashToTeam(killerTeamTag, killReward); GameEvents.FireKillReward(killer, killReward); GameEvents.FireKillFeedEntry(killer.name, "ELIMINATED", $"{victim.name} (+${killReward})"); GameEvents.FirePopupMessage($"+${killReward} KILL REWARD", 1.5f); DevLog.Log($"[CashSystemManager] Kill reward: ${killReward} to {killerTeamTag} ({killer.name} killed {victim.name})"); // Drop vault if victim was carrying one DropVaultOnDeath(victim); // Check for team wipe CheckTeamWipe(victim.tag); } // ─── Character Death + Respawn ─────────────────────────── private void OnCharacterDiedEvent(GameObject character) { if (matchEnded || character == null) return; // Start respawn timer StartCoroutine(RespawnRoutine(character)); } private IEnumerator RespawnRoutine(GameObject character) { if (character == null) yield break; HealthNew hp = character.GetComponent(); string teamTag = character.tag; DevLog.Log($"[CashSystemManager] {character.name} died, respawning in {respawnTime}s"); // Countdown float remaining = respawnTime; while (remaining > 0f) { remaining -= Time.deltaTime; GameEvents.FireRespawnTimerUpdate(character, remaining); yield return null; } if (character == null || matchEnded) yield break; // Respawn at team spawn area Vector3 respawnPos = GetRespawnPosition(teamTag); // Reset health if (hp != null) { hp.currentHealth = hp.maxHealth; hp.isDead = false; if (hp.healthSlider != null) hp.healthSlider.value = hp.currentHealth; } // Teleport to respawn position UnityEngine.AI.NavMeshAgent agent = character.GetComponent(); if (agent != null && agent.enabled) { agent.enabled = false; character.transform.position = respawnPos; agent.enabled = true; agent.Warp(respawnPos); } else { character.transform.position = respawnPos; } // Re-enable combat EnableCharacterCombat(character); // Re-enable the character if deactivated if (!character.activeInHierarchy) character.SetActive(true); GameEvents.FireCharacterRespawned(character); GameEvents.FireKillFeedEntry(character.name, "RESPAWNED", ""); DevLog.Log($"[CashSystemManager] {character.name} respawned at {respawnPos}"); } private Vector3 GetRespawnPosition(string teamTag) { // Use team spawn points or vault/station position Transform[] spawnPts = teamTag == "Player" ? playerTeamSpawnPoints : enemyTeamSpawnPoints; if (spawnPts != null && spawnPts.Length > 0) { Transform pt = spawnPts[Random.Range(0, spawnPts.Length)]; if (pt != null) return pt.position; } // Fallback: near station or vault if (teamTag == "Player") { if (playerStation != null) return playerStation.transform.position + Vector3.forward * 3f; if (playerVault != null) return playerVault.transform.position + Vector3.forward * 3f; } else { if (enemyStation != null) return enemyStation.transform.position + Vector3.back * 3f; if (enemyVault != null) return enemyVault.transform.position + Vector3.back * 3f; } return Vector3.zero; } // ─── Team Wipe Detection (-30% cash penalty) ───────────── private void CheckTeamWipe(string victimTeamTag) { List teamChars = GetTeamCharacters(victimTeamTag); if (teamChars == null || teamChars.Count == 0) return; bool allDead = true; for (int i = 0; i < teamChars.Count; i++) { if (teamChars[i] == null) continue; HealthNew hp = teamChars[i].GetComponent(); if (hp == null || !hp.isDead) { allDead = false; break; } } if (allDead) { ApplyTeamWipePenalty(victimTeamTag); GameEvents.FireTeamWipe(victimTeamTag); } } private void ApplyTeamWipePenalty(string teamTag) { float currentCash = GetTeamTotalCash(teamTag); float penalty = currentCash * teamWipePenaltyPercent; if (penalty <= 0f) return; // Remove from CashoutStation first, then TeamVault if (teamTag == "Player" && playerStation != null) playerStation.RemoveCash(penalty); else if (teamTag == "Enemy" && enemyStation != null) enemyStation.RemoveCash(penalty); else if (teamTag == "Player" && playerVault != null) playerVault.DepositCash(-penalty); // Negative deposit = removal else if (teamTag == "Enemy" && enemyVault != null) enemyVault.DepositCash(-penalty); GameEvents.FirePopupMessage($"TEAM WIPE! -{teamWipePenaltyPercent * 100:F0}% CASH (${penalty:F0})", 3f); GameEvents.FireKillFeedEntry(teamTag, "TEAM WIPED!", $"-${penalty:F0}"); DevLog.Log($"[CashSystemManager] Team wipe penalty: {teamTag} lost ${penalty:F0} ({teamWipePenaltyPercent * 100}%)"); } // ─── Vault Drop on Death ───────────────────────────────── private void DropVaultOnDeath(GameObject character) { var vaults = EntityRegistry.GetAll(); for (int i = 0; i < vaults.Count; i++) { if (vaults[i] != null && vaults[i].IsCarried && vaults[i].Carrier == character) { vaults[i].Drop(character.transform.position); GameEvents.FirePopupMessage("VAULT DROPPED!", 2f); return; } } } // ─── Cash Helpers ──────────────────────────────────────── /// Get total cash for a team from all sources (CashoutStation + TeamVault). public float GetTeamTotalCash(string teamTag) { float total = 0f; if (teamTag == "Player") { if (playerStation != null) total += playerStation.StoredCash; if (playerVault != null) total += playerVault.StoredCash; } else { if (enemyStation != null) total += enemyStation.StoredCash; if (enemyVault != null) total += enemyVault.StoredCash; } return total; } /// Award cash to a team's CashoutStation (or fallback to TeamVault). private void AwardCashToTeam(string teamTag, float amount) { if (teamTag == "Player") { if (playerStation != null) playerStation.AddDirectCash(amount); else if (playerVault != null) playerVault.DepositCash(amount); } else { if (enemyStation != null) enemyStation.AddDirectCash(amount); else if (enemyVault != null) enemyVault.DepositCash(amount); } } private void EndMatch(bool? playerWon = null) { if (matchEnded) return; matchEnded = true; // Restore time scale if buzzer was active if (_buzzerActive) { Time.timeScale = 1f; Time.fixedDeltaTime = 0.02f; } if (playerWon == null) { float playerScore = GetTeamTotalCash("Player"); float enemyScore = GetTeamTotalCash("Enemy"); playerWon = playerScore > enemyScore; } string winnerTag = playerWon == true ? "Player" : "Enemy"; GameEvents.FireCombatEnabled(false); GameEvents.FireMatchEnd(winnerTag); GameEvents.FireMatchResult(playerWon == true); // MatchResultUI subscribes to OnMatchResult and shows itself. // Keep legacy screens as fallback only if MatchResultUI is absent. if (MatchResultUI.Instance == null) { if (playerWon == true && victoryScreen != null) victoryScreen.SetActive(true); else if (playerWon == false && defeatScreen != null) defeatScreen.SetActive(true); } StartCoroutine(EndMatchSequence()); } private IEnumerator EndMatchSequence() { // If MatchResultUI is present, it handles navigation via Continue/Restart buttons. // Only auto-navigate if MatchResultUI is NOT present (legacy fallback). if (MatchResultUI.Instance != null) { // Let the player use the Continue / Restart buttons — don't auto-load. yield break; } yield return new WaitForSeconds(5f); GameEvents.ClearAll(); CashSystemAI.ClearAll(); SelectionOptions.Instance.isCashSystemSelected = false; SceneManager.LoadScene("MenuScene"); } /// /// Get all characters on a team /// public List GetTeamCharacters(string teamTag) { return teamTag == "Player" ? playerTeamCharacters : enemyTeamCharacters; } /// /// Get the vault for a team /// public TeamVault GetVault(string teamTag) { return teamTag == "Player" ? playerVault : enemyVault; } /// /// Switch control to a specific character index in the player team. /// Called by TeamHUDPanel when a portrait is clicked. /// public void SwitchToCharacterAtIndex(int index) { if (index < 0 || index >= playerTeamCharacters.Count) return; if (index == currentControlledIndex) return; GameObject targetCharacter = playerTeamCharacters[index]; if (targetCharacter == null) return; HealthNew targetHealth = targetCharacter.GetComponent(); if (targetHealth != null && targetHealth.isDead) return; // Check TeamControlManager first if (TeamControlManager.Instance != null) { TeamControlManager.Instance.SwitchToCharacter(index); GameObject newControlled = TeamControlManager.Instance.GetCurrentControlledCharacter(); if (newControlled != null) { GameObject oldControlled = currentControlledIndex < playerTeamCharacters.Count ? playerTeamCharacters[currentControlledIndex] : null; currentControlledIndex = index; SelectionOptions.Instance.currentControlledIndex = currentControlledIndex; GameEvents.FireCharacterSwitched(oldControlled, newControlled); } return; } // Fallback: Manual switching int previousIndex = currentControlledIndex; currentControlledIndex = index; SelectionOptions.Instance.currentControlledIndex = currentControlledIndex; // Disable control on previous character GameObject previousCharacter = playerTeamCharacters[previousIndex]; if (previousCharacter != null) { PlayerScript prevPS = previousCharacter.GetComponent(); if (prevPS != null) prevPS.attack = false; TeamAIController prevAI = previousCharacter.GetComponent(); if (prevAI != null) prevAI.enabled = true; } // Enable control on new character if (targetCharacter != null) { PlayerScript newPS = targetCharacter.GetComponent(); if (newPS != null) { newPS.enabled = true; newPS.attack = true; } TeamAIController newAI = targetCharacter.GetComponent(); if (newAI != null) newAI.enabled = false; UpdateCameraToCharacter(targetCharacter); } GameEvents.FireCharacterSwitched(previousCharacter, targetCharacter); DevLog.Log($"[CashSystemManager] Switched control to index {index}: {targetCharacter.name}"); } }