perks/CurseRarityHpCost.cs

A Perk class that applies a health cost when a non-curse perk is chosen, scaled by the perk rarity. It damages the player, shows floater text, plays a sound, and applies visual highlight/ icon tweaks.

Networking
[Perk( Rarity.Unique, curse: true, alwaysOfferDebug: false )]
public class CurseRarityHpCost : Perk
{
	static CurseRarityHpCost()
	{
		Register<CurseRarityHpCost>(
			name: "Blood Price",
			imagePath: "textures/icons/vector/curse_rarity_hp_cost.png",
			description: level => "Choosing a perk\ncosts hp based on rarity"
		);
	}

	public override void OnChoosePerk( TypeDescription type )
	{
		base.OnChoosePerk( type );

		var attrib = type.GetAttribute<PerkAttribute>();
		if ( attrib.Curse )
			return;

		int cost = GetHpCostForRarity( attrib.Rarity );
		if ( cost <= 0 )
			return;

		Player.Damage( cost, DamageType.Self, Player.Position2D, Utils.GetRandomVector(), upwardAmount: 0f, force: 0f, ragdollForce: 0f, enemySource: null, enemyType: EnemyType.None );
		Manager.Instance.SpawnFloaterTextRpc( Player.WorldPosition.WithZ( 65f ), $"-{cost} 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( 8f, 14f ) * (Game.Random.Int( 0, 1 ) == 0 ? -1f : 1f);

		Manager.Instance.PlaySfxNearby( "puddle_splat", Player.Position2D, pitch: Utils.Map( cost, 4, 25, 0.55f, 0.85f ), volume: 1.3f, maxDist: 350f );
	}

	public static int GetHpCostForRarity( Rarity rarity )
	{
		switch ( rarity )
		{
			case Rarity.Common:    default: return 0;
			case Rarity.Uncommon:           return 4;
			case Rarity.Rare:               return 8;
			case Rarity.Epic:               return 12;
			case Rarity.Mythic:             return 15;
			case Rarity.Legendary:          return 20;
			case Rarity.Unique:             return 25;
		}
	}
}