OneMoreRoundWeapon.ViewModelFirePresentation.cs
using Sandbox;
using System;

/// <summary>
/// Keeps the authored ADS fire-animation scale stable for the lifetime of the
/// shot animation. This matters for manual-cycle weapons: gameplay can leave
/// ADS immediately after firing while the AnimGraph is still evaluating the
/// same b_attack pulse. Letting ironsights_fire_scale jump from the scoped
/// value back to 1 during that window can make the scope lurch into the camera.
/// </summary>
public partial class OneMoreRoundWeapon
{
	private bool _viewModelFireScaleLockActive;
	private float _viewModelFireScaleLockUntil = -1f;
	private float _viewModelFireScaleLockValue = 1f;

	public bool IsViewModelFireScaleLocked =>
		_viewModelFireScaleLockActive &&
		RealTime.Now < _viewModelFireScaleLockUntil;

	public float ViewModelFireScaleLockRemaining =>
		IsViewModelFireScaleLocked
			? MathF.Max( _viewModelFireScaleLockUntil - RealTime.Now, 0f )
			: 0f;

	public float EffectiveViewModelFireScale =>
		GetEffectiveViewModelFireScale();

	private void LatchViewModelFireScaleForPrimaryShot()
	{
		if ( !IsAiming )
			return;

		float holdTime = GetViewModelFireScaleHoldTime();
		if ( holdTime <= 0f )
			return;

		_viewModelFireScaleLockActive = true;
		_viewModelFireScaleLockValue = MathX.Clamp( IronSightsFireScale, 0f, 1f );
		_viewModelFireScaleLockUntil = RealTime.Now + holdTime;

		ApplyViewModelFireScaleImmediately();
	}

	private float GetViewModelFireScaleHoldTime()
	{
		if ( !HasOpticProfile )
			return 0f;

		return MathF.Max( OpticProfile.ViewModelFireScaleHoldTime, 0f );
	}

	private float GetEffectiveViewModelFireScale()
	{
		if ( IsViewModelFireScaleLocked )
			return _viewModelFireScaleLockValue;

		return IsAiming
			? MathX.Clamp( IronSightsFireScale, 0f, 1f )
			: 1f;
	}

	private void UpdateViewModelFireScaleGuard( SkinnedModelRenderer renderer )
	{
		if ( renderer is null )
			return;

		if (
			_viewModelFireScaleLockActive &&
			RealTime.Now >= _viewModelFireScaleLockUntil
		)
		{
			_viewModelFireScaleLockActive = false;
			_viewModelFireScaleLockUntil = -1f;
		}

		renderer.Set(
			"ironsights_fire_scale",
			GetEffectiveViewModelFireScale()
		);
	}

	private void ApplyViewModelFireScaleImmediately()
	{
		GameObject viewModel = ViewModel;
		if ( viewModel is null || !viewModel.IsValid )
			return;

		SkinnedModelRenderer renderer = GetViewModelRenderer( viewModel );
		if ( renderer is null )
			return;

		renderer.Set(
			"ironsights_fire_scale",
			GetEffectiveViewModelFireScale()
		);
	}

	private void ResetViewModelFireScaleGuard()
	{
		_viewModelFireScaleLockActive = false;
		_viewModelFireScaleLockUntil = -1f;
		_viewModelFireScaleLockValue = 1f;
	}
}