OMRFirstPersonTuning.cs
using Sandbox;
using System;

/// <summary>
/// Hot-edit friendly first-person presentation tuning for One More Round.
///
/// Revision 21 deliberately keeps presentation separate from gameplay:
/// - no setting in this resource changes AimRay, spread, damage or movement physics;
/// - camera feel is applied through ICameraModifier;
/// - weapon feel is driven through the authored Facepunch viewmodel AnimGraph;
/// - OMR never translates or rotates the spawned viewmodel root.
///
/// This gives us one place to tune first-person feel without structurally
/// hotloading the network player prefab.
/// </summary>
[AssetType(
	Name = "OMR First Person Feel",
	Extension = "omrfp",
	Category = "One More Round/Gameplay"
)]
public sealed partial class OMRFirstPersonTuning : GameResource
{
	// =========================================================
	// GENERAL
	// =========================================================

	[Group( "General" ), Title( "Enable Camera Feel" ), Description( "Master switch for OMR camera bob, roll, movement FOV and slide camera presentation. ADS zoom itself remains weapon-owned." )]
	public bool EnableCameraFeel { get; set; } = true;

	[Group( "General" ), Title( "Enable Viewmodel Animation Feel" ), Description( "Drives the authored Facepunch viewmodel movement and look-inertia AnimGraph parameters. This never moves the viewmodel GameObject itself." )]
	public bool EnableViewModelAnimationFeel { get; set; } = true;

	// =========================================================
	// CAMERA - MOVEMENT
	// =========================================================

	[Group( "Camera - Movement" ), Title( "Enable Camera Bob" ), Description( "Adds subtle positional footstep movement to the gameplay camera while grounded and moving." )]
	public bool EnableCameraBob { get; set; } = true;

	[Group( "Camera - Movement" ), Range( 0.2f, 5f ), Title( "Bob Frequency" ), Description( "Base speed of the camera footstep cycle." )]
	public float CameraBobFrequency { get; set; } = 2.0f;

	[Group( "Camera - Movement" ), Range( 0f, 5f ), Title( "Vertical Bob" ), Description( "Up/down camera movement produced by footsteps." )]
	public float CameraBobVerticalAmount { get; set; } = 1.2f;

	[Group( "Camera - Movement" ), Range( 0f, 5f ), Title( "Horizontal Bob" ), Description( "Side-to-side camera movement produced by footsteps." )]
	public float CameraBobHorizontalAmount { get; set; } = 0.8f;

	[Group( "Camera - Movement" ), Range( 1f, 2.5f ), Title( "Sprint Bob Multiplier" ), Description( "Additional camera-bob intensity/frequency while the locomotion resolver says the player is physically sprinting." )]
	public float CameraSprintBobMultiplier { get; set; } = 1.25f;

	[Group( "Camera - Movement" ), Title( "Enable Strafe Roll" ), Description( "Tilts the camera slightly while moving sideways." )]
	public bool EnableStrafeRoll { get; set; } = true;

	[Group( "Camera - Movement" ), Range( 0f, 8f ), Title( "Maximum Strafe Roll" ), Description( "Maximum camera roll, in degrees, at full lateral movement." )]
	public float MaximumStrafeRoll { get; set; } = 1.5f;

	[Group( "Camera - Movement" ), Range( 0.01f, 0.5f ), Title( "Movement Smoothing" ), Description( "How softly movement amount and strafe roll react. Lower values react faster." ), Advanced]
	public float MovementSmoothing { get; set; } = 0.08f;

	[Group( "Camera - Movement" ), Range( 0.01f, 0.5f ), Title( "Sprint Smoothing" ), Description( "How softly camera presentation enters and leaves the sprint state. Lower values react faster." ), Advanced]
	public float SprintSmoothing { get; set; } = 0.08f;

	// =========================================================
	// CAMERA - FOV
	// =========================================================

	[Group( "Camera - FOV" ), Title( "Enable Movement FOV" ), Description( "Widens the gameplay camera while moving. Sprint receives a separate additional boost instead of sharing the same speed band." )]
	public bool EnableMovementFov { get; set; } = true;

	[Group( "Camera - FOV" ), Range( 0f, 20f ), Title( "Movement FOV Boost" ), Description( "FOV added when normal movement reaches the configured base movement speed." )]
	public float MovementFovBoost { get; set; } = 7.0f;

	[Group( "Camera - FOV" ), Range( 0f, 20f ), Title( "Additional Sprint FOV Boost" ), Description( "Extra FOV added only while sprint is physically resolved. This is added on top of Movement FOV Boost." )]
	public float SprintFovBoost { get; set; } = 5.0f;

	[Group( "Camera - FOV" ), Range( 0f, 12f ), Title( "Slide FOV Boost" ), Description( "Additional FOV while sliding." )]
	public float SlideFovBoost { get; set; } = 2.5f;

	// =========================================================
	// CAMERA - SLIDE / LANDING
	// =========================================================

	[Group( "Camera - Slide / Landing" ), Title( "Enable Slide Camera" ), Description( "Adds a small camera drop and lateral glide during slide." )]
	public bool EnableSlideCamera { get; set; } = true;

	[Group( "Camera - Slide / Landing" ), Range( 0f, 6f ), Title( "Slide Camera Drop" ), Description( "Additional camera lowering during slide. The physical crouched eye height is already handled by PlayerController." )]
	public float SlideCameraDrop { get; set; } = 1.3f;

	[Group( "Camera - Slide / Landing" ), Range( 0f, 3f ), Title( "Slide Side Sway" ), Description( "Slow lateral camera glide while sliding." )]
	public float SlideSwayAmount { get; set; } = 0.5f;

	[Group( "Camera - Slide / Landing" ), Range( 0.1f, 4f ), Title( "Slide Sway Frequency" ), Description( "Speed of the lateral slide-camera glide." )]
	public float SlideSwayFrequency { get; set; } = 0.85f;

	[Group( "Camera - Slide / Landing" ), Range( 0f, 1f ), Title( "Slide Strafe Roll Reduction" ), Description( "How much normal strafe roll is suppressed while sliding." )]
	public float SlideStrafeRollReduction { get; set; } = 0.65f;

	[Group( "Camera - Slide / Landing" ), Range( 0.01f, 0.5f ), Title( "Slide Smoothing" ), Description( "How softly camera presentation enters and leaves slide." ), Advanced]
	public float SlideSmoothing { get; set; } = 0.07f;

	[Group( "Camera - Slide / Landing" ), Title( "Enable Landing Response" ), Description( "Adds a small transient pitch punch on sufficiently hard landings." )]
	public bool EnableLandingResponse { get; set; } = true;

	[Group( "Camera - Slide / Landing" ), Range( 0f, 10f ), Title( "Landing Pitch" ), Description( "Maximum camera pitch response for a hard landing." )]
	public float LandingPitchAmount { get; set; } = 2.0f;

	[Group( "Camera - Slide / Landing" ), Range( 0f, 1f ), Title( "Landing Into Slide Scale" ), Description( "Multiplier applied to landing punch when the landing immediately becomes a slide." )]
	public float LandingSlidePunchMultiplier { get; set; } = 0.35f;

	// =========================================================
	// ADS - CAMERA
	// =========================================================

	[Group( "ADS - Camera" ), Title( "Stabilize Camera In ADS" ), Description( "Reduces camera movement noise while aiming. Weapon alignment and ADS zoom remain controlled by the weapon/AnimGraph." )]
	public bool EnableAdsCameraStabilization { get; set; } = true;

	[Group( "ADS - Camera" ), Range( 0.01f, 0.5f ), Title( "ADS Camera Smoothing" ), Description( "How softly camera effects transition between hipfire and ADS." ), Advanced]
	public float AdsCameraSmoothing { get; set; } = 0.08f;

	[Group( "ADS - Camera" ), Range( 0f, 1f ), Title( "Bob Remaining" ), Description( "Amount of normal camera footstep bob remaining at full ADS." )]
	public float AdsCameraBobScale { get; set; } = 0.18f;

	[Group( "ADS - Camera" ), Range( 0f, 1f ), Title( "Strafe Roll Remaining" ), Description( "Amount of normal camera strafe roll remaining at full ADS." )]
	public float AdsStrafeRollScale { get; set; } = 0.20f;

	[Group( "ADS - Camera" ), Range( 0f, 1f ), Title( "Movement FOV Remaining" ), Description( "Amount of movement/sprint/slide FOV expansion remaining at full ADS. Zero lets weapon zoom fully dominate." )]
	public float AdsMovementFovScale { get; set; } = 0.0f;

	[Group( "ADS - Camera" ), Range( 0f, 1f ), Title( "Slide Camera Remaining" ), Description( "Amount of slide positional camera movement remaining at full ADS." )]
	public float AdsSlideCameraScale { get; set; } = 0.35f;

	// =========================================================
	// ADS - SENSITIVITY
	// =========================================================

	[Group( "ADS - Sensitivity" ), Title( "Zoom-Matched Sensitivity" ), Description( "Scales look sensitivity from the actually composed camera FOV, so weapons with different zoom levels remain consistent." )]
	public bool EnableZoomMatchedSensitivity { get; set; } = true;

	[Group( "ADS - Sensitivity" ), Range( 0.05f, 1f ), Title( "Minimum Sensitivity Scale" ), Description( "Lowest multiplier zoom matching may apply." )]
	public float MinimumZoomSensitivityScale { get; set; } = 0.20f;

	[Group( "ADS - Sensitivity" ), Range( 0.01f, 0.5f ), Title( "Sensitivity Smoothing" ), Description( "How softly look sensitivity reacts to zoom changes." ), Advanced]
	public float SensitivitySmoothing { get; set; } = 0.04f;

	// =========================================================
	// VIEWMODEL - AUTHORED ANIMATION
	// =========================================================

	[Group( "Viewmodel - Authored Animation" ), Range( 0f, 2f ), Title( "Movement Bob Strength" ), Description( "Scales the Facepunch viewmodel AnimGraph move_bob parameter. This produces weapon/arm movement without changing the viewmodel root transform." )]
	public float ViewModelMovementBobScale { get; set; } = 1.0f;

	[Group( "Viewmodel - Authored Animation" ), Range( 0f, 1f ), Title( "ADS Movement Bob Remaining" ), Description( "Amount of authored viewmodel movement bob remaining while fully aiming." )]
	public float ViewModelAdsBobScale { get; set; } = 0.12f;

	[Group( "Viewmodel - Authored Animation" ), Title( "Use Sprint Animation" ), Description( "Drives the Facepunch b_sprint parameter from OMR's physically resolved sprint state." )]
	public bool EnableViewModelSprintAnimation { get; set; } = true;

	[Group( "Viewmodel - Authored Animation" ), Title( "Use Jump Animation" ), Description( "Triggers the Facepunch b_jump parameter when the local player leaves the ground from a jump." )]
	public bool EnableViewModelJumpAnimation { get; set; } = true;

	[Group( "Viewmodel - Authored Animation" ), Range( 0.01f, 0.5f ), Title( "Movement Animation Smoothing" ), Description( "How softly the move_bob parameter follows physical movement speed." ), Advanced]
	public float ViewModelMovementSmoothing { get; set; } = 0.08f;

	// =========================================================
	// VIEWMODEL - LOOK WEIGHT
	// =========================================================

	[Group( "Viewmodel - Look Weight" ), Title( "Enable Look Inertia" ), Description( "Uses Facepunch's aim_pitch_inertia / aim_yaw_inertia AnimGraph parameters to make the weapon lag behind fast camera turns without physically moving its root." )]
	public bool EnableViewModelLookInertia { get; set; } = true;

	[Group( "Viewmodel - Look Weight" ), Range( 1f, 30f ), Title( "Follow Speed" ), Description( "How quickly the animated weapon lag catches up with the player's aim. Lower feels heavier; higher feels tighter." )]
	public float ViewModelLookFollowSpeed { get; set; } = 8.0f;

	[Group( "Viewmodel - Look Weight" ), Range( 0f, 3f ), Title( "Inertia Strength" ), Description( "Multiplier for authored look-lag animation. This affects animation only, not aim direction." )]
	public float ViewModelLookInertiaStrength { get; set; } = 1.0f;

	[Group( "Viewmodel - Look Weight" ), Range( 0f, 20f ), Title( "Maximum Inertia" ), Description( "Maximum value sent to the authored inertia parameters. Keeps very fast mouse movement bounded." )]
	public float ViewModelMaximumLookInertia { get; set; } = 6.0f;

	[Group( "Viewmodel - Look Weight" ), Range( 0f, 1f ), Title( "ADS Inertia Remaining" ), Description( "Amount of authored look inertia remaining while fully aiming." )]
	public float ViewModelAdsInertiaScale { get; set; } = 0.12f;

	[Group( "Viewmodel - Look Weight" ), Range( 0.01f, 0.5f ), Title( "Inertia Output Smoothing" ), Description( "Extra smoothing applied before inertia values are sent to the AnimGraph." ), Advanced]
	public float ViewModelInertiaSmoothing { get; set; } = 0.045f;

	// =========================================================
	// FIRST-PERSON SHADOW
	// =========================================================

	[Group( "First Person Shadow" ), Title( "Use Full Body Shadow" ), Description( "Creates an owner-local shadows-only copy of the animated Citizen so first person casts a complete body silhouette." )]
	public bool UseFullBodyShadow { get; set; } = true;

	[Group( "First Person Shadow" ), Title( "Disable Viewmodel Shadows" ), Description( "Prevents the dedicated first-person arms/weapon model from also casting a floating-arms shadow." )]
	public bool DisableViewModelShadows { get; set; } = true;

	// =========================================================
	// DIAGNOSTICS
	// =========================================================

	[Group( "Diagnostics" ), Title( "Log Presentation Setup" ), Description( "Prints first-person presentation setup once per local player." )]
	public bool LogPresentationSetup { get; set; } = false;

	[Group( "Diagnostics" ), Title( "Log Camera State" ), Description( "Once per second logs movement, sprint, slide, ADS blend and resulting movement FOV. Useful for verifying state wiring." )]
	public bool LogCameraState { get; set; } = false;

	[Group( "Diagnostics" ), Title( "Log Viewmodel State" ), Description( "Once per second logs the values OMR sends to the Facepunch viewmodel AnimGraph." )]
	public bool LogViewModelState { get; set; } = false;

	public int ContentHash()
	{
		HashCode hash = new();

		hash.Add( EnableCameraFeel );
		hash.Add( EnableViewModelAnimationFeel );
		hash.Add( EnableCameraBob );
		hash.Add( CameraBobFrequency );
		hash.Add( CameraBobVerticalAmount );
		hash.Add( CameraBobHorizontalAmount );
		hash.Add( CameraSprintBobMultiplier );
		hash.Add( EnableStrafeRoll );
		hash.Add( MaximumStrafeRoll );
		hash.Add( MovementSmoothing );
		hash.Add( SprintSmoothing );
		hash.Add( EnableMovementFov );
		hash.Add( MovementFovBoost );
		hash.Add( SprintFovBoost );
		hash.Add( SlideFovBoost );
		hash.Add( EnableSlideCamera );
		hash.Add( SlideCameraDrop );
		hash.Add( SlideSwayAmount );
		hash.Add( SlideSwayFrequency );
		hash.Add( SlideStrafeRollReduction );
		hash.Add( SlideSmoothing );
		hash.Add( EnableLandingResponse );
		hash.Add( LandingPitchAmount );
		hash.Add( LandingSlidePunchMultiplier );
		hash.Add( EnableAdsCameraStabilization );
		hash.Add( AdsCameraSmoothing );
		hash.Add( AdsCameraBobScale );
		hash.Add( AdsStrafeRollScale );
		hash.Add( AdsMovementFovScale );
		hash.Add( AdsSlideCameraScale );
		hash.Add( EnableZoomMatchedSensitivity );
		hash.Add( MinimumZoomSensitivityScale );
		hash.Add( SensitivitySmoothing );
		hash.Add( ViewModelMovementBobScale );
		hash.Add( ViewModelAdsBobScale );
		hash.Add( EnableViewModelSprintAnimation );
		hash.Add( EnableViewModelJumpAnimation );
		hash.Add( ViewModelMovementSmoothing );
		hash.Add( EnableViewModelLookInertia );
		hash.Add( ViewModelLookFollowSpeed );
		hash.Add( ViewModelLookInertiaStrength );
		hash.Add( ViewModelMaximumLookInertia );
		hash.Add( ViewModelAdsInertiaScale );
		hash.Add( ViewModelInertiaSmoothing );
		hash.Add( UseFullBodyShadow );
		hash.Add( DisableViewModelShadows );
		hash.Add( LogPresentationSetup );
		hash.Add( LogCameraState );
		hash.Add( LogViewModelState );

		return hash.ToHashCode();
	}
}

public static class OMRFirstPersonResources
{
	public const string DefaultTuningPath = "gameplay/default.omrfp";

	public static OMRFirstPersonTuning DefaultTuning =>
		ResourceLibrary.Get<OMRFirstPersonTuning>( DefaultTuningPath );
}