perks/PerkFreezeArmor.cs

A perk class (PerkFreezeArmor) that gives the player a temporary frozen armor effect. It spawns a frost armor GameObject, can be activated to freeze and damage the next enemy the player touches, then starts a cooldown and updates visual/display state.

File AccessNetworking
using System;
using Sandbox;

[Perk( Rarity.Epic, locked: true, alwaysOfferDebug: false, IncludedCategories = new[] { PerkCategory.Freeze })]
public class PerkFreezeArmor : Perk
{
	private enum Mod { Cooldown, Damage };

	private bool _isActive;
	private TimeSince _timeSinceActivate;

	private GameObject _freezeArmorGo;

	static PerkFreezeArmor()
	{
		Register<PerkFreezeArmor>(
			name: "Frozen Armor",
			imagePath: "textures/icons/vector/freeze_armor.png",
			description: level => $"Freeze and do {GetValue( level, Mod.Damage ).ToString( "0.#" )} dmg to the next enemy you touch __ (cooldown: {GetValue( level, Mod.Cooldown ).ToString( "0.#" )}s)",
			upgradeDescription: level => $"Freeze and do {GetValue( level - 1, Mod.Damage ).ToString( "0.#" )}→{GetValue( level, Mod.Damage ).ToString( "0.#" )} dmg to the next enemy you touch\n(cooldown: {GetValue( level - 1, Mod.Cooldown ).ToString( "0.#" )}→{GetValue( level, Mod.Cooldown ).ToString("0.#")}s)"
		);
	}

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

		_freezeArmorGo = GameObject.Clone( "prefabs/effects/frost_armor.prefab", new CloneConfig { StartEnabled = true, Parent = Player.GameObject } );
		_freezeArmorGo.LocalPosition = new Vector3( 0f, 0f, 6f );
	}

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

		_isActive = true;
	}

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

		Player.Modify( this, PlayerStat.FreezeOnMeleeCooldownDisplay, GetValue( Level, Mod.Cooldown ), ModifierType.Add );
	}

	public override void Colliding( Thing other, float percent, float dt )
	{
		base.Colliding( other, percent, dt );

		if ( !_isActive )
			return;

		if ( other is Enemy enemy && !enemy.IsDying )
		{
			enemy.Freeze( playerSource: Player, enemySource: null, Player.Stats[PlayerStat.FreezeTimeScale], Player.Stats[PlayerStat.FreezeLifetime] );

			Vector2 dir = (enemy.Position2D - Player.Position2D).LengthSquared > Manager.TOUCH_DIST_REQUIRED_SQR
				? (enemy.Position2D - Player.Position2D).Normal
				: Utils.GetRandomVector();

			var hitPos = enemy.WorldPosition - (Vector3)dir * enemy.Radius;

			var damage = GetValue( Level, Mod.Damage );

			//Player.GetAdditionalDamageToEnemy( ref damage, enemy, DamageType.Freeze, dir, canBackstab: true );

			enemy.DamageRpc( damage, Player, DamageType.FrostArmor, hitPos, force: dir * 5f, isCrit: false, shouldFlinch: true );

			//Player.OnDamageEnemy( enemy, damage, DamageType.Freeze, dir, isCrit: false );

			BecomeNotReady();
		}
	}

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

		float cooldown = GetValue( Level, Mod.Cooldown );

		if ( _timeSinceActivate > cooldown )
		{
			BecomeReady();
		}
		else
		{
			DisplayCooldown = Utils.Map( _timeSinceActivate, 0f, cooldown, 1f, 0f );
		}
	}

	private static float GetValue( int level, Mod mod, bool isPercent = false )
	{
		switch ( mod )
		{
			case Mod.Cooldown:
			default:
				return 5f - 1f * level;
			case Mod.Damage:
				switch( level)
				{
					case 1: default: return 20f;
					case 2: return 50f;
					case 3: return 75f;
					case 4: return 100f;
				}
		}
	}

	void BecomeReady()
	{
		_isActive = true;
		ShouldUpdate = false;
		DisplayText = " ";
		DisplayCooldown = 0f;

		HighlightColor = new Color( 1f, 1f, 1f );
		HighlightDuration = 0.15f;
		HighlightOpacity = 0.8f;
		Highlight();

		if ( _freezeArmorGo.IsValid() )
			_freezeArmorGo.Enabled = true;
	}

	void BecomeNotReady()
	{
		_timeSinceActivate = 0f;
		_isActive = false;
		ShouldUpdate = true;
		DisplayText = "❌";

		HighlightColor = new Color( 0.35f, 0.5f, 1f );
		HighlightDuration = 0.25f;
		HighlightOpacity = 2f;
		Highlight();

		if ( _freezeArmorGo.IsValid() )
			_freezeArmorGo.Enabled = false;
	}

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

		BecomeNotReady();
	}

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

		BecomeNotReady();
	}

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

		if ( _freezeArmorGo.IsValid() )
			_freezeArmorGo.Destroy();
	}
}