Partial Boss class that implements the boss enemy shooting behavior. It initializes shooting timers and range, checks distance to target and readiness to start a shoot prepare state, and contains RPCs that play animations, sound effects and spawn either homing or straight projectiles with spread based on boss HP and difficulty.
using System;
using Sandbox;
using Sandbox.Citizen;
public partial class Boss
{
protected float _shootDelayTimer;
protected float _shootDelayMin;
protected float _shootDelayMax;
protected float _shootRange;
void InitShooting()
{
if ( IsProxy )
return;
_shootDelayMin = 3.5f;
_shootDelayMax = 8.5f;
_shootRange = 500f;
_shootDelayTimer = Game.Random.Float( _shootDelayMin, _shootDelayMax ) * Utils.Select( Manager.Instance.Difficulty, 1.3f, 1f, 0.925f );
}
void HandleShooting()
{
if ( !IsReadyForAction || !TargetUnit.IsValid() )
return;
var targetDistSqr = (TargetUnit.Position2D - Position2D).LengthSquared;
if ( targetDistSqr < MathF.Pow( _shootRange, 2f ) )
{
_shootDelayTimer -= Time.Delta * TimeScale;
if ( _shootDelayTimer < 0f && targetDistSqr < MathF.Pow( _shootRange * 0.85f, 2f ) )
SetState( BossState.ShootPrepare );
}
}
[Rpc.Broadcast]
protected void StartShootingRpc()
{
SetAnim( "ShootPrepare" );
//AnimationHelper.HoldType = CitizenAnimationHelper.HoldTypes.Pistol;
Manager.Instance.PlaySfxNearby( "boss.prepare", Position2D, pitch: Game.Random.Float( 0.75f, 0.85f ), volume: 1.4f, maxDist: 500f );
}
[Rpc.Broadcast]
protected void ShootRpc()
{
SetAnim( "Shoot" );
//AnimationHelper.Target.Set( "b_attack", true );
if ( IsProxy )
return;
var dir = (Vector2)WorldRotation.Forward;
var homingChance = Utils.Map( HpPercent, 1f, 0f, 0f, 0.6f, EasingType.Linear );
if ( Game.Random.Float( 0f, 1f ) < homingChance )
{
var numBullets = MathX.FloorToInt( Utils.Map( HpPercent, 0.85f, 0f, 1f, 6f ) );
var spread = 360f;
float currAngleOffset = 0f;
float increment = numBullets > 1 ? (spread / (float)numBullets) : 0f;
for ( int i = 0; i < numBullets; i++ )
{
var currDir = Utils.RotateVector( dir, currAngleOffset + increment * i );
var pos = Position2D + currDir * 20f;
// todo: different types on harder difficulties
EnemyProjectileType projectileType = EnemyProjectileType.Normal;
Manager.Instance.SpawnEnemyHomingProjectile( pos, currDir, shooter: this, enemyType: this.EnemyType, startVel: Game.Random.Float( 50f, 90f ) * Utils.Select( Manager.Instance.Difficulty, 0.9f, 1f, 1.25f ), projectileType );
}
//Manager.Instance.PlaySfxNearbyRpc( "boss.shoot", Position2D, pitch: Game.Random.Float( 0.35f, 0.4f ), volume: 1.3f, maxDist: 600f );
Manager.Instance.PlaySfxNearbyRpc( "squelch", Position2D, pitch: Game.Random.Float( 1f, 1.1f ), volume: 2.4f, maxDist: 600f );
}
else
{
var numBullets = MathX.FloorToInt( Utils.Map( HpPercent, 1f, 0f, 3f, 8f, EasingType.SineIn ) ) + Game.Random.Int( 0, 1 );
var spread = Game.Random.Float( 30f, 80f );
float currAngleOffset = -spread * 0.5f;
float increment = numBullets > 1 ? (spread / (float)(numBullets - 1)) : 0f;
for ( int i = 0; i < numBullets; i++ )
{
var currDir = Utils.RotateVector( dir, currAngleOffset + increment * i );
var pos = Position2D + currDir * 20f;
var vel = Utils.Select( Manager.Instance.Difficulty, 200f, 220f, 260f );
// todo: different types on harder difficulties
EnemyProjectileType projectileType = EnemyProjectileType.Normal;
Manager.Instance.SpawnEnemyProjectile( pos, currDir, shooter: this, enemyType: this.EnemyType, startVel: vel, projectileType );
}
Manager.Instance.PlaySfxNearbyRpc( "squelch", Position2D, pitch: Game.Random.Float( 1.2f, 1.3f ), volume: 2.4f, maxDist: 600f );
}
}
}