perks/PerkDodgeHpDamage.cs

A rare perk class 'PerkDodgeHpDamage' that, when active, causes an attacker to take damage equal to a percent of their remaining HP when their melee attack is dodged. It manages cooldown, UI display colors, highlight effects, and applies damage via enemySource.DamageRpc on OnDodged.

Networking
using Sandbox;
using System;
using System.Numerics;

[Perk( Rarity.Rare, includedAtStart: false, locked: true, minUnlocksReq: 1, alwaysOfferDebug: false )]
public class PerkDodgeHpDamage : Perk
{
	private enum Mod { DmgHpPercent, Cooldown };

	private TimeSince _timeSinceDmg;

	private bool _isActive;

	static PerkDodgeHpDamage()
	{
		Register<PerkDodgeHpDamage>(
			name: "Jujitsu",
			imagePath: "textures/icons/vector/dodge_hp_damage.png",
			description: level => $"When you dodge melee dmg, hurt the attacker for {(int)GetValue( level, Mod.DmgHpPercent, true )}% of their remaining hp __ (cooldown: {GetValue( level, Mod.Cooldown )}s)",
			upgradeDescription: level => $"When you dodge melee dmg, hurt the attacker for {(int)GetValue( level - 1, Mod.DmgHpPercent, true )}%→{(int)GetValue( level, Mod.DmgHpPercent, true )}% of their remaining hp __ (cooldown: {GetValue( level - 1, Mod.Cooldown )}→{GetValue( level, Mod.Cooldown )}s)"
		);
	}

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

		DisplayCooldownColor = new Color( 0.7f, 0.7f, 1f, 0.5f );

		HighlightColor = new Color( 0.3f, 0.4f, 1f );
		HighlightDuration = 0.3f;
		HighlightOpacity = 4f;
	}

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

		_isActive = true;
		ShouldUpdate = false;
		DisplayCooldown = 0f;
	}

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

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

		var cooldown = GetValue( Level, Mod.Cooldown );
		if ( _timeSinceDmg > cooldown )
		{
			_isActive = true;
			DisplayCooldown = 0f;
			ShouldUpdate = false;
		}
		else
		{
			DisplayCooldown = Utils.Map( _timeSinceDmg, 0f, cooldown, 1f, 0f );
		}
	}

	private static float GetValue( int level, Mod mod, bool isPercent = false )
	{
		switch ( mod )
		{
			case Mod.DmgHpPercent:
			default:
				return isPercent
					? 5 + 3 * level
					: 0.05f + 0.03f * level;
			case Mod.Cooldown:
				return 5f - level * 1f;
		}
	}

	public override void OnDodged( float damage, DamageType damageType, Vector2 dir, Enemy enemySource )
	{
		base.OnDodged( damage, damageType, dir, enemySource );

		if ( !_isActive )
			return;

		if ( enemySource == null )
			return;

		if ( !Player.IsDamageTypeMelee( damageType ) )
			return;

		Vector2 pos = Player.Position2D;
		float dmgHpPercent = GetValue( Level, Mod.DmgHpPercent );
		float dmg = enemySource.Health * dmgHpPercent;

		var hitPos = enemySource.WorldPosition + (Vector3)dir * enemySource.Radius;

		enemySource.DamageRpc( dmg, Player, DamageType.Melee, hitPos, force: dir * dmg * 2f, isCrit: false, shouldFlinch: true, damageFlags: DamageResultFlags.DodgeHpDamage );

		// todo: sfx

		_timeSinceDmg = 0f;
		Highlight();
		_isActive = false;
		ShouldUpdate = true;

		DisplayCooldown = 1f;
	}
}