perks/PerkEnemyExplode.cs

A Perk class called PerkEnemyExplode (Combustion). It tracks kill counts and, every Nth kill, enables a temporary player 'combustion' state that causes an explosion effect with radius and damage based on perk level and player stats. It also updates UI display text, cooldown visuals, and handles enabling/disabling the effect on remove.

NetworkingFile Access
using System;
using Sandbox;

[Perk( Rarity.Epic, locked: true, minUnlocksReq: 1, alwaysOfferDebug: false, IncludedCategories = new[] { PerkCategory.Aoe, PerkCategory.Explosion })]
public class PerkEnemyExplode : Perk
{
	private enum Mod { NumKillsReq, HpPercent };

	private int _killCounter;

	private const float EXPLOSION_RADIUS = 80f;


	static PerkEnemyExplode()
	{
		Register<PerkEnemyExplode>(
			name: "Combustion",
			imagePath: "textures/icons/vector/enemy_explode.png",
			description: level => $"Every {(int)GetValue( level, Mod.NumKillsReq )}th enemy you kill explodes for {(int)GetValue( level, Mod.HpPercent, true )}% of their max hp as dmg",
			upgradeDescription: level => $"Every [+]{(int)GetValue( level - 1, Mod.NumKillsReq )}th→{(int)GetValue( level, Mod.NumKillsReq )}th[/+] enemy you kill explodes for {(int)GetValue( level - 1, Mod.HpPercent, true )}%→{(int)GetValue( level, Mod.HpPercent, true )}% of their max hp as dmg"
		);
	}

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

		DisplayCooldownColor = new Color( 1f, 0.2f, 0f, 3f );

		HighlightColor = new Color( 1f, 0.75f, 0.75f );
		HighlightDuration = 0.25f;
		HighlightOpacity = 3f;
	}

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

		int numReq = (int)GetValue( Level, Mod.NumKillsReq );
		DisplayText = Player.CombustionActive ? $"✔️" : $"{Math.Max( numReq - _killCounter, 1 ) }";
		DisplayCooldown = Player.CombustionActive ? 1f : Utils.Map( Math.Min( _killCounter, numReq - 1 ), 0, numReq, 0f, 1f );
	}

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

		if ( !Player.CombustionActive )
		{
			int numReq = (int)GetValue( Level, Mod.NumKillsReq );
			DisplayText = $"{numReq - _killCounter}";
			DisplayCooldown = Utils.Map( _killCounter, 0, numReq, 0f, 1f );
			DisplayCooldownColor = new Color( 1f, 0.2f, 0f, 3f );
			ShouldUpdate = false;
		}
		else
		{
			DisplayCooldownColor = new Color( 1f, 0.2f, 0.0f, Utils.Map( Utils.FastSin( RealTime.Now * 32f ), -1f, 1f, 0.2f, 2f ) );
		}
	}

	private static float GetValue( int level, Mod mod, bool isPercent = false )
	{
		switch ( mod )
		{
			case Mod.NumKillsReq:
			default:
				return 11 - level;
			case Mod.HpPercent:
				return isPercent
					? 10f + 25f * level
					: 0.10f + 0.25f * level;
		}
	}

	public override void OnKill( Enemy enemy, DamageType damageType, bool countsAsKill )
	{
		base.OnKill( enemy, damageType, countsAsKill );
		if ( !countsAsKill ) return;

		//if ( damageType == DamageType.Explosion )
		//	return;

		_killCounter++;
		int numReq = (int)GetValue( Level, Mod.NumKillsReq );
		if ( _killCounter >= numReq )
		{
			float radius = EXPLOSION_RADIUS * Player.Stats[PlayerStat.RadiusMultiplier] * Player.Stats[PlayerStat.ExplosionSizeMultiplier];
			float damageFactor = GetValue( Level, Mod.HpPercent ) * Player.Stats[PlayerStat.ExplosionDamageMultiplier];

			Player.EnableCombustion( radius, damageFactor );
			// todo: if get +radius perk, update active combustion radius

			ShouldUpdate = true;

			_killCounter = 0;

			Highlight();
		}

		DisplayText = Player.CombustionActive ? $"✔️" : $"{numReq - _killCounter}";
		DisplayCooldown = Player.CombustionActive ? 1f : Utils.Map( _killCounter, 0, numReq, 0f, 1f );
	}

	public override void Remove( bool restart = false )
	{
		base.Remove( restart );

		if( Player.CombustionActive )
			Player.DisableCombustion();
	}
}