Partial Player class extension that manages lobby behavior and animations. It syncs lobby state and look offsets, updates player position/rotation to a lobby slot, drives the animation helper for idle lobby pose, provides a debug console command and debug overlay, and contains EnterLobby RPC to initialize lobby state.
using Sandbox;
using Sandbox.Citizen;
using Sandbox.Diagnostics;
using System;
public partial class Player
{
[Sync( SyncFlags.FromHost )] public int LobbySlotIndex { get; set; } = -1;
[Sync] public Vector2 LobbyLookMouseOffset { get; set; }
[Sync] public bool IsInLobby { get; set; }
private Vector2 _displayLobbyLookMouseOffset;
public static bool DebugLobbyAnim { get; set; }
[ConCmd( "debug_lobby_anim" )]
public static void ToggleDebugLobbyAnim()
{
DebugLobbyAnim = !DebugLobbyAnim;
Log.Info( $"debug_lobby_anim: {DebugLobbyAnim}" );
}
public void UpdateLobby()
{
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"IsNoclipping: {AnimationHelper.IsNoclipping}", new global::Transform( WorldPosition + Vector3.Up * 10f ) );
var container = Manager.Instance.CameraContainer;
var cameraPos = container.WorldPosition;
var associatedSlot = Scene.GetAllComponents<LobbyPlayerSlot>().FirstOrDefault( x => x.SlotIndex == LobbySlotIndex );
if ( associatedSlot != null )
{
WorldPosition = associatedSlot.WorldPosition;
var yaw = Rotation.LookAt( cameraPos - WorldPosition, Vector3.Up ).Yaw();
Model.LocalRotation = new Angles( 0f, yaw, 0f );
}
if ( IsProxy )
{
if ( _displayLobbyLookMouseOffset.LengthSquared <= 0.0001f )
_displayLobbyLookMouseOffset = LobbyLookMouseOffset;
_displayLobbyLookMouseOffset = Vector2.Lerp( _displayLobbyLookMouseOffset, LobbyLookMouseOffset, RealTime.Delta * 16f );
}
else
{
_displayLobbyLookMouseOffset = LobbyLookMouseOffset;
}
var lookPos = cameraPos + new Vector3( 0f, -_displayLobbyLookMouseOffset.x, _displayLobbyLookMouseOffset.y ) * 200f;
AnimationHelper.WithLook( lookPos - (WorldPosition + Vector3.Up * 60f) );
AnimationHelper.IsNoclipping = false;
AnimationHelper.IsGrounded = true; // set every frame, setting once in EnterLobby can be lost before the animgraph is ready in published builds
if ( _swapAnimTimeRemaining <= 0f )
AnimationHelper.HoldType = CitizenAnimationHelper.HoldTypes.Shotgun;
AnimationHelper.WithVelocity( Vector3.Zero );
AnimationHelper.WithWishVelocity( Vector3.Zero );
AnimationHelper.Target.Set( "scale_height", 1f ); // not sure why this is necessary, but when game first starts the player has 1.2 height in lobby
if ( IsProxy )
return;
LobbyLookMouseOffset = new Vector2(Utils.Map(Mouse.Position.x, 0f, Screen.Width, -1f, 1f), Utils.Map( Mouse.Position.y, 0f, Screen.Height, 1f, -1f ) );
_displayLobbyLookMouseOffset = LobbyLookMouseOffset;
if ( DebugLobbyAnim )
DrawLobbyAnimDebug();
}
void DrawLobbyAnimDebug()
{
var t = AnimationHelper.Target;
if ( !t.IsValid() )
{
DebugOverlay.ScreenText( new Vector2( 20f, 200f ), "AnimationHelper.Target is null", flags: TextFlag.LeftTop );
return;
}
var graph = t.SceneModel?.AnimationGraph;
var groundedLookup = "no graph";
if ( graph.IsValid() )
{
groundedLookup = graph.TryGetParameterIndex( "b_grounded", out var idx )
? $"idx {idx} name '{graph.GetParameterName( idx )}' type {graph.GetParameterType( "b_grounded" )?.Name}"
: $"NOT FOUND (type {graph.GetParameterType( "b_grounded" )?.Name})";
groundedLookup += $" params {graph.ParamCount}";
}
var renderers = Model.GetComponentsInChildren<SkinnedModelRenderer>();
var text = $"grounded {t.GetBool( "b_grounded" )} noclip {t.GetBool( "b_noclip" )} swim {t.GetBool( "b_swim" )} sit {t.GetBool( "b_sit" )}\n" +
$"b_grounded lookup: {groundedLookup}\n" +
$"holdtype {t.GetInt( "holdtype" )} heel {t.GetFloat( "scale_heel" )} height {t.GetFloat( "scale_height" )} move_z {t.GetFloat( "move_z" )}\n" +
$"target model {t.Model?.ResourcePath} go '{t.GameObject.Name}'\n" +
$"graph {t.SceneModel?.AnimationGraph?.ResourcePath} sceneModel {t.SceneModel.IsValid()} useGraph {t.UseAnimGraph}\n" +
$"renderers under Model: {renderers.Count()} first {renderers.FirstOrDefault()?.GameObject.Name}\n" +
$"z {WorldPosition.z} modelLocal {Model.LocalPosition} IsInLobby {IsInLobby}";
DebugOverlay.ScreenText( new Vector2( 20f, 200f ), text, flags: TextFlag.LeftTop );
}
[Rpc.Broadcast( NetFlags.Reliable )]
public void EnterLobby()
{
IsInLobby = true;
var container = Manager.Instance.CameraContainer;
container.WorldPosition = Manager.LOBBY_CAMERA_POS;
container.WorldRotation = new Angles( 0f, 0f, 0f );
PhysicalCollider.Enabled = true;
AnimationHelper.HoldType = CitizenAnimationHelper.HoldTypes.Shotgun;
AnimationHelper.IsNoclipping = false;
AnimationHelper.IsGrounded = true;
SetGunVisible( true );
//if ( IsProxy )
// return;
//SetLobbyHoldType( GetRandomLobbyHoldType() );
}
//public CitizenAnimationHelper.HoldTypes GetRandomLobbyHoldType()
//{
// return CitizenAnimationHelper.HoldTypes.Shotgun;
//}
//[Rpc.Broadcast(NetFlags.Reliable)]
//public void SetLobbyHoldType( CitizenAnimationHelper.HoldTypes holdType)
//{
// AnimationHelper.HoldType = holdType;
//}
}