things/enemies/Boss.Charging.cs

Boss charging behavior for an enemy Boss. Initializes charge parameters, decides when to prepare and start a charge toward players, sets animations and sound via RPCs, computes charge direction, handles redirects and flips velocity when going out of bounds.

Networking
using System;
using Sandbox;
using Sandbox.Citizen;

public partial class Boss
{
	protected float _chargeDelayTimer;
	protected float _chargeDelayMin;
	protected float _chargeDelayMax;

	protected float _chargeTime;
	protected float _chargeTimeMin;
	protected float _chargeTimeMax;

	protected Vector2 _chargeDir;
	protected Vector2 _chargeVel;
	protected TimeSince _timeSinceChargeCloud;
	protected float _chargeRange;

	protected float _chargeRotateSpeed;

	private float _nextRedirectTime;
	private TimeSince _timeSinceRedirect;

	protected float _chargeWeight;

	void InitCharging()
	{
		if ( IsProxy )
			return;

		_chargeRange = 900f;
		_chargeTimeMin = 2.5f;
		_chargeTimeMax = 6.25f;
		_chargeDelayTimer = Game.Random.Float( 8f, 12f ) * Utils.Select( Manager.Instance.Difficulty, 1.3f, 1f, 0.9f );
		_chargeDelayMin = 2f;
		_chargeDelayMax = 10f;
		_chargeRotateSpeed = 5f;

		_chargeWeight = 15f;
	}

	void HandleCharging()
	{
		if ( !IsReadyForAction || !TargetUnit.IsValid() )
			return;

		var targetDistSqr = (TargetUnit.Position2D - Position2D).LengthSquared;
		if ( targetDistSqr < MathF.Pow( _chargeRange, 2f ) )
		{
			_chargeDelayTimer -= Time.Delta * TimeScale;
			if ( _chargeDelayTimer < 0f )
				SetState( BossState.ChargePrepare );
		}
	}

	protected override void HandleMovement()
	{
		if ( State == BossState.Charge )
			return;

		base.HandleMovement();
	}

	[Rpc.Broadcast]
	public void PrepareToChargeRpc()
	{
		Manager.Instance.PlaySfxNearby( "boss.prepare", Position2D, pitch: Game.Random.Float( 1.05f, 1.1f ), volume: 1.4f, maxDist: 500f );

		SetAnim( "ChargePrepare" );

		//AnimationHelper.DuckLevel = 1f;

		CanAnimate = false;
	}

	[Rpc.Broadcast]
	public void ChargeRpc()
	{
		Manager.Instance.PlaySfxNearby( "boss.charge", Position2D, pitch: Game.Random.Float( 0.9f, 1.05f ), volume: 1.4f, maxDist: 750f );

		SetAnim( "Charge" );

		//AnimationHelper.SpecialMove = CitizenAnimationHelper.SpecialMoveStyle.Roll;
		//SetPlaybackRate( 3f );
		//AnimationHelper.DuckLevel = 0f;

		_timeSinceChargeCloud = 0f;

		if ( IsProxy )
			return;

		Player closestPlayer = Manager.Instance.GetClosestPlayer( Position2D );
		if ( !closestPlayer.IsValid() )
			return;

		var targetPos = closestPlayer.Position2D + closestPlayer.Velocity * Game.Random.Float( 0.2f, 2f ) + new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ) * 100f;
		Vector2 targetDir = (targetPos - Position2D).LengthSquared > Manager.TOUCH_DIST_REQUIRED_SQR
			? (targetPos - Position2D).Normal
			: Utils.GetRandomVector();

		_chargeDir = Utils.RotateVector( targetDir, Game.Random.Float( -10f, 10f ) );

		_chargeTime = Game.Random.Float( _chargeTimeMin, _chargeTimeMax );

		_chargeVel = Vector2.Zero;
		_nextRedirectTime = Game.Random.Float( 0.25f, 1f );

		Weight = _chargeWeight;
	}

	protected override void OnOutOfBounds( Direction direction )
	{
		base.OnOutOfBounds( direction );

		if ( direction == Direction.Left )
		{
			_chargeVel = new Vector2( Math.Abs( _chargeVel.x ), _chargeVel.y );
			_chargeDir = new Vector2( Math.Abs( _chargeDir.x ), _chargeDir.y );
		}
		else if ( direction == Direction.Right )
		{
			_chargeVel = new Vector2( -Math.Abs( _chargeVel.x ), _chargeVel.y );
			_chargeDir = new Vector2( -Math.Abs( _chargeDir.x ), _chargeDir.y );
		}
		else if ( direction == Direction.Down )
		{
			_chargeVel = new Vector2( _chargeVel.x, Math.Abs( _chargeVel.y ) );
			_chargeDir = new Vector2( _chargeDir.x, Math.Abs( _chargeDir.y ) );
		}
		else if ( direction == Direction.Up )
		{
			_chargeVel = new Vector2( _chargeVel.x, -Math.Abs( _chargeVel.y ) );
			_chargeDir = new Vector2( _chargeDir.x, -Math.Abs( _chargeDir.y ) );
		}
	}
}