UI/Screen/MapChooser.razor
@using Sandbox;
@using Sandbox.Network;
@using Sandbox.UI;
@using System.Threading.Tasks;
@using System
@inherits PanelComponent
@namespace Sandbox
<root>
<div class="header">
<h1>Choose a Map</h1>
<div class="subtitle">Pick a map, set lobby options, then launch.</div>
</div>
<div class="content">
<div class="map-list">
<div class="section-title">Maps</div>
<div class="map-entries">
@foreach (var map in Maps)
{
var pkg = GetPackage( map );
<button class="@GetMapClass(map)" onclick="@(() => SelectMap(map))">
<div class="thumb" style="@GetThumbStyle(pkg)"></div>
<div class="text">
<div class="title">@GetPackageTitle(map, pkg)</div>
<div class="ident">@map</div>
</div>
</button>
}
</div>
</div>
<div class="settings">
<div class="section-title">Lobby Settings</div>
<div class="field">
<label>Lobby Name</label>
<TextEntry @ref="LobbyNameEntry" placeholder="Skater Crew"></TextEntry>
</div>
<div class="field split">
<div class="field">
<label>Min Players</label>
<TextEntry @ref="MinPlayersEntry" placeholder="1"></TextEntry>
</div>
<div class="field">
<label>Max Players</label>
<TextEntry @ref="MaxPlayersEntry" placeholder="8"></TextEntry>
</div>
</div>
<div class="field">
<label>Privacy</label>
<div class="privacy">
<button class="@GetPrivacyClass( LobbyPrivacy.Public )" onclick="@(() => SetPrivacy( LobbyPrivacy.Public ))">Public</button>
<button class="@GetPrivacyClass( LobbyPrivacy.FriendsOnly )" onclick="@(() => SetPrivacy( LobbyPrivacy.FriendsOnly ))">Friends</button>
<button class="@GetPrivacyClass( LobbyPrivacy.Private )" onclick="@(() => SetPrivacy( LobbyPrivacy.Private ))">Private</button>
</div>
</div>
<div class="selected-map">
<div class="label">Selected Map</div>
<div class="value">@(_selectedMapIdent ?? "None")</div>
</div>
<button class="launch @GetLaunchClass()" onclick="@StartGame">Launch</button>
</div>
</div>
</root>
@code
{
[Property, TextArea] public string MyStringValue { get; set; } = "Hello World!";
/// <summary>
/// the hash determines if the system should be rebuilt. If it changes, it will be rebuilt
/// </summary>
protected override int BuildHash() => System.HashCode.Combine(
MyStringValue,
_selectedMapIdent,
_privacy,
Maps?.Count ?? 0
);
[Property, MapAssetPath] public List<string> Maps { get; set; } = new();
private string _selectedMapIdent;
private LobbyPrivacy _privacy = LobbyPrivacy.Public;
private TextEntry LobbyNameEntry;
private TextEntry MinPlayersEntry;
private TextEntry MaxPlayersEntry;
private readonly Dictionary<string, Package> _packageCache = new( StringComparer.OrdinalIgnoreCase );
private readonly HashSet<string> _pendingFetch = new( StringComparer.OrdinalIgnoreCase );
private bool _fetching;
private int _lastMapCount;
protected override void OnStart()
{
QueueFetch();
}
protected override void OnUpdate()
{
var count = Maps?.Count ?? 0;
if ( count != _lastMapCount )
{
_lastMapCount = count;
QueueFetch();
}
}
private void SelectMap( string map )
{
_selectedMapIdent = map;
StateHasChanged();
}
private void SetPrivacy( LobbyPrivacy privacy )
{
_privacy = privacy;
StateHasChanged();
}
private string GetPrivacyClass( LobbyPrivacy privacy )
{
return _privacy == privacy ? "active" : string.Empty;
}
private string GetLaunchClass()
{
return string.IsNullOrWhiteSpace( _selectedMapIdent ) ? "disabled" : string.Empty;
}
private void StartGame()
{
if ( string.IsNullOrWhiteSpace( _selectedMapIdent ) )
return;
var lobbyName = LobbyNameEntry?.Text?.Trim();
var minPlayers = ParsePositiveInt( MinPlayersEntry?.Text, 1 );
var maxPlayers = ParsePositiveInt( MaxPlayersEntry?.Text, 8 );
if ( maxPlayers < minPlayers )
maxPlayers = minPlayers;
LaunchArguments.Map = _selectedMapIdent;
LaunchArguments.MaxPlayers = maxPlayers;
LaunchArguments.Privacy = _privacy;
LaunchArguments.ServerName = string.IsNullOrWhiteSpace( lobbyName ) ? $"{Utility.Steam.PersonaName}'s Skate Server" : lobbyName;
var options = new SceneLoadOptions();
options.SetScene( "scenes/system.scene" );
Game.ChangeScene( options );
}
private static int ParsePositiveInt( string value, int fallback )
{
if ( int.TryParse( value, out var parsed ) && parsed > 0 )
return parsed;
return fallback;
}
private Package GetPackage( string map )
{
if ( string.IsNullOrWhiteSpace( map ) )
return null;
_packageCache.TryGetValue( map, out var pkg );
return pkg;
}
private string GetPackageTitle( string map, Package package )
{
if ( package is not null && !string.IsNullOrWhiteSpace( package.Title ) )
return package.Title;
return map;
}
private string GetThumbStyle( Package package )
{
var url = package?.Thumb;
if ( string.IsNullOrWhiteSpace( url ) )
return string.Empty;
return $"background-image: url('{url}')";
}
private void QueueFetch()
{
if ( Maps is null )
return;
foreach ( var map in Maps )
{
if ( string.IsNullOrWhiteSpace( map ) )
continue;
if ( _packageCache.ContainsKey( map ) || _pendingFetch.Contains( map ) )
continue;
_pendingFetch.Add( map );
}
if ( _pendingFetch.Count == 0 )
return;
if ( _fetching )
return;
_ = FetchPackagesAsync();
}
private async Task FetchPackagesAsync()
{
if ( _fetching )
return;
_fetching = true;
try
{
foreach ( var map in _pendingFetch.ToArray() )
{
var pkg = await Package.FetchAsync( map, partial: true );
if ( pkg is not null )
_packageCache[map] = pkg;
_pendingFetch.Remove( map );
StateHasChanged();
}
}
finally
{
_fetching = false;
}
}
private string GetMapClass( string map )
{
return map == _selectedMapIdent ? "active" : string.Empty;
}
}