Code/PlayerMovement.SimulatedPhysics.cs
using Sandbox;
using Sandbox.Physics;
using System;
namespace XMovement;
public partial class PlayerMovement : Component
{
	[Sync]
	public Vector3 PhysicsBodyVelocity { get; set; }

	[ConVar] public static bool debug_playermovement { get; set; } = false;
	[Property, FeatureEnabled( "Physics Integration" )] public bool PhysicsIntegration { get; set; } = true;
	[Property, Feature( "Physics Integration" )] public float Mass { get; set; } = 85;
	[Property, Feature( "Physics Integration" )] public float PushScale { get; set; } = 0.7f;

	[Property, Feature( "Physics Integration" ), Group( "Shadow" )] public GameObject PhysicsShadow { get; set; }
	[Property, Feature( "Physics Integration" ), Group( "Shadow" )] public Rigidbody PhysicsShadowRigidbody;
	[Property, Feature( "Physics Integration" ), Group( "Shadow" )] public BoxCollider PhysicsShadowCollider;
	[Property, Feature( "Physics Integration" ), Group( "Body" )] public GameObject PhysicsBody { get; set; }
	[Property, Feature( "Physics Integration" ), Group( "Body" )] public Rigidbody PhysicsBodyRigidbody;
	[Property, Feature( "Physics Integration" ), Group( "Body" )] public BoxCollider PhysicsBodyCollider;
	bool PreviouslyOnGround = false;

	/// <summary>
	/// You can pre-create the shadow objects if you want to do so, otherwise they will be created at runtime.
	/// </summary>
	//[InfoBox( "You can pre-create the shadow objects if you want to do so, otherwise they will be created at runtime.", tint: EditorTint. )]
	[Button( "Pre Create Shadow Objects" ), Feature( "Physics Integration" )]

	void CreateShadowObjects()
	{
		if ( !PhysicsIntegration ) return;
		if ( IsProxy ) return;

		if ( PhysicsShadow.IsValid() ) return;
		if ( PhysicsBody.IsValid() ) return;

		var pair = new CollisionRules.Pair( "movement", "movement" );

		if ( !this.GameObject.Scene.PhysicsWorld.CollisionRules.Pairs.ContainsKey( pair ) )
		{
			this.GameObject.Scene.PhysicsWorld.CollisionRules.Pairs.Add( pair, CollisionRules.Result.Ignore );
		}


		PhysicsShadow = Scene.CreateObject();
		PhysicsShadow.SetParent( GameObject );
		PhysicsShadow.Name = "PhysicsShadow";
		PhysicsShadow.Tags.Add( "movement" );
		PhysicsShadow.NetworkMode = NetworkMode.Object;

		PhysicsBody = Scene.CreateObject();
		PhysicsBody.SetParent( GameObject );
		PhysicsBody.Name = "PhysicsBody";
		PhysicsBody.Tags.Add( "movement" );
		PhysicsBody.NetworkMode = NetworkMode.Object;


		PhysicsBodyRigidbody = PhysicsBody.Components.GetOrCreate<Rigidbody>();
		PhysicsBodyRigidbody.MassOverride = Mass;
		PhysicsBodyRigidbody.MassCenterOverride = Vector3.Zero;
		PhysicsBodyRigidbody.OverrideMassCenter = true;
		PhysicsBodyRigidbody.Gravity = false;
		PhysicsBodyRigidbody.Locking = new PhysicsLock() { Pitch = true, Roll = true };
		PhysicsBodyRigidbody.RigidbodyFlags = RigidbodyFlags.DisableCollisionSounds;
		PhysicsBodyCollider = PhysicsBody.Components.GetOrCreate<BoxCollider>();
		PhysicsBodyCollider.Scale = BoundingBox.Maxs + BoundingBox.Mins.Abs();
		PhysicsBodyCollider.Center = BoundingBox.Center;

		PhysicsShadowRigidbody = PhysicsShadow.Components.GetOrCreate<Rigidbody>();
		PhysicsShadowRigidbody.MassOverride = Mass;
		PhysicsShadowRigidbody.Locking = new PhysicsLock() { Pitch = true, Yaw = true, Roll = true };
		PhysicsShadowRigidbody.RigidbodyFlags = RigidbodyFlags.DisableCollisionSounds;
		PhysicsShadowCollider = PhysicsShadow.Components.GetOrCreate<BoxCollider>();
		PhysicsShadowCollider.Scale = BoundingBox.Maxs + BoundingBox.Mins.Abs();
		PhysicsShadowCollider.Center = BoundingBox.Center;

		var surf = Surface.FindByName( "xmove_phys" );

		PhysicsBodyCollider.Surface = surf;
		PhysicsShadowCollider.Surface = surf;

		PhysicsShadow.NetworkSpawn();
		PhysicsBody.NetworkSpawn();
	}
	void ResetSimulatedShadow()
	{
		if ( IsProxy ) return;
		if ( !PhysicsIntegration ) return;
		if ( !PhysicsBodyRigidbody.IsValid() ) return;
		if ( !PhysicsShadowRigidbody.IsValid() ) return;
		if ( !PhysicsBodyRigidbody.Enabled ) return;
		if ( !PhysicsShadowRigidbody.Enabled ) return;
		var shvel = Velocity * 1f;
		var whvel = WishVelocity * PushScale;

		shvel.x = MathF.MaxMagnitude( shvel.x, whvel.x );
		shvel.y = MathF.MaxMagnitude( shvel.y, whvel.y );
		shvel.z = MathF.MaxMagnitude( shvel.z, whvel.z );
		PhysicsShadowRigidbody.Velocity = shvel;

		PhysicsShadowCollider.Scale = BoundingBox.Maxs + BoundingBox.Mins.Abs();
		PhysicsShadowCollider.Center = BoundingBox.Center;

		PhysicsBodyCollider.Scale = BoundingBox.Maxs + BoundingBox.Mins.Abs();
		PhysicsBodyCollider.Center = BoundingBox.Center;


		if ( debug_playermovement ) DebugOverlay.Box( PhysicsShadowRigidbody.PhysicsBody.GetBounds(), Color.Blue, 1 );
		if ( debug_playermovement ) DebugOverlay.Box( PhysicsBodyRigidbody.PhysicsBody.GetBounds(), Color.Green, 1 );
	}
	void UpdateFromSimulatedShadow()
	{
		if ( !PhysicsIntegration ) return;
		if ( !PhysicsBodyRigidbody.IsValid() ) return;
		if ( !PhysicsShadowRigidbody.IsValid() ) return;
		if ( !PhysicsBodyRigidbody.Enabled ) return;
		if ( !PhysicsShadowRigidbody.Enabled ) return;

		// We do this so we don't slide down things and off ledges on static geometry or as soon as we land.
		bool OnDynamicGeometry = IsOnDynamicGeometry();
		if ( !OnDynamicGeometry )
		{
			PhysicsBodyRigidbody.AngularVelocity = Vector3.Zero;
			PhysicsBodyRigidbody.LocalRotation = Rotation.Identity;
		}

		//if ( DoGravity ) PhysicsBodyRigidbody.Velocity += Gravity * Time.Delta * 0.5f;
		var vel = PhysicsBodyRigidbody.Velocity;

		/*if ( IsStuck() )
		{
			//vel.z = MathF.Max( 0, vel.z );
			//WorldPosition += vel * Time.Delta;
		}*/
		if ( IsStuck() && PhysicsBodyRigidbody.WorldPosition != Vector3.Zero )
		{
			// If we're stuck, assume the physics simulated body is where we should be.
			var stuckpos = WorldPosition;
			var bodypos = PhysicsBodyRigidbody.WorldPosition;
			var delta = bodypos - stuckpos;
			WorldPosition = bodypos;// IsOnGround && !OnDynamicGeometry ? bodypos.WithZ( WorldPosition.z ) : bodypos;
									//MoveTo( bodypos, true );
			PhysicsBodyVelocity = delta;
		}
		else
		{
			PhysicsBodyVelocity = vel;// * Time.Delta;   
		}

		TryUnstuck();

		//if ( GroundObject == null && PreviouslyOnGround )
		//{
		//	PhysicsShadowVelocity = Vector3.Zero;
		//	PhysicsBodyRigidbody.Velocity = Vector3.Zero;
		//	Velocity += vel;
		//}

		if ( OnDynamicGeometry )
		{
			var a = PhysicsBodyRigidbody.AngularVelocity;
			var angles = new Angles( a.x, a.y, a.z );

			var axis = a.WithX( 0 ).WithY( 0 );

			var length = axis.Length;

			if ( MovementFrequency == MovementFrequencyMode.PerFixedUpdate ) length *= Time.Delta;
			if ( MovementFrequency == MovementFrequencyMode.PerUpdate ) length *= 1f;
			if ( length > 0 && length < 1 )
			{
				GameObject.WorldRotation = GameObject.WorldRotation.RotateAroundAxis( axis.Normal, length );
				GameObject.WorldRotation = GameObject.WorldRotation.RotateAroundAxis( Vector3.Up, PhysicsBodyRigidbody.WorldRotation.Angles().yaw );
			}
		}

		PhysicsBodyRigidbody.AngularVelocity = Vector3.Zero;
		PhysicsShadowRigidbody.AngularVelocity = Vector3.Zero;

		if ( OnDynamicGeometry )
		{
			PhysicsBodyRigidbody.Velocity -= Gravity * Time.Delta * 1f;
		}
		else if ( !IsStuck() && IsOnGround )
		{
			// we need "friction" here since we've got no gravity pulling us down, else we will keep sliding once pushed.
			PhysicsBodyRigidbody.Velocity = ApplyFriction( PhysicsBodyRigidbody.Velocity, GetFriction() * 1f, 0f );
		}

		// If we're on static ground, we want to stay on that ground, so lock moving up.
		var locking = PhysicsBodyRigidbody.Locking;
		locking.Z = IsOnGround && !OnDynamicGeometry;
		locking.Yaw = IsOnGround && !OnDynamicGeometry;
		PhysicsBodyRigidbody.Locking = locking;

		PreviouslyOnGround = GroundObject != null;
		ResetPhysSimPosition();
		ResetPhysSimVelocity();
	}

	bool IsOnDynamicGeometry()
	{
		bool onDynamicGeometry = true;
		if ( (GroundObject.IsValid() &&
		(
		GroundObject.Components.TryGet<MapCollider>( out var mapCollider ) ||
		GroundObject.Components.TryGet<Collider>( out var col ) && col.Static
		)
		)
		|| !GroundObject.IsValid() )
		{
			onDynamicGeometry = false;
		}
		return onDynamicGeometry;
	}

	void ResetPhysSimVelocity()
	{
		PhysicsShadowRigidbody.AngularVelocity = Vector3.Zero;
		PhysicsShadowRigidbody.Velocity = Vector3.Zero;

		PhysicsBodyRigidbody.AngularVelocity = Vector3.Zero;
		//PhysicsBodyRigidbody.Velocity = Vector3.Zero;
	}
	void ResetPhysSimPosition()
	{
		PhysicsShadowRigidbody.LocalPosition = Vector3.Zero;
		PhysicsShadowRigidbody.WorldRotation = Rotation.Identity;

		PhysicsBodyRigidbody.LocalPosition = Vector3.Zero;
		PhysicsBodyRigidbody.WorldRotation = Rotation.Identity;
	}
}