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.
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;
}
}