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

public class CardNinja : Card
{
	public override bool IsAlive => true;

	public override bool ShouldHandleEvent( EventType eventType )
	{
		return eventType == EventType.TurnStart && Game.Random.Float(0f, 1f) < 0.25f;
	}

	public override async Task HandleEventAsync( EventType eventType )
	{
		if ( Manager.Instance.Cards.Count <= 2 )
			return;

		var cards = Manager.Instance.Cards.Where( x => x.CardType != CardType.Ninja && !x.CantBeMoved ).ToList();
		if ( cards.Count < 1 )
			return;

		cards.Shuffle();

		var otherCard = cards.FirstOrDefault();

		await Task.DelayRealtime( 150 );

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

		await Task.DelayRealtime( 200 );

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

		await Task.DelayRealtime( 300 );

		MoveToPos( otherCard.LocalPosition.WithZ( LocalPosition.z ), 0.4f, EasingType.SineInOut );
		otherCard.MoveToPos( LocalPosition.WithZ( otherCard.LocalPosition.z ), 0.4f, EasingType.SineInOut );

		await Task.DelayRealtime( 400 );

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

		await Task.DelayRealtime( 200 );

		await Manager.Instance.SwapCardPositions( this, otherCard );

		IsMovementControlled = false;
		otherCard.IsMovementControlled = false;

		await Task.DelayRealtime( 150 );

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