A Razor UI component for the lobby board. It shows an optional lobby message, a launch countdown when the lobby is launching, or a grid of players with avatar/bot badge, name, and win count, and computes a build hash for change detection.
@using Sandbox
@using Sandbox.UI
@using System.Linq
@inherits PanelComponent
<root>
<div class="board">
@if (LobbyManager.HasActiveLobbyMessage)
{
<div class="message-banner">@LobbyManager.LobbyMessage</div>
}
@if (LaunchSeconds > 0)
{
<div class="title">Starting in</div>
<div class="launch-number">@LaunchSeconds</div>
}
else
{
<div class="title">Players</div>
<div class="grid">
@foreach (PlayerReadyState player in Scene.GetAllComponents<PlayerReadyState>())
{
<div class="cell @(player.IsReady ? "ready" : "notready")">
@if (player.IsBot())
{
<div class="avatar bot-avatar" style="background-color: @(BotColor(player.DisplayName));">
@(BotInitial(player.DisplayName))
</div>
}
else
{
<img class="avatar" src="avatar:@(player.Network.Owner.SteamId)" />
}
<div class="name">@player.DisplayName</div>
<div class="wins" title="Wins">
<div class="trophy">🏆</div>
<div class="count">@player.WinCount</div>
</div>
</div>
}
</div>
}
</div>
</root>
@code {
private int LaunchSeconds
{
get
{
LobbyManager mgr = LobbyManager.Current;
if (mgr is null || !mgr.IsLaunching) return 0;
return System.Math.Max(1, (int)System.MathF.Ceiling(mgr.LaunchSecondsRemaining));
}
}
private static string BotInitial(string name) =>
string.IsNullOrEmpty(name) ? "?" : name.Substring(0, 1).ToUpper();
// Stable per-name HSL so each bot keeps the same badge color across ticks.
private static string BotColor(string seed)
{
int hue = System.Math.Abs(seed.GetHashCode()) % 360;
return $"hsl({hue}, 55%, 45%)";
}
protected override int BuildHash()
{
IEnumerable<string> parts = Scene.GetAllComponents<PlayerReadyState>()
.Select(p => $"{p.DisplayName}:{p.IsReady}:{p.WinCount}");
return System.HashCode.Combine(string.Join(",", parts), LaunchSeconds, LobbyManager.HasActiveLobbyMessage);
}
}