perks/PerkHurtMeleeHealthPack.cs

A perk component that gives the player a health pack after being hit by melee damage a set number of times. It counts melee hits, spawns a flying health pack and plays a sound, and updates HUD display values and highlight visuals.

Networking
using Sandbox;

[Perk( Rarity.Epic, locked: true, alwaysOfferDebug: false )]
public class PerkHurtMeleeHealthPack : Perk
{
	private int _meleeHitCount;

	private enum Mod { HitsRequired }

	static PerkHurtMeleeHealthPack()
	{
		Register<PerkHurtMeleeHealthPack>(
			name: "Pain Relief",
			imagePath: "textures/icons/vector/hurt_melee_health_pack.png",
			description: level => $"+1 healthpack every {(int)GetValue( level, Mod.HitsRequired )} times\nyou're hit by melee dmg",
			upgradeDescription: level => $"+1 healthpack every {(int)GetValue( level - 1, Mod.HitsRequired )}→{(int)GetValue( level, Mod.HitsRequired )} times\nyou're hit by melee dmg"
		);
	}

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

		HighlightColor = new Color( 0.3f, 1f, 0.5f );
		HighlightDuration = 1f;
		HighlightOpacity = 2.5f;

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

		_meleeHitCount = 0;
		RefreshDisplay();
	}

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

	public override void OnHit( float amount, DamageType damageType, bool isSelfInflicted, Vector2 dir, float force, Enemy enemySource, EnemyType enemyType, float previousHealth )
	{
		base.OnHit( amount, damageType, isSelfInflicted, dir, force, enemySource, enemyType, previousHealth );

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

		if ( !(amount > 0f) )
			return;

		_meleeHitCount++;
		Check();
	}

	void Check()
	{
		int hitsRequired = (int)GetValue( Level, Mod.HitsRequired );
		if ( _meleeHitCount >= hitsRequired )
		{
			SpawnHealthPack();
			_meleeHitCount -= hitsRequired;
			Highlight();
			IconScale = 1.4f;
			IconAngleOffset = 20f;
		}

		RefreshDisplay();
	}

	void SpawnHealthPack()
	{
		var targetPos = Player.Position2D;
		int tries = 0;

		while ( targetPos.Distance( Player.Position2D ) < 300f )
		{
			var dir = Utils.GetRandomVector();
			var dist = Game.Random.Float( 300f, 1000f );
			targetPos = Manager.Instance.ClampPosToBounds( Player.Position2D + dir * dist );

			tries++;
			if ( tries > 20 )
				break;
		}
		
		Manager.Instance.SpawnItemFlyToRpc( "health_pack", Player.Position2D, targetPos, time: Game.Random.Float( 1.4f, 1.8f ), height: Game.Random.Float( 220f, 250f ) );

		Manager.Instance.PlaySfxNearbyRpc( "scuffle", Player.Position2D, pitch: Game.Random.Float( 0.95f, 1.05f ), volume: 1.3f, maxDist: 300f );
	}

	void RefreshDisplay()
	{
		int hitsRequired = (int)GetValue( Level, Mod.HitsRequired );
		DisplayText = $"{hitsRequired - _meleeHitCount}";
		DisplayCooldown = (float)_meleeHitCount / hitsRequired;
	}

	private static float GetValue( int level, Mod mod, bool isPercent = false )
	{
		switch ( mod )
		{
			case Mod.HitsRequired:
			default:
				switch( level )
				{
					case 1: default: return 13;
					case 2: return 11;
					case 3: return 9;
					case 4: return 7;
				}
		}
	}
}