player/PlayerLeap.cs

Component attached to a player that implements a leap ability. It listens for the attack1 input, disables regular input, sets animation state, applies an upward+forward velocity to the player Rigidbody, and re-enables controls when the player lands.

using System.Numerics;
using Sandbox;

public sealed class PlayerLeap : Component, PlayerController.IEvents
{
	[Property] PlayerController TargetController { get; set; }
	[Property] GameObject TargetBody { get; set; }
	[Property] SkinnedModelRenderer TargetRenderer { get; set; }
	[Property] float LeapCooldown { get; set; } = 3f;

	bool isLeaping = false;
	private TimeUntil leapCooldownTime = -1f;


	protected override void OnFixedUpdate()
	{
		if ( IsProxy ) return;

		if ( TargetController.UseInputControls == false )
			return;

		if ( Input.Pressed( "attack1" ) && isLeaping == false && leapCooldownTime )
			BeginLeap();
	}

	[Rpc.Broadcast]
	public void BeginLeap()
	{
		// Set state
		isLeaping = true;
		leapCooldownTime = LeapCooldown;

		// Update properties
		TargetController.UseInputControls = false;
		TargetRenderer.Set( "special_movement_states", 2 );

		// Get movement variables
		var rb = TargetController.GetComponent<Rigidbody>();
		Vector3 upVelocity = Vector3.Up * 400f;
		Vector3 flatEyeAngle = new Vector3( TargetController.EyeAngles.Forward.x, TargetController.EyeAngles.Forward.y, 0 );
		Vector3 forwardVelocity = flatEyeAngle.Normal * 400f;
		Vector3 leapVelocity = upVelocity + forwardVelocity;

		// Apply movement
		TargetController.Jump( upVelocity );
		rb.Velocity = leapVelocity;

		Rotation newRotation = Rotation.LookAt( forwardVelocity );
		TargetBody.WorldRotation = newRotation;
	}

	void PlayerController.IEvents.OnLanded( float distance, Vector3 impactVelocity )
	{
		if ( isLeaping )
			FinishLeap();
	}

	private void FinishLeap()
	{
		isLeaping = false;

		TargetRenderer.Set( "special_movement_states", 0 );
		TargetController.UseInputControls = true;
	}

}