using Sandbox; using System; using System.Collections.Generic; namespace MyGame; public class PawnController : EntityComponent { public int StepSize => 24; public int GroundAngle => 45; public int JumpSpeed => 300; public float Gravity => 800f; HashSet ControllerEvents = new( StringComparer.OrdinalIgnoreCase ); bool Grounded => Entity.GroundEntity.IsValid(); public void Simulate( IClient cl ) { ControllerEvents.Clear(); var movement = Entity.InputDirection.Normal; var angles = Entity.ViewAngles.WithPitch( 0 ); var moveVector = Rotation.From( angles ) * movement * 320f; var groundEntity = CheckForGround(); if ( groundEntity.IsValid() ) { if ( !Grounded ) { Entity.Velocity = Entity.Velocity.WithZ( 0 ); AddEvent( "grounded" ); } Entity.Velocity = Accelerate( Entity.Velocity, moveVector.Normal, moveVector.Length, 200.0f * ( Input.Down( "run" ) ? 2.5f : 1f ), 7.5f ); Entity.Velocity = ApplyFriction( Entity.Velocity, 4.0f ); } else { Entity.Velocity = Accelerate( Entity.Velocity, moveVector.Normal, moveVector.Length, 100, 20f ); Entity.Velocity += Vector3.Down * Gravity * Time.Delta; } if ( Input.Pressed( "jump" ) ) { DoJump(); } var mh = new MoveHelper( Entity.Position, Entity.Velocity ); mh.Trace = mh.Trace.Size( Entity.Hull ).Ignore( Entity ); if ( mh.TryMoveWithStep( Time.Delta, StepSize ) > 0 ) { if ( Grounded ) { mh.Position = StayOnGround( mh.Position ); } Entity.Position = mh.Position; Entity.Velocity = mh.Velocity; } Entity.GroundEntity = groundEntity; } void DoJump() { if ( Grounded ) { Entity.Velocity = ApplyJump( Entity.Velocity, "jump" ); } } Entity CheckForGround() { if ( Entity.Velocity.z > 100f ) return null; var trace = Entity.TraceBBox( Entity.Position, Entity.Position + Vector3.Down, 2f ); if ( !trace.Hit ) return null; if ( trace.Normal.Angle( Vector3.Up ) > GroundAngle ) return null; return trace.Entity; } Vector3 ApplyFriction( Vector3 input, float frictionAmount ) { float StopSpeed = 100.0f; var speed = input.Length; if ( speed < 0.1f ) return input; // Bleed off some speed, but if we have less than the bleed // threshold, bleed the threshold amount. float control = (speed < StopSpeed) ? StopSpeed : speed; // Add the amount to the drop amount. var drop = control * Time.Delta * frictionAmount; // scale the velocity float newspeed = speed - drop; if ( newspeed < 0 ) newspeed = 0; if ( newspeed == speed ) return input; newspeed /= speed; input *= newspeed; return input; } Vector3 Accelerate( Vector3 input, Vector3 wishdir, float wishspeed, float speedLimit, float acceleration ) { if ( speedLimit > 0 && wishspeed > speedLimit ) wishspeed = speedLimit; var currentspeed = input.Dot( wishdir ); var addspeed = wishspeed - currentspeed; if ( addspeed <= 0 ) return input; var accelspeed = acceleration * Time.Delta * wishspeed; if ( accelspeed > addspeed ) accelspeed = addspeed; input += wishdir * accelspeed; return input; } Vector3 ApplyJump( Vector3 input, string jumpType ) { AddEvent( jumpType ); return input + Vector3.Up * JumpSpeed; } Vector3 StayOnGround( Vector3 position ) { var start = position + Vector3.Up * 2; var end = position + Vector3.Down * StepSize; // See how far up we can go without getting stuck var trace = Entity.TraceBBox( position, start ); start = trace.EndPosition; // Now trace down from a known safe position trace = Entity.TraceBBox( start, end ); if ( trace.Fraction <= 0 ) return position; if ( trace.Fraction >= 1 ) return position; if ( trace.StartedSolid ) return position; if ( Vector3.GetAngle( Vector3.Up, trace.Normal ) > GroundAngle ) return position; return trace.EndPosition; } public bool HasEvent( string eventName ) { return ControllerEvents.Contains( eventName ); } void AddEvent( string eventName ) { if ( HasEvent( eventName ) ) return; ControllerEvents.Add( eventName ); } }