VrcharacterController.cs
using Sandbox;
using Sandbox.Citizen;
using Sandbox.VR;
using System;
using static Sandbox.Gizmo;
using static Sandbox.VertexLayout;

public sealed class VrCharacterController : Component
{
	[RequireComponent] VRAnchor VRAnchor { get; set; }
	[Property] CameraComponent Camera { get; set; }
	private PlayerCharacterController PlayerCharacterController { get; set; }

	// how far the head is allowed to drift before we shift the world
	[Property] public float RoomScaleLimit { get; set; } = 0.4f;

	/// <summary>Eye height in units when standing upright (5'9" avatar ≈ 64).</summary>
	[Property] public float StandingEyeHeight { get; set; } = 64f;

	/// <summary>Eye height in units at which duck level reaches 1 (fully crouched).</summary>
	[Property] public float CrouchedEyeHeight { get; set; } = 34f;

	/// <summary>When false, headset tracking still runs but locomotion/turning/duck are skipped (join calibration).</summary>
	[Property] public bool MovementEnabled { get; set; } = true;

	private Scoreboard Scoreboard { get; set; }

	private KillFeed KillFeed { get; set; }

	protected override void OnStart()
	{
		if ( GetComponentInParent<VrCalibrationRig>().IsValid() )
			MovementEnabled = false;

		if ( !MovementEnabled )
			return;

		PlayerCharacterController = GetComponentInParent<PlayerCharacterController>();
		PlayerCharacterController.Camera = Camera;
		Scoreboard = PlayerCharacterController.GetComponentInChildren<Scoreboard>();
		KillFeed = PlayerCharacterController.GetComponentInChildren<KillFeed>();

		if ( !IsProxy && PlayerSession.CurrentSession.IsValid() )
		{
			StandingEyeHeight = PlayerSession.CurrentSession.StandingEyeHeight;
			var scale = StandingEyeHeight / PlayerCharacterController.ReferenceStandingEyeHeight;
			CrouchedEyeHeight = 34f * scale;
			// Reserved spawn was only for calibration → first pawn; later respawns pick randomly.
			PlayerSession.CurrentSession.HasReservedSpawn = false;
		}
		else if ( PlayerCharacterController.IsValid() )
		{
			CrouchedEyeHeight = 34f * PlayerCharacterController.HeightScale;
		}
	}

	protected override void OnUpdate()
	{
		if ( !MovementEnabled )
			return;

		if ( !NetworkManager.Instance.IsMapReady )
		{
			PlayerCharacterController.Velocity = Vector3.Zero;
			PlayerCharacterController.SyncedVelocity = Vector3.Zero;
			return;
		}

		UpdateScoreboard();
		HandleVRMovement();
		UpdateAnimationHandsPlacement();
	}

	private void HandleVRMovement()
	{
		// Head reads below go through Input.VR.Head, which resolves against Input.VR.Anchor.
		// Refresh the anchor before reading and again after every step that moves the rig.
		RefreshVrAnchor();
		ApplyRoomScale();
		HandleVRTurning();
		RefreshVrAnchor(); // turning rotated the rig about the head
		UpdateDuckLevel();
		CalculateVelocity();
		PlayerCharacterController.Move();
		PlayerCharacterController.UpdateFallAndBoundsDamage();
		RefreshVrAnchor(); // Move() translated the rig
	}

	/// <summary>This frame's headset pose in world space. Valid only while Input.VR.Anchor is current.</summary>
	private Transform Head => Input.VR.Head;

	/// <summary>
	/// Every Input.VR pose (hands, head, VRHand joints, VRTrackedObject) is resolved lazily as
	/// Input.VR.Anchor.ToWorld(devicePose), and that global is only written by the engine VRAnchor
	/// component's OnUpdate/OnPreRender. s&box dispatches OnUpdate/OnPreRender from flat component
	/// sets with no ordering guarantee, so VRAnchor usually runs before this component moves the
	/// player root and the anchor then points at last frame's rig pose until VRAnchor.OnPreRender
	/// happens to run. Any consumer enumerated before it (held weapon GrabPoint, hand meshes, camera)
	/// renders one locomotion step behind. Writing the anchor right after the rig moves removes
	/// the stale window for every consumer regardless of component order.
	/// </summary>
	private void RefreshVrAnchor()
	{
		Input.VR.Anchor = VRAnchor.WorldTransform;
	}

	private void UpdateScoreboard()
	{
		Scoreboard.Enabled = Input.VR.LeftHand.ButtonA.IsPressed || DeathmatchGame.Instance.State == MatchState.ENDED;
		KillFeed.Enabled = !Scoreboard.Enabled;
	}

	private void UpdateDuckLevel()
	{
		float headHeight = Head.Position.z - GameObject.Parent.WorldPosition.z;
		float duck = MathX.Remap( headHeight, CrouchedEyeHeight, StandingEyeHeight, 1f, 0f );
		PlayerCharacterController.SetDuckLevel( duck );
	}

	private void UpdateAnimationHandsPlacement()
	{
		PlayerCharacterController.AnimationLeftHand.WorldPosition = Input.VR.LeftHand.Transform.Position;
		PlayerCharacterController.AnimationLeftHand.WorldRotation = Input.VR.LeftHand.Transform.Rotation;
		PlayerCharacterController.AnimationRightHand.WorldPosition = Input.VR.RightHand.Transform.Position;
		PlayerCharacterController.AnimationRightHand.WorldRotation = Input.VR.RightHand.Transform.Rotation;
	}

	private void ApplyRoomScale()
	{
		// 1. Get this frame's headset position relative to the Player Root.
		// Input.VR.Head is used instead of the camera GameObject so we react to the current
		// tracking sample rather than the pose the camera was given last PreRender.
		Vector3 headWorldOffset = Head.Position - GameObject.Parent.WorldPosition;

		// 2. Isolate the horizontal floor movement (ignore head bobbing up/down)
		Vector3 flatOffset = new( headWorldOffset.x, headWorldOffset.y, 0 );

		// 3. If the player has walked away from the center of the capsule
		if ( flatOffset.Length > 0.01f )
		{
			// Move the entire physics capsule to sit right under the player's head
			GameObject.Parent.WorldPosition += flatOffset;

			// Counter-shift the VR rig backward by the exact same amount.
			// This keeps the virtual camera perfectly still in world space 
			// while the underlying physics capsule catches up underneath them.
			VRAnchor.WorldPosition -= flatOffset;
		}
	}

	private void CalculateVelocity()
	{
		var input = Input.VR.LeftHand.Joystick.Value;

		// use head yaw only
		var yaw = Head.Rotation.Angles().yaw;
		var rot = Rotation.FromYaw( yaw );

		var moveDir =
			(rot.Forward * input.y) +
			(rot.Left * -input.x);

		if ( moveDir.Length > 1f )
			moveDir = moveDir.Normal;

		if ( PlayerCharacterController.IsGrappling )
		{
			var wishVel = moveDir * PlayerCharacterController.MoveSpeed;
			if ( Input.VR.RightHand.ButtonA.IsPressed && PlayerCharacterController.TryJump( wishVel.WithZ( 0 ) ) )
			{
				PlayerCharacterController.SyncedVelocity = PlayerCharacterController.Velocity;
				return;
			}

			PlayerCharacterController.ApplyGrappleVelocity();
			return;
		}

		var vel = PlayerCharacterController.Velocity;

		vel.x = moveDir.x * PlayerCharacterController.MoveSpeed;
		vel.y = moveDir.y * PlayerCharacterController.MoveSpeed;

		if ( Input.VR.RightHand.ButtonA.IsPressed && PlayerCharacterController.TryJump( vel.WithZ( 0 ) ) )
		{
			vel = PlayerCharacterController.Velocity;
		}
		else if ( PlayerCharacterController.IsOnGround )
		{
			vel.z = 0;
			PlayerCharacterController.Velocity = vel;
		}
		else
		{
			vel.z -= PlayerCharacterController.Gravity * Time.Delta;
			vel.z = MathF.Max( vel.z, -PlayerCharacterController.TerminalVelocity );
			PlayerCharacterController.Velocity = vel;
		}

		PlayerCharacterController.SyncedVelocity = PlayerCharacterController.Velocity;
	}

	private void HandleVRTurning()
	{
		// Get the horizontal input from the right joystick (standard for turning)
		float turnInput = Input.VR.RightHand.Joystick.Value.x;

		// Deadzone check to prevent drift
		if ( MathF.Abs( turnInput ) < 0.1f )
			return;

		// Calculate how much we want to rotate this frame
		float yawDelta = -turnInput * PlayerCharacterController.TurnSpeed * Time.Delta;
		Rotation turnRotation = Rotation.FromYaw( yawDelta );

		// ---- THE VR TURNING TRICK ----
		// We must rotate around the player's actual head position, not the root center.
		Vector3 headWorldPos = Head.Position;

		// 1. Rotate the root's orientation
		GameObject.Parent.WorldRotation = turnRotation * GameObject.Parent.WorldRotation;

		// 2. Pivot the root's position around the head
		// This stops the "swinging ride" effect if they are standing far from the center of their room.
		Vector3 offsetFromHead = GameObject.Parent.WorldPosition - headWorldPos;
		Vector3 rotatedOffset = turnRotation * offsetFromHead;

		// Adjust world position so the head stays exactly where it was before the rotation
		GameObject.Parent.WorldPosition = headWorldPos + rotatedOffset;
	}
}