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

public sealed class ShotgunWeapon : IronSightsWeapon
{
	[Property] public float PrimaryFireRate { get; set; } = 0.8f;
	[Property] public int PelletCount { get; set; } = 8;

	protected override float GetPrimaryFireRate() => PrimaryFireRate;

	protected override bool WantsPrimaryAttack()
	{
		return Input.Pressed( "attack1" );
	}

	public override void PrimaryAttack()
	{
		if ( HasOwner && ( !HasAmmo() || IsReloading() ) )
		{
			TryAutoReload();
			return;
		}

		if ( TimeUntilNextShotAllowed > 0 )
			return;

		if ( HasOwner && !TakeAmmo( 1 ) )
		{
			AddShootDelay( 0.2f );
			return;
		}

		AddShootDelay( PrimaryFireRate );

		var eyeForward = AimRay.Forward;
		var eyeRay = AimRay;

		for ( var i = 0; i < PelletCount; i++ )
		{
			var aimConeAmount = GetAimConeAmount();
			var forward = eyeForward
				.WithAimCone(
					Bullet.AimConeBase.x + aimConeAmount * Bullet.AimConeSpread.x,
					Bullet.AimConeBase.y + aimConeAmount * Bullet.AimConeSpread.y
				);

			var tr = Scene.Trace.Ray( eyeRay with { Forward = forward }, Bullet.Range )
				.IgnoreGameObjectHierarchy( AimIgnoreRoot )
				.WithCollisionRules( "bullet" )
				.WithoutTags( "playercontroller" )
				.Radius( Bullet.BulletRadius )
				.UseHitboxes()
				.Run();

			ShootEffects( tr.EndPosition, tr.Hit, tr.Normal, tr.GameObject, tr.Surface, noEvents: i > 0 );
			TraceAttack( TraceAttackInfo.From( tr, Bullet.Damage ) );
		}

		TimeSinceShoot = 0;

		if ( !HasOwner )
		{
			if ( ShootForce > 0f && GetComponent<Rigidbody>( true ) is var rb )
			{
				var muzzle = WeaponModel?.MuzzleTransform?.WorldTransform ?? WorldTransform;
				rb.ApplyForce( muzzle.Rotation.Up * ShootForce );
			}
			return;
		}

		Owner.Controller.EyeAngles += new Angles(
			Random.Shared.Float( Bullet.RecoilPitch.x, Bullet.RecoilPitch.y ),
			Random.Shared.Float( Bullet.RecoilYaw.x, Bullet.RecoilYaw.y ),
			0
		);

		if ( !Owner.Controller.ThirdPerson && Owner.IsLocalPlayer )
		{
			_ = new Sandbox.CameraNoise.Recoil( Bullet.CameraRecoilStrength, Bullet.CameraRecoilFrequency );
		}
	}

	public override void DrawCrosshair( HudPainter hud, Vector2 center )
	{
		var spread = GetAimConeAmount();
		var radius = 20 + spread * 40;

		var color = !HasAmmo() || IsReloading() || TimeUntilNextShotAllowed > 0 ? CrosshairNoShoot : CrosshairCanShoot;

		hud.SetBlendMode( BlendMode.Lighten );

		const int segments = 32;
		for ( var i = 0; i < segments; i++ )
		{
			var a1 = MathF.PI * 2f * i / segments;
			var a2 = MathF.PI * 2f * (i + 1) / segments;
			var p1 = center + new Vector2( MathF.Cos( a1 ), MathF.Sin( a1 ) ) * radius;
			var p2 = center + new Vector2( MathF.Cos( a2 ), MathF.Sin( a2 ) ) * radius;
			hud.DrawLine( p1, p2, 2f, color );
		}

		hud.DrawCircle( center, 3, color );
	}
}