perks/PerkReloadItem.cs

A perk definition class named PerkReloadItem (Resourceful) that periodically spawns a reload_item powerup near the player. It tracks a timer, computes spawn interval based on perk level, chooses a random nearby position clamped/reflected inside game bounds, calls Manager RPCs to spawn the item and play a sound, and exposes highlight and cooldown display behavior.

Networking
using System;
using Sandbox;

[Perk( Rarity.Epic, locked: true, minUnlocksReq: 2, alwaysOfferDebug: false )]
public class PerkReloadItem : Perk
{
	private enum Mod { Time };

	private float _timer;

	static PerkReloadItem()
	{
		Register<PerkReloadItem>(
			name: "Resourceful",
			imagePath: "textures/icons/vector/powerup_nearby.png",
			description: level => $"Every {GetValue( level, Mod.Time ).ToString( "0.#" )}s, spawn a reload_item\nthat reloads and restores 1 dash",
			upgradeDescription: level => $"Every {GetValue( level - 1, Mod.Time ).ToString("0.#")}→{GetValue( level, Mod.Time ).ToString("0.#")}s, spawn a reload_item\nthat reloads and restores 1 dash"
		);
	}

	public override void Start()
	{
		base.Start();

		ShouldUpdate = true;

		HighlightColor = new Color( 0.6f, 0.6f, 1f );
		HighlightDuration = 0.25f;
		HighlightOpacity = 0.75f;
	}

	public override void Refresh()
	{
		base.Refresh();

	}

	private static float GetValue( int level, Mod mod, bool isPercent = false )
	{
		switch ( mod )
		{
			case Mod.Time:
			default:
				return 6.2f - level * 0.8f;
		}
	}

	public override void Update( float dt )
	{
		base.Update( dt );

		_timer += dt;
		if ( _timer > GetValue( Level, Mod.Time ) )
		{
			SpawnPowerup();
			Highlight();
		}

		DisplayCooldown = Utils.Map( _timer, 0f, GetValue( Level, Mod.Time ), 0f, 1f );
	}

	public void SpawnPowerup()
	{
		Vector2 pos = Player.Position2D + Utils.GetRandomVector() * Game.Random.Float(100f, 250f);

		Vector2 min = Manager.Instance.BOUNDS_MIN;
		Vector2 max = Manager.Instance.BOUNDS_MAX;

		if ( pos.x < min.x )
			pos = new Vector2( min.x + (min.x - pos.x), pos.y );
		else if ( pos.x > max.x )
			pos = new Vector2( max.x - (pos.x - max.x), pos.y );

		if ( pos.y < min.y )
			pos = new Vector2( pos.x, min.y + (min.y - pos.y) );
		else if ( pos.y > max.y )
			pos = new Vector2( pos.x, max.y - (pos.y - max.y) );

		Manager.Instance.SpawnItemRpc( "reload_item", pos, Vector2.Zero );

		Manager.Instance.PlaySfxNearbyRpc( "scuffle", pos, pitch: Game.Random.Float( 1f, 1.1f ), volume: 0.8f, maxDist: 400f );

		_timer = 0f;
	}
}