perks/PerkRegenMissingHp.cs

A rare perk component that heals the player for a percentage of missing HP every 3 seconds. It computes the heal amount based on perk level, updates a regen display modifier each frame, heals on a timer, and shows a highlight and cooldown display.

Native Interop
using System;
using Sandbox;

[Perk( Rarity.Rare, alwaysOfferDebug: false )]
public class PerkRegenMissingHp : Perk
{
	private enum Mod { MissingHpPercent };

	public bool IsShielded { get; private set; }
	private float _timer;

	private const float DELAY = 3f;

	static PerkRegenMissingHp()
	{
		Register<PerkRegenMissingHp>(
			name: "IV Drip",
			imagePath: "textures/icons/vector/regen_missing_hp.png",
			description: level => $"Heal {string.Format( "{0:0}", GetValue( level, Mod.MissingHpPercent, true ) )}% of your\nmissing hp every {DELAY}s",
			upgradeDescription: level => $"Heal {string.Format( "{0:0}", GetValue( level - 1, Mod.MissingHpPercent, true ) )}%→{string.Format( "{0:0}", GetValue( level, Mod.MissingHpPercent, true ) )}% of your\nmissing hp every {DELAY}s"
		);
	}

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

		ShouldUpdate = true;

		HighlightColor = new Color( 0.4f, 1f, 0.4f );
		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.MissingHpPercent:
			default:
				switch ( level )
				{
					case 1: default: return isPercent ? 4f : 0.04f;
					case 2: return isPercent ? 5.5f : 0.055f;
					case 3: return isPercent ? 7f : 0.07f;
					case 4: return isPercent ? 8f : 0.08f;
					case 5: return isPercent ? 9f : 0.09f;
				}
		}
	}

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

		float amount = Player.Health < Player.Stats[PlayerStat.MaxHp] ? (Player.Stats[PlayerStat.MaxHp] - Player.Health) * GetValue( Level, Mod.MissingHpPercent ) : 0f;
		float amountPerSecond = amount / DELAY;
		float displayAmount = amountPerSecond > 0f ? MathF.Max( amountPerSecond, 0.1f ) : 0f;
		Player.Modify( this, PlayerStat.HpRegenDisplay, displayAmount, ModifierType.Add );

		_timer += dt;
		if ( _timer > DELAY )
		{
			if ( Player.Health < Player.Stats[PlayerStat.MaxHp] )
			{
				Player.Heal( amount );
				Highlight();
			}

			_timer = 0f;
		}

		DisplayCooldown = Utils.Map( _timer, 0f, DELAY, 0f, 1f );
	}
}