swb_player/PlayerBase.Movement.cs
using Sandbox.Citizen;
using Sandbox.Sboku.Arena;
using SWB.Demo;
using SWB.Shared;
using static SWB.Demo.DemoPlayer;
namespace SWB.Player;
public partial class PlayerBase
{
[Property] public float FreeWalkSpeed { get; set; } = 450f;
[Property] public float FreeRunSpeed { get; set; } = 750f;
[Property] public float GroundControl { get; set; } = 4.0f;
[Property] public float AirControl { get; set; } = 0.1f;
[Property] public float MaxForce { get; set; } = 50f;
[Property] public float RunSpeed { get; set; } = 290f;
[Property] public float WalkSpeed { get; set; } = 160f;
[Property] public float CrouchSpeed { get; set; } = 90f;
[Property] public float JumpForce { get; set; } = 350f;
[Sync] public Vector3 WishVelocity { get; set; } = Vector3.Zero;
[Sync] public Angles EyeAngles { get; set; }
[Sync] public Vector3 EyeOffset { get; set; } = Vector3.Zero;
[Sync] public bool IsCrouching { get; set; } = false;
[Sync] public bool IsRunning { get; set; } = false;
[Sync] public bool CanMove { get; set; } = true;
public bool IsOnGround => CharacterController.IsOnGround;
public Vector3 Velocity => CharacterController.Velocity;
public Vector3 EyePos => Head.WorldPosition + EyeOffset;
public Angles EyeAnglesNormal => EyeAngles.Normal;
public CharacterController CharacterController { get; set; }
public CitizenAnimationHelper AnimationHelper { get; set; }
public CapsuleCollider BodyCollider { get; set; }
TimeSince timeSinceLastFootstep = 0;
void OnMovementAwake()
{
CharacterController = Components.Get<CharacterController>();
AnimationHelper = Components.Get<CitizenAnimationHelper>();
BodyCollider = Body.Components.Get<CapsuleCollider>();
if ( BodyRenderer is not null )
BodyRenderer.OnFootstepEvent += OnAnimEventFootstep;
}
void OnMovementUpdate()
{
if ( !IsProxy )
{
IsRunning = Input.Down( InputButtonHelper.Run );
if ( Input.Pressed( InputButtonHelper.Jump ) )
Jump();
UpdateCrouch();
}
RotateBody();
UpdateAnimations();
}
void OnMovementFixedUpdate()
{
if ( IsProxy ) return;
BuildWishVelocity();
Move();
}
void BuildWishVelocity()
{
WishVelocity = 0;
if ( !CanMove ) return;
var rot = EyeAngles.ToRotation();
if ( Input.Down( InputButtonHelper.Forward ) ) WishVelocity += rot.Forward;
if ( Input.Down( InputButtonHelper.Backward ) ) WishVelocity += rot.Backward;
if ( Input.Down( InputButtonHelper.Left ) ) WishVelocity += rot.Left;
if ( Input.Down( InputButtonHelper.Right ) ) WishVelocity += rot.Right;
WishVelocity = WishVelocity.WithZ( 0 );
if ( !WishVelocity.IsNearZeroLength ) WishVelocity = WishVelocity.Normal;
float speed;
if (this is DemoPlayer demo && demo.CurrentMovementMode == MovementMode.Free)
{
// 🚀 FREEMODE
speed = IsRunning ? FreeRunSpeed : FreeWalkSpeed;
}
else
{
// 🧱 GRID / NORMAALI
if (IsCrouching) speed = CrouchSpeed;
else if (IsRunning) speed = RunSpeed;
else speed = WalkSpeed;
}
WishVelocity *= speed * UpgradeHolder.SpeedMultiplier;
}
void Move()
{
var gravity = Scene.PhysicsWorld.Gravity;
if ( IsOnGround )
{
// Friction / Acceleration
CharacterController.Velocity = CharacterController.Velocity.WithZ( 0 );
CharacterController.Accelerate( WishVelocity );
CharacterController.ApplyFriction( GroundControl );
}
else
{
// Air control / Gravity
CharacterController.Velocity += gravity * Time.Delta * 0.5f;
CharacterController.Accelerate( WishVelocity.ClampLength( MaxForce ) );
CharacterController.ApplyFriction( AirControl );
}
if ( !(CharacterController.Velocity.IsNearZeroLength && WishVelocity.IsNearZeroLength) )
CharacterController.Move();
// Second half of gravity after movement (to stay accurate)
if ( IsOnGround )
{
CharacterController.Velocity = CharacterController.Velocity.WithZ( 0 );
}
else
{
CharacterController.Velocity += gravity * Time.Delta * 0.5f;
}
}
void RotateBody()
{
var targetRot = new Angles( 0, EyeAngles.ToRotation().Yaw(), 0 ).ToRotation();
float rotateDiff = Body.WorldRotation.Distance( targetRot );
if ( rotateDiff > 20f || CharacterController.Velocity.Length > 10f )
{
Body.WorldRotation = Rotation.Lerp( Body.WorldRotation, targetRot, Time.Delta * 2f );
}
}
void Jump()
{
if ( !IsOnGround ) return;
CharacterController.Punch( Vector3.Up * JumpForce );
AnimationHelper?.TriggerJump();
}
void UpdateAnimations()
{
if ( AnimationHelper is null ) return;
AnimationHelper.WithWishVelocity( WishVelocity );
AnimationHelper.WithVelocity( CharacterController.Velocity );
AnimationHelper.AimAngle = EyeAngles.ToRotation();
AnimationHelper.IsGrounded = IsOnGround;
AnimationHelper.WithLook( EyeAngles.ToRotation().Forward, 1f, 0.75f, 0.5f );
AnimationHelper.MoveStyle = CitizenAnimationHelper.MoveStyles.Run;
AnimationHelper.DuckLevel = IsCrouching ? 1 : 0;
}
void UpdateCrouch()
{
if ( Input.Down( InputButtonHelper.Duck ) && !IsCrouching && IsOnGround )
{
IsCrouching = true;
CharacterController.Height /= 2f;
BodyCollider.End = BodyCollider.End.WithZ( BodyCollider.End.z / 2f );
}
if ( IsCrouching && (!Input.Down( InputButtonHelper.Duck ) || !IsOnGround) )
{
// Check we have space to uncrouch
var targetHeight = CharacterController.Height * 2f;
var upTrace = CharacterController.TraceDirection( Vector3.Up * targetHeight );
if ( !upTrace.Hit )
{
IsCrouching = false;
CharacterController.Height = targetHeight;
BodyCollider.End = BodyCollider.End.WithZ( BodyCollider.End.z * 2f );
}
}
}
void OnAnimEventFootstep( SceneModel.FootstepEvent footstepEvent )
{
if ( !IsAlive || !IsOnGround ) return;
// Walk
var stepDelay = 0.25f;
// Running
if ( Velocity.WithZ( 0 ).Length >= 200 )
{
stepDelay = 0.2f;
}
// Crouching
else if ( IsCrouching )
{
stepDelay = 0.4f;
}
if ( timeSinceLastFootstep < stepDelay )
return;
var tr = Scene.Trace.Ray( footstepEvent.Transform.Position, footstepEvent.Transform.Position + Vector3.Down * 20 )
.Radius( 1 )
.IgnoreGameObject( this.GameObject )
.Run();
if ( !tr.Hit ) return;
var sound = tr.Surface.PlayCollisionSound( footstepEvent.Transform.Position );
if ( sound is not null )
sound.Volume = footstepEvent.Volume;
timeSinceLastFootstep = 0;
}
}