UI Razor panel that displays a leaderboard. It loads a Leaderboards.Board2 for a given difficulty, refreshes entries asynchronously, and renders rank, avatar, name and formatted time, with special handling for survival vs default filtering and loading/error states.
@using Sandbox;
@using Sandbox.UI;
@namespace SS1
@inherits Panel
@attribute [StyleSheet]
<root>
<label class="header">@LabelText</label>
@if (_isLoading)
{
<label class="status">Loading leaderboard…</label>
return;
}
@if (_loadError)
{
<label class="status">Couldn’t load leaderboard.</label>
return;
}
@if (Leaderboard is null || !Leaderboard.Entries.Any(entry => IsSurvival || entry.Value > (10 * 60 - 115)))
{
<label class="status">No scores available.</label>
return;
}
<div class="entries">
@{
var offset = 0;
}
@foreach (var entry in Leaderboard.Entries)
{
@if(!IsSurvival && entry.Value <= (10 * 60 - 115))
{
offset++;
continue;
}
<div class="entry" style="color:@(Color.Lerp(Color.White, new Color(0.3f, 0.3f, 0.3f), Utils.Map(entry.Rank - offset, 0, 40, 0f, 1f, EasingType.QuadOut)).Rgba);">
<label class="rank">##@(entry.Rank - offset)</label>
<div class="avatar" style="background-image: url( avatar:@entry.SteamId )"></div>
<label class="name">@entry.DisplayName</label>
<label class="value">@FormatTime(entry.Value)</label>
</div>
}
</div>
</root>
@code
{
public int Difficulty { get; set; }
private int _lastDifficulty;
public string LabelText { get; set; }
public bool IsSurvival { get; set; }
Sandbox.Services.Leaderboards.Board2 Leaderboard;
bool _isLoading = true;
bool _loadError;
protected override void OnAfterTreeRender(bool firstTime)
{
base.OnAfterTreeRender(firstTime);
if(firstTime || Difficulty != _lastDifficulty)
{
// if(IsSurvival)
// {
// Leaderboard = Sandbox.Services.Leaderboards.GetFromStat(Manager.GetStatName(Difficulty, survival: true));
// Leaderboard.SetAggregationMax();
// Leaderboard.SetSortDescending();
// }
// else
// {
Leaderboard = Sandbox.Services.Leaderboards.GetFromStat(Manager.GetStatName(Difficulty));
Leaderboard.SetAggregationMin();
Leaderboard.SetSortAscending();
// }
// Leaderboard = Sandbox.Services.Leaderboards.Get(LeaderboardName);
_ = Refresh(Leaderboard);
_lastDifficulty = Difficulty;
// _lastLeaderboardName = LeaderboardName;
}
}
async System.Threading.Tasks.Task Refresh(Sandbox.Services.Leaderboards.Board2 board)
{
_isLoading = true;
_loadError = false;
StateHasChanged();
try
{
board.MaxEntries = 150;
await board.Refresh();
}
catch (Exception exception)
{
Log.Warning($"Leaderboard request failed for {board.Stat}: {exception.Message}");
if (ReferenceEquals(board, Leaderboard))
_loadError = true;
}
finally
{
if (ReferenceEquals(board, Leaderboard))
{
_isLoading = false;
StateHasChanged();
}
}
}
// protected override int BuildHash()
// {
// return HashCode.Combine(
// LeaderboardName
// );
// }
public static string FormatTime(double seconds)
{
// Convert the seconds to minutes and remaining seconds
int minutes = (int)seconds / 60;
int remainingSeconds = (int)seconds % 60;
// Format the output as "MM:SS" with leading zeros if needed
return $"{minutes}:{remainingSeconds:D2}";
}
}