perks/PerkMoreRerollsLoseHp.cs

A Perk class 'PerkMoreRerollsLoseHp' that triggers on player level-up. It reduces the player's HP to 1, and if the HP lost meets a threshold it grants a reroll item (or directly increments rerolls), an armor item, and permanently increases player max HP by 1 (tracked via a modifier). It also spawns visual/sound effects and floater text.

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

[Perk( Rarity.Legendary, locked: true, minUnlocksReq: 3, alwaysOfferDebug: false, IncludedCategories = new[] { PerkCategory.SelfDmg, PerkCategory.OneHpLeft })]
public class PerkMoreRerollsLoseHp : Perk
{
	private enum Mod { HpRequired };


	private int _maxHpAdded;

	// todo: make ">30" a richtext, so it doesn't render like "> 30"

	static PerkMoreRerollsLoseHp()
	{
		Register<PerkMoreRerollsLoseHp>(
			name: "Blood Donation",
			imagePath: "textures/icons/vector/more_rerolls_lose_hp.png",
			description: level => $"Lose all but 1 hp on level-up\nIf you lose at least {(int)GetValue( level, Mod.HpRequired )} hp,\nget +1 reroll-item +10 armor-item and +1 max hp",
			upgradeDescription: level => $"Lose all but 1 hp on level-up\nIf you lose at least {(int)GetValue( level - 1, Mod.HpRequired )}→{(int)GetValue( level, Mod.HpRequired )} hp,\nget +1 reroll-item +10 armor-item and +1 max hp"
		);
	}

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

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

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

	}

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

		float hpToLose = Player.Health - 1f;
		if ( hpToLose > 0f )
		{
			// todo: better sfx? can't hear it
			Player.Damage( hpToLose, damageType: DamageType.Self, Player.Position2D, Utils.GetRandomVector(), upwardAmount: 0f, 0f, ragdollForce: 0.1f, enemySource: null, enemyType: EnemyType.None, cantKill: true );

			if( hpToLose >= GetValue(Level, Mod.HpRequired ) )
			{
				if ( Manager.Instance.IsUnpausedChoosing )
				{
					var rerollPos = Player.Position2D + Utils.GetRandomVector() * Game.Random.Float( 1f, 5f );
					Manager.Instance.SpawnItemRpc( "reroll_item", rerollPos, Utils.GetRandomVectorInCone(-Player.FacingDir ) );

					Player.DodgeDuckRpc( dir: Utils.GetRandomVectorInCone( -Player.FacingDir ), time: Game.Random.Float( 0.1f, 0.125f ) );
				}
				else
				{
					Player.AddRerolls( 1 );
				}

				var armorPos = Player.Position2D + Utils.GetRandomVector() * Game.Random.Float( 1f, 5f );
				Manager.Instance.SpawnItemRpc( "armor_item", armorPos, Utils.GetRandomVectorInCone( -Player.FacingDir ) );

				_maxHpAdded += 1;
				Player.Modify( this, PlayerStat.MaxHp, _maxHpAdded, ModifierType.Add );
				Manager.Instance.SpawnFloaterTextRpc( Player.WorldPosition.WithZ( 20f ), $"+1 MAX HP!", new Color( 0f, 0.8f, 0f ), size: 1.2f, FloaterType.PositiveMessage );
			}
		}

		Highlight();
	}

	private static float GetValue( int level, Mod mod, bool isPercent = false )
	{
		switch ( mod )
		{
			case Mod.HpRequired:
			default:
				return 30f - 10f * level;
		}
	}
}