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.
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();
}
}