perks/CurseChooseSamePerk.cs

A unique curse perk called "Obsessive" that penalizes the player when they pick a perk they already have. It subtracts max HP, tracks total max HP lost, shows a floating negative text, and briefly highlights the player.

NetworkingFile Access
using System;
using Sandbox;

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

	private float _totalMaxHpLost;

	static CurseChooseSamePerk()
	{
		Register<CurseChooseSamePerk>(
			name: "Obsessive",
			imagePath: "textures/icons/vector/curse_choose_same_perk.png",
			description: level => $"-{(int)GetValue( level, Mod.MaxHpLost )} max hp when you get\n a perk you already have"
		);
	}

	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 OnAddPerkBefore( TypeDescription type )
	{
		base.OnAddPerkBefore( type );

		var typeIdentity = type.Identity;
		if ( !Player.Perks.ContainsKey( typeIdentity ) )
			return;

		var attrib = type.GetAttribute<PerkAttribute>();
		bool isCurse = attrib.Curse;

		if ( isCurse )
			return;

		int maxHpToLose = (int)GetValue(Level, Mod.MaxHpLost );

		_totalMaxHpLost += maxHpToLose;

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

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

		Highlight();
	}

	private static float GetValue( int level, Mod mod, bool isPercent = false )
	{
		switch ( mod )
		{
			case Mod.MaxHpLost:
			default:
				return 2;
		}
	}
}