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}");
}
}