perks/CurseHealthPackReverseControls.cs

A unique curse perk that reverses player move and aim controls when they pick up a healthpack. It applies modifiers on healthpack pickup, shows UI text/cooldown while active, toggles an overlay effect, and clears the modifiers when the reverse time elapses or on death/remove.

NetworkingFile Access
using System;
using Sandbox;

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

	private TimeSince _timeSince;

	static CurseHealthPackReverseControls()
	{
		Register<CurseHealthPackReverseControls>(
			name: "Wacky Packs",
			imagePath: "textures/icons/vector/curse_health_pack_reverse_controls.png",
			description: level => $"Healthpacks reverses your controls for [-]{GetValue( level, Mod.ReverseTime )}s[/-]"
		);
	}

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

	}

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

	}

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

		var reverseTime = GetValue( Level, Mod.ReverseTime );
		if ( _timeSince > reverseTime )
		{
			Player.StopModifying( this, PlayerStat.ReverseMoveControls );
			Player.StopModifying( this, PlayerStat.ReverseAimControls );

			ShouldUpdate = false;

			DisplayText = " ";
			DisplayCooldown = 0f;

			Manager.Instance.PlaySfxNearby( "player.dash", Player.Position2D, pitch: Game.Random.Float( 2.1f, 2.2f ), volume: 0.5f, maxDist: 200f );

			HighlightColor = new Color( 0.2f, 0.4f, 0.7f );
			HighlightDuration = 0.35f;
			HighlightOpacity = 3f;
			Highlight();

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

			Manager.Instance.OverlayEffects[OverlayEffectsType.HealthPackReverseControls].Enabled = false;
		}
		else
		{
			DisplayText = $"{MathX.CeilToInt( reverseTime - _timeSince )}";
			DisplayCooldown = Utils.Map( _timeSince, 0f, reverseTime, 1f, 0f );
		}
	}

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

	public override void OnGainHealthpack( float amount )
	{
		base.OnGainHealthpack( amount );

		Player.Modify( this, PlayerStat.ReverseMoveControls, 1f, ModifierType.Add );
		Player.Modify( this, PlayerStat.ReverseAimControls, 1f, ModifierType.Add );

		ShouldUpdate = true;
		_timeSince = 0f;

		HighlightColor = new Color( 0.7f, 0.4f, 0.2f );
		HighlightDuration = 0.35f;
		HighlightOpacity = 3f;
		Highlight();

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

		Manager.Instance.PlaySfxNearby( "player.dash", Player.Position2D, pitch: Game.Random.Float( 2.1f, 2.2f ), volume: 0.5f, maxDist: 200f );

		DisplayText = $"{MathX.CeilToInt( GetValue( Level, Mod.ReverseTime ) )}";
		DisplayCooldown = 1f;

		Manager.Instance.OverlayEffects[OverlayEffectsType.HealthPackReverseControls].Enabled = true;
	}

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

		Manager.Instance.OverlayEffects[OverlayEffectsType.HealthPackReverseControls].Enabled = false;
	}

	public override void Remove( bool restart = false )
	{
		base.Remove( restart );

		Manager.Instance.OverlayEffects[OverlayEffectsType.HealthPackReverseControls].Enabled = false;
	}
}