perks/CurseCameraMove.cs

A Perk implementation named CurseCameraMove that overrides player camera position. When active it disables player camera control and moves the camera target in a randomized 2D direction, clamping and reflecting it at world bounds.

using System;
using Sandbox;

[Perk( Rarity.Unique, curse: true, alwaysOfferDebug: false )]
public class CurseCameraMove : Perk
{
	private Vector2 _cameraTargetPos;
	private Vector2 _direction;

	static CurseCameraMove()
	{
		Register<CurseCameraMove>(
			name: "Dissociation",
			imagePath: "textures/icons/vector/curse_camera_move.png",
			description: level => $"No camera control"
		);
	}

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

		ShouldUpdate = true;
	}

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

		Player.ShouldOverrideCameraPos = true;

		_cameraTargetPos = Player.Position2D;

		// Generate a random direction that's at least 25 degrees away from cardinal directions
		// cos(25°) ≈ 0.906
		do
		{
			_direction = Utils.GetRandomVector();
		} while ( MathF.Abs( _direction.x ) > 0.906f || MathF.Abs( _direction.y ) > 0.906f );
	}

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

	}

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

		_cameraTargetPos += _direction * 40f * dt;
		CheckBounds();

		Player.CameraTargetPosOverride = _cameraTargetPos;
	}

	void CheckBounds()
	{
		var camDistScale = Player.GetSyncStat( PlayerStat.CameraDistance ) / 800f;

		if ( !Manager.Instance.IsOrthoCamera )
			camDistScale *= 0.75f;

		if ( camDistScale < 1f )
			camDistScale = MathX.Lerp( camDistScale, 0f, 0.3f );

		float X_BUFFER = 310f * camDistScale;
		float Y_LOWER_BUFFER = 200f * camDistScale;
		float Y_UPPER_BUFFER = 170f * camDistScale;

		if ( _cameraTargetPos.x < Manager.Instance.BOUNDS_MIN.x + X_BUFFER )
			OutOfBounds( Direction.Left, X_BUFFER );
		else if ( _cameraTargetPos.x > Manager.Instance.BOUNDS_MAX.x - X_BUFFER )
			OutOfBounds( Direction.Right, X_BUFFER );

		if ( _cameraTargetPos.y < Manager.Instance.BOUNDS_MIN.y + Y_LOWER_BUFFER )
			OutOfBounds( Direction.Down, Y_LOWER_BUFFER );
		else if ( _cameraTargetPos.y > Manager.Instance.BOUNDS_MAX.y - Y_UPPER_BUFFER )
			OutOfBounds( Direction.Up, Y_UPPER_BUFFER );
	}

	void OutOfBounds( Direction direction, float offset )
	{
		if ( direction == Direction.Left )
		{
			_cameraTargetPos = new Vector2( Manager.Instance.BOUNDS_MIN.x + offset, _cameraTargetPos.y );
			_direction = new Vector2( MathF.Abs( _direction.x ), _direction.y );
		}
		else if ( direction == Direction.Right )
		{
			_cameraTargetPos = new Vector2( Manager.Instance.BOUNDS_MAX.x - offset, _cameraTargetPos.y );
			_direction = new Vector2( -MathF.Abs( _direction.x ), _direction.y );
		}
		else if ( direction == Direction.Down )
		{
			_cameraTargetPos = new Vector2( _cameraTargetPos.x, Manager.Instance.BOUNDS_MIN.y + offset );
			_direction = new Vector2( _direction.x, MathF.Abs( _direction.y ) );
		}
		else if ( direction == Direction.Up )
		{
			_cameraTargetPos = new Vector2( _cameraTargetPos.x, Manager.Instance.BOUNDS_MAX.y - offset );
			_direction = new Vector2( _direction.x, -MathF.Abs( _direction.y ) );
		}
	}

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

		Player.ShouldOverrideCameraPos = false;
	}
}