357 lines
12 KiB
C#
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<CashBag>();
|
|
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
|
|
}
|
|
}
|