A player perk class "PerkRegenLimitedReserve" that grants a limited, delayed health regeneration reserve. It tracks total healed amount, applies periodic heals while the player is not at full HP up to a per-level cap, updates UI display text and cooldown, and modifies a displayed HP regen value.
using System;
using Sandbox;
[Perk( Rarity.Epic, locked: true, minUnlocksReq: 2, alwaysOfferDebug: false )]
public class PerkRegenLimitedReserve : Perk
{
private enum Mod { RegenAmount, HpTotal };
private const float DELAY = 1f;
private bool _isRegenerating;
private float _totalHealed;
private TimeSince _timeSinceHeal;
static PerkRegenLimitedReserve()
{
Register<PerkRegenLimitedReserve>(
name: "Picnic Basket",
imagePath: "textures/icons/vector/regen_limited_reserve.png",
description: level => $"+{GetValue( level, Mod.RegenAmount ).ToString( "0.#" )} hp/s regen\n(limited to {(int)GetValue( level, Mod.HpTotal )} hp total)",
upgradeDescription: level => $"+{GetValue( level - 1, Mod.RegenAmount ).ToString( "0.#" )}→{GetValue( level, Mod.RegenAmount ).ToString( "0.#" )} hp/s regen\n(limited to {(int)GetValue( level - 1, Mod.HpTotal )}→{(int)GetValue( level, Mod.HpTotal )} hp total)"
);
}
public override void Start()
{
base.Start();
HighlightColor = new Color( 0.3f, 1f, 0.3f );
HighlightDuration = 0.2f;
HighlightOpacity = 1f;
_totalHealed = 0f;
DisplayCooldownColor = new Color( 0.3f, 0.8f, 0.3f, 1f );
}
public override void IncreaseLevel()
{
base.IncreaseLevel();
_timeSinceHeal = 0f;
}
public override void Refresh()
{
base.Refresh();
ShouldUpdate = true;
SetIsRegenerating( Player.HpPercent < 1f );
var hpTotal = GetValue( Level, Mod.HpTotal );
DisplayText = $"{Math.Ceiling( hpTotal - _totalHealed )}";
DisplayCooldown = Utils.Map( _totalHealed, 0f, hpTotal, 1f, 0f );
}
public override void Update( float dt )
{
base.Update( dt );
var hpTotal = GetValue( Level, Mod.HpTotal );
bool isRegenerating = _totalHealed < hpTotal && Player.HpPercent < 1f;
if ( isRegenerating )
{
if ( _timeSinceHeal > DELAY )
{
var missingHp = Player.Stats[PlayerStat.MaxHp] - Player.Health;
var regenAmount = GetValue( Level, Mod.RegenAmount );
var amountToHeal = Math.Min( regenAmount, missingHp );
amountToHeal = Math.Min( amountToHeal, hpTotal - _totalHealed );
Player.Heal( amountToHeal );
Highlight();
_timeSinceHeal = 0f;
_totalHealed += amountToHeal;
if( _totalHealed >= hpTotal )
{
isRegenerating = false;
DisplayText = "❌";
DisplayCooldown = 0f;
ShouldUpdate = false;
}
else
{
DisplayText = $"{Math.Ceiling( hpTotal - _totalHealed )}";
DisplayCooldown = Utils.Map( _totalHealed, 0f, hpTotal, 1f, 0f);
}
}
}
if ( isRegenerating != _isRegenerating )
SetIsRegenerating( isRegenerating );
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.RegenAmount:
default:
return 1.0f + 1.0f * level;
case Mod.HpTotal:
return 50f + 150f * level;
}
}
void SetIsRegenerating( bool isRegenerating )
{
_isRegenerating = isRegenerating;
RefreshDisplay();
}
void RefreshDisplay()
{
Player.Modify( this, PlayerStat.HpRegenDisplay, _isRegenerating ? (GetValue( Level, Mod.RegenAmount ) / DELAY) : 0f, ModifierType.Add );
}
}