MainHud/UnoDuelEventRunner.cs
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" };
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;
// =========================================================================
// 🌐 MULTIPLAYER NETWORK STATE MATRIX
// =========================================================================
private static bool isPlayerOneFinished = false;
private static bool isPlayerTwoFinished = false;
private static bool isUnoResolved = false;
private static int p1SavedChoice = 0;
private static int p2SavedChoice = 0;
// =========================================================================
private static int DrawCardType()
{
int roll = Random.Shared.Next( 0, 100 );
if ( roll < 65 ) return 0;
if ( roll < 77 ) return 1;
if ( roll < 92 ) return 2;
return 3;
}
private static string GetCardTypeName( int t )
{
if ( t == 0 ) return "Number";
if ( t == 1 ) return "Reverse";
if ( t == 2 ) return "Draw Two";
return "Wild";
}
private static GameManager GetManager()
{
return Sandbox.Game.ActiveScene?.Get<GameManager>();
}
public static void StartEvent( Player player, Player ai )
{
var manager = GetManager();
if ( manager == null )
{
Log.Error( "[UNO DUEL CRITICAL] Failed to resolve GameManager from active scene tree setup!" );
return;
}
bool isMultiplayer = manager.CurrentMode == GameManager.PlayMode.Multiplayer;
GameManager.AddSystemLog( "\n=== UNO DUEL MATCH PROTOCOL ===" );
cachedPlayer = player;
cachedAI = ai;
// Symmetrically deal starting card packets across the frame arrays
topColor = Random.Shared.Next( 0, 4 );
topNumber = Random.Shared.Next( 0, 10 );
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();
if ( isMultiplayer )
{
isPlayerOneFinished = false;
isPlayerTwoFinished = false;
isUnoResolved = false;
p1SavedChoice = 0;
p2SavedChoice = 0;
GameManager.AddSystemLog( $"\n[DECK STACK] Top Discard Pile: {colors[topColor]} {topNumber} ({GetCardTypeName( topType )})" );
GameManager.AddPlayerLog( $"[{cachedPlayer.name}'s HAND] Dealt Profile: {colors[playerColor]} {playerNumber} ({GetCardTypeName( playerType )})" );
GameManager.AddAILog( $"[{cachedAI.name}'s HAND] Dealt Profile: {colors[aiColor]} {aiNumber} ({GetCardTypeName( aiType )})" );
GameManager.AddSystemLog( "⚠️ AWAITING SECURITY HANDSHAKE DISCARD PROTOCOLS..." );
// Begin sequential phase matching loop with Player 1 (Host)
RunMultiplayerUnoLoop( true );
}
else
{
// 🤖 SINGLE PLAYER MODE: Exact original career rules configuration
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..." );
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 )}). Will you match the sector or pull from the core stack?",
};
unoEvent.Options.Add( new EventOption { ButtonText = "🃏 DISCARD / PLAY CARD", ActionToExecute = () => ResolveUnoDuel( 1 ) } );
unoEvent.Options.Add( new EventOption { ButtonText = "➕ DRAW PACKET & SKIP", ActionToExecute = () => ResolveUnoDuel( 2 ) } );
manager.TriggerChoicePopup( unoEvent );
}
}
// =========================================================================
// 👥 MULTIPLAYER SEQUENTIAL CARD DISCARD ROUTER
// =========================================================================
private static void RunMultiplayerUnoLoop( bool isPlayerOneTurn )
{
var manager = GetManager();
if ( manager == null ) return;
string activeName = isPlayerOneTurn ? cachedPlayer.name : cachedAI.name;
int activeColor = isPlayerOneTurn ? playerColor : aiColor;
int activeNumber = isPlayerOneTurn ? playerNumber : aiNumber;
int activeType = isPlayerOneTurn ? playerType : aiType;
bool canActivePlay =
activeType == 3 ||
activeType == topType ||
activeColor == topColor ||
activeNumber == topNumber;
bool shouldDrawPopup = (isPlayerOneTurn && Networking.IsHost) || (!isPlayerOneTurn && !Networking.IsHost);
if ( shouldDrawPopup )
{
var unoMpEvent = new ChoiceEvent
{
Title = "🃏 PROTOCOL CARD DECK DUEL",
PromptText = $"Top Card: {colors[topColor]} {topNumber} ({GetCardTypeName( topType )}). Your Card: {colors[activeColor]} {activeNumber} ({GetCardTypeName( activeType )}). Execute discard vector sub-routine:",
};
unoMpEvent.Options.Add( new EventOption
{
ButtonText = canActivePlay ? "🃏 DISCARD / PLAY CARD (VALID MATCH)" : "❌ DISCARD / PLAY CARD (FORCES MISPLAY PENALTY)",
ActionToExecute = () =>
{
if ( isPlayerOneTurn ) { p1SavedChoice = 1; isPlayerOneFinished = true; }
else { p2SavedChoice = 1; isPlayerTwoFinished = true; }
// Symmetrically pass the card properties forward into the stack top card pointer context
topColor = activeColor;
topNumber = activeNumber;
topType = activeType;
AdvanceMultiplayerUnoPhase( isPlayerOneTurn );
}
} );
unoMpEvent.Options.Add( new EventOption
{
ButtonText = "➕ DRAW PACKET & SKIP",
ActionToExecute = () =>
{
if ( isPlayerOneTurn ) { p1SavedChoice = 2; isPlayerOneFinished = true; }
else { p2SavedChoice = 2; isPlayerTwoFinished = true; }
AdvanceMultiplayerUnoPhase( isPlayerOneTurn );
}
} );
manager.TriggerChoicePopup( unoMpEvent );
}
else
{
if ( manager.IsChoiceEventActive ) manager.IsChoiceEventActive = false;
GameManager.AddSystemLog( $"⏳ Waiting for {activeName} to finalize their card discard handshake..." );
}
}
private static void AdvanceMultiplayerUnoPhase( bool wasPlayerOneTurn )
{
var manager = GetManager();
if ( wasPlayerOneTurn && !isPlayerTwoFinished )
{
// Player 1 logged their card -> dynamically pass prompt controls over to Player 2
RunMultiplayerUnoLoop( false );
}
else if ( isPlayerOneFinished && isPlayerTwoFinished && !isUnoResolved )
{
isUnoResolved = true;
GameManager.AddSystemLog( $"\n📊 [NET UNO DUEL EXECUTION]" );
// Calculate flat competitive baseline damage metrics for multiplayer wires
int pReverseDmg = 10;
int pDrawTwoDmg = 20;
int pPenaltyDmg = 15;
// --- EVALUATE PLAYER 1 OUTCOME ---
bool p1CanPlayInitial = playerType == 3 || playerType == topType || playerColor == topColor || playerNumber == topNumber;
if ( p1SavedChoice == 1 )
{
if ( p1CanPlayInitial )
{
GameManager.AddPlayerLog( $"🟢 {cachedPlayer.name} played their {colors[playerColor]} {GetCardTypeName( playerType )}!" );
if ( playerType == 1 )
{
GameManager.AddPlayerLog( $"🔄 REVERSE DIRECTION! {cachedAI.name} suffers -{pReverseDmg} HP | {cachedPlayer.name} heals +{pReverseDmg} HP!" );
CombatManager.applyDamage( ref cachedAI, pReverseDmg );
cachedPlayer.hp = Math.Min( cachedPlayer.maxHp, cachedPlayer.hp + pReverseDmg );
}
else if ( playerType == 2 )
{
GameManager.AddPlayerLog( $"➕ DRAW TWO! {cachedAI.name} takes -{pDrawTwoDmg} HP!" );
CombatManager.applyDamage( ref cachedAI, pDrawTwoDmg );
}
else if ( playerType == 3 ) GameManager.AddPlayerLog( $"🌈 {cachedPlayer.name} triggered a Wild color shift vector!" );
}
else
{
GameManager.AddPlayerLog( $"❌ {cachedPlayer.name} forced an INVALID SEQUENCE! Penalty feedback hits for -{pPenaltyDmg} HP!" );
CombatManager.applyDamage( ref cachedPlayer, pPenaltyDmg );
}
}
else GameManager.AddPlayerLog( $"📥 {cachedPlayer.name} drew a packet and skipped their action." );
// --- EVALUATE PLAYER 2 OUTCOME ---
bool p2CanPlayInitial = aiType == 3 || aiType == topType || aiColor == topColor || aiNumber == topNumber;
if ( p2SavedChoice == 1 )
{
if ( p2CanPlayInitial )
{
GameManager.AddAILog( $"🟢 {cachedAI.name} played their {colors[aiColor]} {GetCardTypeName( aiType )}!" );
if ( aiType == 1 )
{
GameManager.AddAILog( $"🔄 REVERSE DIRECTION! {cachedPlayer.name} suffers -{pReverseDmg} HP | {cachedAI.name} heals +{pReverseDmg} HP!" );
CombatManager.applyDamage( ref cachedPlayer, pReverseDmg );
cachedAI.hp = Math.Min( cachedAI.maxHp, cachedAI.hp + pReverseDmg );
}
else if ( aiType == 2 )
{
GameManager.AddAILog( $"➕ DRAW TWO! {cachedPlayer.name} takes -{pDrawTwoDmg} HP!" );
CombatManager.applyDamage( ref cachedPlayer, pDrawTwoDmg );
}
else if ( aiType == 3 ) GameManager.AddAILog( $"🌈 {cachedAI.name} triggered a Wild color shift vector!" );
}
else
{
GameManager.AddAILog( $"❌ {cachedAI.name} forced an INVALID SEQUENCE! Penalty feedback hits for -{pPenaltyDmg} HP!" );
CombatManager.applyDamage( ref cachedAI, pPenaltyDmg );
}
}
else GameManager.AddAILog( $"📥 {cachedAI.name} drew a packet and skipped their action." );
// Hard clamp health values safely
if ( cachedPlayer.hp < 0 ) cachedPlayer.hp = 0;
if ( cachedAI.hp < 0 ) cachedAI.hp = 0;
GameManager.AddLog( $"{cachedPlayer.name} HP: {cachedPlayer.hp} | {cachedAI.name} HP: {cachedAI.hp}" );
if ( manager != null ) manager.IsChoiceEventActive = false;
}
}
// =========================================================================
private static void ResolveUnoDuel( int playerChoice )
{
var manager = GetManager();
int prestige = manager != null ? manager.CurrentPrestige : 0;
int scaledReverseDamage = 10 + (prestige * 1);
int scaledDrawTwoDamage = 20 + (prestige * 3);
int scaledPenaltyDamage = 15 + (prestige * 2);
if ( playerChoice == 1 )
{
if ( playerCanPlay )
{
GameManager.AddPlayerLog( $"🟢 CARD MATCHED! You successfully play your {colors[playerColor]} {GetCardTypeName( playerType )}!" );
if ( playerType == 1 )
{
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;
}
else if ( playerType == 2 )
{
GameManager.AddPlayerLog( $"➕ DRAW TWO! Forcing stack overflow on AI registers! AI loses {scaledDrawTwoDamage} HP!" );
CombatManager.applyDamage( ref cachedAI, scaledDrawTwoDamage );
}
else if ( playerType == 3 )
{
topColor = Random.Shared.Next( 0, 4 );
GameManager.AddPlayerLog( $"🌈 [WILD CARD ACTIVATE] Firewalls re-encrypted! Mainframe color shifted to {colors[topColor]}!" );
}
else
{
GameManager.AddPlayerLog( $"💾 Data segment discarded cleanly: {colors[playerColor]} {playerNumber} overrides the stack." );
}
topColor = playerColor;
topNumber = playerNumber;
topType = playerType;
}
else
{
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
{
GameManager.AddPlayerLog( "📥 Packet pulled. You draw from the core stack and skip your action." );
}
bool aiCanPlay = aiType == 3 || aiType == topType || aiColor == topColor || aiNumber == topNumber;
GameManager.AddAILog( "\nAI's turn!" );
if ( aiCanPlay && cachedAI.hp > 15 )
{
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;
}
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
{
GameManager.AddAILog( "AI draws a card from the core stack." );
if ( !aiCanPlay )
{
GameManager.AddAILog( $"🚨 AI MISPLAY PENALTY! AI failed to match parameters!" );
}
}
}
}