Files
2026-06-26 22:41:39 +05:30

357 lines
12 KiB
C#

using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// 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.
/// </summary>
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<float> OnCashDepositedLocal;
public UnityEvent<float> OnCashStolenLocal;
public UnityEvent OnVaultUnderAttackLocal;
// ─── Runtime ─────────────────────────────────────────────
private bool _isBeingStolen;
private CashCarrier _currentThief;
private float _stealProgress;
private Coroutine _stealCoroutine;
private Coroutine _magnetCoroutine;
private readonly HashSet<CashCarrier> _depositingCarriers = new HashSet<CashCarrier>();
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<CashBoxImportController>();
if (_importController == null)
_importController = gameObject.AddComponent<CashBoxImportController>();
// 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<TeamVault>.Register(this);
}
private void OnDisable()
{
EntityRegistry<TeamVault>.Unregister(this);
if (_magnetCoroutine != null) StopCoroutine(_magnetCoroutine);
}
// ─── Trigger ─────────────────────────────────────────────
private void OnTriggerEnter(Collider other)
{
CashCarrier carrier = other.GetComponentInParent<CashCarrier>();
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<CashCarrier>();
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<CashCarrier>.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);
}
/// <summary>Direct deposit (called by CashCarrier.DepositToVault or legacy code).</summary>
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<HealthNew>();
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<CashSystemManager/CashSystemUI>
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);
}
/// <summary>Force interrupt steal (e.g., when thief takes damage).</summary>
public void InterruptSteal()
{
CancelSteal();
}
/// <summary>
/// Spawn an imported cash bag at this vault's position. Uses PoolManager if available.
/// Returns the spawned CashBag GameObject or null.
/// </summary>
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<CashMag>();
if (bag != null)
{
bag.SetCashValue(bag.CashValue);
bag.Drop(transform.position); // Ensure bag is in dropped/available state
}
return obj;
}
/// <summary>Get the opposing vault via EntityRegistry (no FindObjectsOfType).</summary>
public TeamVault GetOpposingVault()
{
var vaults = EntityRegistry<TeamVault>.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
}
}