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

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

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

		HP = GetMaxHP( CardType );
	}

	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.Clown ),
				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.Clown )}",
				emojiText: "",
				lifetime: 1f,
				color: new Color( 1f, 0.3f, 0.4f ),
				velocity: new Vector2( 0f, 100f ),
				deceleration: 0.5f,
				fontSize: 60f
			);

			var hpText = "";
			for ( int i = 0; i < Card.GetMaxHP( CardType.Clown ); 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
			);

			// todo: clown laugh sfx
			Manager.Instance.PlaySfxCenter( "clown_honk", volume: 0.7f, 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( 600 );

				List<Card> cardsToShuffle = new() { Manager.Instance.ChosenCards[1], Manager.Instance.ChosenCards[0] };

				List<Card> validCards = new();
				foreach(var card in Manager.Instance.Cards)
				{
					if ( !cardsToShuffle.Contains( card ) && !card.CantBeMoved )
						validCards.Add( card );
				}

				validCards.Shuffle();

				for ( int i = 0; i < Math.Min( 5 - HP, validCards.Count ); i++ )
					cardsToShuffle.Add( validCards[i] );

				if ( cardsToShuffle.Count > 2 )
				{
					await Task.DelayRealtime( 400 );

					Manager.Instance.PlayCardSfxBetween( "clown_hurt", Manager.Instance.ChosenCards[1], Manager.Instance.ChosenCards[0], volume: 1.55f, pitch: Utils.Map( HP, GetMaxHP( CardType ), 0, 1f, 0.75f ) );

					foreach ( var card in cardsToShuffle )
					{
						card.MoveToPos( Manager.GetCardPos( card.GridPos ).WithZ( 80f ), 0.2f, EasingType.SineOut );
					}

					await Task.DelayRealtime( 400 );

					Manager.Instance.PlayCardSfxBetween( "card_flip", Manager.Instance.ChosenCards[1], Manager.Instance.ChosenCards[0], volume: 0.9f, pitch: Game.Random.Float( 0.65f, 0.75f ) );

					foreach ( var card in cardsToShuffle )
					{
						if ( card.IsRevealed )
							Manager.Instance.HideCard( card );
					}

					await Task.DelayRealtime( 400 );

					// todo: get faster with less HP?
					for( int i = 0; i < Game.Random.Int(5, 8); i++ )
					{
						var card0 = cardsToShuffle[Game.Random.Int(0, cardsToShuffle.Count - 1)];

						var otherCards = cardsToShuffle.Where( x => x != card0 ).ToList();
						otherCards.Shuffle();
						var card1 = otherCards.FirstOrDefault();

						Manager.Instance.PlayCardSfx( "clown_honk", this, volume: 0.7f, pitch: Utils.Map(i, 0, 8, 0.6f, 1f) * Game.Random.Float( 0.95f, 1.05f ) );

						card0.MoveToPos( card0.LocalPosition.WithZ( Game.Random.Float( 90f, 100f ) ), 0.3f - i * 0.025f, EasingType.SineOut );
						card1.MoveToPos( card1.LocalPosition.WithZ( Game.Random.Float( 90f, 100f ) ), 0.3f - i * 0.025f, EasingType.SineOut );

						await Task.DelayRealtime( 300 - i * 25 );

						card0.MoveToPos( card1.LocalPosition.WithZ( card0.LocalPosition.z ), 0.4f - i * 0.035f, EasingType.SineInOut );
						card1.MoveToPos( card0.LocalPosition.WithZ( card1.LocalPosition.z ), 0.4f - i * 0.035f, EasingType.SineInOut );

						await Task.DelayRealtime( 400 - i * 35 );

						card0.MoveToPos( card0.LocalPosition.WithZ( 80f ), 0.2f - i * 0.022f, EasingType.SineOut );
						card1.MoveToPos( card1.LocalPosition.WithZ( 80f ), 0.2f - i * 0.022f, EasingType.SineOut );

						await Task.DelayRealtime( 200 - i * 22 );

						await Manager.Instance.SwapCardPositions( card0, card1 );
					}

					//Manager.Instance.PlayCardSfx( "police_radio", this, volume: 1.3f, pitch: Game.Random.Float( 0.88f, 0.94f ) );

					await Task.DelayRealtime( 200 );

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

					// move self and others to new positions
					int count = 0;
					foreach ( Card card in cardsToShuffle )
					{
						card.MoveToPos( Manager.GetCardPos( card.GridPos ).WithZ( Globals.CARD_DEFAULT_HEIGHT ), 0.2f, EasingType.QuadInOut );
						count++;
					}

					await Task.DelayRealtime( 200 );

					foreach ( Card card in cardsToShuffle )
						card.IsMovementControlled = false;

					await Task.DelayRealtime( 100 );

					Manager.Instance.PopEventMessage();

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

					Manager.Instance.PopEventMessage();
				}
			}
			else
			{
				await Task.DelayRealtime( 700 );

				// todo: death sfx
			}
		}
	}

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