Pawn/SkatePawn.cs
using Skateboard.Tricks;
using Skateboard.Utils;
using System;
namespace Skateboard.Player;
public sealed class SkatePawn : Component
{
public static SkatePawn Local => Game.ActiveScene.GetAll<SkatePawn>().FirstOrDefault( x => x.Network.IsOwner );
public enum BailType { Normal, Landing, Bail }
[Property] public SkinnedModelRenderer BodyRenderer { get; set; }
[Property] public SkinnedModelRenderer BoardRenderer { get; set; }
[Property] public GameObject Head { get; set; }
[Property] public string BodyModel { get; set; } = "models/skateanimations.vmdl";
[Property] public string BoardModel { get; set; } = "models/skateboard_animated.vmdl";
[Property] public TrickScoreHolder TrickScores { get; set; }
[Property] public SkateController Controller { get; set; }
[Property] public SkateAnimator Animator { get; set; }
[Property] public SkateInput Input { get; set; }
[Property] public SkateAudioHelper SkateAudio { get; set; }
[Property] public GrindAudioHelper GrindAudio { get; set; }
[Sync] public float TurnRight { get; set; }
[Sync] public bool Crouch { get; set; }
[Sync] public bool OnVert { get; set; }
[Sync] public Vector3 VertNormal { get; set; }
[Sync] public Vector3 Velocity { get; set; }
[Sync] public Rotation RealRotation { get; set; }
[Sync] public Guid OwningConnectionId { get; set; }
[Sync] public float BailTime { get; set; } = 1.5f;
[Sync] public bool Bailed { get; private set; }
[Sync] public Vector3 EyeLocalPosition { get; set; }
[Sync] public Rotation EyeLocalRotation { get; set; }
private float _timeBailed;
public GameObject Ragdoll { get; private set; }
public GameObject BoardRagdoll { get; private set; }
public Vector3 EyePosition
{
get => WorldTransform.PointToWorld( EyeLocalPosition );
set => EyeLocalPosition = WorldTransform.PointToLocal( value );
}
public Rotation EyeRotation
{
get => WorldTransform.RotationToWorld( EyeLocalRotation );
set => EyeLocalRotation = WorldTransform.RotationToLocal( value );
}
public bool Grinding => Controller?.OnGrind ?? false;
protected override void OnStart()
{
EnsureRenderers();
Input ??= Components.GetOrCreate<SkateInput>();
Controller ??= Components.GetOrCreate<SkateController>();
Animator ??= Components.GetOrCreate<SkateAnimator>();
TrickScores ??= Components.GetOrCreate<TrickScoreHolder>();
SkateAudio ??= Components.GetOrCreate<SkateAudioHelper>();
GrindAudio ??= Components.GetOrCreate<GrindAudioHelper>();
Components.GetOrCreate<Skateboard.ClothingModelSuffixStripper>();
Controller.Pawn = this;
Controller.Input = Input;
Controller.TrickScores = TrickScores;
Controller.Animator = Animator;
Input.Pawn = this;
Animator.Pawn = this;
Animator.Target = BodyRenderer;
Animator.BoardRenderer = BoardRenderer;
SkateAudio.Pawn = this;
GrindAudio.Pawn = this;
TrickScores.OnComboFinish += OnComboFinish;
TrickScores.OnComboFailed += OnComboFail;
TurnRight = 0f;
Crouch = false;
RealRotation = WorldRotation;
Velocity = 0f;
Bailed = false;
_timeBailed = 0f;
Tags.Add( "player" );
}
protected override void OnFixedUpdate()
{
if ( !IsLocallyControlled() )
return;
BodyRenderer.Model = Model.Load( BodyModel );
if ( Bailed )
{
_timeBailed += Time.Delta;
if ( _timeBailed >= BailTime )
RespawnAfterBail();
return;
}
}
private void EnsureRenderers()
{
BodyRenderer ??= Components.GetOrCreate<SkinnedModelRenderer>();
BodyRenderer.Model ??= Model.Load( BodyModel );
if ( BoardRenderer.IsValid() )
{
BoardRenderer.Model ??= Model.Load( BoardModel );
return;
}
var boardObject = new GameObject( GameObject, true, "Board" );
BoardRenderer = boardObject.Components.GetOrCreate<SkinnedModelRenderer>();
BoardRenderer.Model ??= Model.Load( BoardModel );
}
internal bool HasHelmet()
{
foreach ( var child in GameObject.Children )
{
if ( !child.IsValid() )
continue;
var renderer = child.Components.Get<ModelRenderer>();
if ( renderer?.Model is null )
continue;
var modelName = renderer.Model.Name?.ToLowerInvariant() ?? string.Empty;
if ( modelName.Contains( "helmet" ) || modelName.Contains( "hardhat" ) )
return true;
}
return false;
}
public void Bail( BailType bailType = BailType.Normal )
{
if ( Bailed || !IsLocallyControlled() )
return;
TrickScores.Failed = true;
Bailed = true;
var hasHelmet = HasHelmet();
var headPos = WorldPosition + WorldRotation.Up * 70f;
if ( bailType == BailType.Landing )
{
if ( !hasHelmet )
{
PlaySound( "impact-bullet-flesh", headPos );
}
else
{
PlaySound( "helmet_hit", headPos );
}
var to = headPos + Vector3.Down * 50f;
var headTr = Scene.Trace.Ray( headPos, to ).WithAnyTags( "solid", "player", "npc" ).Run();
if ( headTr.Hit )
{
// Decal placement can be added back if needed.
}
}
if ( bailType != BailType.Bail )
PlaySound( "body_fall", WorldPosition );
Head.Enabled = false;
CreateRagdoll();
BodyRenderer.Enabled = false;
Controller.Enabled = false;
Animator.Enabled = false;
BoardRenderer.Enabled = false;
_timeBailed = 0f;
}
public void CreateRagdoll()
{
if ( BodyRenderer is null )
return;
CreateBoardRagdoll();
var ragdollObject = BodyRenderer.GameObject.Clone(
BodyRenderer.WorldTransform,
Game.ActiveScene,
startEnabled: false,
name: $"{GameObject.Name}_ragdoll"
);
Ragdoll = ragdollObject;
DisableRagdollDressers( ragdollObject );
ragdollObject.Enabled = true;
SetClothingEnabled( BodyRenderer.GameObject, false );
SetClothingEnabled( ragdollObject, true );
var bodyModel = ragdollObject.Components.Get<SkinnedModelRenderer>();
if ( bodyModel is null )
return;
ragdollObject.DestroyAsync( 5f );
bodyModel.UseAnimGraph = false;
foreach ( var skinnedModelRenderer in bodyModel.Components.GetAll<SkinnedModelRenderer>() )
{
skinnedModelRenderer.RenderType = ModelRenderer.ShadowRenderType.On;
}
bodyModel.Tags.Add( "ragdoll" );
var modelPhysics = bodyModel.GetOrAddComponent<ModelPhysics>();
modelPhysics.Model = bodyModel.Model;
modelPhysics.Renderer = bodyModel;
modelPhysics.MotionEnabled = true;
foreach ( var body in modelPhysics.Bodies )
{
body.Component.Velocity += Velocity;
}
}
private void RespawnAfterBail()
{
RealRotation = MathLD.FromToRotation( Vector3.Up * RealRotation, Vector3.Up ) * RealRotation;
WorldRotation = RealRotation;
var respawn = RespawnHelper.FindRespawnPosition( WorldPosition );
if ( respawn.Found )
{
WorldPosition = respawn.Location;
Respawn();
return;
}
Respawn();
}
public void Respawn()
{
var spawn = Scene.GetAllComponents<SpawnPoint>().OrderBy( _ => Guid.NewGuid() ).FirstOrDefault();
if ( spawn is not null )
{
WorldTransform = spawn.WorldTransform.WithPosition( spawn.WorldPosition + Vector3.Up * 50f );
}
Controller.Enabled = true;
Animator.Enabled = true;
BodyRenderer.Enabled = true;
BoardRenderer.Enabled = true;
Head.Enabled = true;
Bailed = false;
_timeBailed = 0f;
Velocity = 0f;
Ragdoll = null;
BoardRagdoll = null;
SetClothingEnabled( BodyRenderer.GameObject, true );
Controller.ResetState();
}
private void CreateBoardRagdoll()
{
if ( !BoardRenderer.IsValid() )
return;
var boardObject = BoardRenderer.GameObject;
var boardRagdoll = boardObject.Clone(
boardObject.WorldTransform,
Game.ActiveScene,
startEnabled: false,
name: $"{boardObject.Name}_ragdoll"
);
BoardRagdoll = boardRagdoll;
var boardRenderer = boardRagdoll.Components.Get<SkinnedModelRenderer>();
if ( boardRenderer.IsValid() )
{
boardRenderer.UseAnimGraph = false;
boardRenderer.BoneMergeTarget = null;
var ragdollModel = Model.Load( "models/skateboard.vmdl" );
boardRenderer.Model = ragdollModel;
}
var collider = boardRagdoll.Components.GetOrCreate<ModelCollider>();
if ( boardRenderer.IsValid() )
collider.Model = boardRenderer.Model;
var body = boardRagdoll.Components.GetOrCreate<Rigidbody>();
body.MotionEnabled = true;
body.Velocity = Velocity;
boardRagdoll.Enabled = true;
boardRagdoll.DestroyAsync( 5f );
}
private static void SetClothingEnabled( GameObject root, bool enabled )
{
if ( root is null )
return;
foreach ( var child in root.GetAllObjects( false ) )
{
if ( child.Tags.Has( "clothing" ) )
child.Enabled = enabled;
}
}
private static void DisableRagdollDressers( GameObject root )
{
if ( root is null )
return;
foreach ( var dresser in root.Components.GetAll<Dresser>( FindMode.EverythingInSelfAndDescendants ) )
{
dresser.Enabled = false;
}
foreach ( var stripper in root.Components.GetAll<Skateboard.ClothingModelSuffixStripper>( FindMode.EverythingInSelfAndDescendants ) )
{
stripper.Enabled = false;
}
}
private void OnComboFinish()
{
ComboFinishClient();
ClientTrickUpdate();
}
private void OnComboFail()
{
ClientTrickUpdate();
}
[Rpc.Owner]
private void ComboFinishClient()
{
Sound.Play( "combo_finish", WorldPosition );
}
[Rpc.Owner]
private void ClientTrickUpdate()
{
if ( GameObject.Network.IsOwner )
TrickScoreHolder.OnLocalTrickScoreUpdate?.Invoke();
}
public void PlaySound( string eventName, Vector3 position )
{
if ( Networking.IsActive && !IsLocallyControlled() )
return;
PlaySoundRpc( eventName, position );
}
private bool IsLocallyControlled()
{
if ( !Networking.IsActive )
return true;
if ( GameObject.Network.IsOwner )
return true;
return OwningConnectionId == Connection.Local.Id;
}
[Rpc.Broadcast( NetFlags.Unreliable )]
private void PlaySoundRpc( string eventName, Vector3 position )
{
Sound.Play( eventName, position );
}
[ConCmd( "kill" )]
public static void Kill()
{
Local.Bail();
}
}