cards/CardBalloon.cs
using Sandbox;
using System.Threading.Tasks;

public class CardBalloon : Card
{
	public override bool ValidateStartingGridPos()
	{
		if ( GridPos.y < Manager.Instance.GridHeight - 1 )
			return true;

		int NUM_TRIES = 100;
		for ( int i = 0; i < NUM_TRIES; i++ )
		{
			var newGridPos = Manager.Instance.GetRandomGridPos( except: GridPos );
			var otherCard = Manager.Instance.GetCardAtGridPos( newGridPos );

			if ( newGridPos.y < Manager.Instance.GridHeight - 1 && otherCard.CardType != CardType.Chipmunk && otherCard.CardType != CardType.Spider && otherCard.CardType != CardType.Rock && otherCard.CardType != CardType.Balloon && otherCard.CardType != CardType.Pawn )
			{
				Manager.Instance.SwapCardPositionsNonAsync( this, otherCard );
				break;
			}
		}

		return false;
	}

	public override bool ShouldHandleEvent( EventType eventType )
	{
		if ( eventType == EventType.TurnStart || eventType == EventType.AfterCardsMoved )
		{
			var gridPosAbove = GridPos + new IntVector2( 0, 1 );
			if ( !Manager.Instance.IsGridPosInBounds( gridPosAbove ) )
				return false;

			var cardAbove = Manager.Instance.GetCardAtGridPos( gridPosAbove );
			if ( cardAbove != null )
				return false;

			return true;
		}

		return false;
	}

	public override async Task HandleEventAsync( EventType eventType )
	{
		var gridPosAbove = GridPos + new IntVector2( 0, 1 );

		await Task.DelayRealtime( 300 );

		Manager.Instance.PlayCardSfx( "card_move", this, volume: 1.1f, pitch: Game.Random.Float( 0.85f, 1.15f ) );

		await Task.DelayRealtime( 100 );

		Manager.Instance.RemoveCardGridPos( this );
		await Manager.Instance.SetCardGridPos( this, gridPosAbove );

		await Task.DelayRealtime( 400 );

		await Manager.Instance.EventHappened( EventType.AfterCardsMoved );
	}
}