things/enemies/ChargerElite.cs

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.

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