UI/LeaderboardPanel.razor
@using Sandbox.UI
@using System
@using System.Linq
@using System.Threading.Tasks
@namespace Prototype
@inherits Panel
<root>
<div class="lb-heading"><div><label style="font-size:38px; font-weight:700; color:#f1f6fa; letter-spacing:0px;">Leaderboard</label><label class="lb-muted">Best completed runs</label></div><button disabled="@_busy" onclick="@Refresh"><label style="font-size:18px; color:#ffffff; letter-spacing:0px;">@(_busy ? "Loading..." : "Refresh")</label></button></div>
<div class="lb-toolbar"><div class="lb-tabs">@foreach(var mode in Modes){var item=mode;<button class="@(_mode==item ? "active" : "")" onclick="@(() => Select(item))"><label style="font-size:20px; font-weight:500; color:#eef6fa; letter-spacing:0px;">@item</label></button>}</div><label class="lb-muted">@GameProfile.BoardSource(_mode)</label></div>
<div class="lb-columns lb-header"><label class="lb-rank">#</label><label class="lb-team">@(_mode=="Solo" ? "PLAYER" : "TEAM")</label><label class="lb-score">SCORE</label><label class="lb-stat">KILLS</label><label class="lb-stat">LEVELS</label><label class="lb-time">TIME</label></div>
<div class="lb-list">
@{var index=0;}
@foreach(var run in GameProfile.Category(_mode).Take(30))
{
var place=run.GlobalRank>0 ? run.GlobalRank : ++index;
<div class="lb-columns lb-entry @(Own(run) ? "own" : "")">
<label class="lb-rank" style="font-size:23px; color:@(place==1 ? "#edce79" : place==2 ? "#cedce6" : place==3 ? "#d4a27a" : "#b3c5d1"); letter-spacing:0px;">@place</label>
<div class="lb-team lb-names">
@foreach(var name in run.PlayerName.Split(" + ", StringSplitOptions.RemoveEmptyEntries))
{<label style="font-size:20px; font-weight:500; color:#edf5fa; letter-spacing:0px;">@name</label>}
@if(Own(run)){<label class="lb-you">YOUR RUN</label>}
</div>
<label class="lb-score" style="font-size:26px; font-weight:700; color:#ecd080; letter-spacing:0px;">@run.Score.ToString("N0")</label>
<label class="lb-stat">@run.Kills</label><label class="lb-stat">@run.Levels</label><label class="lb-time">@Clock(run.Seconds)</label>
</div>
}
@if(!GameProfile.Category(_mode).Any()){<label class="lb-empty">@(_busy ? "Loading results..." : "No completed runs in this mode yet.")</label>}
</div>
<div class="lb-best"><div class="lb-best-title"><label class="lb-you">YOUR BEST RUN</label><label class="lb-muted">@_mode</label></div>
@if(Best is { } best){@if(BestRank > 0){<label style="font-size:23px; color:#70e2dc; letter-spacing:0px;">#@BestRank</label>}<label class="lb-best-name" style="font-size:19px; color:#edf5fa; letter-spacing:0px;">@best.PlayerName</label><label style="font-size:26px; font-weight:700; color:#ecd080; letter-spacing:0px;">@best.Score.ToString("N0")</label><label class="lb-muted">points</label>}
else {<label class="lb-muted">Complete a run to set your first record.</label>}
</div>
<label class="lb-note">Ranked by score. Team names are shown when saved with the result.</label>
</root>
@code {
private static readonly string[] Modes={"Solo","Duo","Trio","Squad"};
private string _mode="Solo";
private bool _busy;
private int _revision;
private RunRecord Best => GameProfile.Runs.Where(x=>x.Category==_mode).OrderByDescending(x=>x.Score).ThenByDescending(x=>x.Levels).ThenByDescending(x=>x.Kills).ThenBy(x=>x.Seconds).FirstOrDefault();
private long BestRank => Best is { } best ? GameProfile.Category(_mode).FirstOrDefault(x => x.GlobalRank > 0 && !string.IsNullOrEmpty(best.RunId) && x.RunId == best.RunId)?.GlobalRank ?? 0 : 0;
private bool Own(RunRecord r) => r.OwnerSteamId == (long)Sandbox.Game.SteamId || (!string.IsNullOrEmpty(r.RunId) && GameProfile.Runs.Any(x=>x.RunId==r.RunId)) || r.OwnerSteamId==0;
private static string Clock(float seconds) {var t=TimeSpan.FromSeconds(Math.Max(0,seconds));return $"{(int)t.TotalMinutes:00}:{t.Seconds:00}";}
protected override void OnAfterTreeRender(bool firstTime) { if(firstTime) _=Refresh(); }
private void Select(string mode) { _mode=mode; _revision++; StateHasChanged(); if(!_busy) _=Refresh(); }
private async Task Refresh() { if(_busy)return; _busy=true; StateHasChanged(); var mode=_mode; await GameProfile.RefreshGlobal(mode); _busy=false; _revision++; StateHasChanged(); if(mode!=_mode) await Refresh(); }
protected override int BuildHash()=>HashCode.Combine(_mode,_busy,_revision);
}