A Blazor component that renders the in-game lobby panel UI. It displays connected players, their readiness status, and includes buttons for the player to toggle ready status or the host to start the match.
@namespace CitySim
@using CitySim.DesignSystem
@using CitySim.Match
@using CitySim.Punchboard
@inherits MatchPanel
<root class="center-stage lobby-stage">
<div class="pb-lobby-layout"><PlotterSurface Title=@(GameText.Get("ui.lobby_panel.city_lobby")) Kind=@(PbPanelKind.Raised)><ChildContent><div class="pb-lobby-content">
<div class="pb-screen-title">@(GameText.Get("ui.lobby_panel.build_a_city_together"))</div>
@if (City.Session?.Lobby is { } room) {
<div>@(GameText.Get("ui.lobby_panel.player_count", room.Members.Count(m=>m.Connected && !m.Spectator), room.Capacity))</div>
<div class="pb-lobby-list">@foreach (var p in room.Members.Where(m=>m.Connected)) {<div class="lobby-member pb-lobby-row"><PbMeeple /><div class="pb-lobby-name">@(GameText.Plain($"{(p.Id==room.Host ? "♛ " : "")}{p.Name}{(p.Id==CityGame.Me ? GameText.Get("ui.lobby_panel.you") : "")}"))</div><div>@(p.Spectator ? GameText.Get("ui.lobby_panel.watching") : p.Ready ? GameText.Get("ui.lobby_panel.ready") : GameText.Get("ui.lobby_panel.not_ready"))</div></div> }</div>
@if (City.Session.IsHost) { <div>@(GameText.Get("ui.lobby_panel.friends_can_join_with_host_steam_id", City.Session.InviteAddress))</div> }
<div class="pb-lobby-actions">@if (room.Members.FirstOrDefault(m=>m.Id==CityGame.Me) is {Spectator:false} own) {<PlotterButton CssClass="primary" Text=@(own.Ready ? GameText.Get("ui.lobby_panel.not_ready_2") : GameText.Get("ui.lobby_panel.ready_2")) [email protected] Clicked=@(() => { City.Session.ToggleReady(); }) /> } @if (City.Session.IsHost) { <PlotterButton CssClass="start-match" Text=@(GameText.Get("ui.lobby_panel.start_match")) Disabled=@(!City.Session.CanStart) Clicked=@(() => { City.Session.StartMatch(); }) /> } <PlotterButton Text=@(GameText.Get("ui.lobby_panel.leave_lobby")) Kind=@(PbButtonKind.Ghost) Clicked=@(() => { City.RequestMenu(); }) /></div>
@if (room.Members.Count(m=>m.Connected && !m.Spectator)<2) { <div>@(GameText.Get("ui.lobby_panel.waiting_for_at_least_one_more_player"))</div>} else if (!City.Session.CanStart && City.Session.IsHost) {<div>@(GameText.Get("ui.lobby_panel.waiting_for_players"))</div> }
} else { <div>@(GameText.Get("ui.lobby_panel.connecting"))</div><PlotterButton Text=@(GameText.Get("ui.contract_pick_panel.back_to_menu")) Kind=@(PbButtonKind.Ghost) Clicked=@(() => { City.RequestMenu(); }) /> }
</div></ChildContent></PlotterSurface></div>
</root>
@code {
readonly Dictionary<Guid,bool> ready=new();
readonly Dictionary<Guid,float> pops=new();
void Changed()
{
var changed=false;
foreach (var m in City.Session.Lobby?.Members ?? Array.Empty<LobbyMember>())
{
if (ready.TryGetValue(m.Id,out var previous) && previous!=m.Ready) { pops[m.Id]=0; changed=true; }
ready[m.Id]=m.Ready;
}
if (changed) Sfx.Play("sounds/feedback/chime.sound",City.Player.WorldPosition);
Refresh();
}
protected override void Subscribe() { if (City!=null) City.UiChanged+=Changed; Changed(); }
protected override void Unsubscribe() { if (City!=null) City.UiChanged-=Changed; }
public override void Tick()
{
base.Tick(); var members=City.Session.Lobby?.Members.Where(m=>m.Connected).ToArray() ?? Array.Empty<LobbyMember>();
var rows=Descendants.Where(p=>p.HasClass("lobby-member")).ToArray();
for (var i=0;i<members.Length && i<rows.Length;i++) if (pops.TryGetValue(members[i].Id,out var age))
{
age+=Time.Delta; var t=Math.Clamp(age/0.18f,0,1);
rows[i].Style.Set("transform",FormattableString.Invariant($"scale({1+0.025f*MathF.Sin(t*MathF.PI):0.###})"));
if (t>=1) pops.Remove(members[i].Id); else pops[members[i].Id]=age;
}
}
}