things/enemies/MinibossCharger.cs

Enemy subclass for a miniboss variant of Charger. It configures stats, visuals and behaviors like charge timings, aggro ranges, coin rewards, spawn scaling, and overrides collision behavior to fling other enemies or players when charging.

using System;
using Sandbox;

public class MinibossCharger : Charger
{
	public override EnemyType EnemyType => EnemyType.MinibossCharger;
	public override string GibFolder => "miniboss_charger";
	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.5f );
	public override bool ShowHealthbar => true;
	public override float HealthbarOffset => State == ChargerState.Charge ? 70f : 105f;
	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;

	public override float ParticleYPosOverride => 0.5f;
	public override float StunParticleYPosOverride => 0.85f;

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

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

		PushStrength = 8000f;
		Weight = _baseWeight = 2.3f;
		_chargeWeight = 4.5f;

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

		if ( IsProxy )
			return;

		AggroRange = 70f;
		DetectTargetRange = 900f;
		LoseTargetRange = 1400f;
		LoseTargetTime = 9f;
		MeleeDamage = Utils.Select( Manager.Instance.Difficulty, 9f, 13f, 14f );
		DamageTargetDelay = 0.8f;
		Acceleration = 300f;
		AccelerationAttacking = 350f;
		Deceleration = 1.5f;
		DecelerationAttacking = 1.4f;

		_personalTurnSpeed = Game.Random.Float( 5f, 7f );
		_personalChargeRange = 650f;

		_chargeTimeMin = 2.5f;
		_chargeTimeMax = 5f;
		_chargeDelayMin = 2.2f;
		_chargeDelayMax = 5f;
		_chargeDelayTimer = Game.Random.Float( _chargeDelayMin, _chargeDelayMax );
		_chargeRotateSpeed = 9f;
		_chargeVelMax = 500f;
	}

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

		if ( IsProxy )
			return;

	}

	public override void Colliding( Thing other, float percent, float dt )
	{
		base.Colliding( other, percent, dt );

		if ( IsInTheAir )
			return;

		if ( State == ChargerState.Charge && _timeSinceChangeState < _chargeTime - 0.4f )
		{
			if ( other is Enemy enemy && !(enemy is Boss) )
			{
				if ( !enemy.IsInTheAir && !enemy.IsSpawning )
				{
					var targetPos = TargetUnit.IsValid() && Game.Random.Float( 0f, 1f ) < 0.5f
						? TargetUnit.Position2D + TargetUnit.Velocity * Game.Random.Float( 0f, 4f ) + Velocity * Game.Random.Float( 0f, 3f ) + Utils.GetRandomVector() * Game.Random.Float(80f, 350f)
						: Position2D + Velocity * Game.Random.Float( 0.5f, 4f ) + new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ) * 120f;

					enemy.JumpRpc( Manager.Instance.ClampPosToBounds( targetPos ), height: Game.Random.Float( 270f, 320f ), lifetime: Game.Random.Float( 1.1f, 1.5f ) );
					// todo: sfx
				}
			}
			else if ( other is Player player && !player.IsDead && !player.IsInTheAir && !player.IsInvincible )
			{
				var targetPos = TargetUnit.IsValid()
						? TargetUnit.Position2D + _chargeDir * Game.Random.Float( 200f, 400f ) + Velocity * Game.Random.Float( 0f, 3f ) + new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ) * 120f
						: Position2D + _chargeDir * Game.Random.Float( 200f, 400f ) + new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ) * 120f;

				player.JumpRpc( Manager.Instance.ClampPosToBounds( targetPos ), height: Game.Random.Float( 270f, 320f ), lifetime: Game.Random.Float( 1.1f, 1.5f ) );
				// todo: sfx
				// todo: if player dodges, don't fling them?
			}
		}
	}

}