A unique curse perk that periodically reverses the player's movement and aim controls. It toggles state on a timer, applies/removes player stat modifiers, updates UI highlight/icon properties, plays a sound, and enables/disables a reverse-controls overlay effect.
using System;
using Sandbox;
[Perk( Rarity.Unique, curse: true, alwaysOfferDebug: false )]
public class CurseReverseControls : Perk
{
private enum Mod { ReverseTime, Delay, };
private bool _areControlsReversed;
private TimeSince _timeSince;
private float _currDelayTime;
static CurseReverseControls()
{
Register<CurseReverseControls>(
name: "Backwards Man",
imagePath: "textures/icons/vector/curse_reverse_controls.png",
description: level => $"Your controls are\nsometimes reversed"
);
}
public override void Start()
{
base.Start();
ShouldUpdate = true;
DisplayCooldownColor = new Color( 0.75f, 0.25f, 0.45f );
_timeSince = 0f;
_currDelayTime = GetValue( Level, Mod.Delay );
}
public override void Refresh()
{
base.Refresh();
}
public override void Update( float dt )
{
base.Update( dt );
if ( _timeSince > _currDelayTime )
{
_areControlsReversed = !_areControlsReversed;
if(_areControlsReversed)
{
Player.Modify( this, PlayerStat.ReverseMoveControls, 1f, ModifierType.Add );
Player.Modify( this, PlayerStat.ReverseAimControls, 1f, ModifierType.Add );
HighlightColor = new Color( 0.5f, 0f, 0f );
HighlightDuration = 0.25f;
HighlightOpacity = 3.5f;
Highlight();
IconScale = Game.Random.Float( 1.1f, 1.15f );
IconAngleOffset = Game.Random.Float( 5f, 10f ) * (Game.Random.Int( 0, 1 ) == 0 ? -1f : 1f);
DisplayCooldownColor = new Color( 0.75f, 0.25f, 0.45f );
Manager.Instance.OverlayEffects[OverlayEffectsType.ReverseControls].Enabled = true;
}
else
{
Player.StopModifying( this, PlayerStat.ReverseMoveControls );
Player.StopModifying( this, PlayerStat.ReverseAimControls );
HighlightColor = new Color( 0f, 0f, 0.5f );
HighlightDuration = 0.25f;
HighlightOpacity = 2.5f;
Highlight();
IconScale = Game.Random.Float( 1.2f, 1.3f );
IconAngleOffset = Game.Random.Float( 10f, 20f ) * (Game.Random.Int( 0, 1 ) == 0 ? -1f : 1f);
DisplayCooldownColor = new Color( 0.55f, 0.25f, 0.65f );
Manager.Instance.OverlayEffects[OverlayEffectsType.ReverseControls].Enabled = false;
}
_timeSince = 0f;
_currDelayTime = _areControlsReversed
? MathF.Min( GetValue( Level, Mod.ReverseTime ), GetValue( Level, Mod.ReverseTime ) ) // roll twice and pick the lower value
: GetValue( Level, Mod.Delay );
Manager.Instance.PlaySfxNearby( "player.dash", Player.Position2D, pitch: Game.Random.Float( 2.1f, 2.2f ), volume: 0.5f, maxDist: 200f );
}
DisplayText = $"{MathX.CeilToInt( _currDelayTime - _timeSince )}";
DisplayCooldown = _areControlsReversed
? Utils.Map( _timeSince, 0f, _currDelayTime, 1f, 0f )
: Utils.Map( _timeSince, 0f, _currDelayTime, 0f, 1f );
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.ReverseTime:
default:
return Game.Random.Float( 3f, 13f );
//return level == 1
// ? Game.Random.Float( 2f, 8f )
// : Game.Random.Float( 5f, 20f );
case Mod.Delay:
return Game.Random.Float( 7f, 40f );
//return level == 1
// ? Game.Random.Float( 10f, 35f )
// : Game.Random.Float( 2f, 15f );
}
}
public override void OnDie()
{
base.OnDie();
Manager.Instance.OverlayEffects[OverlayEffectsType.ReverseControls].Enabled = false;
}
public override void Remove( bool restart = false )
{
base.Remove( restart );
Manager.Instance.OverlayEffects[OverlayEffectsType.ReverseControls].Enabled = false;
}
}