Weapons/HornetGun/HornetGunWeapon.cs
using Sandbox.Rendering;
using Sandbox.Utility;

public class HornetGunWeapon : BaseWeapon
{
	/// <summary>
	/// Time between shots if not using turbo
	/// </summary>
	[Property] public float TimeBetweenShots { get; set; } = 2f;

	/// <summary>
	/// Time between shots if using turbo
	/// </summary>
	[Property] public float TurboDelay { get; set; } = 0.2f;

	/// <summary>
	/// Sound to play when shooting this weapon
	/// </summary>
	[Property] public SoundEvent FireSound { get; set; }

	/// <summary>
	/// Projectile prefab to create when shooting this weapon
	/// </summary>
	[Property] public GameObject ProjectilePrefab { get; set; }

	/// <summary>
	/// How frequently should we regenerate ammo for this weapon
	/// </summary>
	[Property] public float AmmoRegenerationRate { get; set; } = 1f; // Hornets per second

	/// <summary>
	/// The max ammo for this weapon
	/// </summary>
	[Property] public int MaxAmmo { get; set; } = 8;

	private float ammo = 8;

	protected override void OnStart()
	{
		base.OnStart();
		ammo = MaxAmmo;
	}

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

		if ( Input.Down( "attack1" ) )
		{
			if ( Input.Pressed( "attack1" ) )
				StartAttack();

			Shoot( player, HornetProjectile.FireMode.Normal );
		}
		else
		{
			if ( Input.Released( "attack1" ) )
				StopAttack();
		}

		if ( Input.Down( "attack2" ) )
		{
			Shoot( player, HornetProjectile.FireMode.Turbo );
		}
	}

	protected override void OnUpdate()
	{
		base.OnUpdate();

		// Regenerate ammo over time
		if ( ammo < MaxAmmo )
		{
			ammo += AmmoRegenerationRate * Time.Delta;
			ammo = Math.Clamp( ammo, 0, MaxAmmo );
		}
	}

	TimeSince TimeSinceShoot;

	public void Shoot( Player player, HornetProjectile.FireMode fireMode )
	{
		if ( !CanShoot() )
			return;

		// Some debug asserts because we're apparently getting WeaponModel is not valid.
		Assert.True( player.IsValid(), "HornetGunWeapon:::Shoot - player is invalid?" );
		Assert.True( WeaponModel.IsValid(), "HornetGunWeapon:::Shoot - WeaponModel is invalid?" );
		Assert.True( WeaponModel.MuzzleTransform.IsValid(), "HornetGunWeapon:::Shoot - MuzzleTransform GameObject is invalid?" );

		if ( ammo < 1 )
		{
			AddShootDelay( 0.2f );
			return;
		}

		if ( !GameSettings.InfiniteAmmo )
			ammo -= 1;

		TimeSinceShoot = 0;
		AddShootDelay( fireMode == HornetProjectile.FireMode.Turbo ? TurboDelay : TimeBetweenShots );

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

		var projectilePosition = CheckProjectilePosition( player, player.EyeTransform.Position, WeaponModel.MuzzleTransform.WorldPosition );
		var direction = tr.EndPosition - projectilePosition;
		CreateProjectile( projectilePosition, tr.Direction, fireMode );

		player.Controller.EyeAngles += new Angles( Random.Shared.Float( -0.2f, -0.3f ), Random.Shared.Float( -0.1f, 0.1f ), 0 );

		if ( !player.Controller.ThirdPerson && player.IsLocalPlayer )
		{
			new Sandbox.CameraNoise.Punch( new Vector3( Random.Shared.Float( 45, 35 ), Random.Shared.Float( -10, -5 ), 0 ), 1.5f, 2, 0.5f );
			new Sandbox.CameraNoise.Shake( 1f, 0.6f );
		}
	}

	private Vector3 CheckProjectilePosition( Player player, Vector3 eyePosition, Vector3 targetPosition )
	{
		var tr = Scene.Trace.Box( BBox.FromPositionAndSize( Vector3.Zero, 8.0f ), eyePosition, targetPosition )
			.WithoutTags( "trigger", "ragdoll" )
			.IgnoreGameObjectHierarchy( player.GameObject )
			.Run();

		if ( tr.Hit )
		{
			return tr.EndPosition;
		}

		return targetPosition;
	}

	/// <summary>
	/// Creates the projectile with the host's permission
	/// </summary>
	[Rpc.Host]
	void CreateProjectile( Vector3 start, Vector3 direction, HornetProjectile.FireMode fireMode )
	{
		if ( !Owner.IsValid() ) return;

		var go = ProjectilePrefab?.Clone( start );

		var projectile = go.GetComponent<HornetProjectile>();
		Assert.True( projectile.IsValid(), "RpgProjectile not on projectile prefab" );

		projectile.Instigator = Owner.PlayerData;
		projectile.Mode = fireMode;
		projectile.Weapon = GameObject;

		go.NetworkSpawn();

		//Projectile = projectile;
		projectile.UpdateDirection( direction, projectile.GetSpeed() );

		GameObject.PlaySound( FireSound );
	}

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

		var gap = 6 + Easing.EaseOut( tss ) * 32;
		var w = 2;

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

		// Define the size of the square
		var squareSize = 64f;

		// Draw the four edges of the square
		hud.DrawLine( center + new Vector2( -squareSize / 2, -squareSize / 2 ), center + new Vector2( squareSize / 2, -squareSize / 2 ), w, color ); // Top edge
		hud.DrawLine( center + new Vector2( squareSize / 2, -squareSize / 2 ), center + new Vector2( squareSize / 2, squareSize / 2 ), w, color );   // Right edge
		hud.DrawLine( center + new Vector2( squareSize / 2, squareSize / 2 ), center + new Vector2( -squareSize / 2, squareSize / 2 ), w, color );  // Bottom edge
		hud.DrawLine( center + new Vector2( -squareSize / 2, squareSize / 2 ), center + new Vector2( -squareSize / 2, -squareSize / 2 ), w, color ); // Left edge
	}
}