Enemy subclass ChargerElite. It configures stats, spawning scale, movement and charge behavior, and updates redirection during a charge to predict and aim at the closest player.
using System;
using Sandbox;
public class ChargerElite : Charger
{
public override EnemyType EnemyType => EnemyType.ChargerElite;
public override float GetMaxHealth()
{
switch ( Manager.Instance.Difficulty )
{
case 0: default: return 210f;
case 1: return 250f;
case 2: return 275f;
}
}
public override Vector3 SpawnScale => new Vector3( 1.45f );
private float _nextRedirectTime;
private TimeSince _timeSinceRedirect;
protected float _redirectDelayMin;
protected float _redirectDelayMax;
public override int ExtraDeathBloodSprayAmount => 20;
public override float ParticleYPosOverride => 0.65f;
public override float StunParticleYPosOverride => 0.9f;
protected override void OnStart()
{
base.OnStart();
CoinValueMin = 6;
CoinValueMax = 9;
CoinChance = 1f;
PushStrength = 16000f;
Weight = _baseWeight = 2.6f;
_chargeWeight = 4.5f;
_personalSpeedScale = Game.Random.Float( 1.3f, 1.35f );
_personalSpeedFreq = Game.Random.Float( 9f, 11f );
if ( IsProxy )
return;
AggroRange = 85f;
DetectTargetRange = 900f;
LoseTargetRange = 1400f;
LoseTargetTime = 9f;
MeleeDamage = Utils.Select( Manager.Instance.Difficulty, 7f, 11f, 13f );
DamageTargetDelay = 0.8f;
Acceleration = Utils.Select( Manager.Instance.Difficulty, 220f, 245f, 250f );
AccelerationAttacking = Utils.Select( Manager.Instance.Difficulty, 250f, 295f, 300f );
Deceleration = 2.4f;
DecelerationAttacking = 2.2f;
_personalTurnSpeed = Game.Random.Float( 4f, 7f );
_personalChargeRange = Game.Random.Float( 550f, 620f );
_chargeTimeMin = 2.5f;
_chargeTimeMax = 4f;
_chargeDelayMin = 2f;
_chargeDelayMax = 6f;
_chargeDelayTimer = Game.Random.Float( _chargeDelayMin, _chargeDelayMax );
_chargeRotateSpeed = 9f;
_chargeSpeed = Utils.Select( Manager.Instance.Difficulty, 270f, 315f, 320f );
_chargeVelMax = Utils.Select( Manager.Instance.Difficulty, 350f, 450f, 450f );
_redirectDelayMin = 0.075f;
_redirectDelayMax = 0.5f;
}
protected override void OnUpdate()
{
base.OnUpdate();
if ( Manager.Instance.IsGameOver )
return;
if ( IsProxy )
return;
if ( State == ChargerState.Charge )
{
if ( _timeSinceRedirect > _nextRedirectTime * (1f / TimeScale) )
{
Player closestPlayer = Manager.Instance.GetClosestPlayer( Position2D );
if ( closestPlayer.IsValid() )
{
var velOffset = closestPlayer.Velocity * Game.Random.Float( 0.2f, 1.25f );
var dist = (closestPlayer.Position2D - Position2D).Length;
// don't lead target if target is very close and heading toward us
if ( dist < 110f )
{
var dot = Vector2.Dot( (Position2D - closestPlayer.Position2D).Normal, closestPlayer.Velocity.Normal );
if ( dot > 0.7f )
velOffset = Vector2.Zero;
}
var targetPos = closestPlayer.Position2D + velOffset + 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 ) );
}
_nextRedirectTime = Game.Random.Float( _redirectDelayMin, _redirectDelayMax );
_timeSinceRedirect = 0f;
}
}
}
protected override void Charge()
{
base.Charge();
if ( IsProxy )
return;
_nextRedirectTime = Game.Random.Float( _redirectDelayMin, _redirectDelayMax );
_timeSinceRedirect = 0f;
}
}