MainHud/UnoDuelEventRunner.cs

Manager class that runs a small UNO-like duel mini-game between a player and an AI. It deals simulated cards, constructs a popup with two choices, resolves the player's and AI's actions, applies HP changes via CombatManager, and logs events to GameManager.

NetworkingFile Access
using Keyboard_Warriors_.Models;
using Keyboard_Warriors_.Systems;
using Sandbox;
using System;

namespace Keyboard_Warriors_.Managers;

public static class UnoDuelEventRunner
{
	private static Player cachedPlayer;
	private static Player cachedAI;

	private static readonly string[] colors = { "Red", "Blue", "Green", "Yellow" };

	// Class properties to securely preserve the deck variables between button clicks
	private static int topColor;
	private static int topNumber;
	private static int topType;

	private static int playerColor;
	private static int playerNumber;
	private static int playerType;

	private static int aiColor;
	private static int aiNumber;
	private static int aiType;

	private static bool playerCanPlay;

	private static int DrawCardType()
	{
		int roll = Random.Shared.Next( 0, 100 ); // 0 to 99 inclusive
		if ( roll < 65 ) return 0;  // Number
		if ( roll < 77 ) return 1;  // Reverse
		if ( roll < 92 ) return 2;  // Draw Two
		return 3;                   // Wild
	}

	private static string GetCardTypeName( int t )
	{
		if ( t == 0 ) return "Number";
		if ( t == 1 ) return "Reverse";
		if ( t == 2 ) return "Draw Two";
		return "Wild";
	}

	// Safe helper method to fetch the GameManager directly from the active engine layout context
	private static GameManager GetManager()
	{
		return Sandbox.Game.ActiveScene?.Get<GameManager>();
	}

	public static void StartEvent( Player player, Player ai )
	{
		// Defensive guard assertion prevents multi-popup panel UI thread registration crashes
		var manager = GetManager();
		if ( manager == null )
		{
			Log.Error( "[UNO DUEL CRITICAL] Failed to resolve GameManager from active scene tree setup!" );
			return;
		}

		// ⚙️ SYSTEM CHANNEL: Outer announcement barriers stay neutral white
		GameManager.AddSystemLog( "\n=== UNO DUEL MATCH PROTOCOL ===" );

		cachedPlayer = player;
		cachedAI = ai;

		// Generate random positions safely using Random.Shared
		topColor = Random.Shared.Next( 0, 4 );     // 0 to 3
		topNumber = Random.Shared.Next( 0, 10 );   // 0 to 9
		topType = DrawCardType();

		playerColor = Random.Shared.Next( 0, 4 );
		playerNumber = Random.Shared.Next( 0, 10 );
		playerType = DrawCardType();

		aiColor = Random.Shared.Next( 0, 4 );
		aiNumber = Random.Shared.Next( 0, 10 );
		aiType = DrawCardType();

		GameManager.AddSystemLog( $"\n[DECK STACK] Top Discard Pile: {colors[topColor]} {topNumber} ({GetCardTypeName( topType )})" );
		GameManager.AddPlayerLog( $"[YOUR HAND] Dealt Profile: {colors[playerColor]} {playerNumber} ({GetCardTypeName( playerType )})" );
		GameManager.AddAILog( $"[AI BUFFER] Dealt Profile: {colors[aiColor]} {aiNumber} ({GetCardTypeName( aiType )})" );
		GameManager.AddSystemLog( "⚠️ AWAITING SECURITY HANDSHAKE DISCARD PROTOCOL..." );

		// Set the rule lookup cache flag based on standard matching matrices
		playerCanPlay =
			playerType == 3 ||
			playerType == topType ||
			playerColor == topColor ||
			playerNumber == topNumber;

		var unoEvent = new ChoiceEvent
		{
			Title = "🃏 PROTOCOL CARD DECK DUEL",
			PromptText = $"The system has dealt an encryption card profile. Top Card: {colors[topColor]} {topNumber} ({GetCardTypeName( topType )}). Your Card: {colors[playerColor]} {playerNumber} ({GetCardTypeName( playerType )}). Evaluate your data packets. Will you match the sector or pull from the core stack?",
		};

		// --- BUTTON 1: PLAY CARD ---
		unoEvent.Options.Add( new EventOption
		{
			ButtonText = "🃏 DISCARD / PLAY CARD",
			ActionToExecute = () =>
			{
				ResolveUnoDuel( 1 );
			}
		} );

		// --- BUTTON 2: DRAW AND SKIP ---
		unoEvent.Options.Add( new EventOption
		{
			ButtonText = "➕ DRAW PACKET & SKIP",
			ActionToExecute = () =>
			{
				ResolveUnoDuel( 2 );
			}
		} );

		// Execute popup utilizing the secure found scene reference
		manager.TriggerChoicePopup( unoEvent );
	}

	private static void ResolveUnoDuel( int playerChoice )
	{
		var manager = GetManager();
		int prestige = manager != null ? manager.CurrentPrestige : 0;

		// Calculate scaled prestige modifications (+1 to +3 lineally per prestige level)
		int scaledReverseDamage = 10 + (prestige * 1);
		int scaledDrawTwoDamage = 20 + (prestige * 3);
		int scaledPenaltyDamage = 15 + (prestige * 2);

		// --- PLAYER ACTION REACTION TREE ---
		if ( playerChoice == 1 )
		{
			if ( playerCanPlay )
			{
				// 🔵 PLAYER HIGHLIGHT: Valid card matches turn blue and print a clear explanation
				GameManager.AddPlayerLog( $"🟢 CARD MATCHED! You successfully play your {colors[playerColor]} {GetCardTypeName( playerType )}!" );

				if ( playerType == 1 ) // REVERSE CARD
				{
					int finalHeal = scaledReverseDamage + cachedPlayer.eventHealingBuff;
					GameManager.AddPlayerLog( $"🔄 REVERSE DIRECTION! Data stream inverted! AI loses {scaledReverseDamage} HP | You heal +{finalHeal} HP!" );
					CombatManager.applyDamage( ref cachedAI, scaledReverseDamage );
					cachedPlayer.hp += finalHeal;
					cachedPlayer.eventHealingBuff = 0; // Safe flush
				}
				else if ( playerType == 2 ) // DRAW TWO CARD
				{
					GameManager.AddPlayerLog( $"➕ DRAW TWO! Forcing stack overflow on AI registers! AI loses {scaledDrawTwoDamage} HP!" );
					CombatManager.applyDamage( ref cachedAI, scaledDrawTwoDamage );
				}
				else if ( playerType == 3 ) // WILD CARD
				{
					topColor = Random.Shared.Next( 0, 4 );
					GameManager.AddPlayerLog( $"🌈 [WILD CARD ACTIVATE] Firewalls re-encrypted! Mainframe color shifted to {colors[topColor]}!" );
				}
				else // STANDARD NUMBER CARD
				{
					GameManager.AddPlayerLog( $"💾 Data segment discarded cleanly: {colors[playerColor]} {playerNumber} overrides the stack." );
				}

				// The card you played now safely becomes the fresh Top Card of the deck for the AI's turn!
				topColor = playerColor;
				topNumber = playerNumber;
				topType = playerType;
			}
			else
			{
				// 🔵 PLAYER HIGHLIGHT: Mismatched cards throw a clear warning log explaining why you failed!
				GameManager.AddPlayerLog( $"❌ INVALID SEQUENCE! Your {colors[playerColor]} {GetCardTypeName( playerType )} does not match the pile color or number! Card rejected!" );
				GameManager.AddPlayerLog( $"🚨 UNO MISPLAY PENALTY! System feedback loop hits you for -{scaledPenaltyDamage} HP!" );
				CombatManager.applyDamage( ref cachedPlayer, scaledPenaltyDamage );
			}
		}
		else
		{
			// 🔵 PLAYER HIGHLIGHT: Your voluntary skip turns turn blue
			GameManager.AddPlayerLog( "📥 Packet pulled. You draw from the core stack and skip your action." );
		}

		// --- AI TURNING REACTION TREE ---
		bool aiCanPlay =
			aiType == 3 ||
			aiType == topType ||
			aiColor == topColor ||
			aiNumber == topNumber;

		// 🔴 AI HIGHLIGHT: The AI's turn announcement headers turn red
		GameManager.AddAILog( "\nAI's turn!" );

		if ( aiCanPlay && cachedAI.hp > 15 )
		{
			// 🔴 AI HIGHLIGHT: AI card deployments turn red
			GameManager.AddAILog( $"🔴 AI MATCHED! AI plays its {colors[aiColor]} {GetCardTypeName( aiType )} onto the pile!" );

			if ( aiType == 1 )
			{
				int finalAIHeal = scaledReverseDamage + cachedAI.eventHealingBuff;
				GameManager.AddAILog( $"🔄 AI REVERSE! AI steals health! (You lose {scaledReverseDamage} HP | AI heals +{finalAIHeal} HP!)" );
				CombatManager.applyDamage( ref cachedPlayer, scaledReverseDamage );
				cachedAI.hp += finalAIHeal;
				cachedAI.eventHealingBuff = 0; // Safe flush
			}
			else if ( aiType == 2 )
			{
				GameManager.AddAILog( $"➕ AI DRAW TWO! You lose {scaledDrawTwoDamage} HP!" );
				CombatManager.applyDamage( ref cachedPlayer, scaledDrawTwoDamage );
			}
			else if ( aiType == 3 )
			{
				topColor = Random.Shared.Next( 0, 4 );
				GameManager.AddAILog( $"🌈 AI uses WILD! Mainframe color shifted to {colors[topColor]}" );
			}
			else
			{
				GameManager.AddAILog( $"💾 AI discards cleanly: {colors[aiColor]} {aiNumber} overrides the stack." );
			}

			topColor = aiColor;
			topNumber = aiNumber;
			topType = aiType;
		}
		else
		{
			// 🔴 AI HIGHLIGHT: AI cards skips or failures turn red
			GameManager.AddAILog( "AI draws a card from the core stack." );
			if ( !aiCanPlay )
			{
				GameManager.AddAILog( $"🚨 AI MISPLAY PENALTY! AI failed to match parameters! Feedback loop hits AI for -{scaledPenaltyDamage} HP!" );
				CombatManager.applyDamage( ref cachedAI, scaledPenaltyDamage );
			}
		}

		// Clamp logic using prestige-scaled maximum health pools safely
		if ( cachedPlayer.hp < 0 ) cachedPlayer.hp = 0;
		if ( cachedAI.hp < 0 ) cachedAI.hp = 0;

		if ( manager != null )
		{
			if ( manager.LocalPlayer != null && cachedPlayer.hp > manager.LocalPlayer.maxHp )
				cachedPlayer.hp = manager.LocalPlayer.maxHp;

			if ( manager.AIEnemy != null && cachedAI.hp > manager.AIEnemy.maxHp )
				cachedAI.hp = manager.AIEnemy.maxHp;

			// Force state diagnostics to redraw the health bars immediately on the viewport panel
			manager.NotifyStateChanged();
		}
		else
		{
			if ( cachedPlayer.hp > 100 ) cachedPlayer.hp = 100;
			if ( cachedAI.hp > 100 ) cachedAI.hp = 100;
		}

		// ⚙️ SYSTEM CHANNEL: Displays the final score and results of the mini-game clearly
		GameManager.AddSystemLog( "\n=== END OF UNO DUEL ===" );
		GameManager.AddPlayerLog( $"STATUS REPORT -> {cachedPlayer.name} | HP: {cachedPlayer.hp} | Armor: {cachedPlayer.defense}" );
		GameManager.AddAILog( $"STATUS REPORT -> {cachedAI.name} | HP: {cachedAI.hp} | Armor: {cachedAI.defense}" );
	}
}