Weapons/Sniper/SniperWeapon.cs
using Sandbox.Rendering;
public sealed class SniperWeapon : BaseBulletWeapon
{
[Property] public float PrimaryFireRate { get; set; } = 1.2f;
[Property] public float ScopedFov { get; set; } = 20f;
[Property] public float ScopeSensitivity { get; set; } = 0.3f;
[Property] public SoundEvent BoltPullSound { get; set; }
private bool _isScoped;
private float _mouseDelta;
private SniperScopeEffect _scopeEffect;
private bool _hasFired;
private TimeUntil _timeUntilHideViewModel;
private bool _viewModelHidden;
public bool IsScoped => _isScoped;
protected override float GetPrimaryFireRate() => PrimaryFireRate;
protected override bool WantsPrimaryAttack()
{
return Input.Pressed( "attack1" );
}
protected override bool WantsSecondaryAttack()
{
return false;
}
public override bool CanSecondaryAttack()
{
return false;
}
public override void PrimaryAttack()
{
ShootBullet( PrimaryFireRate );
_hasFired = true;
}
private void SetScoped( bool scoped )
{
_isScoped = scoped;
// Trigger ironsights animation
ViewModel?.RunEvent<ViewModel>( x =>
{
x.Renderer?.Set( "ironsights", _isScoped ? 1 : 0 );
} );
if ( _isScoped )
{
// Delay hiding the viewmodel until the ADS animation finishes
_timeUntilHideViewModel = 0.2f;
}
else
{
ShowViewModel();
_scopeEffect?.Destroy();
_scopeEffect = default;
}
}
private void HideViewModel()
{
if ( _viewModelHidden ) return;
_viewModelHidden = true;
if ( ViewModel.IsValid() )
Scene.Camera.RenderExcludeTags.Add( "sniper_scoped" );
if ( ViewModel.IsValid() )
ViewModel.Tags.Add( "sniper_scoped" );
}
private void ShowViewModel()
{
if ( !_viewModelHidden ) return;
_viewModelHidden = false;
Scene.Camera.RenderExcludeTags.Remove( "sniper_scoped" );
if ( ViewModel.IsValid() )
ViewModel.Tags.Remove( "sniper_scoped" );
}
protected override void OnDisabled()
{
base.OnDisabled();
if ( _isScoped )
SetScoped( false );
ShowViewModel();
}
public override void OnControl( Player player )
{
base.OnControl( player );
// Hold right mouse to scope
var wantsScope = Input.Down( "attack2" );
if ( wantsScope != _isScoped )
{
SetScoped( wantsScope );
}
// Hide viewmodel once ADS animation has finished, then enable scope overlay
if ( _isScoped && !_viewModelHidden && _timeUntilHideViewModel )
{
HideViewModel();
_scopeEffect = Scene.Camera.Components.GetOrCreate<SniperScopeEffect>();
_scopeEffect.Flags |= ComponentFlags.NotNetworked;
}
if ( _hasFired && Input.Released( "attack1" ) )
{
_hasFired = false;
if ( BoltPullSound is not null )
Sound.Play( BoltPullSound, WorldPosition );
ViewModel?.RunEvent<ViewModel>( x =>
{
x.Renderer?.Set( "speed_reload", 1 );
x.Renderer?.Set( "b_reload_bolt", true );
} );
}
}
public override void OnCameraSetup( Player player, CameraComponent camera )
{
if ( !player.Network.IsOwner || !Network.IsOwner ) return;
if ( _isScoped && _viewModelHidden )
{
camera.FieldOfView = ScopedFov;
}
}
public override void OnCameraMove( Player player, ref Angles angles )
{
_mouseDelta = new Vector2( angles.yaw, angles.pitch ).Length;
if ( _isScoped && _viewModelHidden )
{
angles *= ScopeSensitivity;
}
}
public override void DrawHud( HudPainter painter, Vector2 crosshair )
{
if ( _isScoped )
{
DrawScopeOverlay( painter, crosshair );
return;
}
DrawCrosshair( painter, crosshair );
}
public override void DrawCrosshair( HudPainter hud, Vector2 center )
{
var color = !HasAmmo() || IsReloading() || TimeUntilNextShotAllowed > 0 ? CrosshairNoShoot : CrosshairCanShoot;
hud.SetBlendMode( BlendMode.Normal );
hud.DrawCircle( center, 5, Color.Black );
hud.DrawCircle( center, 3, color );
}
private void DrawScopeOverlay( HudPainter hud, Vector2 center )
{
if ( !_scopeEffect.IsValid() )
return;
var mouseBlur = _mouseDelta * 0.25f;
var velocityBlur = HasOwner ? (Owner.Controller.Velocity.Length / 300f).Clamp( 0f, 1f ) : 0f;
_scopeEffect.BlurInput = MathF.Min( mouseBlur + velocityBlur, 1f );
}
}