swb_player/PlayerBase.cs
using SWB.Shared;
using System;
using System.Collections.Generic;
using System.Linq;
namespace SWB.Player;
[Group( "SWB" )]
[Title( "PlayerBase" )]
public partial class PlayerBase : Component, Component.INetworkSpawn, IPlayerBase
{
public static PlayerBase Local { get; private set; }
public static List<PlayerBase> All { get; private set; } = new();
[Property] public GameObject Head { get; set; }
[Property] public GameObject Body { get; set; }
[Property] public SkinnedModelRenderer BodyRenderer { get; set; }
[Property] public CameraComponent Camera { get; set; }
[Property] public CameraComponent ViewModelCamera { get; set; }
[Property] public PanelComponent RootDisplay { get; set; }
[Property] public Voice Voice { get; set; }
[Sync] public bool IsBot { get; set; }
public IInventory Inventory { get; set; }
public ICameraMovement CameraMovement { get; set; }
public bool IsFirstPerson => !IsBot ? CameraMovement.IsFirstPerson : false;
public string DisplayName => !IsBot ? (Network.Owner?.DisplayName ?? "Disconnected") : GameObject.Name;
public SteamId SteamId => !IsBot ? Network.Owner.SteamId : new( 0 );
public bool IsHost => !IsBot && Network.Owner.IsHost;
public bool IsSpeaking => !IsBot ? Voice.Amplitude > 0 : false;
bool taggedLights;
public float InputSensitivity
{
get { return CameraMovement.InputSensitivity; }
set { CameraMovement.InputSensitivity = value; }
}
public float FieldOfView
{
get
{
return Camera.FieldOfView;
}
set
{
Camera.FieldOfView = value;
}
}
Guid IPlayerBase.Id { get => GameObject.Id; }
protected override void OnAwake()
{
All.Add( this );
Inventory = Components.Create<Inventory>();
CameraMovement = Components.GetInChildren<CameraMovement>();
OnMovementAwake();
// Reset body animations, only works here
BodyRenderer.OnComponentDisabled += () =>
{
BodyRenderer.ClearParameters();
};
// Hack: Hide client until fully loaded in OnStart
WorldPosition = new( 0, 0, -999999 );
Network.ClearInterpolation();
}
public virtual void OnNetworkSpawn( Connection connection ) { }
protected override void OnDestroy()
{
if ( RagdollGO.IsValid() )
RagdollGO.Destroy();
if ( Local == this )
Local = null;
All.Remove( this );
}
protected override void OnStart()
{
if ( !IsProxy && !IsBot )
{
Local = this;
OnInputDeviceSwitch();
}
ApplyClothes();
if ( IsProxy || IsBot )
{
if ( Camera is not null )
{
Camera.Enabled = false;
Camera.GameObject.Enabled = false;
}
if ( ViewModelCamera is not null )
{
ViewModelCamera.Enabled = false;
ViewModelCamera.GameObject.Enabled = false;
}
}
if ( IsBot )
{
var screenPanel = Components.GetInChildrenOrSelf<ScreenPanel>();
if ( screenPanel is not null )
screenPanel.Enabled = false;
}
if ( !IsProxy )
Respawn();
}
[Rpc.Owner]
public void Kill()
{
if ( !IsAlive ) return;
Health = 0;
OnDeath( new() { Attacker = GameObject } );
}
[Rpc.Owner]
public void Kick( string reason )
{
Log.Info( reason );
Game.Disconnect();
}
[Rpc.Broadcast]
public virtual void OnDeath( Shared.DamageInfo info )
{
if ( !IsValid ) return;
var attackerGO = info.Attacker;
if ( attackerGO is not null && !attackerGO.IsProxy )
{
var attacker = attackerGO.Components.Get<PlayerBase>();
if ( attacker is not null && attacker != this )
attacker.Kills++;
}
if ( IsProxy ) return;
Deaths++;
Ragdoll( info.Force, info.Origin, CharacterController.Velocity );
CharacterController.Velocity = 0;
Inventory.Clear();
RespawnWithDelay( 2 );
}
public async virtual void RespawnWithDelay( float delay, Transform? respawnAt = null )
{
await GameTask.DelaySeconds( delay );
Respawn( respawnAt );
}
[Rpc.Broadcast]
public void RespawnWithDelayBroadCast( float delay, Transform? respawnAt = null )
{
RespawnWithDelay( delay, respawnAt );
}
[Rpc.Broadcast]
public void RespawnBroadCast( Transform? respawnAt = null )
{
Respawn( respawnAt );
}
public virtual void Respawn( Transform? respawnAt = null )
{
// Only works when player has spawned
if ( !taggedLights && !IsBot )
{
taggedLights = true;
MapUtil.TagLights();
OverlayUIWorkaround();
}
if ( IsUsingController )
stickyActiveButtons.Clear();
Ammo.Clear();
Inventory.Clear();
Health = MaxHealth;
CharacterController.Velocity = Vector3.Zero;
WishVelocity = Vector3.Zero;
var spawnPos = Vector3.Zero;
var spawnRot = Rotation.Identity;
if ( respawnAt.HasValue )
{
spawnPos = respawnAt.Value.Position;
spawnRot = respawnAt.Value.Rotation.Angles();
}
else
{
var spawnLocation = GetSpawnLocation();
spawnPos = spawnLocation.Position;
spawnRot = spawnLocation.Rotation;
}
WorldPosition = spawnPos;
if ( IsFirstPerson || IsBot )
EyeAngles = spawnRot.Angles();
else
Camera.WorldRotation = spawnRot;
Network.ClearInterpolation();
Unragdoll();
}
public virtual Transform GetSpawnLocation()
{
var spawnPoints = Scene.Components.GetAll<SpawnPoint>().ToList();
if ( spawnPoints.Count == 0 )
return new Transform();
var randomSpawnPoint = spawnPoints[Random.Shared.Next( spawnPoints.Count )];
return randomSpawnPoint.Transform.World;
}
async void OverlayUIWorkaround()
{
Camera.IsMainCamera = false;
await GameTask.Delay( 1000 );
if ( Camera.IsValid() )
Camera.IsMainCamera = true;
}
protected override void OnUpdate()
{
if ( !IsProxy && !IsBot )
{
ViewModelCamera.Enabled = IsFirstPerson && IsAlive;
HandleFlinch();
HandleScreenShake();
}
if ( IsAlive )
OnMovementUpdate();
UpdateClothes();
}
protected override void OnFixedUpdate()
{
if ( !IsAlive ) return;
HandleMovementImpacts();
OnMovementFixedUpdate();
if ( !IsProxy && !IsBot && IsUsingController != Input.UsingController )
{
OnInputDeviceSwitch();
}
}
public void TriggerAnimation( Animations animation )
{
string animationName = animation switch
{
Animations.Attack => "b_attack",
Animations.Reload => "b_reload",
_ => ""
};
if ( animationName == "" ) return;
BodyRenderer.Set( animationName, true );
}
public void ApplyEyeAnglesOffset( Angles offset )
{
CameraMovement?.EyeAnglesOffset += offset;
}
public void ParentToBone( GameObject weaponObject, string boneName, bool deleteOnFail = true, Action<GameObject> onFail = null )
{
ModelUtil.ParentToBone( weaponObject, BodyRenderer, boneName, deleteOnFail: deleteOnFail, onFail: onFail );
}
}