An enemy subclass 'Exploder' for a game, representing a suicide/exploding enemy. It sets stats (health, speed, explosion radius/damage), controls animation playback rates and movement factors, starts explosion on death, and spawns lava blob projectiles when exploding.
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;
}
}