OMRPlayerSettings.cs
using Sandbox;
using System;
using System.Linq;

/// <summary>
/// Local One More Round player preferences.
///
/// The current local s&box build exposes Sandbox.Preferences values
/// as readable engine preferences, but they are not assignable from
/// game code. OMR therefore stores its own game-specific values in
/// Game.Cookies and applies them on top of the engine's normal input
/// and camera pipeline.
/// </summary>
public static class OMRPlayerSettings
{
	private const string SensitivityKey =
		"omr.settings.look_sensitivity";

	private const string FieldOfViewKey =
		"omr.settings.field_of_view";

	private const string CrouchBehaviorKey =
		"omr.settings.crouch_behavior";

	private const string SprintBehaviorKey =
		"omr.settings.sprint_behavior";

	/// <summary>
	/// Game-specific multiplier applied after the player's normal
	/// s&box sensitivity preference.
	/// </summary>
	public const float MinimumSensitivity =
		0.10f;

	public const float MaximumSensitivity =
		5.00f;

	public const float MinimumFieldOfView =
		60f;

	public const float MaximumFieldOfView =
		100f;

	public static float Sensitivity
	{
		get
		{
			float value =
				Game.Cookies.Get(
					SensitivityKey,
					1.0f
				);

			return MathX.Clamp(
				value,
				MinimumSensitivity,
				MaximumSensitivity
			);
		}

		set
		{
			Game.Cookies.Set(
				SensitivityKey,
				MathX.Clamp(
					value,
					MinimumSensitivity,
					MaximumSensitivity
				)
			);
		}
	}

	public static float FieldOfView
	{
		get
		{
			float defaultFov =
				MathX.Clamp(
					Preferences.FieldOfView,
					MinimumFieldOfView,
					MaximumFieldOfView
				);

			float value =
				Game.Cookies.Get(
					FieldOfViewKey,
					defaultFov
				);

			return MathX.Clamp(
				value,
				MinimumFieldOfView,
				MaximumFieldOfView
			);
		}

		set
		{
			Game.Cookies.Set(
				FieldOfViewKey,
				MathX.Clamp(
					value,
					MinimumFieldOfView,
					MaximumFieldOfView
				)
			);
		}
	}

	public static CrouchBehavior CrouchBehavior
	{
		get
		{
			int raw =
				Game.Cookies.Get(
					CrouchBehaviorKey,
					(int)CrouchBehavior.Toggle
				);

			return Enum.IsDefined(
				typeof( CrouchBehavior ),
				raw
			)
				? (CrouchBehavior)raw
				: CrouchBehavior.Toggle;
		}

		set
		{
			Game.Cookies.Set(
				CrouchBehaviorKey,
				(int)value
			);

			ApplyToLocalPlayer();
		}
	}

	public static SprintInputBehavior SprintBehavior
	{
		get
		{
			int raw =
				Game.Cookies.Get(
					SprintBehaviorKey,
					(int)SprintInputBehavior.Hybrid
				);

			return Enum.IsDefined(
				typeof( SprintInputBehavior ),
				raw
			)
				? (SprintInputBehavior)raw
				: SprintInputBehavior.Hybrid;
		}

		set
		{
			Game.Cookies.Set(
				SprintBehaviorKey,
				(int)value
			);

			ApplyToLocalPlayer();
		}
	}

	public static void ResetDefaults()
	{
		Game.Cookies.Set(
			SensitivityKey,
			1.0f
		);

		Game.Cookies.Set(
			FieldOfViewKey,
			80f
		);

		Game.Cookies.Set(
			CrouchBehaviorKey,
			(int)CrouchBehavior.Toggle
		);

		Game.Cookies.Set(
			SprintBehaviorKey,
			(int)SprintInputBehavior.Hybrid
		);

		ApplyToLocalPlayer();
	}

	public static void ApplyToLocalPlayer()
	{
		Scene scene =
			Game.ActiveScene;

		if ( scene is null )
			return;

		PlayerState localPlayer =
			scene
				.GetAllComponents<PlayerState>()
				.FirstOrDefault(
					x =>
						!x.IsProxy &&
					!x.IsBot
				);

		if ( localPlayer is null )
			return;

		ApplyToPlayer(
			localPlayer.GameObject
		);
	}

	public static void ApplyToPlayer(
		GameObject player
	)
	{
		if ( player is null )
			return;

		PlayerCrouch crouch =
			player.Components.Get<PlayerCrouch>();

		if ( crouch is not null )
		{
			crouch.Behavior =
				CrouchBehavior;
		}

		PlayerSprint sprint =
			player.Components.Get<PlayerSprint>();

		if ( sprint is not null )
		{
			sprint.Behavior =
				SprintBehavior;
		}
	}
}