perks/CurseHurtHover.cs

A unique perk class 'CurseHurtHover' that damages the local player when their mouse is hovering over their own player model. It tracks hover time and periodically applies self-damage, plays a highlight effect and randomizes icon visuals.

Networking
using System;
using Sandbox;

[Perk( Rarity.Unique, curse: true, controlMode: ControlMode.MouseAndKeyboard, alwaysOfferDebug: false, IncludedCategories = new[] { PerkCategory.SelfDmg })]
public class CurseHurtHover : Perk
{
	private enum Mod { HpLoss };


	// Track player rotation over time
	private float _accumulatedTime;
	private TimeSince _timeSinceHurt;

	static CurseHurtHover()
	{
		Register<CurseHurtHover>(
			name: "Sharp Cursor",
			imagePath: "textures/icons/vector/curse_hurt_hover.png",
			description: level => $"-{(int)GetValue( level, Mod.HpLoss )} hp when pointing at yourself"
		);
	}

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

		ShouldUpdate = true;

		HighlightColor = new Color( 1f, 0.3f, 0.6f );
		HighlightDuration = 0.75f;
		HighlightOpacity = 6f;
	}

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

	}

	public override void Update( float dt )
	{
		base.Update( dt );

		if( IsHoveringSelf() )
		{
			_accumulatedTime += dt;

			var timeReq = 0.05f;
			if( _accumulatedTime > timeReq && _timeSinceHurt > 0.5f )
			{
				Player.Damage( GetValue( Level, Mod.HpLoss ), DamageType.Self, Player.Position2D, Utils.GetRandomVector(), upwardAmount: Game.Random.Float(0f, 0.2f), force: 0f, ragdollForce: 1f, enemySource: null, enemyType: EnemyType.None);

				Highlight();

				IconScale = Game.Random.Float( 1.2f, 1.3f );
				IconAngleOffset = Game.Random.Float( 10f, 20f ) * (Game.Random.Int( 0, 1 ) == 0 ? -1f : 1f);

				_accumulatedTime -= timeReq;
				_timeSinceHurt = 0f;
			}
		}
		else
		{
			_accumulatedTime *= (1f - Time.Delta * 4f);
		}
	}

	bool IsHoveringSelf()
	{
		var tr = Player.Scene.Trace.Sphere( 3f, Player.Scene.Camera.ScreenPixelToRay( Mouse.Position ), 2500f ).HitTriggersOnly().WithAllTags( "player" ).Run();
		if ( tr.Hit )
		{
			var player = tr.GameObject?.GetComponent<Player>();

			return player == Player;
		}

		return false;
	}

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