perks/CurseHurtLoseMaxHp.cs

A unique curse perk that reduces the player's maximum HP when they take a single hit meeting a damage threshold. It tracks cumulative max HP lost, applies a negative MaxHp modifier, shows a floater text and visual highlight on each trigger.

Networking
using System;
using Sandbox;

[Perk( Rarity.Unique, curse: true, alwaysOfferDebug: false )]
public class CurseHurtLoseMaxHp : Perk
{
	private enum Mod { ReqDamage };

	private float _totalMaxHpLost;

	static CurseHurtLoseMaxHp()
	{
		Register<CurseHurtLoseMaxHp>(
			name: "Crippling Injuries",
			imagePath: "textures/icons/vector/curse_hurt_lose_max_hp.png",
			description: level => $"-1 max hp when hit\nfor at least [-]{(int)GetValue( level, Mod.ReqDamage )}[/-] dmg"
		);
	}

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

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

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

	}

	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( damageType == DamageType.Self )
			return;

		if ( amount >= GetValue( Level, Mod.ReqDamage ) )
		{
			_totalMaxHpLost += 1;

			Player.Modify( this, PlayerStat.MaxHp, -_totalMaxHpLost, ModifierType.Add );

			Manager.Instance.SpawnFloaterTextRpc( Player.WorldPosition.WithZ( 65f ), $"-1 MAX HP", new Color( 0.8f, 0.1f, 0.5f ), size: 1.1f, floaterType: FloaterType.NegativeMessage );

			Highlight();

			IconScale = Game.Random.Float( 1.1f, 1.2f );
			IconAngleOffset = Game.Random.Float( 5f, 8f ) * (Game.Random.Int( 0, 1 ) == 0 ? -1f : 1f);
		}
	}

	private static float GetValue( int level, Mod mod, bool isPercent = false )
	{
		switch ( mod )
		{
			case Mod.ReqDamage:
			default:
				return 12f;
				//switch(level)
				//{
				//	case 1: default: return 10;
				//	case 2: return 5;
				//	case 3: return 1;
				//}
		}
	}
}