BlockDragon entity that handles a dragon block's shooting behavior. It starts a windup on high-speed impacts, spawns a fireball aimed at the closest player (bouncing variant in phase 2), plays face/mouth animations and a recover period.
namespace BlockParty;
/// <summary>
/// Dragon block — on a hard impact (speed > 40) at phase 1 it spits a straight fireball at
/// the player; at phase 2 a faster bouncing one. Brief "shooting" face during the shot.
/// Port of the original <c>BlockDragon</c> (coroutine → sim-timer windup/recover).
/// </summary>
public sealed class BlockDragon : Block
{
enum ShootState { Idle, Windup, Recover }
ShootState _shootState = ShootState.Idle;
float _shootTimer;
bool _shootBouncing;
protected override bool CanActivateTimedAbility => _shootState == ShootState.Idle;
const float SHOOT_WINDUP = 0.35f;
const float SHOOT_RECOVER = 0.65f;
public override void Impact( Block otherBlock )
{
base.Impact( otherBlock );
if ( IsDead ) return;
if ( _moveSpeed > 40.0f && (Phase == 1 || Phase == 2) && _shootState == ShootState.Idle )
StartShoot( bouncing: Phase == 2 );
}
void StartShoot( bool bouncing )
{
_shootBouncing = bouncing;
_shootState = ShootState.Windup;
_shootTimer = SHOOT_WINDUP;
FaceBlinkAnimOverride = $"face_shooting_{Phase}"; // the palette blink keeps flashing the shooting face
_face.PlayAnimation( $"face_shooting_{Phase}" );
_mouth.PlayAnimation( $"mouth_shooting_{Phase}" );
}
public override void Tick( float dt )
{
base.Tick( dt );
if ( IsDead || _shootState == ShootState.Idle ) return;
_shootTimer -= dt;
if ( _shootTimer > 0f ) return;
if ( _shootState == ShootState.Windup )
{
if ( !TrySpawnFireball( _shootBouncing ) )
{
FinishShoot();
return;
}
Audio.PlaySfx( SfxType.FireballShoot, Position );
_shootState = ShootState.Recover;
_shootTimer = SHOOT_RECOVER;
}
else // Recover
{
FinishShoot();
}
}
void FinishShoot()
{
FaceBlinkAnimOverride = null; // hand the blink back to the regular face
RestoreFaceAndMouth();
_shootState = ShootState.Idle;
}
bool TrySpawnFireball( bool bouncing )
{
var player = Stage.ClosestTargetablePlayer( Position );
if ( player is null ) return false;
float speed = bouncing ? 33.0f : 15.0f;
Vector2 vel = Utils.Normalized( player.Position - Position ) * speed;
Stage.AddFireball( Position + new Vector2( 0f, -8f ), vel, bouncing );
return true;
}
}