{"TotalCount":22,"Files":[{"Ident":"rwtrcsc.lobbysystem","Path":"Code/LobbyNetworkManager.cs","FileName":"LobbyNetworkManager.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"namespace LobbySystem;\n\n/// \u003Csummary\u003E\n/// Auto-hosts a lobby so Steam friends can join, and keeps one networked pawn per connection plus optional\n/// bots by cloning \u003Csee cref=\u0022PlayerPrefab\u0022/\u003E. The pawn only has to implement \u003Csee cref=\u0022ILobbyAgent\u0022/\u003E.\n/// Spawning is de-duped and runs in OnUpdate so a join can\u0027t fire mid-enumeration.\n/// \u003C/summary\u003E\npublic sealed class LobbyNetworkManager : Component, Component.INetworkListener\n{\n\t[Property] public GameObject PlayerPrefab { get; set; }\n\t[Property] public int BotCount { get; set; } = 1;\n\n\t/// \u003Csummary\u003EWhen true, bots only exist during an active round.\u003C/summary\u003E\n\t[Property] public bool BotsOnlyDuringRound { get; set; } = true;\n\n\t[Property] public Color BotTint { get; set; } = new Color( 1f, 0.35f, 0.3f );\n\n\t// Lobby spawn ring, used before a round map loads.\n\treadonly Vector3[] _spawns =\n\t{\n\t\tnew Vector3( 0f, -300f, 40f ), new Vector3( 300f, 0f, 40f ),\n\t\tnew Vector3( 0f, 300f, 40f ),  new Vector3( -300f, 0f, 40f ),\n\t\tnew Vector3( 250f, 250f, 40f ), new Vector3( -250f, -250f, 40f ),\n\t};\n\tint _spawnIndex;\n\treadonly Dictionary\u003CGuid, GameObject\u003E _pawns = new();\n\treadonly List\u003CGameObject\u003E _bots = new();\n\n\tbool _reconcileNow;\n\tTimeUntil _nextReconcile;\n\tTimeUntil _nextSweep;\n\n\tprotected override async Task OnLoad()\n\t{\n\t\t// When joining a friend the engine is mid-connect and IsActive is briefly false, so poll for a\n\t\t// moment before hosting. Otherwise a joiner would spin up its own solo lobby.\n\t\tif ( Networking.IsActive ) return;\n\t\tfor ( int i = 0; i \u003C 6 \u0026\u0026 !Networking.IsActive; i\u002B\u002B )\n\t\t\tawait Task.DelayRealtimeSeconds( 0.1f );\n\t\tif ( !Networking.IsActive )\n\t\t\tNetworking.CreateLobby( new() );\n\t}\n\n\tvoid INetworkListener.OnActive( Connection channel ) =\u003E _reconcileNow = true;\n\n\tprotected override void OnUpdate()\n\t{\n\t\tif ( !Networking.IsHost || PlayerPrefab is null ) return;\n\n\t\tif ( !_reconcileNow \u0026\u0026 _nextReconcile \u003E 0f ) return;\n\t\t_reconcileNow = false;\n\t\t_nextReconcile = 0.25f;\n\n\t\ttry\n\t\t{\n\t\t\tbool wantBots = !BotsOnlyDuringRound || (LobbyDirector.Current?.State == LobbyState.Active);\n\t\t\tReconcileBots( wantBots ? Math.Max( 0, BotCount ) : 0 );\n\n\t\t\tforeach ( var conn in Connection.All.ToList() )\n\t\t\t{\n\t\t\t\tif ( conn is null || !conn.IsActive ) continue;\n\t\t\t\tif ( _pawns.TryGetValue( conn.Id, out var pawn ) \u0026\u0026 pawn.IsValid() ) continue;\n\t\t\t\tvar id = conn.Id;\n\t\t\t\t_pawns[id] = FindConnectionPawn( id ) ?? SpawnPawn( false, conn.DisplayName, conn );\n\t\t\t}\n\n\t\t\tSweep();\n\t\t}\n\t\tcatch\n\t\t{\n\t\t\t// Connection or scene list changed during the pass; retry next frame.\n\t\t}\n\t}\n\n\tvoid ReconcileBots( int target )\n\t{\n\t\t_bots.RemoveAll( b =\u003E !b.IsValid() );\n\t\twhile ( _bots.Count \u003E target )\n\t\t{\n\t\t\tvar b = _bots[_bots.Count - 1];\n\t\t\t_bots.RemoveAt( _bots.Count - 1 );\n\t\t\tif ( b.IsValid() ) b.Destroy();\n\t\t}\n\t\twhile ( _bots.Count \u003C target )\n\t\t\t_bots.Add( SpawnPawn( true, \u0022Bot\u0022, null ) );\n\t}\n\n\tGameObject FindConnectionPawn( Guid id )\n\t{\n\t\tforeach ( var a in Scene.GetAllComponents\u003CILobbyAgent\u003E() )\n\t\t{\n\t\t\tif ( !a.IsValid() || a.IsBot ) continue;\n\t\t\tif ( a is Component c \u0026\u0026 c.Network.OwnerId == id ) return c.GameObject;\n\t\t}\n\t\treturn null;\n\t}\n\n\tvoid Sweep()\n\t{\n\t\tif ( _nextSweep \u003E 0f ) return;\n\t\t_nextSweep = 1f;\n\t\tforeach ( var key in _pawns.Where( kv =\u003E !kv.Value.IsValid() ).Select( kv =\u003E kv.Key ).ToList() )\n\t\t\t_pawns.Remove( key );\n\t}\n\n\tGameObject SpawnPawn( bool isBot, string displayName, Connection owner )\n\t{\n\t\tvar go = PlayerPrefab.Clone( NextSpawn() );\n\t\tgo.Name = isBot ? \u0022Bot\u0022 : $\u0022Player - {displayName}\u0022;\n\t\tgo.Enabled = true;\n\n\t\tvar agent = go.Components.Get\u003CILobbyAgent\u003E() ?? go.Components.GetInChildren\u003CILobbyAgent\u003E();\n\t\tagent?.InitAgent( isBot, displayName );\n\n\t\tif ( isBot )\n\t\t{\n\t\t\tvar rend = go.Components.GetInChildren\u003CSkinnedModelRenderer\u003E();\n\t\t\tif ( rend is not null ) rend.Tint = BotTint;\n\t\t}\n\n\t\tif ( owner is not null ) go.NetworkSpawn( owner );\n\t\telse go.NetworkSpawn();\n\t\treturn go;\n\t}\n\n\tVector3 NextSpawn()\n\t{\n\t\tint idx = _spawnIndex\u002B\u002B;\n\t\tvar dir = LobbyDirector.Current;\n\t\tif ( dir is not null \u0026\u0026 dir.UseRoundMap \u0026\u0026 dir.MapReady )\n\t\t\treturn dir.RoundSpawnPoint( idx );\n\t\treturn _spawns[idx % _spawns.Length];\n\t}\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"Code/LobbyState.cs","FileName":"LobbyState.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"namespace LobbySystem;\n\n/// \u003Csummary\u003ELifecycle of the lobby: Lobby, then Active, then Ended before looping back.\u003C/summary\u003E\npublic enum LobbyState\n{\n\t/// \u003Csummary\u003EFree roam before and after a round. The mode button works here.\u003C/summary\u003E\n\tLobby,\n\t/// \u003Csummary\u003EA round is running.\u003C/summary\u003E\n\tActive,\n\t/// \u003Csummary\u003ERound finished; results show before returning to the lobby.\u003C/summary\u003E\n\tEnded\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"LobbyModeButton.cs","FileName":"LobbyModeButton.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"namespace LobbySystem;\n\n/// \u003Csummary\u003E\n/// In-world button that opens the mode menu for the host, or a local suggestion menu for a client. It needs\n/// a ModelRenderer to be visible and is hidden while a round is live. When the local player is within\n/// \u003Csee cref=\u0022UseRange\u0022/\u003E and presses Use, the menu opens.\n/// \u003C/summary\u003E\npublic sealed class LobbyModeButton : Component\n{\n\t[Property] public float UseRange { get; set; } = 130f;\n\t[Property] public bool GlowWhenInRange { get; set; } = true;\n\t[Property] public Color IdleTint { get; set; } = new Color( 0.85f, 0.4f, 0.15f );\n\t[Property] public Color ActiveTint { get; set; } = new Color( 1f, 0.85f, 0.3f );\n\n\tModelRenderer _renderer;\n\tILobbyAgent _me;\n\n\tprotected override void OnStart()\n\t{\n\t\t_renderer = Components.Get\u003CModelRenderer\u003E() ?? Components.GetInChildren\u003CModelRenderer\u003E();\n\t\tif ( _renderer is not null ) _renderer.Tint = IdleTint;\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\tvar dir = LobbyDirector.Current;\n\t\tbool inLobby = dir is null || !dir.RoundLive;\n\t\tif ( _renderer is not null \u0026\u0026 _renderer.Enabled != inLobby )\n\t\t\t_renderer.Enabled = inLobby;\n\t\tif ( !inLobby ) return;\n\n\t\tvar me = LocalPlayer();\n\t\tbool inRange = me is not null \u0026\u0026 WorldPosition.Distance( me.WorldPosition ) \u003C= UseRange;\n\n\t\tif ( GlowWhenInRange \u0026\u0026 _renderer is not null )\n\t\t\t_renderer.Tint = inRange ? ActiveTint : IdleTint;\n\n\t\tif ( inRange \u0026\u0026 Input.Pressed( \u0022Use\u0022 ) )\n\t\t\tdir?.RequestModeMenu();\n\t}\n\n\tILobbyAgent LocalPlayer()\n\t{\n\t\tif ( _me is not null \u0026\u0026 _me.IsValid() \u0026\u0026 !_me.IsBot \u0026\u0026 !_me.IsProxy ) return _me;\n\t\ttry { _me = Scene.GetAllComponents\u003CILobbyAgent\u003E().FirstOrDefault( c =\u003E c.IsValid() \u0026\u0026 !c.IsBot \u0026\u0026 !c.IsProxy ); }\n\t\tcatch { _me = null; }\n\t\treturn _me;\n\t}\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"Assembly.cs","FileName":"Assembly.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"global using Sandbox;\nglobal using System;\nglobal using System.Collections.Generic;\nglobal using System.Linq;\nglobal using System.Threading.Tasks;\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"Code/Examples/UI/LobbyHud.razor","FileName":"LobbyHud.razor","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"@using Sandbox\n@using Sandbox.UI\n@using LobbySystem\n@inherits PanelComponent\n@namespace LobbySystem.Examples\n\n\u003Croot\u003E\n\t@if ( Dir is null )\n\t{\n\t\t\u003Ctext\u003E\u003C/text\u003E\n\t}\n\telse if ( Dir.MenuOpen || Dir.SuggestMenuOpen )\n\t{\n\t\t\u003Cdiv class=\u0022menu\u0022\u003E\n\t\t\t\u003Cdiv class=\u0022title\u0022\u003EMULTIPLAYER LOBBY\u003C/div\u003E\n\t\t\t\u003Cdiv class=\u0022sub\u0022\u003E@(Dir.MenuOpen ? \u0022Choose a game mode\u0022 : \u0022Suggest a mode to the host\u0022)\u003C/div\u003E\n\t\t\t\u003Cdiv class=\u0022row\u0022\u003E\n\t\t\t\t@for ( int i = 0; i \u003C Dir.Modes.Count; i\u002B\u002B )\n\t\t\t\t{\n\t\t\t\t\tvar idx = i;\n\t\t\t\t\t\u003Cbutton class=\u0022mode\u0022 onclick=@(() =\u003E Dir.PickMode( idx ))\u003E\n\t\t\t\t\t\t\u003Cdiv class=\u0022k\u0022\u003E@(idx \u002B 1)\u003C/div\u003E\n\t\t\t\t\t\t\u003Cdiv class=\u0022n\u0022\u003E@Dir.Modes[idx].DisplayName\u003C/div\u003E\n\t\t\t\t\t\u003C/button\u003E\n\t\t\t\t}\n\t\t\t\u003C/div\u003E\n\t\t\t\u003Cbutton class=\u0022back\u0022 onclick=@(() =\u003E Dir.RequestCloseMenu())\u003EBack to lobby  [E]\u003C/button\u003E\n\t\t\u003C/div\u003E\n\t}\n\telse\n\t{\n\t\t\u003Cdiv class=\u0022top\u0022\u003E\n\t\t\t@if ( Dir.RoundLive )\n\t\t\t{\n\t\t\t\t\u003Cdiv class=\u0022mode\u0022\u003E@(Dir.ActiveMode?.DisplayName ?? \u0022\u0022)\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022timer @(Dir.TimeLeftSeconds \u003C 15 ? \u0022urgent\u0022 : \u0022\u0022)\u0022\u003E@TimerText\u003C/div\u003E\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t\u003Cdiv class=\u0022lobby\u0022\u003ELOBBY\u003C/div\u003E\n\t\t\t}\n\t\t\u003C/div\u003E\n\n\t\t@if ( !Dir.RoundLive \u0026\u0026 !string.IsNullOrEmpty( Dir.StatusMessage ) )\n\t\t{\n\t\t\t\u003Cdiv class=\u0022status\u0022\u003E@Dir.StatusMessage\u003C/div\u003E\n\t\t}\n\t\t@if ( Dir.ChatVisible )\n\t\t{\n\t\t\t\u003Cdiv class=\u0022chat\u0022\u003E@Dir.ChatLine\u003C/div\u003E\n\t\t}\n\n\t\t\u003Cdiv class=\u0022hints\u0022\u003E\n\t\t\t\u003Cspan\u003E[WASD] Move\u003C/span\u003E\n\t\t\t\u003Cspan\u003E[Space] Jump\u003C/span\u003E\n\t\t\t@if ( !Dir.RoundLive )\n\t\t\t{\n\t\t\t\t\u003Cspan\u003EWalk to the pad and press [E] to start a round\u003C/span\u003E\n\t\t\t}\n\t\t\u003C/div\u003E\n\t}\n\u003C/root\u003E\n\n@code\n{\n\tLobbyDirector Dir =\u003E LobbyDirector.Current;\n\n\tstring TimerText\n\t{\n\t\tget\n\t\t{\n\t\t\tint t = Dir?.TimeLeftSeconds ?? 0;\n\t\t\tif ( t \u003C 0 ) t = 0;\n\t\t\treturn $\u0022{t / 60:D2}:{t % 60:D2}\u0022;\n\t\t}\n\t}\n\n\tprotected override int BuildHash() =\u003E System.HashCode.Combine(\n\t\tDir?.State, Dir?.MenuOpen, Dir?.SuggestMenuOpen, Dir?.RoundLive,\n\t\tDir?.TimeLeftSeconds, Dir?.StatusMessage, Dir?.ChatLine, Dir?.ChatVisible );\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"Code/Examples/LobbyPlayer.cs","FileName":"LobbyPlayer.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"using Sandbox;\nusing Sandbox.Citizen;\n\nnamespace LobbySystem.Examples;\n\n/// \u003Csummary\u003E\n/// Self-contained networked third-person player: WASD with run and jump, a third-person camera created on\n/// demand, Citizen animation and owner-synced facing, plus \u003Csee cref=\u0022ILobbyAgent\u0022/\u003E so the lobby can name,\n/// spawn, place and reset it. Built on the standard s\u0026amp;box Citizen; copy or extend it for your own game.\n/// \u003C/summary\u003E\npublic sealed class LobbyPlayer : Component, ILobbyAgent\n{\n\t[Property] public float WalkSpeed { get; set; } = 130f;\n\t[Property] public float RunSpeed { get; set; } = 260f;\n\t[Property] public float JumpForce { get; set; } = 320f;\n\t[Property] public GameObject Head { get; set; }\n\t[Property] public GameObject Body { get; set; }\n\t[Property] public SkinnedModelRenderer Renderer { get; set; }\n\n\t[Sync( SyncFlags.FromHost )] public bool IsBot { get; set; }\n\t[Sync( SyncFlags.FromHost )] public string DisplayName { get; set; } = \u0022Player\u0022;\n\t[Sync] public float LookYaw { get; set; }\n\n\tCharacterController _cc;\n\tCitizenAnimationHelper _anim;\n\tCameraComponent _cam;\n\tVector3 _wish;\n\tVector3 _lastPos;\n\tVector3 _proxyVel;\n\tfloat _jumpUntil;\n\n\tbool _dressed;\n\tTimeUntil _nextDressTry;\n\n\tVector3 _wanderDir = Vector3.Forward;\n\tfloat _wanderTime;\n\n\t// True on the peer that simulates this pawn: the owner, or anyone in single-player.\n\tbool Controlled =\u003E !Networking.IsActive || !IsProxy;\n\n\tpublic void InitAgent( bool isBot, string displayName ) { IsBot = isBot; DisplayName = displayName; }\n\tpublic void ResetForRound() { if ( _cc is not null ) _cc.Velocity = Vector3.Zero; }\n\n\t[Rpc.Broadcast]\n\tpublic void TeleportTo( Vector3 position )\n\t{\n\t\tWorldPosition = position;\n\t\tif ( _cc is not null ) _cc.Velocity = Vector3.Zero;\n\t}\n\n\tprotected override void OnStart()\n\t{\n\t\t_cc = Components.Get\u003CCharacterController\u003E();\n\t\tBody ??= GameObject.Children.FirstOrDefault( c =\u003E c.Name == \u0022Body\u0022 );\n\t\tHead ??= GameObject.Children.FirstOrDefault( c =\u003E c.Name == \u0022Head\u0022 );\n\t\tRenderer ??= Body?.Components.Get\u003CSkinnedModelRenderer\u003E();\n\t\tif ( Renderer is not null ) _anim = Renderer.Components.GetOrCreate\u003CCitizenAnimationHelper\u003E();\n\t\t_lastPos = WorldPosition;\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\tTryDressFromAvatar();\n\n\t\tif ( !Controlled )\n\t\t{\n\t\t\t_proxyVel = Time.Delta \u003E 0f ? (WorldPosition - _lastPos) / Time.Delta : Vector3.Zero;\n\t\t\t_lastPos = WorldPosition;\n\t\t\tif ( Head is not null ) Head.WorldRotation = Rotation.FromYaw( LookYaw );\n\t\t\tAnimate();\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !IsBot )\n\t\t{\n\t\t\tUpdateLookAndCamera();\n\t\t\tif ( Input.Pressed( \u0022Jump\u0022 ) ) _jumpUntil = Time.Now \u002B 0.2f;\n\t\t}\n\t\tAnimate();\n\t}\n\n\tprotected override void OnFixedUpdate()\n\t{\n\t\tif ( _cc is null || !Controlled ) return;\n\t\t_wish = IsBot ? WanderWish() : InputWish();\n\t\tMove();\n\t}\n\n\tVector3 InputWish()\n\t{\n\t\tif ( Head is null ) return Vector3.Zero;\n\t\tvar yaw = Head.WorldRotation.Angles().WithPitch( 0 ).ToRotation();\n\t\tvar move = Input.AnalogMove;\n\t\tfloat speed = Input.Down( \u0022Run\u0022 ) ? RunSpeed : WalkSpeed;\n\t\treturn yaw * new Vector3( move.x, move.y, 0f ) * speed;\n\t}\n\n\t// Bots stroll around and turn back when they drift too far from the centre.\n\tVector3 WanderWish()\n\t{\n\t\t_wanderTime -= Time.Delta;\n\t\tif ( _wanderTime \u003C= 0f )\n\t\t{\n\t\t\t_wanderDir = Rotation.FromYaw( Game.Random.Float( 0f, 360f ) ).Forward;\n\t\t\t_wanderTime = Game.Random.Float( 2f, 4f );\n\t\t}\n\n\t\tvar flat = WorldPosition.WithZ( 0 );\n\t\tif ( flat.Length \u003E 700f ) _wanderDir = (-flat).Normal;\n\t\treturn _wanderDir.WithZ( 0 ).Normal * WalkSpeed;\n\t}\n\n\tvoid Move()\n\t{\n\t\tvar g = Scene.PhysicsWorld.Gravity;\n\t\tif ( _cc.IsOnGround )\n\t\t{\n\t\t\t_cc.Velocity = _cc.Velocity.WithZ( 0 );\n\t\t\t_cc.Accelerate( _wish );\n\t\t\t_cc.ApplyFriction( 5f );\n\t\t\tif ( Time.Now \u003C= _jumpUntil ) { _cc.Punch( Vector3.Up * JumpForce ); _jumpUntil = 0f; }\n\t\t}\n\t\telse\n\t\t{\n\t\t\t_cc.Velocity \u002B= g * Time.Delta * 0.5f;\n\t\t\t_cc.Accelerate( _wish.ClampLength( 60f ) );\n\t\t\t_cc.ApplyFriction( 0.1f );\n\t\t}\n\n\t\t_cc.Move();\n\n\t\tif ( _cc.IsOnGround ) _cc.Velocity = _cc.Velocity.WithZ( 0 );\n\t\telse _cc.Velocity \u002B= g * Time.Delta * 0.5f;\n\t}\n\n\tvoid UpdateLookAndCamera()\n\t{\n\t\tif ( Head is null ) return;\n\t\tvar look = Input.AnalogLook;\n\t\tvar a = Head.WorldRotation.Angles();\n\t\ta.pitch = MathX.Clamp( a.pitch \u002B look.pitch, -89f, 89f );\n\t\ta.yaw \u002B= look.yaw;\n\t\ta.roll = 0f;\n\t\tHead.WorldRotation = a.ToRotation();\n\t\tLookYaw = a.yaw;\n\n\t\tif ( !_cam.IsValid() )\n\t\t{\n\t\t\t_cam = Scene.GetAllComponents\u003CCameraComponent\u003E().FirstOrDefault();\n\t\t\tif ( _cam is null )\n\t\t\t{\n\t\t\t\tvar go = Scene.CreateObject();\n\t\t\t\tgo.Name = \u0022Player Camera\u0022;\n\t\t\t\t_cam = go.Components.Create\u003CCameraComponent\u003E();\n\t\t\t}\n\t\t}\n\n\t\tvar rot = Head.WorldRotation;\n\t\t_cam.WorldPosition = WorldPosition \u002B rot.Backward * 150f \u002B rot.Right * 28f \u002B Vector3.Up * 50f;\n\t\t_cam.WorldRotation = rot;\n\t}\n\n\tvoid Animate()\n\t{\n\t\tif ( _anim is null || _cc is null ) return;\n\t\tvar vel = Controlled ? _cc.Velocity : _proxyVel;\n\t\t_anim.WithVelocity( vel );\n\t\t_anim.WithWishVelocity( Controlled ? _wish : _proxyVel );\n\t\t_anim.IsGrounded = Controlled ? _cc.IsOnGround : MathF.Abs( _proxyVel.z ) \u003C 50f;\n\t\tif ( !IsBot \u0026\u0026 Head is not null )\n\t\t\t_anim.WithLook( Head.WorldRotation.Forward, 1f, 0.75f, 0.5f );\n\t\tFaceMovement( vel );\n\t}\n\n\tvoid FaceMovement( Vector3 velocity )\n\t{\n\t\tif ( Body is null ) return;\n\n\t\tRotation target;\n\t\tif ( !IsBot \u0026\u0026 Head is not null )\n\t\t{\n\t\t\ttarget = Rotation.FromYaw( Head.WorldRotation.Yaw() );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvar flat = velocity.WithZ( 0 );\n\t\t\ttarget = flat.Length \u003E 20f ? Rotation.LookAt( flat, Vector3.Up ) : Body.WorldRotation;\n\t\t}\n\n\t\tBody.WorldRotation = Rotation.Lerp( Body.WorldRotation, target, Time.Delta * 10f );\n\t}\n\n\t// Dress the Citizen in the player\u0027s own s\u0026box avatar. Runs on every peer and retries until ownership has\n\t// replicated. Bots keep the plain model.\n\tvoid TryDressFromAvatar()\n\t{\n\t\tif ( _dressed || Renderer is null || _nextDressTry \u003E 0f ) return;\n\t\t_nextDressTry = 0.25f;\n\t\tif ( IsBot ) { _dressed = true; return; }\n\n\t\tClothingContainer clothing;\n\t\ttry\n\t\t{\n\t\t\tvar owner = Network.Owner;\n\t\t\tif ( owner is not null )\n\t\t\t{\n\t\t\t\tvar json = owner.GetUserData( \u0022avatar\u0022 );\n\t\t\t\tif ( string.IsNullOrEmpty( json ) ) return;\n\t\t\t\tclothing = new ClothingContainer();\n\t\t\t\tclothing.Deserialize( json );\n\t\t\t}\n\t\t\telse if ( !Networking.IsActive )\n\t\t\t{\n\t\t\t\tclothing = ClothingContainer.CreateFromLocalUser();\n\t\t\t}\n\t\t\telse return;\n\t\t}\n\t\tcatch { _dressed = true; return; }\n\n\t\t_dressed = true;\n\t\tApplyClothingAsync( clothing );\n\t}\n\n\tasync void ApplyClothingAsync( ClothingContainer clothing )\n\t{\n\t\ttry { await clothing.ApplyAsync( Renderer, System.Threading.CancellationToken.None ); }\n\t\tcatch { }\n\t}\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"Code/Examples/UI/LobbyHud.razor.scss","FileName":"LobbyHud.razor.scss","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"$accent: #ffb13b;\n\nroot {\n\tposition: absolute;\n\twidth: 100%;\n\theight: 100%;\n\tfont-family: \u0022Poppins\u0022, sans-serif;\n\tcolor: white;\n\tpointer-events: none;\n\tflex-direction: column;\n}\n\n.top {\n\tflex-direction: column;\n\talign-items: center;\n\twidth: 100%;\n\tmargin-top: 36px;\n\n\t.mode { font-size: 20px; font-weight: 600; letter-spacing: 4px; color: $accent; text-shadow: 0 2px 6px black; }\n\t.lobby { font-size: 40px; font-weight: 800; letter-spacing: 3px; text-shadow: 0 3px 10px black; }\n\t.timer { font-size: 30px; font-weight: 700; text-shadow: 0 2px 6px black; \u0026.urgent { color: #ff5a4d; } }\n}\n\n.status {\n\tposition: absolute; top: 48%; width: 100%; justify-content: center;\n\tfont-size: 24px; font-weight: 600; text-shadow: 0 2px 8px black;\n}\n\n.chat {\n\tposition: absolute; top: 70%; width: 100%; justify-content: center;\n\tfont-size: 18px; color: $accent; text-shadow: 0 2px 6px black;\n}\n\n.hints {\n\tposition: absolute; bottom: 24px; width: 100%; justify-content: center; gap: 18px;\n\tspan {\n\t\tfont-size: 15px; color: rgba(255,255,255,0.85);\n\t\tbackground-color: rgba(0,0,0,0.35); padding: 5px 12px; border-radius: 6px;\n\t}\n}\n\n.menu {\n\tpointer-events: all;\n\tposition: absolute; width: 100%; height: 100%;\n\tflex-direction: column; align-items: center; justify-content: center;\n\tbackground-color: rgba(8, 10, 16, 0.72);\n\n\t.title { font-size: 40px; font-weight: 800; letter-spacing: 6px; }\n\t.sub { font-size: 18px; color: rgba(255,255,255,0.7); margin-bottom: 28px; }\n\t.row { flex-direction: row; gap: 18px; }\n\n\t.mode {\n\t\tflex-direction: column; align-items: center; justify-content: center;\n\t\twidth: 220px; height: 150px; border-radius: 14px;\n\t\tbackground: linear-gradient(160deg, rgba(40,46,66,0.95), rgba(22,26,40,0.95));\n\t\tborder: 2px solid rgba(255,255,255,0.08);\n\t\ttransition: all 0.12s ease-out; cursor: pointer;\n\t\t\u0026:hover { border-color: $accent; transform: scale(1.04); }\n\t\t.k { font-size: 22px; color: $accent; font-weight: 700; }\n\t\t.n { font-size: 24px; font-weight: 700; margin-top: 8px; }\n\t}\n\n\t.back {\n\t\tmargin-top: 30px; font-size: 16px; color: rgba(255,255,255,0.8);\n\t\tbackground-color: rgba(255,255,255,0.08); padding: 10px 20px; border-radius: 8px; cursor: pointer;\n\t\t\u0026:hover { background-color: rgba(255,255,255,0.16); }\n\t}\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"Code/Examples/ExampleMode.cs","FileName":"ExampleMode.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"namespace LobbySystem.Examples;\n\n/// \u003Csummary\u003E\n/// The simplest reference mode: it runs the round timer and nothing else, so you can see the full lobby,\n/// round and back-to-lobby loop. Replace the bodies with your own rules.\n/// \u003C/summary\u003E\npublic sealed class ExampleMode : IGameMode\n{\n\tpublic string DisplayName =\u003E \u0022FREE PLAY\u0022;\n\n\tpublic void OnRoundStart( LobbyDirector director, IReadOnlyList\u003CILobbyAgent\u003E agents )\n\t{\n\t}\n\n\tpublic bool IsRoundOver( LobbyDirector director, IReadOnlyList\u003CILobbyAgent\u003E agents ) =\u003E false;\n\n\tpublic string ResultText( LobbyDirector director, IReadOnlyList\u003CILobbyAgent\u003E agents ) =\u003E \u0022Round over!\u0022;\n}\n\n/// \u003Csummary\u003E\n/// Declares which modes the menu offers and in what order. Put one on the Game Manager.\n/// \u003C/summary\u003E\npublic sealed class ExampleModeCatalog : Component, IGameModeCatalog\n{\n\tstatic readonly IGameMode[] _modes = { new ExampleMode() };\n\tpublic IReadOnlyList\u003CIGameMode\u003E Modes =\u003E _modes;\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"Code/ILobbyAgent.cs","FileName":"ILobbyAgent.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"namespace LobbySystem;\n\n/// \u003Csummary\u003E\n/// Implemented by a player or bot pawn so the lobby can count, place, reset and teleport it without knowing\n/// anything about the gameplay. Keep roles (who is \u0022it\u0022, score, team) on your own type and cast back to it\n/// inside an \u003Csee cref=\u0022IGameMode\u0022/\u003E.\n/// \u003C/summary\u003E\npublic interface ILobbyAgent : IValid\n{\n\t/// \u003Csummary\u003ETrue for AI pawns.\u003C/summary\u003E\n\tbool IsBot { get; }\n\n\t/// \u003Csummary\u003ETrue on peers that don\u0027t own this pawn.\u003C/summary\u003E\n\tbool IsProxy { get; }\n\n\t/// \u003Csummary\u003ESteam name for players, a label for bots.\u003C/summary\u003E\n\tstring DisplayName { get; }\n\n\t/// \u003Csummary\u003EUsed for spawn placement and proximity checks.\u003C/summary\u003E\n\tVector3 WorldPosition { get; }\n\n\t/// \u003Csummary\u003EHost brands the pawn just after spawn. Network these fields.\u003C/summary\u003E\n\tvoid InitAgent( bool isBot, string displayName );\n\n\t/// \u003Csummary\u003EReturn all gameplay state to a neutral pre-round condition.\u003C/summary\u003E\n\tvoid ResetForRound();\n\n\t/// \u003Csummary\u003EMove the pawn on every peer. Implement with \u003Cc\u003E[Rpc.Broadcast]\u003C/c\u003E.\u003C/summary\u003E\n\tvoid TeleportTo( Vector3 position );\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"IGameMode.cs","FileName":"IGameMode.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"namespace LobbySystem;\n\n/// \u003Csummary\u003E\n/// The rules of one gamemode. The \u003Csee cref=\u0022LobbyDirector\u0022/\u003E owns the lifecycle and calls into the active\n/// mode for gameplay decisions. Keep implementations stateless so they can be shared as singletons. All\n/// three methods run on the host.\n/// \u003C/summary\u003E\npublic interface IGameMode\n{\n\t/// \u003Csummary\u003EName shown on the HUD and in the menu.\u003C/summary\u003E\n\tstring DisplayName { get; }\n\n\t/// \u003Csummary\u003ECalled once when a round begins. Assign starting roles here.\u003C/summary\u003E\n\tvoid OnRoundStart( LobbyDirector director, IReadOnlyList\u003CILobbyAgent\u003E agents );\n\n\t/// \u003Csummary\u003EReturn true to end the round before the timer expires.\u003C/summary\u003E\n\tbool IsRoundOver( LobbyDirector director, IReadOnlyList\u003CILobbyAgent\u003E agents );\n\n\t/// \u003Csummary\u003EResult line shown when the round ends.\u003C/summary\u003E\n\tstring ResultText( LobbyDirector director, IReadOnlyList\u003CILobbyAgent\u003E agents );\n}\n\n/// \u003Csummary\u003E\n/// One component in the scene implements this to declare the available modes and their order. The menu\n/// hotkeys 1, 2 and 3 map to the list indices.\n/// \u003C/summary\u003E\npublic interface IGameModeCatalog\n{\n\tIReadOnlyList\u003CIGameMode\u003E Modes { get; }\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"Examples/ExampleMode.cs","FileName":"ExampleMode.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"namespace LobbySystem.Examples;\n\n/// \u003Csummary\u003E\n/// The simplest reference mode: it runs the round timer and nothing else, so you can see the full lobby,\n/// round and back-to-lobby loop. Replace the bodies with your own rules.\n/// \u003C/summary\u003E\npublic sealed class ExampleMode : IGameMode\n{\n\tpublic string DisplayName =\u003E \u0022FREE PLAY\u0022;\n\n\tpublic void OnRoundStart( LobbyDirector director, IReadOnlyList\u003CILobbyAgent\u003E agents )\n\t{\n\t}\n\n\tpublic bool IsRoundOver( LobbyDirector director, IReadOnlyList\u003CILobbyAgent\u003E agents ) =\u003E false;\n\n\tpublic string ResultText( LobbyDirector director, IReadOnlyList\u003CILobbyAgent\u003E agents ) =\u003E \u0022Round over!\u0022;\n}\n\n/// \u003Csummary\u003E\n/// Declares which modes the menu offers and in what order. Put one on the Game Manager.\n/// \u003C/summary\u003E\npublic sealed class ExampleModeCatalog : Component, IGameModeCatalog\n{\n\tstatic readonly IGameMode[] _modes = { new ExampleMode() };\n\tpublic IReadOnlyList\u003CIGameMode\u003E Modes =\u003E _modes;\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"Code/IGameMode.cs","FileName":"IGameMode.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"namespace LobbySystem;\n\n/// \u003Csummary\u003E\n/// The rules of one gamemode. The \u003Csee cref=\u0022LobbyDirector\u0022/\u003E owns the lifecycle and calls into the active\n/// mode for gameplay decisions. Keep implementations stateless so they can be shared as singletons. All\n/// three methods run on the host.\n/// \u003C/summary\u003E\npublic interface IGameMode\n{\n\t/// \u003Csummary\u003EName shown on the HUD and in the menu.\u003C/summary\u003E\n\tstring DisplayName { get; }\n\n\t/// \u003Csummary\u003ECalled once when a round begins. Assign starting roles here.\u003C/summary\u003E\n\tvoid OnRoundStart( LobbyDirector director, IReadOnlyList\u003CILobbyAgent\u003E agents );\n\n\t/// \u003Csummary\u003EReturn true to end the round before the timer expires.\u003C/summary\u003E\n\tbool IsRoundOver( LobbyDirector director, IReadOnlyList\u003CILobbyAgent\u003E agents );\n\n\t/// \u003Csummary\u003EResult line shown when the round ends.\u003C/summary\u003E\n\tstring ResultText( LobbyDirector director, IReadOnlyList\u003CILobbyAgent\u003E agents );\n}\n\n/// \u003Csummary\u003E\n/// One component in the scene implements this to declare the available modes and their order. The menu\n/// hotkeys 1, 2 and 3 map to the list indices.\n/// \u003C/summary\u003E\npublic interface IGameModeCatalog\n{\n\tIReadOnlyList\u003CIGameMode\u003E Modes { get; }\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"Examples/LobbyPlayer.cs","FileName":"LobbyPlayer.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"using Sandbox;\nusing Sandbox.Citizen;\n\nnamespace LobbySystem.Examples;\n\n/// \u003Csummary\u003E\n/// Self-contained networked third-person player: WASD with run and jump, a third-person camera created on\n/// demand, Citizen animation and owner-synced facing, plus \u003Csee cref=\u0022ILobbyAgent\u0022/\u003E so the lobby can name,\n/// spawn, place and reset it. Built on the standard s\u0026amp;box Citizen; copy or extend it for your own game.\n/// \u003C/summary\u003E\npublic sealed class LobbyPlayer : Component, ILobbyAgent\n{\n\t[Property] public float WalkSpeed { get; set; } = 130f;\n\t[Property] public float RunSpeed { get; set; } = 260f;\n\t[Property] public float JumpForce { get; set; } = 320f;\n\t[Property] public GameObject Head { get; set; }\n\t[Property] public GameObject Body { get; set; }\n\t[Property] public SkinnedModelRenderer Renderer { get; set; }\n\n\t[Sync( SyncFlags.FromHost )] public bool IsBot { get; set; }\n\t[Sync( SyncFlags.FromHost )] public string DisplayName { get; set; } = \u0022Player\u0022;\n\t[Sync] public float LookYaw { get; set; }\n\n\tCharacterController _cc;\n\tCitizenAnimationHelper _anim;\n\tCameraComponent _cam;\n\tVector3 _wish;\n\tVector3 _lastPos;\n\tVector3 _proxyVel;\n\tfloat _jumpUntil;\n\n\tbool _dressed;\n\tTimeUntil _nextDressTry;\n\n\tVector3 _wanderDir = Vector3.Forward;\n\tfloat _wanderTime;\n\n\t// True on the peer that simulates this pawn: the owner, or anyone in single-player.\n\tbool Controlled =\u003E !Networking.IsActive || !IsProxy;\n\n\tpublic void InitAgent( bool isBot, string displayName ) { IsBot = isBot; DisplayName = displayName; }\n\tpublic void ResetForRound() { if ( _cc is not null ) _cc.Velocity = Vector3.Zero; }\n\n\t[Rpc.Broadcast]\n\tpublic void TeleportTo( Vector3 position )\n\t{\n\t\tWorldPosition = position;\n\t\tif ( _cc is not null ) _cc.Velocity = Vector3.Zero;\n\t}\n\n\tprotected override void OnStart()\n\t{\n\t\t_cc = Components.Get\u003CCharacterController\u003E();\n\t\tBody ??= GameObject.Children.FirstOrDefault( c =\u003E c.Name == \u0022Body\u0022 );\n\t\tHead ??= GameObject.Children.FirstOrDefault( c =\u003E c.Name == \u0022Head\u0022 );\n\t\tRenderer ??= Body?.Components.Get\u003CSkinnedModelRenderer\u003E();\n\t\tif ( Renderer is not null ) _anim = Renderer.Components.GetOrCreate\u003CCitizenAnimationHelper\u003E();\n\t\t_lastPos = WorldPosition;\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\tTryDressFromAvatar();\n\n\t\tif ( !Controlled )\n\t\t{\n\t\t\t_proxyVel = Time.Delta \u003E 0f ? (WorldPosition - _lastPos) / Time.Delta : Vector3.Zero;\n\t\t\t_lastPos = WorldPosition;\n\t\t\tif ( Head is not null ) Head.WorldRotation = Rotation.FromYaw( LookYaw );\n\t\t\tAnimate();\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !IsBot )\n\t\t{\n\t\t\tUpdateLookAndCamera();\n\t\t\tif ( Input.Pressed( \u0022Jump\u0022 ) ) _jumpUntil = Time.Now \u002B 0.2f;\n\t\t}\n\t\tAnimate();\n\t}\n\n\tprotected override void OnFixedUpdate()\n\t{\n\t\tif ( _cc is null || !Controlled ) return;\n\t\t_wish = IsBot ? WanderWish() : InputWish();\n\t\tMove();\n\t}\n\n\tVector3 InputWish()\n\t{\n\t\tif ( Head is null ) return Vector3.Zero;\n\t\tvar yaw = Head.WorldRotation.Angles().WithPitch( 0 ).ToRotation();\n\t\tvar move = Input.AnalogMove;\n\t\tfloat speed = Input.Down( \u0022Run\u0022 ) ? RunSpeed : WalkSpeed;\n\t\treturn yaw * new Vector3( move.x, move.y, 0f ) * speed;\n\t}\n\n\t// Bots stroll around and turn back when they drift too far from the centre.\n\tVector3 WanderWish()\n\t{\n\t\t_wanderTime -= Time.Delta;\n\t\tif ( _wanderTime \u003C= 0f )\n\t\t{\n\t\t\t_wanderDir = Rotation.FromYaw( Game.Random.Float( 0f, 360f ) ).Forward;\n\t\t\t_wanderTime = Game.Random.Float( 2f, 4f );\n\t\t}\n\n\t\tvar flat = WorldPosition.WithZ( 0 );\n\t\tif ( flat.Length \u003E 700f ) _wanderDir = (-flat).Normal;\n\t\treturn _wanderDir.WithZ( 0 ).Normal * WalkSpeed;\n\t}\n\n\tvoid Move()\n\t{\n\t\tvar g = Scene.PhysicsWorld.Gravity;\n\t\tif ( _cc.IsOnGround )\n\t\t{\n\t\t\t_cc.Velocity = _cc.Velocity.WithZ( 0 );\n\t\t\t_cc.Accelerate( _wish );\n\t\t\t_cc.ApplyFriction( 5f );\n\t\t\tif ( Time.Now \u003C= _jumpUntil ) { _cc.Punch( Vector3.Up * JumpForce ); _jumpUntil = 0f; }\n\t\t}\n\t\telse\n\t\t{\n\t\t\t_cc.Velocity \u002B= g * Time.Delta * 0.5f;\n\t\t\t_cc.Accelerate( _wish.ClampLength( 60f ) );\n\t\t\t_cc.ApplyFriction( 0.1f );\n\t\t}\n\n\t\t_cc.Move();\n\n\t\tif ( _cc.IsOnGround ) _cc.Velocity = _cc.Velocity.WithZ( 0 );\n\t\telse _cc.Velocity \u002B= g * Time.Delta * 0.5f;\n\t}\n\n\tvoid UpdateLookAndCamera()\n\t{\n\t\tif ( Head is null ) return;\n\t\tvar look = Input.AnalogLook;\n\t\tvar a = Head.WorldRotation.Angles();\n\t\ta.pitch = MathX.Clamp( a.pitch \u002B look.pitch, -89f, 89f );\n\t\ta.yaw \u002B= look.yaw;\n\t\ta.roll = 0f;\n\t\tHead.WorldRotation = a.ToRotation();\n\t\tLookYaw = a.yaw;\n\n\t\tif ( !_cam.IsValid() )\n\t\t{\n\t\t\t_cam = Scene.GetAllComponents\u003CCameraComponent\u003E().FirstOrDefault();\n\t\t\tif ( _cam is null )\n\t\t\t{\n\t\t\t\tvar go = Scene.CreateObject();\n\t\t\t\tgo.Name = \u0022Player Camera\u0022;\n\t\t\t\t_cam = go.Components.Create\u003CCameraComponent\u003E();\n\t\t\t}\n\t\t}\n\n\t\tvar rot = Head.WorldRotation;\n\t\t_cam.WorldPosition = WorldPosition \u002B rot.Backward * 150f \u002B rot.Right * 28f \u002B Vector3.Up * 50f;\n\t\t_cam.WorldRotation = rot;\n\t}\n\n\tvoid Animate()\n\t{\n\t\tif ( _anim is null || _cc is null ) return;\n\t\tvar vel = Controlled ? _cc.Velocity : _proxyVel;\n\t\t_anim.WithVelocity( vel );\n\t\t_anim.WithWishVelocity( Controlled ? _wish : _proxyVel );\n\t\t_anim.IsGrounded = Controlled ? _cc.IsOnGround : MathF.Abs( _proxyVel.z ) \u003C 50f;\n\t\tif ( !IsBot \u0026\u0026 Head is not null )\n\t\t\t_anim.WithLook( Head.WorldRotation.Forward, 1f, 0.75f, 0.5f );\n\t\tFaceMovement( vel );\n\t}\n\n\tvoid FaceMovement( Vector3 velocity )\n\t{\n\t\tif ( Body is null ) return;\n\n\t\tRotation target;\n\t\tif ( !IsBot \u0026\u0026 Head is not null )\n\t\t{\n\t\t\ttarget = Rotation.FromYaw( Head.WorldRotation.Yaw() );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvar flat = velocity.WithZ( 0 );\n\t\t\ttarget = flat.Length \u003E 20f ? Rotation.LookAt( flat, Vector3.Up ) : Body.WorldRotation;\n\t\t}\n\n\t\tBody.WorldRotation = Rotation.Lerp( Body.WorldRotation, target, Time.Delta * 10f );\n\t}\n\n\t// Dress the Citizen in the player\u0027s own s\u0026box avatar. Runs on every peer and retries until ownership has\n\t// replicated. Bots keep the plain model.\n\tvoid TryDressFromAvatar()\n\t{\n\t\tif ( _dressed || Renderer is null || _nextDressTry \u003E 0f ) return;\n\t\t_nextDressTry = 0.25f;\n\t\tif ( IsBot ) { _dressed = true; return; }\n\n\t\tClothingContainer clothing;\n\t\ttry\n\t\t{\n\t\t\tvar owner = Network.Owner;\n\t\t\tif ( owner is not null )\n\t\t\t{\n\t\t\t\tvar json = owner.GetUserData( \u0022avatar\u0022 );\n\t\t\t\tif ( string.IsNullOrEmpty( json ) ) return;\n\t\t\t\tclothing = new ClothingContainer();\n\t\t\t\tclothing.Deserialize( json );\n\t\t\t}\n\t\t\telse if ( !Networking.IsActive )\n\t\t\t{\n\t\t\t\tclothing = ClothingContainer.CreateFromLocalUser();\n\t\t\t}\n\t\t\telse return;\n\t\t}\n\t\tcatch { _dressed = true; return; }\n\n\t\t_dressed = true;\n\t\tApplyClothingAsync( clothing );\n\t}\n\n\tasync void ApplyClothingAsync( ClothingContainer clothing )\n\t{\n\t\ttry { await clothing.ApplyAsync( Renderer, System.Threading.CancellationToken.None ); }\n\t\tcatch { }\n\t}\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"Code/LobbyDirector.cs","FileName":"LobbyDirector.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"namespace LobbySystem;\n\n/// \u003Csummary\u003E\n/// Gamemode-agnostic round and lobby lifecycle. Drives the Lobby, Active and Ended states, the mode menu,\n/// the synced countdown, optional map loading and agent tracking, then hands the actual rules to the active\n/// \u003Csee cref=\u0022IGameMode\u0022/\u003E. Host-authoritative; clients read the synced values.\n/// \u003C/summary\u003E\npublic sealed class LobbyDirector : Component\n{\n\tpublic static LobbyDirector Current { get; private set; }\n\n\t[Property] public float RoundDuration { get; set; } = 180f;\n\t[Property] public float RestartDelay { get; set; } = 4f;\n\t[Property] public int MinPlayers { get; set; } = 2;\n\n\t/// \u003Csummary\u003EOptional map cloned in when a round starts. Leave null to play in the lobby scene.\u003C/summary\u003E\n\t[Property] public GameObject MapPrefab { get; set; }\n\n\t/// \u003Csummary\u003ELobby floor; its renderer hides once a round map has loaded.\u003C/summary\u003E\n\t[Property] public GameObject LobbyFloor { get; set; }\n\n\t/// \u003Csummary\u003ETurn on when you use \u003Csee cref=\u0022MapPrefab\u0022/\u003E so clients load it too.\u003C/summary\u003E\n\t[Property, Sync( SyncFlags.FromHost )] public bool UseRoundMap { get; set; }\n\n\t[Sync( SyncFlags.FromHost )] public LobbyState State { get; set; } = LobbyState.Lobby;\n\t[Sync( SyncFlags.FromHost )] public int ActiveModeIndex { get; set; }\n\t[Sync( SyncFlags.FromHost )] public bool MenuOpen { get; set; }\n\t[Sync( SyncFlags.FromHost )] public int TimeLeftSeconds { get; set; }\n\t[Sync( SyncFlags.FromHost )] public string StatusMessage { get; set; } = \u0022\u0022;\n\t[Sync( SyncFlags.FromHost )] public string Banner { get; set; } = \u0022\u0022;\n\t[Sync( SyncFlags.FromHost )] public int EventPulse { get; set; }\n\n\t/// \u003Csummary\u003ELocal flag: a client opened the menu to suggest a mode to the host.\u003C/summary\u003E\n\tpublic bool SuggestMenuOpen { get; set; }\n\n\tfloat _timeRemaining;\n\tfloat _restartAt;\n\tTimeUntil _startGrace;\n\tGameObject _mapInstance;\n\tModelRenderer _floorRenderer;\n\tbool _floorResolved;\n\n\tList\u003CILobbyAgent\u003E _agents = new();\n\tpublic IReadOnlyList\u003CILobbyAgent\u003E Agents =\u003E _agents;\n\tint LiveCount =\u003E _agents.Count;\n\n\tIReadOnlyList\u003CIGameMode\u003E _modes;\n\tpublic IReadOnlyList\u003CIGameMode\u003E Modes =\u003E _modes ?? ResolveModes();\n\tpublic IGameMode ActiveMode =\u003E Modes.Count \u003E 0 ? Modes[ Math.Clamp( ActiveModeIndex, 0, Modes.Count - 1 ) ] : null;\n\n\tIReadOnlyList\u003CIGameMode\u003E ResolveModes()\n\t{\n\t\ttry\n\t\t{\n\t\t\tvar catalog = Scene.GetAllComponents\u003CIGameModeCatalog\u003E().FirstOrDefault();\n\t\t\t_modes = catalog?.Modes ?? (IReadOnlyList\u003CIGameMode\u003E)Array.Empty\u003CIGameMode\u003E();\n\t\t}\n\t\tcatch\n\t\t{\n\t\t\t_modes = Array.Empty\u003CIGameMode\u003E();\n\t\t}\n\t\treturn _modes;\n\t}\n\n\tprotected override void OnAwake() =\u003E Current = this;\n\tprotected override void OnDestroy() { if ( Current == this ) Current = null; }\n\n\tbool _roundLive;\n\n\t/// \u003Csummary\u003E\n\t/// True while a round is running. The RPC flag flips instantly for connected peers; OR-ing the synced\n\t/// State also catches players who join mid-round. Drive lobby-vs-round UI off this, not State.\n\t/// \u003C/summary\u003E\n\tpublic bool RoundLive =\u003E _roundLive || State == LobbyState.Active;\n\t[Rpc.Broadcast] public void RpcSetRoundLive( bool live ) =\u003E _roundLive = live;\n\tvoid SetRoundLive( bool live ) { if ( Networking.IsActive ) RpcSetRoundLive( live ); else _roundLive = live; }\n\n\tpublic bool MapReady =\u003E _mapInstance.IsValid();\n\n\tTimeUntil _nextScan;\n\tvoid RefreshAgents()\n\t{\n\t\tif ( _nextScan \u003E 0f \u0026\u0026 _agents.Count \u003E 0 ) return;\n\t\t_nextScan = 0.2f;\n\t\ttry\n\t\t{\n\t\t\tvar fresh = new List\u003CILobbyAgent\u003E();\n\t\t\tforeach ( var a in Scene.GetAllComponents\u003CILobbyAgent\u003E() )\n\t\t\t\tif ( a.IsValid() ) fresh.Add( a );\n\t\t\t_agents = fresh;\n\t\t}\n\t\tcatch\n\t\t{\n\t\t\t// Scene changed during the scan; keep the previous list and retry next frame.\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003EA spawn-point position spread by index. False until the map collision has loaded, so retry.\u003C/summary\u003E\n\tpublic bool TryRoundSpawn( int i, out Vector3 pos )\n\t{\n\t\tpos = default;\n\t\tList\u003CSpawnPoint\u003E spawns;\n\t\ttry { spawns = Scene.GetAllComponents\u003CSpawnPoint\u003E().Where( s =\u003E s.IsValid() ).ToList(); }\n\t\tcatch { return false; }\n\t\tif ( spawns.Count == 0 ) return false;\n\t\tvar sp = spawns[ ((i % spawns.Count) \u002B spawns.Count) % spawns.Count ];\n\t\tpos = sp.WorldPosition \u002B Vector3.Up * 20f;\n\t\treturn true;\n\t}\n\tpublic Vector3 RoundSpawnPoint( int i ) =\u003E TryRoundSpawn( i, out var p ) ? p : Vector3.Up * 300f;\n\n\tvoid SyncRoundMap()\n\t{\n\t\tif ( !UseRoundMap || MapPrefab is null ) return;\n\t\t// Clone once. Toggling or destroying a MapInstance cancels its async load.\n\t\tif ( RoundLive \u0026\u0026 !_mapInstance.IsValid() )\n\t\t\t_mapInstance = MapPrefab.Clone( Vector3.Zero );\n\t}\n\n\tvoid SyncLobbyFloor()\n\t{\n\t\tif ( !_floorResolved )\n\t\t{\n\t\t\t_floorRenderer = LobbyFloor?.Components.Get\u003CModelRenderer\u003E();\n\t\t\t_floorResolved = true;\n\t\t}\n\t\tif ( _floorRenderer is not null )\n\t\t{\n\t\t\tbool show = !MapReady;\n\t\t\tif ( _floorRenderer.Enabled != show ) _floorRenderer.Enabled = show;\n\t\t}\n\t}\n\n\tpublic void RequestModeMenu()\n\t{\n\t\tbool host = !Networking.IsActive || Networking.IsHost;\n\t\tif ( host ) { if ( MenuOpen ) RequestCloseMenu(); else RequestOpenMenu(); }\n\t\telse SuggestMenuOpen = !SuggestMenuOpen;\n\t}\n\tpublic void RequestOpenMenu() { if ( !Networking.IsActive ) OpenMenu(); else RpcOpenMenu(); }\n\t[Rpc.Broadcast] public void RpcOpenMenu() { if ( Networking.IsActive \u0026\u0026 !Networking.IsHost ) return; OpenMenu(); }\n\tvoid OpenMenu() { MenuOpen = true; State = LobbyState.Lobby; }\n\n\tpublic void RequestCloseMenu() { SuggestMenuOpen = false; if ( !Networking.IsActive ) CloseMenu(); else RpcCloseMenu(); }\n\t[Rpc.Broadcast] public void RpcCloseMenu() { if ( Networking.IsActive \u0026\u0026 !Networking.IsHost ) return; CloseMenu(); }\n\tvoid CloseMenu() { MenuOpen = false; State = LobbyState.Lobby; Banner = \u0022\u0022; }\n\n\t/// \u003Csummary\u003ECalled by the HUD when a mode is clicked. The host starts it; a client suggests it.\u003C/summary\u003E\n\tpublic void PickMode( int index )\n\t{\n\t\tbool host = !Networking.IsActive || Networking.IsHost;\n\t\tif ( host ) ChooseMode( index );\n\t\telse { SuggestMenuOpen = false; SuggestMode( index ); }\n\t}\n\tpublic void ChooseMode( int index ) { MenuOpen = false; Banner = \u0022\u0022; StartRound( index ); }\n\n\tpublic string ChatLine { get; private set; } = \u0022\u0022;\n\tTimeUntil _chatUntil;\n\tpublic bool ChatVisible =\u003E !string.IsNullOrEmpty( ChatLine ) \u0026\u0026 _chatUntil \u003E 0f;\n\tpublic void SuggestMode( int index )\n\t{\n\t\tstring who = Connection.Local?.DisplayName ?? \u0022Someone\u0022;\n\t\tstring mode = (index \u003E= 0 \u0026\u0026 index \u003C Modes.Count) ? Modes[index].DisplayName : \u0022a mode\u0022;\n\t\tRpcSuggest( who, mode );\n\t}\n\t[Rpc.Broadcast] public void RpcSuggest( string who, string mode ) { ChatLine = $\u0022{who} suggested starting {mode}\u0022; _chatUntil = 6f; }\n\n\tprotected override void OnUpdate()\n\t{\n\t\tRefreshAgents();\n\t\tSyncRoundMap();\n\t\tSyncLobbyFloor();\n\n\t\t// Only the host drives round state; clients render the synced values.\n\t\tif ( Networking.IsActive \u0026\u0026 !Networking.IsHost ) return;\n\n\t\tif ( MenuOpen ) { StatusMessage = \u0022Select a game mode\u0022; return; }\n\n\t\tswitch ( State )\n\t\t{\n\t\t\tcase LobbyState.Lobby:\n\t\t\t\tStatusMessage = \u0022Waiting in the lobby\u0022;\n\t\t\t\tbreak;\n\n\t\t\tcase LobbyState.Active:\n\t\t\t\t_timeRemaining -= Time.Delta;\n\t\t\t\tint secs = (int)MathF.Ceiling( _timeRemaining );\n\t\t\t\tif ( secs != TimeLeftSeconds ) TimeLeftSeconds = secs;\n\t\t\t\tbool graceOver = _startGrace \u003C= 0f;\n\t\t\t\tbool earlyOver = graceOver \u0026\u0026 (LiveCount \u003C MinPlayers || (ActiveMode?.IsRoundOver( this, _agents ) ?? false));\n\t\t\t\tif ( _timeRemaining \u003C= 0f || earlyOver ) EndRound();\n\t\t\t\tbreak;\n\n\t\t\tcase LobbyState.Ended:\n\t\t\t\tif ( Time.Now \u003E= _restartAt )\n\t\t\t\t{\n\t\t\t\t\tBanner = \u0022\u0022;\n\t\t\t\t\tState = LobbyState.Lobby;\n\t\t\t\t\tSetRoundLive( false );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003EBegin a round with the given mode index. Host only.\u003C/summary\u003E\n\tpublic void StartRound( int modeIndex )\n\t{\n\t\tif ( Modes.Count == 0 ) return;\n\t\tActiveModeIndex = Math.Clamp( modeIndex, 0, Modes.Count - 1 );\n\n\t\tvar live = _agents.Where( a =\u003E a.IsValid() ).ToList();\n\t\tif ( live.Count \u003C 1 ) { State = LobbyState.Lobby; return; }\n\n\t\tState = LobbyState.Active;\n\t\tSetRoundLive( true );\n\t\t_startGrace = 1.5f;\n\t\t_timeRemaining = RoundDuration;\n\t\tTimeLeftSeconds = (int)RoundDuration;\n\n\t\tforeach ( var a in live ) a.ResetForRound();\n\t\tActiveMode?.OnRoundStart( this, live );\n\n\t\tStatusMessage = \u0022\u0022;\n\t\tBanner = ActiveMode?.DisplayName ?? \u0022\u0022;\n\t}\n\n\tvoid EndRound()\n\t{\n\t\tState = LobbyState.Ended;\n\t\t_timeRemaining = 0f;\n\t\tTimeLeftSeconds = 0;\n\t\t_restartAt = Time.Now \u002B RestartDelay;\n\t\tStatusMessage = ActiveMode?.ResultText( this, _agents ) ?? \u0022Round over!\u0022;\n\t\tBanner = \u0022ROUND OVER\u0022;\n\t}\n\n\t/// \u003Csummary\u003EBump \u003Csee cref=\u0022EventPulse\u0022/\u003E to flash every HUD once.\u003C/summary\u003E\n\tpublic void PulseEvent() =\u003E EventPulse\u002B\u002B;\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"Examples/UI/LobbyHud.razor","FileName":"LobbyHud.razor","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"@using Sandbox\n@using Sandbox.UI\n@using LobbySystem\n@inherits PanelComponent\n@namespace LobbySystem.Examples\n\n\u003Croot\u003E\n\t@if ( Dir is null )\n\t{\n\t\t\u003Ctext\u003E\u003C/text\u003E\n\t}\n\telse if ( Dir.MenuOpen || Dir.SuggestMenuOpen )\n\t{\n\t\t\u003Cdiv class=\u0022menu\u0022\u003E\n\t\t\t\u003Cdiv class=\u0022title\u0022\u003EMULTIPLAYER LOBBY\u003C/div\u003E\n\t\t\t\u003Cdiv class=\u0022sub\u0022\u003E@(Dir.MenuOpen ? \u0022Choose a game mode\u0022 : \u0022Suggest a mode to the host\u0022)\u003C/div\u003E\n\t\t\t\u003Cdiv class=\u0022row\u0022\u003E\n\t\t\t\t@for ( int i = 0; i \u003C Dir.Modes.Count; i\u002B\u002B )\n\t\t\t\t{\n\t\t\t\t\tvar idx = i;\n\t\t\t\t\t\u003Cbutton class=\u0022mode\u0022 onclick=@(() =\u003E Dir.PickMode( idx ))\u003E\n\t\t\t\t\t\t\u003Cdiv class=\u0022k\u0022\u003E@(idx \u002B 1)\u003C/div\u003E\n\t\t\t\t\t\t\u003Cdiv class=\u0022n\u0022\u003E@Dir.Modes[idx].DisplayName\u003C/div\u003E\n\t\t\t\t\t\u003C/button\u003E\n\t\t\t\t}\n\t\t\t\u003C/div\u003E\n\t\t\t\u003Cbutton class=\u0022back\u0022 onclick=@(() =\u003E Dir.RequestCloseMenu())\u003EBack to lobby  [E]\u003C/button\u003E\n\t\t\u003C/div\u003E\n\t}\n\telse\n\t{\n\t\t\u003Cdiv class=\u0022top\u0022\u003E\n\t\t\t@if ( Dir.RoundLive )\n\t\t\t{\n\t\t\t\t\u003Cdiv class=\u0022mode\u0022\u003E@(Dir.ActiveMode?.DisplayName ?? \u0022\u0022)\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022timer @(Dir.TimeLeftSeconds \u003C 15 ? \u0022urgent\u0022 : \u0022\u0022)\u0022\u003E@TimerText\u003C/div\u003E\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t\u003Cdiv class=\u0022lobby\u0022\u003ELOBBY\u003C/div\u003E\n\t\t\t}\n\t\t\u003C/div\u003E\n\n\t\t@if ( !Dir.RoundLive \u0026\u0026 !string.IsNullOrEmpty( Dir.StatusMessage ) )\n\t\t{\n\t\t\t\u003Cdiv class=\u0022status\u0022\u003E@Dir.StatusMessage\u003C/div\u003E\n\t\t}\n\t\t@if ( Dir.ChatVisible )\n\t\t{\n\t\t\t\u003Cdiv class=\u0022chat\u0022\u003E@Dir.ChatLine\u003C/div\u003E\n\t\t}\n\n\t\t\u003Cdiv class=\u0022hints\u0022\u003E\n\t\t\t\u003Cspan\u003E[WASD] Move\u003C/span\u003E\n\t\t\t\u003Cspan\u003E[Space] Jump\u003C/span\u003E\n\t\t\t@if ( !Dir.RoundLive )\n\t\t\t{\n\t\t\t\t\u003Cspan\u003EWalk to the pad and press [E] to start a round\u003C/span\u003E\n\t\t\t}\n\t\t\u003C/div\u003E\n\t}\n\u003C/root\u003E\n\n@code\n{\n\tLobbyDirector Dir =\u003E LobbyDirector.Current;\n\n\tstring TimerText\n\t{\n\t\tget\n\t\t{\n\t\t\tint t = Dir?.TimeLeftSeconds ?? 0;\n\t\t\tif ( t \u003C 0 ) t = 0;\n\t\t\treturn $\u0022{t / 60:D2}:{t % 60:D2}\u0022;\n\t\t}\n\t}\n\n\tprotected override int BuildHash() =\u003E System.HashCode.Combine(\n\t\tDir?.State, Dir?.MenuOpen, Dir?.SuggestMenuOpen, Dir?.RoundLive,\n\t\tDir?.TimeLeftSeconds, Dir?.StatusMessage, Dir?.ChatLine, Dir?.ChatVisible );\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"ILobbyAgent.cs","FileName":"ILobbyAgent.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"namespace LobbySystem;\n\n/// \u003Csummary\u003E\n/// Implemented by a player or bot pawn so the lobby can count, place, reset and teleport it without knowing\n/// anything about the gameplay. Keep roles (who is \u0022it\u0022, score, team) on your own type and cast back to it\n/// inside an \u003Csee cref=\u0022IGameMode\u0022/\u003E.\n/// \u003C/summary\u003E\npublic interface ILobbyAgent : IValid\n{\n\t/// \u003Csummary\u003ETrue for AI pawns.\u003C/summary\u003E\n\tbool IsBot { get; }\n\n\t/// \u003Csummary\u003ETrue on peers that don\u0027t own this pawn.\u003C/summary\u003E\n\tbool IsProxy { get; }\n\n\t/// \u003Csummary\u003ESteam name for players, a label for bots.\u003C/summary\u003E\n\tstring DisplayName { get; }\n\n\t/// \u003Csummary\u003EUsed for spawn placement and proximity checks.\u003C/summary\u003E\n\tVector3 WorldPosition { get; }\n\n\t/// \u003Csummary\u003EHost brands the pawn just after spawn. Network these fields.\u003C/summary\u003E\n\tvoid InitAgent( bool isBot, string displayName );\n\n\t/// \u003Csummary\u003EReturn all gameplay state to a neutral pre-round condition.\u003C/summary\u003E\n\tvoid ResetForRound();\n\n\t/// \u003Csummary\u003EMove the pawn on every peer. Implement with \u003Cc\u003E[Rpc.Broadcast]\u003C/c\u003E.\u003C/summary\u003E\n\tvoid TeleportTo( Vector3 position );\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"LobbyState.cs","FileName":"LobbyState.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"namespace LobbySystem;\n\n/// \u003Csummary\u003ELifecycle of the lobby: Lobby, then Active, then Ended before looping back.\u003C/summary\u003E\npublic enum LobbyState\n{\n\t/// \u003Csummary\u003EFree roam before and after a round. The mode button works here.\u003C/summary\u003E\n\tLobby,\n\t/// \u003Csummary\u003EA round is running.\u003C/summary\u003E\n\tActive,\n\t/// \u003Csummary\u003ERound finished; results show before returning to the lobby.\u003C/summary\u003E\n\tEnded\n}\n"},{"Ident":"rwtrcsc.lobbysystem","Path":".obj/__compiler_extra.cs","FileName":"__compiler_extra.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"global using static Sandbox.Internal.GlobalGameNamespace;\r\nglobal using Microsoft.AspNetCore.Components;\r\nglobal using Microsoft.AspNetCore.Components.Rendering;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonTitle\u0022, \u0022Lobby System\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonIdent\u0022, \u0022lobbysystem\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022OrgIdent\u0022, \u0022rwtrcsc\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022Ident\u0022, \u0022rwtrcsc.lobbysystem\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022CompileTime\u0022, \u00226/15/2026 4:29:44 PM\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineVersion\u0022, \u002225\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineMinorVersion\u0022, \u00221\u0022 )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \u0022.NETCoreApp,Version=v9.0\u0022, FrameworkDisplayName = \u0022.NET 9.0\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\u00220.0.112.0\u0022)]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\u00220.0.112.0\u0022)]"},{"Ident":"rwtrcsc.lobbysystem","Path":"Code/Assembly.cs","FileName":"Assembly.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"global using Sandbox;\nglobal using System;\nglobal using System.Collections.Generic;\nglobal using System.Linq;\nglobal using System.Threading.Tasks;\n"},{"Ident":"rwtrcsc.lobbysystem","Path":"Code/LobbyModeButton.cs","FileName":"LobbyModeButton.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":281752,"IsPrivate":false,"Code":"namespace LobbySystem;\n\n/// \u003Csummary\u003E\n/// In-world button that opens the mode menu for the host, or a local suggestion menu for a client. It needs\n/// a ModelRenderer to be visible and is hidden while a round is live. When the local player is within\n/// \u003Csee cref=\u0022UseRange\u0022/\u003E and presses Use, the menu opens.\n/// \u003C/summary\u003E\npublic sealed class LobbyModeButton : Component\n{\n\t[Property] public float UseRange { get; set; } = 130f;\n\t[Property] public bool GlowWhenInRange { get; set; } = true;\n\t[Property] public Color IdleTint { get; set; } = new Color( 0.85f, 0.4f, 0.15f );\n\t[Property] public Color ActiveTint { get; set; } = new Color( 1f, 0.85f, 0.3f );\n\n\tModelRenderer _renderer;\n\tILobbyAgent _me;\n\n\tprotected override void OnStart()\n\t{\n\t\t_renderer = Components.Get\u003CModelRenderer\u003E() ?? Components.GetInChildren\u003CModelRenderer\u003E();\n\t\tif ( _renderer is not null ) _renderer.Tint = IdleTint;\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\tvar dir = LobbyDirector.Current;\n\t\tbool inLobby = dir is null || !dir.RoundLive;\n\t\tif ( _renderer is not null \u0026\u0026 _renderer.Enabled != inLobby )\n\t\t\t_renderer.Enabled = inLobby;\n\t\tif ( !inLobby ) return;\n\n\t\tvar me = LocalPlayer();\n\t\tbool inRange = me is not null \u0026\u0026 WorldPosition.Distance( me.WorldPosition ) \u003C= UseRange;\n\n\t\tif ( GlowWhenInRange \u0026\u0026 _renderer is not null )\n\t\t\t_renderer.Tint = inRange ? ActiveTint : IdleTint;\n\n\t\tif ( inRange \u0026\u0026 Input.Pressed( \u0022Use\u0022 ) )\n\t\t\tdir?.RequestModeMenu();\n\t}\n\n\tILobbyAgent LocalPlayer()\n\t{\n\t\tif ( _me is not null \u0026\u0026 _me.IsValid() \u0026\u0026 !_me.IsBot \u0026\u0026 !_me.IsProxy ) return _me;\n\t\ttry { _me = Scene.GetAllComponents\u003CILobbyAgent\u003E().FirstOrDefault( c =\u003E c.IsValid() \u0026\u0026 !c.IsBot \u0026\u0026 !c.IsProxy ); }\n\t\tcatch { _me = null; }\n\t\treturn _me;\n\t}\n}\n"}]}