using System.Collections.Generic; using TMPro; using UnityEngine; /// /// Binds the match scoreboard section inside the pause/session panel (Online Session Manager) to the /// result rows replicates: LIVE while a match runs (roster from Hide start, /// re-sent on every hit / mid-match leave; winner still None -> pending hiders read "Hiding") and frozen /// with the winner banner when Seek ends (the browser UI auto-opens the panel then). The section root is /// hidden while no result data exists (Main / lobby). Rows are pooled clones of the disabled row /// template, rebuilt only when a new result version arrives (). /// [DisallowMultipleComponent] public class MatchResultScreen : MonoBehaviour { [Tooltip("Big winner banner (\"Hunters win!\" / \"Hiders win!\").")] [SerializeField] private TMP_Text _winnerText; [Tooltip("One-line summary under the banner (counts: survived / found / converted).")] [SerializeField] private TMP_Text _summaryText; [Tooltip("Parent the result rows are instantiated under (a static header row may live here too).")] [SerializeField] private RectTransform _rowContainer; [Tooltip("Disabled template row with TMP children named Name / Outcome / Time / FoundBy; cloned per result row.")] [SerializeField] private GameObject _rowTemplate; [Tooltip("Optional: root of the whole result section; hidden while no result data exists (in Main / " + "the lobby) so the pause panel shows no empty scoreboard scaffold.")] [SerializeField] private GameObject _content; private sealed class Row { public GameObject Root; public TMP_Text Name, Outcome, Time, FoundBy; } private readonly List _rows = new List(); private int _boundVersion = -1; private void Awake() { bool templateOk = _rowTemplate && FindText(_rowTemplate, "Name") && FindText(_rowTemplate, "Outcome") && FindText(_rowTemplate, "Time") && FindText(_rowTemplate, "FoundBy"); if (!_winnerText || !_summaryText || !_rowContainer || !templateOk) { Debug.LogError("[MatchResultScreen] Unwired reference(s) or row template missing a TMP child " + "(Name/Outcome/Time/FoundBy) - fix the wiring in the Inspector.", this); enabled = false; return; } } private void Update() { var match = MatchManager.Instance; bool hasResults = match && match.LocalResults.Count > 0; if (_content && _content.activeSelf != hasResults) _content.SetActive(hasResults); if (!hasResults) { _boundVersion = -1; return; } if (_boundVersion != match.ResultVersion) Bind(match); } private void Bind(MatchManager match) { _boundVersion = match.ResultVersion; // Winner == None means the rows are the LIVE mid-match scoreboard: un-hit hiders read "Hiding" // (their survival time is still open), everything else is already frozen at its hit moment. bool live = match.LocalWinner == MatchManager.MatchWinner.None; _winnerText.text = live ? "Match in progress" : match.LocalWinner == MatchManager.MatchWinner.Hunters ? "Hunters win!" : "Hiders win!"; IReadOnlyList results = match.LocalResults; int survived = 0, found = 0, converted = 0; for (int i = 0; i < results.Count; i++) { switch (results[i].Outcome) { case MatchManager.PlayerOutcome.Survived: survived++; break; case MatchManager.PlayerOutcome.Found: found++; break; case MatchManager.PlayerOutcome.Converted: converted++; break; } } _summaryText.text = BuildSummary(survived, found, converted, live); for (int i = 0; i < results.Count; i++) { Row row = i < _rows.Count ? _rows[i] : CreateRow(); MatchManager.PlayerResult r = results[i]; bool pending = live && r.Outcome == MatchManager.PlayerOutcome.Survived; row.Root.SetActive(true); row.Name.text = r.Name; row.Outcome.text = pending ? "Hiding" : OutcomeLabel(r.Outcome); row.Time.text = !pending && HasTime(r.Outcome) ? FormatTime(r.SurvivalTime) : "-"; row.FoundBy.text = string.IsNullOrEmpty(r.FoundBy) ? "-" : r.FoundBy; } for (int i = results.Count; i < _rows.Count; i++) _rows[i].Root.SetActive(false); } private Row CreateRow() { GameObject go = Instantiate(_rowTemplate, _rowContainer); var row = new Row { Root = go, Name = FindText(go, "Name"), Outcome = FindText(go, "Outcome"), Time = FindText(go, "Time"), FoundBy = FindText(go, "FoundBy") }; _rows.Add(row); return row; } private static TMP_Text FindText(GameObject root, string childName) { Transform t = root.transform.Find(childName); return t ? t.GetComponent() : null; } private static string BuildSummary(int survived, int found, int converted, bool live) { var sb = new System.Text.StringBuilder(64); AppendCount(sb, survived, live ? "hiding" : "survived"); AppendCount(sb, found, "found"); AppendCount(sb, converted, "converted"); return sb.Length > 0 ? sb.ToString() : "No hiders in this match"; } private static void AppendCount(System.Text.StringBuilder sb, int count, string label) { if (count == 0) return; if (sb.Length > 0) sb.Append(" • "); sb.Append(count).Append(' ').Append(label); } private static bool HasTime(MatchManager.PlayerOutcome outcome) => outcome == MatchManager.PlayerOutcome.Survived || outcome == MatchManager.PlayerOutcome.Found || outcome == MatchManager.PlayerOutcome.Converted; private static string FormatTime(float seconds) { int s = Mathf.Max(0, Mathf.RoundToInt(seconds)); return $"{s / 60:0}:{s % 60:00}"; } private static string OutcomeLabel(MatchManager.PlayerOutcome outcome) { switch (outcome) { case MatchManager.PlayerOutcome.Hunter: return "Hunter"; case MatchManager.PlayerOutcome.Survived: return "Survived"; case MatchManager.PlayerOutcome.Found: return "Found"; case MatchManager.PlayerOutcome.Converted: return "Converted"; case MatchManager.PlayerOutcome.Left: return "Left"; default: return ""; } } }