cards/CardHelicopter.cs
using Sandbox;
using System.Reflection.Metadata.Ecma335;
using System.Threading.Tasks;

public class CardHelicopter : Card
{
	public override bool ShouldHandleEvent( EventType eventType )
	{
		return eventType == EventType.Mismatch && Manager.Instance.ChosenCards.Contains( this ) && !Manager.Instance.IsMismatchALockedMatch;
	}

	public override async Task HandleEventAsync( EventType eventType )
	{
		var adjacentCards = Manager.Instance.GetNearbyCards(GridPos, adjacentOnly: true);
		adjacentCards.Shuffle();

		// get target grid pos, prioritizing empty spaces, but not allowing any that share neighbours with our neighbours
		var targetGridPos = new IntVector2( -1, -1 );
		var emptyGridPositions = Manager.Instance.GetAllGridPositions(empty: true);
		emptyGridPositions.Shuffle();
		foreach(var emptyGridPos in emptyGridPositions)
		{
			if(!SharesAnyNeighbours(emptyGridPos))
			{
				targetGridPos = emptyGridPos;
				break;
			}
		}

		if(targetGridPos.x == -1)
		{
			var fullGridPositions = Manager.Instance.GetAllGridPositions( empty: false );
			fullGridPositions.Shuffle();
			foreach(var fullGridPos in fullGridPositions)
			{
				if ( fullGridPos.Equals(GridPos) )
					continue;

				if ( !SharesAnyNeighbours( fullGridPos ) )
				{
					targetGridPos = fullGridPos;
					break;
				}
			}
		}

		if ( targetGridPos.x == -1 )
			return;

		Card carriedCard0 = null;
		Card carriedCard1 = null;

		if ( adjacentCards.Count > 0 )
			carriedCard0 = adjacentCards[0];

		//if ( nearbyCards.Count > 1 )
		//	carriedCard1 = nearbyCards[1];

		Manager.Instance.PushEventMessage( this, eventType );

		await Task.DelayRealtime( 400 );

		var targetCard = Manager.Instance.GetCardAtGridPos( targetGridPos );

		var nearTargetEmptyGridPositions = Manager.Instance.GetNearbyGridPositions( targetGridPos, empty: true );
		nearTargetEmptyGridPositions.Shuffle();
		var nearTargetFullGridPositions = Manager.Instance.GetNearbyGridPositions( targetGridPos, empty: false, adjacentOnly: true );
		nearTargetFullGridPositions.Shuffle();

		IntVector2 carriedTargetGridPos0 = (nearTargetEmptyGridPositions.Count > 0)
			? nearTargetEmptyGridPositions[0]
			: nearTargetFullGridPositions[0];

		IntVector2 carriedTargetGridPos1 = (nearTargetEmptyGridPositions.Count > 1)
			? nearTargetEmptyGridPositions[1]
			: nearTargetFullGridPositions[nearTargetEmptyGridPositions.Count > 0 ? 0 : 1];

		var carriedTargetCard0 = Manager.Instance.GetCardAtGridPos( carriedTargetGridPos0 );
		var carriedTargetCard1 = Manager.Instance.GetCardAtGridPos( carriedTargetGridPos1 );

		await Task.DelayRealtime( 200 );

		Manager.Instance.PlayCardSfxBetweenGridPositions( "helicopter", GridPos, targetGridPos, volume: 0.7f, pitch: Game.Random.Float(0.95f, 1.05f) );

		this.MoveToPos( this.LocalPosition.WithZ( Game.Random.Float( 70f, 90f ) ), 0.3f, EasingType.SineOut );
		targetCard?.MoveToPos( targetCard.LocalPosition.WithZ( Game.Random.Float( 70f, 90f ) ), 0.3f, EasingType.SineOut );

		if(carriedCard0 != null)
		{
			carriedCard0.MoveToPos( carriedCard0.LocalPosition.WithZ( Game.Random.Float( 70f, 90f ) ), 0.3f, EasingType.SineOut );
			carriedTargetCard0?.MoveToPos( carriedTargetCard0.LocalPosition.WithZ( Game.Random.Float( 70f, 90f ) ), 0.3f, EasingType.SineOut );
		}

		if ( carriedCard1 != null )
		{
			carriedCard1.MoveToPos( carriedCard1.LocalPosition.WithZ( Game.Random.Float( 70f, 90f ) ), 0.3f, EasingType.SineOut );
			carriedTargetCard1?.MoveToPos( carriedTargetCard1.LocalPosition.WithZ( Game.Random.Float( 70f, 90f ) ), 0.3f, EasingType.SineOut );
		}

		await Task.DelayRealtime( 450 );

		this.MoveToPos( Manager.GetCardPos(targetGridPos).WithZ( this.LocalPosition.z ), 0.85f, EasingType.SineInOut );
		targetCard?.MoveToPos( Manager.GetCardPos( this.GridPos ).WithZ( targetCard.LocalPosition.z ), 0.85f, EasingType.SineInOut );

		if ( carriedCard0 != null )
		{
			carriedCard0.MoveToPos( Manager.GetCardPos( carriedTargetGridPos0 ).WithZ( carriedCard0.LocalPosition.z ), 0.85f, EasingType.SineInOut );
			carriedTargetCard0?.MoveToPos( Manager.GetCardPos( carriedCard0.GridPos ).WithZ( carriedTargetCard0.LocalPosition.z ), 0.85f, EasingType.SineInOut );
		}

		if ( carriedCard1 != null )
		{
			carriedCard1.MoveToPos( Manager.GetCardPos( carriedTargetGridPos1 ).WithZ( carriedCard1.LocalPosition.z ), 0.85f, EasingType.SineInOut );
			carriedTargetCard1?.MoveToPos( Manager.GetCardPos( carriedCard1.GridPos ).WithZ( carriedTargetCard1.LocalPosition.z ), 0.85f, EasingType.SineInOut );
		}

		await Task.DelayRealtime( 950 );

		this.MoveToPos( this.LocalPosition.WithZ( Globals.CARD_DEFAULT_HEIGHT + Globals.CARD_ADD_HEIGHT_REVEALED_OR_HOVERED ), 0.2f, EasingType.SineOut );
		targetCard?.MoveToPos( targetCard.LocalPosition.WithZ( Globals.CARD_DEFAULT_HEIGHT ), 0.2f, EasingType.SineOut );

		if ( carriedCard0 != null )
		{
			carriedCard0.MoveToPos( carriedCard0.LocalPosition.WithZ( Globals.CARD_DEFAULT_HEIGHT + ( carriedCard0.IsRevealed ? Globals.CARD_ADD_HEIGHT_REVEALED_OR_HOVERED : 0f ) ), 0.2f, EasingType.SineOut );
			carriedTargetCard0?.MoveToPos( carriedTargetCard0.LocalPosition.WithZ( Globals.CARD_DEFAULT_HEIGHT ), 0.2f, EasingType.SineOut );
		}

		if ( carriedCard1 != null )
		{
			carriedCard1.MoveToPos( carriedCard1.LocalPosition.WithZ( Globals.CARD_DEFAULT_HEIGHT + ( carriedCard1.IsRevealed ? Globals.CARD_ADD_HEIGHT_REVEALED_OR_HOVERED : 0f ) ), 0.2f, EasingType.SineOut );
			carriedTargetCard1?.MoveToPos( carriedTargetCard1.LocalPosition.WithZ( Globals.CARD_DEFAULT_HEIGHT ), 0.2f, EasingType.SineOut );
		}

		await Task.DelayRealtime( 350 );

		if ( targetCard != null )
		{
			await Manager.Instance.SwapCardPositions( this, targetCard );
		}
		else
		{
			Manager.Instance.RemoveCardGridPos( this );
			await Manager.Instance.SetCardGridPos( this, targetGridPos );
		}


		if ( carriedCard0 != null )
		{
			if ( carriedTargetCard0 != null )
			{
				await Manager.Instance.SwapCardPositions( carriedCard0, carriedTargetCard0 );
			}
			else
			{
				Manager.Instance.RemoveCardGridPos( carriedCard0 );
				await Manager.Instance.SetCardGridPos( carriedCard0, carriedTargetGridPos0 );
			}
		}

		if ( carriedCard1 != null )
		{
			if ( carriedTargetCard1 != null )
			{
				await Manager.Instance.SwapCardPositions( carriedCard1, carriedTargetCard1 );
			}
			else
			{
				Manager.Instance.RemoveCardGridPos( carriedCard1 );
				await Manager.Instance.SetCardGridPos( carriedCard1, carriedTargetGridPos1 );
			}
		}

		this.IsMovementControlled = false;
		if(targetCard != null)
			targetCard.IsMovementControlled = false;

		if ( carriedCard0 != null )
		{
			carriedCard0.IsMovementControlled = false;
			if ( carriedTargetCard0 != null )
				carriedTargetCard0.IsMovementControlled = false;
		}

		if ( carriedCard1 != null )
		{
			carriedCard1.IsMovementControlled = false;
			if ( carriedTargetCard1 != null )
				carriedTargetCard1.IsMovementControlled = false;
		}

		await Task.DelayRealtime( 400 );

		Manager.Instance.PopEventMessage();

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

	bool SharesAnyNeighbours(IntVector2 targetGridPos)
	{
		var neighbours = Manager.Instance.GetNearbyGridPositions( GridPos );
		var targetNeighbours = Manager.Instance.GetNearbyGridPositions( targetGridPos );

		return neighbours.Intersect( targetNeighbours ).Any();
	}
}