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;
}
}