things/enemies/Exploder.cs

Enemy subclass Exploder, defines behavior and stats for an exploding enemy type. Sets health, movement and explosion parameters, controls animation speed mappings, starts exploding on death, and spawns lava blobs on explosion.

NetworkingFile Access
using System;
using Sandbox;

public class Exploder : Enemy
{
	public override EnemyType EnemyType => EnemyType.Exploder;
	public override float GetMaxHealth()
	{
		return 40f;
	}

	public override Vector3 SpawnScale => new Vector3( 1.1f );

	protected override bool ShouldSpawnBloodDecal => false;
	public override bool CanTurn => !IsDying && !IsStunned;

	protected float _personalExplosionRadius;
	protected float _personalExplosionDamage;
	protected float _personalExplodeTime;

	public override bool CanCombust => false;

	protected override void OnStart()
	{
		base.OnStart();

		CoinValueMin = 2;
		CoinValueMax = 3;
		CoinChance = 1f;

		PushStrength = 7000f;
		Weight = 1.5f;

		_personalSpeedScale = Game.Random.Float( 0.9f, 1.1f );
		_personalSpeedFreq = Game.Random.Float( 4f, 5f );

		_personalExplosionRadius = 110f;
		_personalExplosionDamage = 40f;
		_personalExplodeTime = 1.75f;

		_explodeAnim = "Explode";

		if ( IsProxy )
			return;

		AggroRange = 85f;
		DetectTargetRange = 250f;
		LoseTargetRange = 350f;
		LoseTargetTime = 3.5f;
		MeleeDamage = Utils.Select( Manager.Instance.Difficulty, 9f, 11f, 12f );
		DamageTargetDelay = 0.75f;
		_personalTurnSpeed = Game.Random.Float( 0.5f, 2f );
		Acceleration = 100f * Utils.Select( Manager.Instance.Difficulty, 0.9f, 1f, 1.05f );
		AccelerationAttacking = 235f * Utils.Select( Manager.Instance.Difficulty, 0.9f, 1f, 1.05f );
		Deceleration = 2.5f;
		DecelerationAttacking = 2.2f;
	}

	protected override void OnUpdate()
	{
		base.OnUpdate();

		//Gizmo.Draw.Color = Color.White;
		//Gizmo.Draw.Text( $"{ModelRenderer.PlaybackRate.ToString( "N2" )}", new global::Transform( WorldPosition ) );
		//Gizmo.Draw.Text( $"{ModelRenderer.SceneModel.CurrentSequence.TimeNormalized.ToString("N2")}", new global::Transform( WorldPosition ) );

		if ( IsProxy )
			return;
	}

	protected override void HandleExplodingPlaybackRate( float explodeTime )
	{
		SetPlaybackRate( Utils.Map( _timeSinceExplodeStart, 0f, explodeTime, 1f, 6f, EasingType.QuadIn ) * AnimSpeedModifier );
	}

	protected override float GetMoveSpeedFactor()
	{
		var progress = ModelRenderer.SceneModel.CurrentSequence.TimeNormalized;
		return Exploder.GetWalkAnimSpeed( progress, IsAttacking, EasingType.QuadInOut );
	}

	public static float GetWalkAnimSpeed( float progress, bool isAttacking, EasingType easingType )
	{
		if ( isAttacking )
		{
			var move0Start = 0.1f;
			var move0End = 0.40f;
			var move1Start = 0.5f;
			var move1End = 0.65f;
			var move2Start = 0.8f;
			var move2End = 0.975f;

			if ( progress > move0Start && progress < move0End )
				return Utils.Map( progress, move0Start, move0End, 0f, 1f, easingType );
			else if ( progress > move1Start && progress < move1End )
				return Utils.Map( progress, move1Start, move1End, 0f, 1f, easingType );
			else if ( progress > move2Start && progress < move2End )
				return Utils.Map( progress, move2Start, move2End, 0f, 1f, easingType );
		}
		else
		{
			var leftFootStart = 0.4f;
			var leftFootEnd = 0.90f;
			var rightFootStart = 0.0f;
			var rightFootEnd = 0.36f;

			if ( progress > leftFootStart && progress < leftFootEnd )
				return Utils.Map( progress, leftFootStart, leftFootEnd, 0f, 1f, easingType );
			else if ( progress > rightFootStart && progress < rightFootEnd )
				return Utils.Map( progress, rightFootStart, rightFootEnd, 0f, 1f, easingType );
		}

		return 0f;
	}

	protected override float GetAnimSpeedFactor()
	{
		if ( HasTarget && TargetUnit.IsValid() )
		{
			float distSqr = (TargetUnit.Position2D - Position2D).LengthSquared;
			float attackDistSqr = MathF.Pow( AggroRange, 2f );

			return _personalSpeedScale * Utils.Map( distSqr, attackDistSqr, 0f, 1f, 1.5f, EasingType.Linear );
		}

		return _personalSpeedScale;
	}

	protected override void StartDying( Vector2 dir, float force, Player player, DamageType damageType )
	{
		IsDying = true;
		IsSpawning = false;

		if ( IsMiniboss )
			StartMinibossDeathWatchdog( "exploder", player, damageType );

		Velocity *= 0.5f;

		//if ( player.IsValid() && player.CombustionActive )
		//	Combust( player );
		//else
			StartExplodingRpc( _personalExplodeTime, _personalExplosionRadius, _personalExplosionDamage, player );
	}

	public override void Explode()
	{
		base.Explode();

		int numLavaBlobs = GetNumLavaBlobs();
		for(int i = 0; i < numLavaBlobs; i++ )
		{
			if ( Manager.Instance.GetLavaBlobEndPos( Position2D, out Vector2 endPos ) )
				Manager.Instance.SpawnLavaBlob( Position2D, endPos, puddleRadius: Game.Random.Float( 170f, 250f ), enemySource: this, enemyType: EnemyType );
		}
	}

	public virtual int GetNumLavaBlobs()
	{
		var numBlobs = Utils.Select( Manager.Instance.Difficulty, 0, 1, Game.Random.Int( 1, 2 ) );
		numBlobs += GetNumExtraLavaBlobs();

		return numBlobs;
	}

	protected virtual int GetNumExtraLavaBlobs()
	{
		var numExtraBlobs = 0;

		if ( Manager.Instance.ElapsedTime > 17f * 60f ) numExtraBlobs += Game.Random.Int( 0, 1 );
		if ( Manager.Instance.ElapsedTime > 24f * 60f ) numExtraBlobs += Game.Random.Int( 0, 1 );
		if ( Manager.Instance.ElapsedTime > 32f * 60f ) numExtraBlobs += Game.Random.Int( 0, 1 );
		if ( Manager.Instance.ElapsedTime > 42f * 60f ) numExtraBlobs += 1;
		if ( Manager.Instance.ElapsedTime > 55f * 60f ) numExtraBlobs += 1;
		if ( Manager.Instance.ElapsedTime > 60f * 60f ) numExtraBlobs += 1;
		if ( Manager.Instance.ElapsedTime > 70f * 60f ) numExtraBlobs += 1;
		if ( Manager.Instance.ElapsedTime > 80f * 60f ) numExtraBlobs += 1;

		return numExtraBlobs;
	}
}