chunk 1: core gameplay scripts scenes runtime assets
This commit is contained in:
222
Assets/Scripts/CashSystem/UI/MatchStatTracker.cs
Normal file
222
Assets/Scripts/CashSystem/UI/MatchStatTracker.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// MatchStatTracker — Listens to GameEvents during a match and records
|
||||
/// per-character stats (kills, deaths, assists, damage dealt, cash deposited, cash stolen).
|
||||
/// Singleton that lives for the duration of the match scene.
|
||||
/// MatchResultUI reads from this at match end.
|
||||
/// </summary>
|
||||
public class MatchStatTracker : MonoBehaviour
|
||||
{
|
||||
/// <summary>Stats for one character.</summary>
|
||||
public class CharacterStats
|
||||
{
|
||||
public string name;
|
||||
public int kills;
|
||||
public int deaths;
|
||||
public int assists;
|
||||
public float damageDealt;
|
||||
public float cashDeposited;
|
||||
public float cashStolen;
|
||||
public float cashCollected;
|
||||
}
|
||||
|
||||
// Key = character GameObject instance ID
|
||||
private Dictionary<int, CharacterStats> _stats = new Dictionary<int, CharacterStats>();
|
||||
|
||||
// Track recent attackers for assist detection (victimID → list of attackerIDs within last N seconds)
|
||||
private Dictionary<int, List<(int attackerID, float time)>> _recentAttackers
|
||||
= new Dictionary<int, List<(int, float)>>();
|
||||
private const float ASSIST_WINDOW = 5f;
|
||||
|
||||
private static MatchStatTracker _instance;
|
||||
public static MatchStatTracker Instance => _instance;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_instance != null && _instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
GameEvents.OnKill += OnKill;
|
||||
GameEvents.OnDamageDealt += OnDamageDealt;
|
||||
GameEvents.OnCharacterDied += OnCharacterDied;
|
||||
GameEvents.OnCashDeposited += OnCashDeposited;
|
||||
GameEvents.OnVaultStolen += OnVaultStolen;
|
||||
GameEvents.OnBagPickedUp += OnBagPickedUp;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
GameEvents.OnKill -= OnKill;
|
||||
GameEvents.OnDamageDealt -= OnDamageDealt;
|
||||
GameEvents.OnCharacterDied -= OnCharacterDied;
|
||||
GameEvents.OnCashDeposited -= OnCashDeposited;
|
||||
GameEvents.OnVaultStolen -= OnVaultStolen;
|
||||
GameEvents.OnBagPickedUp -= OnBagPickedUp;
|
||||
}
|
||||
|
||||
// ─── Public API ──────────────────────────────────────────
|
||||
|
||||
/// <summary>Get stats for a specific character. Creates entry if missing.</summary>
|
||||
public CharacterStats GetStats(GameObject character)
|
||||
{
|
||||
if (character == null) return new CharacterStats { name = "Unknown" };
|
||||
int id = character.GetInstanceID();
|
||||
if (!_stats.ContainsKey(id))
|
||||
{
|
||||
_stats[id] = new CharacterStats
|
||||
{
|
||||
name = character.name.Replace("(Clone)", "").Trim()
|
||||
};
|
||||
}
|
||||
return _stats[id];
|
||||
}
|
||||
|
||||
/// <summary>Get stats for all characters in a list (team).</summary>
|
||||
public List<CharacterStats> GetTeamStats(List<GameObject> teamCharacters)
|
||||
{
|
||||
List<CharacterStats> result = new List<CharacterStats>();
|
||||
if (teamCharacters == null) return result;
|
||||
foreach (var c in teamCharacters)
|
||||
{
|
||||
if (c != null) result.Add(GetStats(c));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ─── Event Handlers ──────────────────────────────────────
|
||||
|
||||
private void OnKill(GameObject killer, GameObject victim)
|
||||
{
|
||||
if (killer != null)
|
||||
GetStats(killer).kills++;
|
||||
if (victim != null)
|
||||
GetStats(victim).deaths++;
|
||||
|
||||
// Award assists to other recent attackers of the victim
|
||||
if (victim != null)
|
||||
{
|
||||
int victimID = victim.GetInstanceID();
|
||||
int killerID = killer != null ? killer.GetInstanceID() : -1;
|
||||
|
||||
if (_recentAttackers.ContainsKey(victimID))
|
||||
{
|
||||
HashSet<int> assistedIDs = new HashSet<int>();
|
||||
foreach (var (attackerID, time) in _recentAttackers[victimID])
|
||||
{
|
||||
if (attackerID != killerID && Time.time - time <= ASSIST_WINDOW && !assistedIDs.Contains(attackerID))
|
||||
{
|
||||
assistedIDs.Add(attackerID);
|
||||
if (_stats.ContainsKey(attackerID))
|
||||
_stats[attackerID].assists++;
|
||||
}
|
||||
}
|
||||
_recentAttackers.Remove(victimID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDamageDealt(GameObject attacker, GameObject victim, float amount)
|
||||
{
|
||||
if (attacker != null)
|
||||
GetStats(attacker).damageDealt += amount;
|
||||
|
||||
// Track for assists
|
||||
if (attacker != null && victim != null)
|
||||
{
|
||||
int victimID = victim.GetInstanceID();
|
||||
int attackerID = attacker.GetInstanceID();
|
||||
|
||||
if (!_recentAttackers.ContainsKey(victimID))
|
||||
_recentAttackers[victimID] = new List<(int, float)>();
|
||||
|
||||
_recentAttackers[victimID].Add((attackerID, Time.time));
|
||||
|
||||
// Prune old entries
|
||||
_recentAttackers[victimID].RemoveAll(e => Time.time - e.time > ASSIST_WINDOW);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCharacterDied(GameObject character)
|
||||
{
|
||||
// Death is already tracked via OnKill, but if OnKill isn't fired for some deaths:
|
||||
if (character != null)
|
||||
{
|
||||
var s = GetStats(character);
|
||||
// Only increment if OnKill hasn't already done it this frame
|
||||
// (simple guard — check if death count was already incremented)
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCashDeposited(string teamTag, float value, int combo)
|
||||
{
|
||||
// Attribute to the character who deposited — find the closest carrier
|
||||
CashSystemManager mgr = CashSystemManager.Instance;
|
||||
if (mgr == null) return;
|
||||
|
||||
var team = mgr.GetTeamCharacters(teamTag);
|
||||
if (team == null) return;
|
||||
|
||||
// Find the character who just deposited (nearest to their vault)
|
||||
TeamVault vault = mgr.GetVault(teamTag);
|
||||
if (vault == null) return;
|
||||
|
||||
float bestDist = float.MaxValue;
|
||||
GameObject depositor = null;
|
||||
foreach (var c in team)
|
||||
{
|
||||
if (c == null) continue;
|
||||
float d = Vector3.Distance(c.transform.position, vault.transform.position);
|
||||
if (d < bestDist)
|
||||
{
|
||||
bestDist = d;
|
||||
depositor = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (depositor != null)
|
||||
GetStats(depositor).cashDeposited += value;
|
||||
}
|
||||
|
||||
private void OnVaultStolen(TeamVault vault, float amount)
|
||||
{
|
||||
// Attribute to the closest enemy character to this vault
|
||||
if (vault == null) return;
|
||||
CashSystemManager mgr = CashSystemManager.Instance;
|
||||
if (mgr == null) return;
|
||||
|
||||
string enemyTag = vault.TeamTag == "Player" ? "Enemy" : "Player";
|
||||
var enemies = mgr.GetTeamCharacters(enemyTag);
|
||||
if (enemies == null) return;
|
||||
|
||||
float bestDist = float.MaxValue;
|
||||
GameObject thief = null;
|
||||
foreach (var c in enemies)
|
||||
{
|
||||
if (c == null) continue;
|
||||
float d = Vector3.Distance(c.transform.position, vault.transform.position);
|
||||
if (d < bestDist)
|
||||
{
|
||||
bestDist = d;
|
||||
thief = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (thief != null)
|
||||
GetStats(thief).cashStolen += amount;
|
||||
}
|
||||
|
||||
private void OnBagPickedUp(CashBag bag, CashCarrier carrier)
|
||||
{
|
||||
if (carrier != null && carrier.gameObject != null)
|
||||
GetStats(carrier.gameObject).cashCollected += bag != null ? bag.CashValue : 0f;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user