A LaneBlock-derived wind block. It controls mouth animation based on EyesOpenAmount and Phase, supplies outward force direction, particle behavior, overlay tint, and forwards player impulse to AddWindImpulse.
namespace BlockParty;
/// <summary>
/// Wind block — while its eyes are open (phase 1+) it blows a block-width lane of gusting wind in the
/// direction it faces, pushing the player and projectiles (fireballs, teardrops) OUTWARD but NOT blocks
/// or lasers. Phase 1 blows one lane in <see cref="Block.MoveDirection"/>; phase 2 blows four lanes
/// outward in every cardinal direction at once.
///
/// All of the lane geometry, per-column blocking, force application, particle streaming and overlay
/// rendering live in <see cref="LaneBlock"/>; wind just supplies the direction (outward), the player's
/// wind channel, dust particles that fly straight out and die at the blocker, and a faint white tint.
/// </summary>
public sealed class BlockWind : LaneBlock
{
// Alpha at full strength (eyes fully open).
const float OVERLAY_ALPHA = 0.2f;
// Tracks the mouth animation currently playing so we only re-issue PlayAnimation on a change.
string _mouthAnim;
public override void Tick( float dt )
{
base.Tick( dt );
UpdateMouth();
}
/// <summary>Blow the mouth open while the gust is active and close it as the block slams to a stop /
/// while parked, so a closed mouth reads as "no wind". The open-mouth frame follows
/// <see cref="Block.EyesOpenAmount"/> (the same ramp that drives the gust strength), so the mouth
/// closes then re-opens across each slam→resume. Never opens in phase 0 (no gust there).</summary>
void UpdateMouth()
{
if ( IsDead ) return; // the death sequence owns the mouth (mouth_dead)
int level = Phase >= 1 ? Math.Clamp( (int)MathF.Round( EyesOpenAmount * 3f ), 0, 3 ) : 0;
string want = level > 0 ? $"mouth_open_{Phase}_{level}" : $"mouth_{Phase}";
if ( want == _mouthAnim ) return;
_mouth.PlayAnimation( want );
_mouthAnim = want;
}
protected override Direction GetPhase1Direction() => MoveDirection;
protected override Vector2 GetForceDirection( in Lane lane ) => lane.D; // blow outward
protected override void ApplyPlayerForce( Player player, Vector2 impulse ) => player.AddWindImpulse( impulse );
protected override ParticleKind ParticleKind => ParticleKind.Dust;
// Wind particles fly OUTWARD, so they travel the remaining reach past their spawn point.
protected override float GetParticleTravel( float reach, float along ) => reach - along;
protected override Color GetOverlayColor( float strength ) => new Color( 1f, 1f, 1f, OVERLAY_ALPHA * strength );
}