things/enemies/ExploderMini.cs

An enemy NPC class 'ExploderMini' that inherits from Exploder. It configures stats, movement and explosion behavior for a small suicide-bomber type enemy, including health, speed, explosion radius/damage, animation playback adjustments, targeting offsets and death/attack triggers that start its explosion.

Networking
using System;
using System.Numerics;
using Sandbox;

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

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

	protected override bool ShouldSpawnBloodDecal => false;
	public override bool CanHaveTarget => !IsStunned;
	public override bool CanMove => !IsStunned;
	public override bool CanTurn => !IsStunned;
	public override bool CanDamageByTouch => IsAttacking && !IsStunned && !IsInTheAir;

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

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

		PushStrength = 7000f;
		Weight = 1.5f;

		_personalSpeedScale = Game.Random.Float( 3.7f, 3.9f );
		_personalSpeedFreq = Game.Random.Float( 4f, 5f );

		_personalExplosionRadius = 60f;
		_personalExplosionDamage = Utils.Select( Manager.Instance.Difficulty, 10f, 13f, 16f );
		_personalExplodeTime = Game.Random.Float( 0.8f, 1.4f );

		//_explodeAnim = "Squash_UpperBody";
		_explodeAnim = "Attack";
		_explosionColor = new Color( 1f, 0f, 0.8f );
		_explosionDamagesEnemies = false;
		ExplodeFlashMaterial = Manager.Instance.UnitFlashMaterials[UnitFlashType.SelfDmg];
		_explodeForce = 550f;

		if ( IsProxy )
			return;

		DetectTargetRange = 600f;
		LoseTargetRange = 800f;
		LoseTargetTime = 5f;
		MeleeDamage = Utils.Select( Manager.Instance.Difficulty, 6f, 7f, 8f );
		DamageTargetDelay = 0.7f;
		_personalTurnSpeed = Game.Random.Float( 3f, 4f );
		Acceleration = Game.Random.Float( 240f, 275f ) * Utils.Select( Manager.Instance.Difficulty, 1f, 1f, 1.1f );
		AccelerationAttacking = Game.Random.Float( 290f, 330f ) * Utils.Select( Manager.Instance.Difficulty, 1f, 1f, 1.1f );
		Deceleration = 0.6f;
		DecelerationAttacking = 0.3f;
	}

	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, 2f, EasingType.QuadIn ) * AnimSpeedModifier );
	}

	protected override float GetMoveSpeedFactor()
	{
		var progress = ModelRenderer.SceneModel.CurrentSequence.TimeNormalized;

		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.QuadInOut );
			else if ( progress > move1Start && progress < move1End )
				return Utils.Map( progress, move1Start, move1End, 0f, 1f, EasingType.QuadInOut );
			else if ( progress > move2Start && progress < move2End )
				return Utils.Map( progress, move2Start, move2End, 0f, 1f, EasingType.QuadInOut );
		}
		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.QuadInOut );
			else if ( progress > rightFootStart && progress < rightFootEnd )
				return Utils.Map( progress, rightFootStart, rightFootEnd, 0f, 1f, EasingType.QuadInOut );
		}
		
		return 0f;
	}

	protected override Vector2 GetTargetOffset()
	{
		// todo: variance
		return TargetUnit.Velocity * (0.9f + Utils.FastSin( TimeSinceSpawn * 0.32f ) * 0.5f) * (TargetUnit.Position2D - Position2D).Length * 0.025f;
	}

	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 * (IsAttacking ? 0.3f : 1f);
	}

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

		Velocity *= 0.5f;

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

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

		if ( IsProxy )
			return;

		StartExplodingRpc( _personalExplodeTime, _personalExplosionRadius, _personalExplosionDamage, null );
	}

	public override int GetNumLavaBlobs()
	{
		var numBlobs = Manager.Instance.Difficulty == 2 ? 1 : 0;
		numBlobs += GetNumExtraLavaBlobs();

		return numBlobs;
	}
}