PlayerControllerPlus/Modes/Swim.cs

A swim movement mode for a player controller. It disables gravity, increases damping for swimming, tracks water level from body contacts with the "water" tag, influences mode scoring based on swim level, sets swimming state on begin/end, allows a jump impulse when exiting water, and adds upward input while the jump key is held.

Reflection
using System.Linq;

namespace Sandbox.MovementPlus;

public class MoveModeSwimPlus : MoveModePlus
{
	public float WaterLevel { get; private set; }

	public override void UpdateRigidBody( Rigidbody body )
	{
		body.Gravity = false;
		body.LinearDamping = 3.3f;
		body.AngularDamping = 1f;
	}

	public override int Score( PlayerControllerPlus controller )
	{
		if ( WaterLevel > controller.SwimLevel ) return controller.SwimPriority;
		return -100;
	}

	public override void OnModeBegin()
	{
		Controller.IsSwimming = true;
	}

	public override void OnModeEnd( MoveModePlus next )
	{
		Controller.IsSwimming = false;

		if ( Input.Down( "Jump" ) )
		{
			Controller.Jump( Vector3.Up * 300 );
		}
	}

	public override void FixedUpdate()
	{
		UpdateWaterLevel();
	}

	void UpdateWaterLevel()
	{
		if ( Controller?.Body == null )
			return;

		var wt = Controller.WorldTransform;
		Vector3 head = wt.PointToWorld( new Vector3( 0, 0, Controller.CurrentHeight ) );
		Vector3 foot = wt.Position;

		float waterLevel = 0;

		foreach ( var touch in Controller.Body.Touching )
		{
			if ( !touch.Tags.Contains( "water" ) ) continue;

			var waterSurface = touch.FindClosestPoint( head );
			var level = Vector3.InverseLerp( waterSurface, foot, head, true );
			level = (level * 100).CeilToInt() / 100.0f;

			if ( level > waterLevel )
				waterLevel = level;
		}

		if ( WaterLevel != waterLevel )
		{
			WaterLevel = waterLevel;
		}
	}

	public override Vector3 UpdateMove( Rotation eyes, Vector3 input )
	{
		if ( Input.Down( "jump" ) )
		{
			input += Vector3.Up;
		}

		return base.UpdateMove( eyes, input );
	}
}