Weapons/ShotgunWeapon.cs
using Sandbox.Rendering;
using Sandbox.Utility;

public class ShotgunWeapon : BaseBulletWeapon
{
	[Property] public float Damage { get; set; } = 12.0f;
	[Property] public float AltDamage { get; set; } = 20.0f;
	[Property] public int Pellets { get; set; } = 12;
	[Property] public Vector2 Spread { get; set; } = new Vector2( 8, 4 );

	public override void OnControl( Player player )
	{
		base.OnControl( player );

		if ( Input.Pressed( "attack1" ) )
		{
			ShootBullet( player );
		}

		if ( Input.Pressed( "Attack2" ) )
		{
			ShootBulletAlternate( player );
		}
	}

	[Rpc.Broadcast]
	void OneShotEvent( Vector3 hitPos, float shootPitchModifier = 1f )
	{
		var ev = new IWeaponEvent.AttackEvent( ViewModel.IsValid() );
		IWeaponEvent.PostToGameObject( GameObject.Root, x => x.OnAttack( ev ) );
		IWeaponEvent.PostToGameObject( GameObject.Root, x => x.CreateRangedEffects( this, hitPos ) );

		if ( ShootSound.IsValid() )
		{
			var snd = GameObject.PlaySound( ShootSound );
			// If we're shooting, the sound should not be spatialized
			if ( Owner.IsValid() && Owner.IsLocalPlayer && snd.IsValid() )
			{
				snd.SpacialBlend = 0;
			}

			snd.Pitch *= shootPitchModifier;
		}
	}

	public void ShootBullet( Player player )
	{
		if ( !CanShoot() || !TakeAmmo( 1 ) )
		{
			TryAutoReload();
			return;
		}

		AddShootDelay( 0.9f );

		var hitPos = Vector3.Zero;

		for ( int i = 0; i < Pellets; i++ )
		{
			var forward = player.EyeTransform.Rotation.Forward.WithAimCone( Spread.x, Spread.y );
			var bulletRadius = GameSettings.ShotgunBulletRadius * GameSettings.BulletRadius;

			var tr = Scene.Trace.Ray( player.EyeTransform.ForwardRay with { Forward = forward }, 4096 )
								.IgnoreGameObjectHierarchy( player.GameObject )
								.WithCollisionRules( "bullet" )
								.Radius( bulletRadius )
								.UseHitboxes()
								.Run();

			// DebugOverlay.Line( tr.StartPosition, tr.EndPosition, duration: 30 );

			ShootEffects( tr.EndPosition, tr.Hit, tr.Normal, tr.GameObject, tr.Surface, null, true );
			TraceAttack( TraceAttackInfo.From( tr, Damage ) );
			TimeSinceShoot = 0;

			if ( player.IsLocalPlayer )
			{
				HitMarker.CreateFromTrace( tr );
			}

			hitPos = tr.EndPosition;
		}

		OneShotEvent( hitPos );

		player.Controller.EyeAngles += new Angles( Random.Shared.Float( -2, -3 ), Random.Shared.Float( -2, 2 ), 0 );

		if ( !player.Controller.ThirdPerson && player.IsLocalPlayer )
		{
			new Sandbox.CameraNoise.Punch( new Vector3( Random.Shared.Float( -50, -65 ), Random.Shared.Float( -20, 0 ), 0 ), 2.0f, 2f, 0.7f );
			new Sandbox.CameraNoise.Shake( 3.5f, 1.0f );
		}
	}

	public void ShootBulletAlternate( Player player )
	{
		if ( !CanShoot() )
		{
			TryAutoReload();
			return;
		}

		if ( !TakeAmmo( 2 ) )
		{
			ShootBullet( player );
			return;
		}

		AddShootDelay( 1.5f );

		var hitPos = Vector3.Zero;

		for ( int i = 0; i < Pellets * 2; i++ )
		{
			var forward = player.EyeTransform.Rotation.Forward.WithAimCone( Spread.x * 2f, Spread.y * 2f );
			var bulletRadius = GameSettings.ShotgunBulletRadius * GameSettings.BulletRadius;

			var tr = Scene.Trace.Ray( player.EyeTransform.ForwardRay with { Forward = forward }, 4096 )
								.IgnoreGameObjectHierarchy( player.GameObject )
								.WithCollisionRules( "bullet" )
								.Radius( bulletRadius )
								.UseHitboxes()
								.Run();

			ShootEffects( tr.EndPosition, tr.Hit, tr.Normal, tr.GameObject, tr.Surface, null, true );
			TraceAttack( TraceAttackInfo.From( tr, Damage ) );
			TimeSinceShoot = 0;

			if ( player.IsLocalPlayer )
			{
				HitMarker.CreateFromTrace( tr );
			}

			hitPos = tr.EndPosition;
		}

		OneShotEvent( hitPos, shootPitchModifier: 0.8f );

		player.Controller.EyeAngles += new Angles( Random.Shared.Float( -2, -3 ), Random.Shared.Float( -2, 2 ), 0 );

		if ( !player.Controller.ThirdPerson && player.IsLocalPlayer )
		{
			new Sandbox.CameraNoise.Punch( new Vector3( Random.Shared.Float( -50, -65 ), Random.Shared.Float( -20, 0 ), 0 ), 2.0f, 1f, 0.7f );
			new Sandbox.CameraNoise.Shake( 6.0f, 1.3f );
		}
	}

	public override void DrawCrosshair( HudPainter hud, Vector2 center )
	{
		var tss = TimeSinceShoot.Relative.Remap( 0, 0.8f, 1, 0 );

		var len = 64 + Easing.BounceIn( tss ) * 128;

		Color color = !CanShoot() ? UI.CrosshairInactive : UI.CrosshairActive;

		var rect = new Rect( center - len * 0.5f, len );

		hud.DrawRect( rect, Color.Transparent, cornerRadius: new Vector4( 512 ), borderWidth: new Vector4( 2 ), borderColor: color );
	}
}