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