A Mythic perk class 'PerkShieldDeath' that prevents player death by restoring 1 health, granting a shield and a short invulnerability window, then starts a cooldown before it can trigger again. It manages timers, UI display state, sounds, chat/floater messages, and perk registration metadata.
using System;
using Sandbox;
[Perk( Rarity.Mythic, alwaysOfferDebug: false, IncludedCategories = new[] { PerkCategory.Shield })]
public class PerkShieldDeath : Perk
{
private enum Mod { RechargeTime, InvulnTime, };
public bool IsReady { get; set; }
private float _timer;
private float _invulnTimer;
private bool _isInvuln;
static PerkShieldDeath()
{
Register<PerkShieldDeath>(
name: "Saving Grace",
imagePath: "textures/icons/vector/death_shield.png",
description: level => $"Instead of dying, get a shield and become invulnerable for {GetValue( level, Mod.InvulnTime ).ToString( "0.#" )}s\n(cooldown: {(int)GetValue( level, Mod.RechargeTime )}s)",
upgradeDescription: level => $"Instead of dying, get a shield and become invulnerable for {GetValue( level - 1, Mod.InvulnTime ).ToString("0.#")}→{GetValue( level, Mod.InvulnTime ).ToString("0.#")}s\n(cooldown: {(int)GetValue( level - 1, Mod.RechargeTime )}→{(int)GetValue( level, Mod.RechargeTime )}s)"
);
}
public override void Start()
{
base.Start();
}
public override void IncreaseLevel()
{
base.IncreaseLevel();
BecomeReady( playSfx: Level > 1 );
}
public override void Refresh()
{
base.Refresh();
}
public override void Update( float dt )
{
if ( _isInvuln )
{
_invulnTimer += dt;
if ( _invulnTimer > GetValue( Level, Mod.InvulnTime ) )
{
//Player.StopModifying( this, PlayerStat.InvulnAmount );
_isInvuln = false;
}
else
{
DisplayCooldownColor = new Color( 1f, 1f, 0f, 5f );
DisplayCooldown = Utils.Map( _invulnTimer, 0f, GetValue( Level, Mod.InvulnTime ), 1f, 0f );
}
}
if ( !IsReady )
{
_timer += dt;
if ( _timer > GetValue( Level, Mod.RechargeTime ) )
{
BecomeReady( playSfx: true );
}
else
{
DisplayText = $"{MathX.CeilToInt( GetValue( Level, Mod.RechargeTime ) - _timer )}";
if ( !_isInvuln )
{
DisplayCooldownColor = new Color( 0.6f, 0.5f, 0.5f, 2.5f );
DisplayCooldown = Utils.Map( _timer, 0f, GetValue( Level, Mod.RechargeTime ), 1f, 0f );
}
}
}
}
public void BecomeReady( bool playSfx = false )
{
IsReady = true;
DisplayText = "✔️";
ShouldUpdate = false;
DisplayCooldown = 0f;
if ( playSfx )
Manager.Instance.PlaySfxUI( "saving_grace_ready", pitch: Game.Random.Float( 0.95f, 1.05f ), volume: 0.75f );
HighlightColor = new Color( 0.7f, 0.5f, 1f );
HighlightDuration = 0.3f;
HighlightOpacity = 4f;
Highlight();
}
public override bool TryPreventDeath()
{
if ( !IsReady )
return false;
Activate();
return true;
}
public void Activate()
{
Player.Health = 1f;
if ( !Player.IsShielded )
Player.GainShield();
IsReady = false;
ShouldUpdate = true;
_timer = 0f;
//Player.Modify( this, PlayerStat.InvulnAmount, 1f, ModifierType.Add );
Player.BecomeInvincible( GetValue( Level, Mod.InvulnTime ) );
_isInvuln = true;
_invulnTimer = 0f;
HighlightColor = new Color( 1f, 1f, 0f );
HighlightDuration = 0.35f;
HighlightOpacity = 3f;
Highlight();
Manager.Instance.Chat.AddLocalChatMessage( $"{Perk.GetRichTextToken( GetType() )} Saved from death!", from: "" );
Manager.Instance.SpawnFloaterTextRpc( Player.WorldPosition.WithZ( 65f ), "SAVED!", new Color( 0.5f, 1f, 1f ), size: 1.2f, floaterType: FloaterType.PositiveMessage );
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.RechargeTime:
default:
return 80f - 20f * level;
case Mod.InvulnTime:
return 0.5f + 0.5f * level;
}
}
}