chunk 1: core gameplay scripts scenes runtime assets
This commit is contained in:
356
Assets/Scripts/CashSystem/TeamVault.cs
Normal file
356
Assets/Scripts/CashSystem/TeamVault.cs
Normal file
@@ -0,0 +1,356 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user