using UnityEngine; using UnityEngine.Events; using System.Collections; using System.Collections.Generic; /// /// TeamVault — Deposit zone for team cash with steal mechanic. /// Uses GameEvents for all notifications (no FindObjectOfType in hot paths). /// EntityRegistry for fast lookups. Magnet zone for auto-deposit at close range. /// public class TeamVault : MonoBehaviour { [Header("Team Settings")] [SerializeField] private string teamTag = "Player"; // "Player" or "Enemy" [SerializeField] private float storedCash = 0f; [Header("Interaction Settings")] [SerializeField] private float depositTime = 1.5f; [SerializeField] private float stealTime = 3f; [SerializeField] private float stealAmount = 100f; [SerializeField] private float magnetRange = 2.5f; // Auto-deposit range [SerializeField] private float magnetInterval = 0.3f; // How often to check magnet [Header("Visual/Audio")] [SerializeField] private GameObject depositEffect; [SerializeField] private GameObject stealEffect; [SerializeField] private ParticleSystem activeParticles; [Header("Import / Spawn")] [SerializeField] private GameObject importCashBagPrefab; [SerializeField] private bool spawnImportOnStart = false; [SerializeField] private int importCount = 1; [Header("CashBox Import Animation")] [Tooltip("Enable animated import with countdown, camera focus, and sounds")] [SerializeField] private bool useAnimatedImport = true; [Tooltip("CashBox prefab to spawn at vault (has Animator with 'close' animation)")] [SerializeField] private GameObject cashBoxPrefab; [SerializeField] private Vector3 cashBoxOffset = Vector3.zero; [SerializeField] private Vector3 cashBoxRotation = new Vector3(-90f, 0f, 0f); [SerializeField] private Vector3 cashBoxScale = new Vector3(60f, 60f, 60f); [Header("Events (Inspector)")] public UnityEvent OnCashDepositedLocal; public UnityEvent OnCashStolenLocal; public UnityEvent OnVaultUnderAttackLocal; // ─── Runtime ───────────────────────────────────────────── private bool _isBeingStolen; private CashCarrier _currentThief; private float _stealProgress; private Coroutine _stealCoroutine; private Coroutine _magnetCoroutine; private readonly HashSet _depositingCarriers = new HashSet(); private CashBoxImportController _importController; public float StoredCash => storedCash; public string TeamTag => teamTag; public bool IsBeingStolen => _isBeingStolen; public float StealProgress => _stealProgress; // ─── Lifecycle ─────────────────────────────────────────── private void Start() { if (SelectionOptions.Instance == null || !SelectionOptions.Instance.isCashSystemSelected) { enabled = false; return; } _magnetCoroutine = StartCoroutine(MagnetPulseRoutine()); // Optional: spawn imported cash bags at vault position if (spawnImportOnStart && importCashBagPrefab != null) { for (int i = 0; i < Mathf.Max(1, importCount); i++) { SpawnImportedBag(importCashBagPrefab); } } // Setup CashBox import controller if animated import is enabled if (useAnimatedImport && cashBoxPrefab != null) { SetupImportController(); } } private void SetupImportController() { _importController = GetComponent(); if (_importController == null) _importController = gameObject.AddComponent(); // Pass the TeamVault's prefab and transform settings to the controller _importController.Configure(cashBoxPrefab, cashBoxOffset, cashBoxRotation, cashBoxScale); // Spawn the CashBox above the vault _importController.SpawnCashBox(); } private void OnEnable() { EntityRegistry.Register(this); } private void OnDisable() { EntityRegistry.Unregister(this); if (_magnetCoroutine != null) StopCoroutine(_magnetCoroutine); } // ─── Trigger ───────────────────────────────────────────── private void OnTriggerEnter(Collider other) { CashCarrier carrier = other.GetComponentInParent(); if (carrier == null) return; // Same team → deposit if (carrier.TeamTag == teamTag) { if (carrier.CarriedCash > 0 && !_depositingCarriers.Contains(carrier)) StartCoroutine(DepositRoutine(carrier)); } // Enemy team → steal else { if (storedCash > 0 && !_isBeingStolen) StartStealing(carrier); } } private void OnTriggerExit(Collider other) { CashCarrier carrier = other.GetComponentInParent(); if (carrier == null) return; if (carrier == _currentThief) CancelSteal(); } // ─── Magnet Auto-Deposit ───────────────────────────────── private IEnumerator MagnetPulseRoutine() { var wait = new WaitForSeconds(magnetInterval); while (true) { yield return wait; var carriers = EntityRegistry.GetAll(); for (int i = 0; i < carriers.Count; i++) { var c = carriers[i]; if (c == null || c.CarriedCash <= 0 || c.TeamTag != teamTag) continue; if (_depositingCarriers.Contains(c)) continue; if (Vector3.Distance(c.transform.position, transform.position) <= magnetRange) StartCoroutine(DepositRoutine(c)); } } } // ─── Deposit ───────────────────────────────────────────── private IEnumerator DepositRoutine(CashCarrier carrier) { _depositingCarriers.Add(carrier); // Check if this is a player and we should use animated import bool isPlayer = carrier.CompareTag("Player") || carrier.gameObject.layer == LayerMask.NameToLayer("Player"); bool useImport = useAnimatedImport && _importController != null && isPlayer; if (useImport && _importController.CanImport(carrier)) { // Use animated import with countdown, camera focus, sounds _importController.StartImport(carrier); // Wait for import to complete while (_importController.IsImporting) yield return null; } else { // Standard deposit flow (for AI or when import controller unavailable) yield return new WaitForSeconds(depositTime); if (carrier != null && carrier.CarriedCash > 0) { // Let carrier handle combo scoring via DepositToVault carrier.DepositToVault(this); if (depositEffect != null) Instantiate(depositEffect, transform.position + Vector3.up, Quaternion.identity); } } _depositingCarriers.Remove(carrier); } /// Direct deposit (called by CashCarrier.DepositToVault or legacy code). public void DepositCash(float amount) { storedCash += amount; OnCashDepositedLocal?.Invoke(amount); // Fire global events instead of FindObjectOfType GameEvents.FireScoreChanged(teamTag, storedCash); GameEvents.FireKillFeedEntry(teamTag, "deposited", $"${amount:F0}"); } // ─── Steal ─────────────────────────────────────────────── private void StartStealing(CashCarrier thief) { if (_isBeingStolen) return; _isBeingStolen = true; _currentThief = thief; _stealProgress = 0f; OnVaultUnderAttackLocal?.Invoke(); GameEvents.FireVaultUnderAttack(this); _stealCoroutine = StartCoroutine(StealRoutine(thief)); } private IEnumerator StealRoutine(CashCarrier thief) { float elapsed = 0f; while (elapsed < stealTime) { if (thief == null || !thief.enabled) { CancelSteal(); yield break; } HealthNew health = thief.GetComponent(); if (health != null && health.isDead) { CancelSteal(); yield break; } elapsed += Time.deltaTime; _stealProgress = elapsed / stealTime; yield return null; } CompleteSteal(thief); } private void CompleteSteal(CashCarrier thief) { if (thief == null) return; float stolen = Mathf.Min(stealAmount, storedCash); storedCash -= stolen; thief.AddCash(stolen); if (stealEffect != null) Instantiate(stealEffect, transform.position + Vector3.up, Quaternion.identity); OnCashStolenLocal?.Invoke(stolen); _isBeingStolen = false; _currentThief = null; _stealProgress = 0f; // Fire global events — replaces FindObjectOfType GameEvents.FireVaultStolen(this, stolen); GameEvents.FireScoreChanged(teamTag, storedCash); GameEvents.FireKillFeedEntry(thief.gameObject.name, "stole", $"${stolen:F0}"); } private void CancelSteal() { if (_stealCoroutine != null) StopCoroutine(_stealCoroutine); _isBeingStolen = false; _currentThief = null; _stealProgress = 0f; GameEvents.FireVaultDefended(this); } /// Force interrupt steal (e.g., when thief takes damage). public void InterruptSteal() { CancelSteal(); } /// /// Spawn an imported cash bag at this vault's position. Uses PoolManager if available. /// Returns the spawned CashBag GameObject or null. /// public GameObject SpawnImportedBag(GameObject prefab) { if (prefab == null) return null; GameObject obj = null; // Use PoolManager if present var poolManagerType = typeof(PoolManager); try { obj = PoolManager.Instance != null ? PoolManager.Instance.Spawn(prefab, transform.position + Vector3.up * 0.5f, Quaternion.identity) : null; } catch { obj = null; } if (obj == null) { obj = Instantiate(prefab, transform.position + Vector3.up * 0.5f, Quaternion.identity); } var bag = obj.GetComponent(); if (bag != null) { bag.SetCashValue(bag.CashValue); bag.Drop(transform.position); // Ensure bag is in dropped/available state } return obj; } /// Get the opposing vault via EntityRegistry (no FindObjectsOfType). public TeamVault GetOpposingVault() { var vaults = EntityRegistry.GetAll(); for (int i = 0; i < vaults.Count; i++) { if (vaults[i] != null && vaults[i].teamTag != this.teamTag) return vaults[i]; } return null; } private void OnDrawGizmos() { Gizmos.color = teamTag == "Player" ? new Color(0, 0, 1, 0.3f) : new Color(1, 0, 0, 0.3f); Gizmos.DrawSphere(transform.position, 2f); Gizmos.color = new Color(1, 1, 0, 0.15f); Gizmos.DrawSphere(transform.position, magnetRange); #if UNITY_EDITOR UnityEditor.Handles.Label(transform.position + Vector3.up * 3f, $"{teamTag} Vault\n${storedCash}"); #endif } }