perks/PerkAutoReroll.cs

A Legendary perk component called PerkAutoReroll. It tracks time since the player last rerolled or chose a perk while choosing a level-up reward, and after a time limit deals self-damage and forces a free reroll. It updates UI cooldown visuals and resets its timer on various player events.

Networking
using System;
using Sandbox;

[Perk( Rarity.Legendary, onlyUnpausedChoosing: true, locked: true, minUnlocksReq: 3, alwaysOfferDebug: false, IncludedCategories = new[] { PerkCategory.SelfDmg })]
public class PerkAutoReroll : Perk
{
	private enum Mod { TimeLimit, HpLoss };

	private TimeSince _timeSinceTrigger;


	static PerkAutoReroll()
	{
		Register<PerkAutoReroll>(
			name: "Rerolling Machine",
			imagePath: "textures/icons/vector/auto_reroll.png",
			description: level => $"Every {(int)GetValue( level, Mod.TimeLimit )}s that you haven't rerolled or chosen a perk, lose {(int)GetValue( level, Mod.HpLoss)} hp and reroll for free",
			upgradeDescription: level => $"Every {(int)GetValue( level - 1, Mod.TimeLimit )}→{(int)GetValue( level, Mod.TimeLimit )}s that you haven't rerolled or chosen a perk, lose {(int)GetValue( level - 1, Mod.HpLoss )}→{(int)GetValue( level, Mod.HpLoss )} hp and reroll for free"
		);
	}

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

		ShouldUpdate = true;
		_timeSinceTrigger = 0f;

		HighlightColor = new Color( 1f, 0.5f, 0.7f );
		HighlightDuration = 0.3f;
		HighlightOpacity = 5f;
	}

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

	}

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

		if ( Player.IsChoosingLevelUpReward && _timeSinceTrigger > GetValue( Level, Mod.TimeLimit ) )
		{
			Player.UseReroll( free: true );
			_timeSinceTrigger = 0f;

			// todo: this can work with paused choosing too?

			Player.Damage( GetValue( Level, Mod.HpLoss ), DamageType.Self, Player.Position2D, Utils.GetRandomVector(), upwardAmount: 0f, force: 0f, ragdollForce: 0.5f, enemySource: null, enemyType: EnemyType.None );

			Highlight();

			Player.ScaleHeightRpc( amount: 1.4f, time: Game.Random.Float( 0.05f, 0.06f ) );
		}

		if ( Player.IsChoosingLevelUpReward )
		{
			DisplayCooldown = Utils.Map( _timeSinceTrigger, 0f, GetValue( Level, Mod.TimeLimit ), 0f, 1f );

			DisplayCooldownColor = Color.Lerp(
				Color.Black,
				Color.Lerp( Color.White, Color.Red, 0.5f + Utils.FastSin( RealTime.Now * 24f ) * 0.5f ),
				Utils.Map( _timeSinceTrigger, 0f, GetValue( Level, Mod.TimeLimit ), 0f, 1f, EasingType.SineIn )
			);
		}
		else
		{
			DisplayCooldown = 0f;
		}
	}

	public override void OnChoosePerk( TypeDescription type )
	{
		base.OnChoosePerk( type );

		_timeSinceTrigger = 0f;
	}

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

		_timeSinceTrigger = 0f;
	}

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

		if( Player.NumPerkPointsAvailable <= 1 )
			_timeSinceTrigger = 0f;
	}

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

		_timeSinceTrigger = 0f; 
	}

	private static float GetValue( int level, Mod mod, bool isPercent = false )
	{
		switch ( mod )
		{
			case Mod.TimeLimit:
			default:
				return 20f - 5f * level;
			case Mod.HpLoss:
				return 20f - 5f * level;
		}
	}
}