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

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

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

		HP = GetMaxHP( CardType );
	}

	public override bool ShouldHandleEvent( EventType eventType )
	{
		if( eventType == EventType.TurnStart && ( Manager.Instance.Stats[StatType.KingMoveForced] > 0f ) )
		{
			return true;
		}

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

			var hpText = "";
			for ( int i = 0; i < Card.GetMaxHP( CardType.King ); 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( "king_choose", volume: 1f, pitch: Game.Random.Float( 0.9f, 0.95f ) );

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

			// todo: sfx not playing at center?
			var centerPos = Manager.Instance.CenterPos;
			Manager.Instance.PlaySfx( "king_choose", new Vector3( 0f, 0f, Manager.Instance.Camera.WorldPosition.z - 100f ), volume: 1f, pitch: Game.Random.Float( 0.9f, 1f ) );

			await Task.DelayRealtime( Game.Random.Int( 1600, 2100 ) );

			var validCards = Manager.Instance.Cards.Count > 2
				? Manager.Instance.Cards.Where( x => x.CardType != CardType.King ).ToList()
				: Manager.Instance.Cards;

			validCards.Shuffle();

			Manager.Instance.Stats[StatType.KingMoveForced] = 0f;

			Manager.Instance.PopEventMessage();

			await Manager.Instance.ChooseCardAsync( validCards.First() );
		}
		else if(eventType == EventType.HurtCards)
		{
			if( HP > 0 )
			{
				Manager.Instance.PushEventMessage( this, eventType );

				await Task.DelayRealtime( 600 );

				var validOtherCards = Manager.Instance.Cards.Where( x => x.CardType != CardType.King ).ToList();
				if ( validOtherCards.Count > 0 )
				{
					List<Card> cardsToShuffle = new() { this };

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

					cardsToShuffle.Add( Manager.Instance.ChosenCards[0] );

					await Task.DelayRealtime( 200 );

					Manager.Instance.PlayCardSfx( "king_whoosh", this, volume: 1.3f, pitch: Game.Random.Float( 0.98f, 1.03f ) );

					await Task.DelayRealtime( 100 );

					MoveToPos( Manager.GetCardPos( GridPos ).WithZ( 100f ), 0.2f, EasingType.SineOut );

					await Task.DelayRealtime( 400 );

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

					await Task.DelayRealtime( 400 );

					// move other cards to self
					int cardNum = 0;
					foreach ( var card in cardsToShuffle )
					{
						if ( card == this )
							continue;

						card.MoveToPos( Manager.GetCardPos( GridPos ).WithZ( 50f - cardNum * 0.2f ), 0.6f, EasingType.SineInOut );
						cardNum++;
					}

					await Task.DelayRealtime( 700 );

					Manager.Instance.PlayCardSfx( "card_flip", this, volume: 0.9f, pitch: Game.Random.Float( 0.65f, 0.75f ) );
					Manager.Instance.HideCard( this );

					await Task.DelayRealtime( 200 );

					MoveToPos( Manager.GetCardPos( GridPos ).WithZ( Game.Random.Float( 48f, 50f ) ), 0.2f, EasingType.SineOut );

					await Task.DelayRealtime( 200 );

					List<IntVector2> newGridPositions = new();
					foreach ( var card in cardsToShuffle )
						newGridPositions.Add( card.GridPos );
					newGridPositions.Shuffle();

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

					Manager.Instance.PlayCardSfx( "king_whoosh", this, volume: 1.3f, pitch: Game.Random.Float( 0.88f, 0.92f ) );

					await Task.DelayRealtime( 200 );

					// move self and others to new positions
					int count = 0;
					foreach ( Card card in cardsToShuffle )
					{
						card.MoveToPos( Manager.GetCardPos( newGridPositions[count] ).WithZ( Globals.CARD_DEFAULT_HEIGHT + (card.IsRevealed ? Globals.CARD_ADD_HEIGHT_REVEALED_OR_HOVERED : 0f) ), 0.6f, EasingType.QuadInOut );
						count++;
					}

					await Task.DelayRealtime( 700 );

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

					await Task.DelayRealtime( 400 );

					Manager.Instance.Stats[StatType.KingMoveForced] = 1f;

					Manager.Instance.PopEventMessage();

					await Manager.Instance.EventHappened( EventType.AfterCardsMoved );
				}
				else
				{
					Manager.Instance.Stats[StatType.KingMoveForced] = 1f;

					await Task.DelayRealtime( 1500 );

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

				// todo: death sfx
			}
		}
	}

	public override string GetEventText( EventType eventType )
	{
		if ( eventType == EventType.TurnStart )
		{
			return "The King is choosing for you...";
		}
		else
		{
			return "💔Damage King: shuffle with other cards, and the King decides your next choice";
		}
	}

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