using System; using System.Collections.Generic; using System.Globalization; using AcDream.Core.Net; namespace AcDream.App.UI.Layout; /// /// Retained port of retail gmLinkStatusUI. LayoutDesc owns the page and /// chrome; this controller owns its five-second text refresh and 120-second /// ping cadence. /// public sealed class LinkStatusUiController : IRetainedPanelController { public const uint LayoutId = 0x2100001Du; public const uint RootId = 0x10000167u; public const uint MainTextId = 0x10000169u; public const uint CloseId = 0x100000FCu; private const double UpdateIntervalSeconds = 5d; private const double PingIntervalSeconds = 120d; private readonly UiText _mainText; private readonly UiButton? _close; private readonly Func _snapshot; private readonly Func _currentTime; private readonly Action _requestPing; private readonly LinkStatusStrings _strings; private IReadOnlyList _lines = Array.Empty(); private double _nextUpdateTime = -1d; private double _lastPingRequestTime = -1d; private bool _pleaseRequestPing; private bool _visible; private LinkStatusUiController( ImportedLayout layout, Func snapshot, Func currentTime, Action requestPing, LinkStatusStrings strings, Action? close) { _mainText = (UiText)layout.FindElement(MainTextId)!; _close = layout.FindElement(CloseId) as UiButton; _snapshot = snapshot; _currentTime = currentTime; _requestPing = requestPing; _strings = strings; _mainText.LinesProvider = () => _lines; if (_close is not null) _close.OnClick = close; } public static LinkStatusUiController? Bind( ImportedLayout layout, Func snapshot, Func currentTime, Action requestPing, LinkStatusStrings strings, Action? close = null) { ArgumentNullException.ThrowIfNull(layout); ArgumentNullException.ThrowIfNull(snapshot); ArgumentNullException.ThrowIfNull(currentTime); ArgumentNullException.ThrowIfNull(requestPing); ArgumentNullException.ThrowIfNull(strings); return layout.FindElement(MainTextId) is UiText ? new LinkStatusUiController( layout, snapshot, currentTime, requestPing, strings, close) : null; } /// /// Port of gmLinkStatusUI::ListenToGlobalMessage @ 0x004AB110. /// public void Tick() { if (!_visible) return; double now = _currentTime(); if (!double.IsFinite(now) || now < _nextUpdateTime) return; _nextUpdateTime = now + UpdateIntervalSeconds; Update(now); } public void OnShown() { _visible = true; _pleaseRequestPing = true; Update(_currentTime()); } public void OnHidden() { _visible = false; _pleaseRequestPing = false; } private void Update(double now) { LinkStatusSnapshot value = _snapshot(); string ping = value.RoundTripSeconds is double seconds && double.IsFinite(seconds) && seconds >= 0d ? (seconds * 1000d).ToString("F0", CultureInfo.InvariantCulture) : "????"; string body = _strings.Description + _strings.Legend + _strings.DisconnectWarning + _strings.PacketLossPrefix + value.PacketLossPercentage.ToString("F2", CultureInfo.InvariantCulture) + _strings.PingPrefix + ping; _lines = IndicatorDetailText.Shape(_mainText, body); if (!double.IsFinite(now)) return; bool periodicPing = _lastPingRequestTime >= 0d && now - _lastPingRequestTime >= PingIntervalSeconds; if (!_pleaseRequestPing && !periodicPing) return; _lastPingRequestTime = now; _pleaseRequestPing = false; _requestPing(); } public void Dispose() { if (_close is not null) _close.OnClick = null; } } public sealed record LinkStatusStrings( string Description, string Legend, string DisconnectWarning, string PacketLossPrefix, string PingPrefix) { public static LinkStatusStrings English { get; } = new( "The Link Indicator shows the current status of your connection to the game servers.", "\n\nGREEN = your link is good.\nYELLOW = no packets for at least 5 sec.\nRED = no packets for at least 20 sec.", "\n\nIf approximately forty seconds pass without receiving a packet, you will be disconnected from the server.", "\n\n\nPacket loss for the last 10 sec: ", "\n\nRoundtrip Ping time to Server: "); }