things/enemies/MinibossSpiker.cs

Enemy subclass MinibossSpiker, a specialized Spiker enemy with miniboss stats and behavior. It sets health, scaling, spawn visuals, AI ranges, timers, projectile type, and shooting/digging parameters and overrides a few behavior methods.

NetworkingFile Access
using System;
using Sandbox;

public class MinibossSpiker : Spiker
{
	public override EnemyType EnemyType => EnemyType.MinibossSpiker;
	public override string GibFolder => "miniboss_spiker";
	public override float OverrideGibChance => 1f;
	public override int ExtraDeathBloodSprayAmount => 25;
	protected override float MinibossHealthScale => 1.2f;
	public override float GetMaxHealth() => MinibossBaseHealth * MinibossHealthScale;

	public override Vector3 SpawnScale => new Vector3( 1.7f );
	public override float SpawnZPos => -180f;

	public override bool ShowHealthbar => true;
	public override float HealthbarOffset => 115f;
	public override float HealthbarOpacity => Utils.EasePercent( SpawnProgress, EasingType.QuadIn );
	public override float HealthbarArmorOpacity => Utils.EasePercent( SpawnProgress, EasingType.QuadIn );
	public override bool IsBoss => true;
	public override bool IsMiniboss => true;

	protected override bool ShouldCircleTarget => (Utils.FastSin( TimeSinceSpawn * 1.5f * _personalSpeedScale ) < 0.4f);

	private float _shootAnimRate;
	public override float ParticleYPosOverride => 0.4f;
	public override float StunParticleYPosOverride => 0.8f;

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

		CoinValueMin = 10;
		CoinValueMax = 18;
		CoinChance = 1f;

		PushStrength = 6000f;
		Weight = 1.4f;

		_personalSpeedScale = Game.Random.Float( 0.9f, 1.1f );
		_personalSpeedFreq = Game.Random.Float( 9f, 11f );

		_projectileName = "spiker_head_miniboss";
		_crackParticleColor = new Color( 0.05f, 0.02f, 0.05f );

		if ( IsProxy )
			return;

		AggroRange = 65f;
		DetectTargetRange = 950f;
		LoseTargetRange = 1300f;
		LoseTargetTime = 5.5f;
		MeleeDamage = Utils.Select( Manager.Instance.Difficulty, 11f, 15f, 16f );
		DamageTargetDelay = 0.75f;
		_personalTurnSpeed = Game.Random.Float( 3f, 6f );
		Acceleration = 220f * Utils.Select( Manager.Instance.Difficulty, 0.9f, 1f, 1.05f );
		AccelerationAttacking = 250f * Utils.Select( Manager.Instance.Difficulty, 0.9f, 1f, 1.05f );
		Deceleration = 1.8f;
		DecelerationAttacking = 1.6f;

		_shootDelayMin = 1.5f;
		_shootDelayMax = 6f;
		_shootRange = 750f;
		_maxExtraHeadDist = 37f;
		_shootReverseDelay = 1f;

		_digDelayMin = 4f;
		_digDelayMax = 13f;
		_digRange = 1000f;
		_digTime = 1.5f;
		_digOffsetMin = 200f;
		_digOffsetMax = 600f;

		_shootDelayTimer = Game.Random.Float( _shootDelayMin, _shootDelayMax );
		_digDelayTimer = Game.Random.Float( _digDelayMin, _digDelayMax );

		_moveClockwise = Game.Random.Int( 0, 1 ) == 0;
	}

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

		//Gizmo.Draw.Color = Color.White;
		//Gizmo.Draw.Text( $"SpawnProgress: {SpawnProgress}", new global::Transform( WorldPosition ) );
		//Gizmo.Draw.Text( $"_numTimesShot: {_numTimesShot} _numShotsTotal: {_numShotsTotal}\n_timeSinceShoot: {_timeSinceShoot}", new global::Transform( WorldPosition ) );
	}

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

		if ( _numTimesShot == 0 )
			_shootAnimRate = Utils.Map( HpPercent, 1f, 0f, 0.33f, 0.2f );

		SetPlaybackRate( _shootAnimRate );
	}

	protected override float GetShootSpawnCracksDelay()
	{
		return _numTimesShot == 0 ? 1.12f : 0.08f;
	}

	protected override int GetNumShotsTotal()
	{
		return (int)MathF.Round( Utils.Map( HpPercent, 1f, 0f, 3f, 6f, EasingType.QuadIn ) );
	}

	protected override Vector2 GetShootTargetPos( Player targetPlayer )
	{
		return targetPlayer.IsValid()
			? targetPlayer.Position2D + targetPlayer.Velocity * Game.Random.Float( 0f, 1.75f ) + new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ) * 120f
			: Position2D + Utils.GetRandomVector() * Game.Random.Float( 200f, 340f );
	}

}