VanguardReMaster/client/Assets/Vanguard/Runtime/UI/ReplayView.cs

275 lines
10 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using Vanguard.Api;
using Vanguard.Api.Contracts;
using Vanguard.Meta;
using Vanguard.Shared.Protocol;
namespace Vanguard.UI
{
/// <summary>Просмотр реплея: события и кадры состояния, собранные сервером.</summary>
public sealed class ReplayView
{
private readonly MetaService _meta;
private readonly Action _onClose;
private readonly RectTransform _root;
private readonly Text _title;
private readonly Text _result;
private readonly Text _frameLabel;
private readonly Text _summary;
private readonly Text _status;
private readonly RectTransform _events;
private ReplayResponseDto _replay;
private int _frameIndex;
private bool _loading;
public ReplayView(Transform parent, MetaService meta, Action onClose)
{
_meta = meta;
_onClose = onClose;
_root = UiKit.CreatePanel(parent, "ReplayScreen", new Color(0.015f, 0.025f, 0.05f, 0.97f));
var top = UiKit.CreatePanel(_root, "TopBar", new Color(0.06f, 0.08f, 0.13f, 1f));
top.anchorMin = new Vector2(0f, 1f);
top.anchorMax = new Vector2(1f, 1f);
top.pivot = new Vector2(0.5f, 1f);
top.anchoredPosition = Vector2.zero;
top.sizeDelta = new Vector2(0f, 88f);
UiKit.AddHorizontalLayout(top, 22, 16);
var back = UiKit.CreateButton(top, "Back", "← К истории", () => _onClose(), 18);
UiKit.SetPreferredHeight(back, 44f, 0f);
_title = UiKit.CreateText(top, "Title", "Реплей", 30, Color.white, TextAnchor.MiddleLeft);
_title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(_title, 46f);
_result = UiKit.CreateText(top, "Result", string.Empty, 20,
new Color(0.85f, 0.90f, 1f), TextAnchor.MiddleRight);
UiKit.SetPreferredHeight(_result, 36f);
var body = UiKit.CreatePanel(_root, "Body", new Color(0f, 0f, 0f, 0f));
body.anchorMin = Vector2.zero;
body.anchorMax = Vector2.one;
body.offsetMin = new Vector2(40f, 28f);
body.offsetMax = new Vector2(-40f, -110f);
UiKit.AddVerticalLayout(body, 8, 10);
var summaryPanel = UiKit.CreatePanel(body, "SummaryPanel", new Color(0.06f, 0.08f, 0.13f, 1f));
UiKit.SetPreferredHeight(summaryPanel, 250f);
UiKit.AddVerticalLayout(summaryPanel, 18, 8);
_frameLabel = UiKit.CreateText(
summaryPanel, "Frame", "Кадр", 19, Color.white, TextAnchor.MiddleLeft);
_frameLabel.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(_frameLabel, 28f);
_summary = UiKit.CreateText(
summaryPanel, "Summary", string.Empty, 16,
new Color(0.72f, 0.78f, 0.92f), TextAnchor.UpperLeft);
_summary.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(_summary, 170f);
var controls = UiKit.CreatePanel(body, "Controls", new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(controls, 52f);
UiKit.AddHorizontalLayout(controls, 0, 12);
var prev = UiKit.CreateButton(controls, "Prev", "◀ Кадр", OnPrev, 18);
UiKit.SetPreferredHeight(prev, 46f);
var next = UiKit.CreateButton(controls, "Next", "Кадр ▶", OnNext, 18);
UiKit.SetPreferredHeight(next, 46f);
var eventsTitle = UiKit.CreateText(
body, "EventsTitle", "События", 22, Color.white, TextAnchor.MiddleLeft);
eventsTitle.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(eventsTitle, 30f);
var scroll = UiKit.CreateScrollView(body, "EventsScroll", out _events, 4f);
var scrollElement = scroll.gameObject.AddComponent<LayoutElement>();
scrollElement.flexibleHeight = 1f;
scrollElement.minHeight = 160f;
_status = UiKit.CreateText(
body, "Status", string.Empty, 17,
new Color(0.62f, 0.68f, 0.82f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(_status, 30f);
_root.gameObject.SetActive(false);
}
public void Show(string matchId)
{
UiKit.SetActive(_root, true);
_replay = null;
_frameIndex = 0;
SetStatus("Загружаем реплей…", new Color(0.62f, 0.68f, 0.82f));
_ = LoadAsync(matchId);
}
public void Hide() => UiKit.SetActive(_root, false);
private async Task LoadAsync(string matchId)
{
if (_loading)
return;
_loading = true;
try
{
_replay = await _meta.GetReplayAsync(matchId);
if (_root == null || !_root.gameObject.activeSelf)
return;
_title.text = "Реплей · " + _replay.MatchId;
_result.text = _replay.Winner == _replay.ViewerSeat
? "Победа"
: _replay.Winner < 0 ? "Ничья" : "Поражение";
_result.color = _replay.Winner == _replay.ViewerSeat
? new Color(0.45f, 0.85f, 0.55f)
: new Color(1f, 0.58f, 0.45f);
_frameIndex = Math.Max(0, (_replay.Frames?.Count ?? 0) - 1);
RebuildEvents();
UpdateFrame();
SetStatus(
"Кадров: " + (_replay.Frames?.Count ?? 0) + " · событий: "
+ (_replay.Events?.Count ?? 0),
new Color(0.62f, 0.68f, 0.82f));
}
catch (ApiException exception)
{
SetStatus(exception.Message, new Color(1f, 0.55f, 0.45f));
}
catch (Exception exception)
{
Debug.LogException(exception);
SetStatus("Не удалось загрузить реплей.", new Color(1f, 0.55f, 0.45f));
}
finally
{
_loading = false;
}
}
private void RebuildEvents()
{
ClearChildren(_events);
if (_replay?.Events == null || _replay.Events.Count == 0)
{
var empty = UiKit.CreateText(
_events, "Empty", "Событий нет.", 16,
new Color(0.62f, 0.68f, 0.82f));
UiKit.SetPreferredHeight(empty, 60f);
return;
}
foreach (var item in _replay.Events)
{
var line = UiKit.CreateText(
_events,
"Event",
"#" + item.Seq + " " + item.Kind,
15,
new Color(0.72f, 0.78f, 0.92f),
TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(line, 22f);
}
}
private void OnPrev()
{
if (_replay?.Frames == null || _replay.Frames.Count == 0)
return;
_frameIndex = Math.Max(0, _frameIndex - 1);
UpdateFrame();
}
private void OnNext()
{
if (_replay?.Frames == null || _replay.Frames.Count == 0)
return;
_frameIndex = Math.Min(_replay.Frames.Count - 1, _frameIndex + 1);
UpdateFrame();
}
private void UpdateFrame()
{
if (_replay?.Frames == null || _replay.Frames.Count == 0)
{
_frameLabel.text = "Кадров нет";
_summary.text = string.Empty;
return;
}
var frame = _replay.Frames[Mathf.Clamp(_frameIndex, 0, _replay.Frames.Count - 1)];
_frameLabel.text = "Кадр " + (_frameIndex + 1) + " / " + _replay.Frames.Count
+ " · eventSeq " + frame.EventSeq;
var view = frame.View;
if (view == null)
{
_summary.text = "Состояние кадра недоступно.";
return;
}
var text = "Фаза: " + view.Phase + " · шаг: " + view.Step
+ " · ход " + view.TurnNumber
+ " · решает: " + (view.ActivePlayer == view.You ? "вы" : "противник")
+ "\n";
var opponent = view.You == 0 ? 1 : 0;
foreach (var index in new[] { view.You, opponent })
{
if (view.Players == null || index >= view.Players.Count)
continue;
var player = view.Players[index];
text += (index == view.You ? "Вы: " : "Противник: ") + player.Name
+ " · рука " + player.HandCount
+ " · колода " + player.DeckCount
+ " · урон " + (player.FaceDownDamage + player.FaceUpDamage.Count)
+ " · Vanguard " + (player.Vanguard?.PrintingId ?? "—")
+ "\n RG: " + RearGuards(player) + "\n";
}
_summary.text = text;
}
private static string RearGuards(PlayerView player)
{
if (player.RearGuards == null)
return "—";
var parts = new System.Collections.Generic.List<string>();
for (var i = 0; i < player.RearGuards.Length; i++)
{
var card = player.RearGuards[i];
parts.Add("R" + (i + 1) + ":" + (card?.PrintingId ?? "—"));
}
return string.Join(" · ", parts);
}
private void SetStatus(string text, Color color)
{
if (_status == null)
return;
_status.text = text ?? string.Empty;
_status.color = color;
}
private static void ClearChildren(Transform parent)
{
for (var i = parent.childCount - 1; i >= 0; i--)
UnityEngine.Object.Destroy(parent.GetChild(i).gameObject);
}
}
}