perks/CurseObscureScreen.cs

A unique curse perk that obscures the player screen when the player is hit. It enables an overlay vignette, animates intensity (fade in, hold, fade out) over 5 seconds, updates UI cooldown and display text, and clears the effect on death or removal.

NetworkingFile Access
using System;
using Sandbox;

[Perk( Rarity.Unique, curse: true, alwaysOfferDebug: false )]
public class CurseObscureScreen : Perk
{
	private const float TOTAL_TIME = 5f;
	private const float FADE_IN_TIME = 0.2f;
	private const float FADE_OUT_START = 4.5f;
	private const float MAX_INTENSITY = 2f;

	private TimeSince _timeSinceHurt;
	private bool _isActive;
	private float _fadeInStartIntensity;

	static CurseObscureScreen()
	{
		Register<CurseObscureScreen>(
			name: "Blindness",
			imagePath: "textures/icons/vector/curse_obscure_screen.png",
			description: level => $"Reduce vision for {TOTAL_TIME}s when hit"
		);
	}

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

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

		if ( !_isActive ) 
			return;

		var obscure = Manager.Instance.OverlayEffects[OverlayEffectsType.Obscure];
		var vignette = obscure.GetComponent<Vignette>();

		if ( _timeSinceHurt > TOTAL_TIME )
		{
			obscure.Enabled = false;
			_isActive = false;
			ShouldUpdate = false;
			DisplayText = " ";
			DisplayCooldown = 0f;
		}
		else
		{
			float intensity;

			if ( _timeSinceHurt < FADE_IN_TIME )
				intensity = Utils.Map( _timeSinceHurt, 0f, FADE_IN_TIME, _fadeInStartIntensity, MAX_INTENSITY, EasingType.SineOut );
			else if ( _timeSinceHurt < FADE_OUT_START )
				intensity = MAX_INTENSITY;
			else
				intensity = Utils.Map( _timeSinceHurt, FADE_OUT_START, TOTAL_TIME, MAX_INTENSITY, 0f, EasingType.SineIn );

			vignette.Intensity = intensity;

			DisplayText = $"{MathX.CeilToInt( TOTAL_TIME - _timeSinceHurt )}";
			DisplayCooldown = Utils.Map( _timeSinceHurt, 0f, TOTAL_TIME, 1f, 0f );
		}
	}

	public override void OnHit( float amount, DamageType damageType, bool isSelfInflicted, Vector2 dir, float force, Enemy enemySource, EnemyType enemyType, float previousHealth )
	{
		base.OnHit( amount, damageType, isSelfInflicted, dir, force, enemySource, enemyType, previousHealth );

		if ( damageType == DamageType.Self ) 
			return;

		var obscure = Manager.Instance.OverlayEffects[OverlayEffectsType.Obscure];
		var vignette = obscure.GetComponent<Vignette>();

		_fadeInStartIntensity = _isActive ? vignette.Intensity : 0f;
		obscure.Enabled = true;

		_timeSinceHurt = 0f;
		_isActive = true;
		ShouldUpdate = true;

		DisplayCooldown = 1f;

		HighlightColor = new Color( 1f, 0f, 0.5f );
		HighlightDuration = 0.4f;
		HighlightOpacity = 2.5f;
		Highlight();

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

	void DisableEffect()
	{
		Manager.Instance.OverlayEffects[OverlayEffectsType.Obscure].Enabled = false;
		_isActive = false;
		ShouldUpdate = false;
	}

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

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

		DisableEffect();
	}
}