perks/PerkMarkEnemy.cs

A perk class 'PerkMarkEnemy' that marks an enemy when the player touches them. It sets a marked state on the enemy, applies periodic damage based on perk level, handles cooldown, visual highlight and UI icon adjustments, and unmarks the enemy when the perk is removed or another is marked.

Networking
using System;
using Sandbox;

[Perk( Rarity.Epic, locked: true, minUnlocksReq: 2, alwaysOfferDebug: false )]
public class PerkMarkEnemy : Perk
{
	private const float COOLDOWN = 0.1f;

	private Enemy _markedEnemy;
	private TimeSince _timeSinceMark;

	static PerkMarkEnemy()
	{
		Register<PerkMarkEnemy>(
			name: "Mark of Doom",
			imagePath: "textures/icons/vector/mark_enemy.png",
			description: level => $"Touching an enemy marks them\nThe marked enemy takes {GetDamage( level )} dmg/s",
			upgradeDescription: level => $"Touching an enemy marks them\nThe marked enemy\ntakes {GetDamage( level - 1 )}→{GetDamage( level )} dmg/s"
		);
	}

	private static float GetDamage( int level ) => 3f * level;

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

		ShouldUpdate = true;
		_timeSinceMark = COOLDOWN;

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

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

		DisplayCooldown = _timeSinceMark < COOLDOWN ? Utils.Map( _timeSinceMark, 0f, COOLDOWN, 1f, 0f ) : 0f;
	}

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

		if ( _markedEnemy.IsValid() )
		{
			var status = _markedEnemy.GetUnitStatus<UnitStatusMarked>();
			if ( status != null )
				status.Damage = GetDamage( Level );
		}
	}

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

		if ( other is Enemy enemy && !enemy.IsDying && !enemy.IsMarked && enemy != _markedEnemy && _timeSinceMark > COOLDOWN )
		{
			if ( _markedEnemy.IsValid() )
				_markedEnemy.UnMark();

			_markedEnemy = enemy;
			_timeSinceMark = 0f;

			enemy.Mark( Player, GetDamage( Level ) );

			Highlight();
			IconScale = 1.35f;
			IconAngleOffset = Game.Random.Float( 12f, 18f ) * (Game.Random.Int( 0, 1 ) == 0 ? -1f : 1f);
		}
	}

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

		if ( _markedEnemy.IsValid() )
			_markedEnemy.UnMark();
	}
}