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

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

	public override void Init(CardType cardType)
	{
		base.Init(cardType);

		HP = GetMaxHP( CardType );
	}

	// todo: sfx when revealed?

	public override bool ShouldHandleEvent( EventType eventType )
	{
		return (eventType == EventType.LevelStartBoss && CardTypeID == 0) || (eventType == EventType.HurtCards && Manager.Instance.ChosenCards[1] == this);
	}

	public override async Task HandleEventAsync( EventType eventType )
	{
		if(eventType == EventType.LevelStartBoss )
		{
			var camera = Scene.Camera;
			var ray = camera.ScreenPixelToRay( new Vector3( Screen.Width / 2, Screen.Height * 0.65f, 0f ) );
			var tr = Scene.Trace.Ray( ray, 10000f ).Run();

			Manager.Instance.SpawnFloaterImage(
				pos: tr.EndPosition,
				filename: Card.GetIconFilename( CardType.Ogre ),
				lifetime: 1f,
				velocity: new Vector2( 0f, 100f ),
				deceleration: 0.5f
			);

			Manager.Instance.SpawnFloaterText(
				pos: tr.EndPosition + new Vector3(0f, -70f, 0f),
				text: $"Boss",
				emojiText: "",
				lifetime: 1f,
				color: new Color( 1f, 1f, 1f ),
				velocity: new Vector2( 0f, 100f ),
				deceleration: 0.5f,
				fontSize: 45f
			);

			Manager.Instance.SpawnFloaterText(
				pos: tr.EndPosition + new Vector3( 0f, -86f, 0f ),
				text: $"{Card.GetName(CardType.Ogre)}",
				emojiText: "",
				lifetime: 1f,
				color: new Color( 0.5f, 1f, 0.5f ),
				velocity: new Vector2( 0f, 100f ),
				deceleration: 0.5f,
				fontSize: 60f
			);

			var hpText = "";
			for ( int i = 0; i < Card.GetMaxHP( CardType.Ogre ); i++ )
				hpText += "❤️";

			Manager.Instance.SpawnFloaterText(
				pos: tr.EndPosition + new Vector3( 0f, -103f, 0f ),
				text: $"",
				emojiText: hpText,
				lifetime: 1f,
				color: new Color( 1f, 1f, 1f ),
				velocity: new Vector2( 0f, 100f ),
				deceleration: 0.5f,
				fontSize: 35f
			);

			Manager.Instance.PlaySfxCenter( "ogre_hurt", volume: 1.3f, pitch: Game.Random.Float( 0.6f, 0.65f ) );

			await Task.DelayRealtime( 875 );
		}
		else if(eventType == EventType.HurtCards)
		{
			if( HP > 0 )
			{
				Manager.Instance.PushEventMessage( this, eventType );

				await Task.DelayRealtime( 500 );

				await ShuffleOgre( Manager.Instance.ChosenCards[1] );
				await ShuffleOgre( Manager.Instance.ChosenCards[0] );

				Manager.Instance.PopEventMessage();

				await Manager.Instance.EventHappened( EventType.AfterCardsMoved );
			}
			else
			{
				await Task.DelayRealtime( 800 );

				// todo: death sfx
			}
		}
	}

	async Task ShuffleOgre(Card ogre)
	{
		var nearbyCards = Manager.Instance.GetNearbyCards( ogre.GridPos );
		List<Card> cardsToShuffle = new() { ogre };
		cardsToShuffle.AddRange( nearbyCards );
		cardsToShuffle.Shuffle();

		Manager.Instance.PlayCardSfx( "king_whoosh", this, volume: 0.8f, pitch: Game.Random.Float( 0.7f, 0.8f ) );

		foreach ( var card in cardsToShuffle )
		{
			card.MoveToPos( Manager.GetCardPos( card.GridPos ).WithZ( Game.Random.Float( 60f, 120f ) ), 0.35f, EasingType.SineOut );
		}

		await Task.DelayRealtime( 350 );

		foreach ( var card in cardsToShuffle )
		{
			if ( card.IsRevealed && !( card.CardType == CardType.Ogre && card != ogre ) )
				Manager.Instance.HideCard( card );
		}

		await Task.DelayRealtime( 350 );

		List<IntVector2> newGridPositions1 = new() { ogre.GridPos };
		newGridPositions1.AddRange( Manager.Instance.GetNearbyGridPositions( ogre.GridPos ) );
		newGridPositions1.Shuffle();

		foreach ( var card in cardsToShuffle )
			Manager.Instance.RemoveCardGridPos( card );

		Manager.Instance.PlayCardSfx( "king_whoosh", this, volume: 0.8f, pitch: Game.Random.Float( 0.6f, 0.65f ) );

		int count = 0;
		foreach ( Card card in cardsToShuffle )
		{
			card.MoveToPos( Manager.GetCardPos( newGridPositions1[count] ).WithZ( card.LocalPosition.z ), Game.Random.Float( 0.65f, 0.75f ), EasingType.SineInOut );
			count++;
		}

		await Task.DelayRealtime( 750 );

		Manager.Instance.PlayCardSfx( "king_whoosh", this, volume: 0.6f, pitch: Game.Random.Float( 0.7f, 0.8f ) );

		foreach ( Card card in cardsToShuffle )
		{
			card.MoveToPos( card.LocalPosition.WithZ( Globals.CARD_DEFAULT_HEIGHT ), 0.35f, EasingType.SineInOut );
			count++;
		}

		await Task.DelayRealtime( 350 );

		count = 0;
		foreach ( Card card in cardsToShuffle )
		{
			await Manager.Instance.SetCardGridPos( card, newGridPositions1[count] );
			card.IsMovementControlled = false;
			count++;
		}

		await Task.DelayRealtime( 300 );
	}

	public override void PlayHurtSfx( Card card0, Card card1 )
	{
		Manager.Instance.PlayCardSfxBetween( "ogre_hurt", card0, card1, volume: 1.5f, pitch: Utils.Map(HP, GetMaxHP(CardType), 0, 1.2f, 0.7f));
	}
}