perks/CurseXpCoinHurt.cs

A Perk class named CurseXpCoinHurt that applies a unique curse: when the player gains XP coins they take a small amount of self-damage and the perk modifies a player stat for coin damage. It registers UI metadata, sets highlight visuals, applies a negative modifier to PlayerStat.CoinFlatDmgAmount, and deals Damage on OnGainXpCoin.

Native Interop
using System;
using Sandbox;

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

	static CurseXpCoinHurt()
	{
		Register<CurseXpCoinHurt>(
			name: "Sharp Coins",
			imagePath: "textures/icons/vector/curse_xp_coin_hurt.png",
			description: level => $"-{GetValue( level, Mod.DmgAmount ).ToString("0.##")} hp when you get xp coins"
		);
	}

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

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

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

		Player.Modify( this, PlayerStat.CoinFlatDmgAmount, -GetValue( Level, Mod.DmgAmount ), ModifierType.Add );
	}

	public override void OnGainXpCoin( float xp )
	{
		base.OnGainXpCoin( xp );

		Player.Damage( GetValue( Level, Mod.DmgAmount ), DamageType.Self, Player.Position2D, Utils.GetRandomVector(), upwardAmount: 0f, force: 0f, ragdollForce: 1f, enemySource: null, enemyType: EnemyType.None );

		Highlight();

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

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