Central game manager for a turn-based cyber RPG duel game. Holds player and AI state, weapons, perks, rounds, combat logs, sounds, multiplayer turn sync, achievements and saved progress.
using Keyboard_Warriors_.Managers;
using Keyboard_Warriors_.Models;
using Sandbox;
using Sandbox.MainHud;
using System;
using System.Collections.Generic;
using System.Data;
using System.Numerics;
using System.Text;
using static Sandbox.MainHud.KeyboardWarriorsHUD;
using static Sandbox.Streamer;
namespace Keyboard_Warriors_.Systems;
public class GameManager : Component
{
public bool HasPlayerTakenAnyDamage { get; set; } = false;
public static GameManager Instance { get; private set; }
private static Random rand = new Random();
// Persistent engine tracking properties - Fully bound to your Razor HUD
[Property] public Player LocalPlayer;
[Property] public Player AIEnemy;
[Property] public Weapon ChosenWeapon;
[Property] public Weapon AIWeapon;
[Property] public int PlayerPoints { get; set; } = 0;
[Property] public int CurrentPrestige { get; set; } = 0;
[Property] public int MaxWinStreak { get; set; } = 0;
[Property] public int MaxLossStreak { get; set; } = 0;
[Property] public int MaxPrestige { get; set; } = 0;
[Property] public int MaxPoints { get; set; } = 0;
[Property] public int Armor { get; set; }
[Property] public int Magic { get; set; }
[Property] public SoundEvent RandomEventPing { get; set; }
[Property] public SoundEvent MenuTheme { get; set; }
[Property] public SoundEvent CritSound { get; set; }
[Property] public SoundEvent BlockSound { get; set; }
[Property] public SoundEvent FirewallSound { get; set; }
[Property] public SoundEvent HealSpellSound { get; set; }
[Property] public SoundEvent HealSound { get; set; }
[Property] public SoundEvent RoboticGlitchSound { get; set; }
[Property] public SoundEvent ClickSound { get; set; }
[Property] public SoundEvent PrestigeSound { get; set; }
[Property] public SoundEvent Prestige10Sound { get; set; }
[Property] public SoundEvent FirstLossSound { get; set; }
[Property, ImageAssetPath] public string BackgroundImage { get; set; } = "/ui/cyber_nebula.png";
[Property] public bool IsPerkEventActive { get; set; } = false;
[Property] public List<Keyboard_Warriors_.Models.PerkCard> ActivePerkChoices { get; set; } = new List<Keyboard_Warriors_.Models.PerkCard>();
[Property] public List<string> PlayerOwnedPerkIds { get; set; } = new List<string>();
[Property] public List<string> AIOwnedPerkIds { get; set; } = new List<string>();
public int PointsRequiredForPrestige { get; set; } = 1000;
public int ActiveAttackPotions { get; set; }
public int ActiveArmorPotions { get; set; }
public int ChoiceEventBuffModifier = 0;
public int ChoiceEventAIBuffModifier = 0;
private int aiBuff { get; set; } = 0;
private int lastAIMove { get; set; } = 0;
private int globalAiBuff { get; set; } = 0;
public string AICurrentStance { get; set; } = "UNKNOWN";
public int AICurrentMoveIndex { get; set; } = 0;
public bool IsAIActionIntercepted { get; set; } = false;
public float AIHealthMultiplier => 1.0f + (CurrentPrestige * 0.25f);
// Core State Variables
public int Round = 1;
public bool IsBattleActive { get; set; } = false;
// Stats Tracking
public int Wins { get; set; } = 0;
public int Losses { get; set; } = 0;
public int WinStreak { get; set; } = 0;
public int LossStreak { get; set; } = 0;
public static int SessionWinStreak { get; set; } = 0;
public static int SessionLossStreak { get; set; } = 0;
public bool HasDefiedDeathThisMatch { get; set; } = false;
public bool HasAIDefiedDeathThisMatch { get; set; } = false;
public bool HasUnlockedMilestone20 { get; set; } = false;
public bool HasUnlockedMilestone30 { get; set; } = false;
public bool HasUnlockedMilestone50 { get; set; } = false;
public bool HasUnlockedMilestone100 { get; set; } = false;
// Active UI logs parsed by your Razor log-scroll area
public struct CombatLogEntry
{
public string Message { get; set; }
public string ColorClass { get; set; } // Tracks "player-text", "ai-text", or "system-text"
public string AnimationClass { get; set; }
}
public List<CombatLogEntry> CombatLogs { get; set; } = new List<CombatLogEntry>();
public string SelectedWeapon => ChosenWeapon?.name ?? "None";
// Legacy pattern counters for AI Memory evaluation
private int lastPlayerMove = 0;
private int repeatCount = 0;
private int attackCount = 0;
private int blockCount = 0;
private int healCount = 0;
private int specialCount = 0;
// Multiplayer Mode
public enum PlayMode { SinglePlayer, Multiplayer }
[Property, Sync] public PlayMode CurrentMode { get; set; } = PlayMode.SinglePlayer;
// Symmetrical multiplayer player registers
[Property, Sync] public Player PlayerOne { get; set; }
[Property, Sync] public Player PlayerTwo { get; set; }
// ⏱️ 10-SECOND ACTIVE ACTION TIMER REGISTERS
[Property, Sync] public float TurnCountdownClock { get; set; } = 10.0f;
[Property, Sync] public bool IsWaitingForClientInput { get; set; } = false;
// If true, Player 1 (Host) is choosing. If false, Player 2 (Client) is choosing.
[Property, Sync] public bool IsPlayerOneActiveTurn { get; set; } = true;
// Track if the current active turn has submitted a move selection packet
[Property, Sync] public bool IsTurnMoveCached { get; set; } = false;
// Preserving streak states without global point overhead
[Property, Sync] public int PlayerWinStreak { get; set; } = 0;
[Property, Sync] public int PlayerLossStreak { get; set; } = 0;
// To have the player 2 select their own moves.
[Property, Sync] public int NetworkedPlayerTwoMove { get; set; } = 1;
// Chat messaging between players
[Property, Sync] public List<string> ChatMessages { get; set; } = new List<string>();
// Network move caches
[Property, Sync] public int PlayerOneStoredMove { get; set; } = 0; // 0 = Awaiting Choice
[Property, Sync] public int PlayerTwoStoredMove { get; set; } = 0;
public ref Player LocalPlayerTwo => ref AIEnemy;
[Property, Sync] public bool IsPlayerOneDazed { get; set; } = false;
[Property, Sync] public bool IsPlayerTwoDazed { get; set; } = false;
public void InitializeGameSetup()
{
// s&box utilizes Game.Random internally; local rand instantiation is usually unneeded
int prestige = GameManager.Instance.CurrentPrestige;
float multiplier = 1.0f + (prestige * 0.25f);
int startingHp = (int)System.Math.Ceiling( 100 * multiplier );
int startingArmor = prestige * 10;
Player player = new Player( "You", 100, 15, 0, false );
Player ai = new Player( "AI Opponent", startingHp, 15, startingArmor, false );
ai.defense = startingArmor;
char playAgain;
int wins = 0;
int losses = 0;
int winStreak = 0;
int lossStreak = 0;
List<Weapon> weapons = new List<Weapon>()
{
new Weapon("Sword", 5, 1.0f, 5),
new Weapon("Axe", 10, 0.8f, 2),
new Weapon("Shield", 0, 1.5f, 8),
new Weapon("Dagger", 3, 1.2f, 4)
};
GameManager.AddLog( "Choose your weapon:" );
for ( int i = 0; i < weapons.Count; i++ )
{
GameManager.AddLog(
$"{i + 1}. {weapons[i].name} " +
$"(Damage +{weapons[i].damageBonus}, " +
$"Block x{weapons[i].blockModifier}, " +
$"Heal +{weapons[i].healBonus})" );
}
// NOTE: In s&box, weaponChoice and enchantChoice will be set via
// your Razor UI UI buttons clicking into an event method,
// replacing the blocking while(true) Console.ReadLine() loops.
}
protected override void OnAwake()
{
Instance = this;
PointsRequiredForPrestige = 1000;
LoadProgress();
ApplyPrestigeScaling();
WinStreak = 0;
LossStreak = 0;
InitializeGameSetup();
if ( MenuTheme != null )
{
Sound.Play( MenuTheme );
}
else
{
Log.Error( "MenuTheme slot is empty! Drag your music file here." );
}
}
protected override void OnUpdate()
{
// If an interactive choice event is active on screen and has a countdown, tick it down!
if ( IsChoiceEventActive && ActiveChoiceEvent != null && ActiveChoiceEvent.HasTimeLimit )
{
// Time.Delta extracts the exact fraction of a second that passed between this frame and the last frame
ActiveChoiceEvent.TimeRemaining -= Time.Delta;
// Immediately forces the Razor templates to redraw the red bar shrinking smoothly
OnStateChanged?.Invoke();
// The exact millisecond the clock expires, trigger the penalty forfeit sequence
if ( ActiveChoiceEvent.TimeRemaining <= 0f )
{
ExecuteTimeoutForfeit();
}
}
// Only calculate countdown tick marks if a live multiplayer battle is active
if ( CurrentMode != PlayMode.Multiplayer || !IsBattleActive || !IsWaitingForClientInput ) return;
// Natively subtract frame delta time from your clock register
TurnCountdownClock -= RealTime.Delta;
if ( TurnCountdownClock <= 0f )
{
TurnCountdownClock = 10.0f; // Recharge the timer capacitor instantly
Log.Warning( "[NET TIMER] Turn countdown hit zero! Force-submitting fallback attack index 1." );
if ( Sandbox.Connection.Local != null )
{
// The server host submits on behalf of whoever timed out to keep network states completely synced
if ( Sandbox.Networking.IsHost )
{
SubmitNetworkMove( IsPlayerOneActiveTurn, 1 );
}
}
}
}
private void ExecuteTimeoutForfeit()
{
if ( ActiveChoiceEvent == null ) return;
Log.Warning( "⏱️ Firewall lockdown! Player ran out of execution time." );
// 1. Fire the custom penalty action mapped to the specific case
ActiveChoiceEvent.OnTimeoutAction?.Invoke();
// 2. Shut down the screen overlay barrier smoothly
IsChoiceEventActive = false;
ActiveChoiceEvent = null;
OnStateChanged?.Invoke();
}
// Method sits outside OnAwake, but still inside the GameManager class
// =========================================================================
// 📄 UPDATE 1.5.4: THE COLORED COMBAT LOG CORE PIPELINE
// =========================================================================
// 1. NON-STATIC METHOD: Handles actual memory list storage on your UI instance object
public void AddLogInstance( string message, string colorClass = "system-text", string animationClass = "" )
{
if ( CombatLogs == null ) CombatLogs = new List<CombatLogEntry>();
CombatLogs.Add( new CombatLogEntry
{
Message = message,
ColorClass = colorClass,
AnimationClass = animationClass
} );
// Force s&box rendering tree updates
NotifyStateChanged();
}
public static void AddLog( string message )
{
Instance?.AddLogInstance( message, "system-text", "" );
}
// 3. NEW COLOR OVERLOAD HOOKS: For colorful combat updates
public static void AddPlayerLog( string message )
{
Instance?.AddLogInstance( message, "player-text", "" );
}
public static void AddAILog( string message )
{
Instance?.AddLogInstance( message, "ai-text", "" );
}
public static void AddSystemLog( string message )
{
Instance?.AddLogInstance( message, "system-text", "" );
}
public static void AddCriticalLog( string message, bool isPlayer = true )
{
string targetColor = isPlayer ? "player-text" : "ai-text";
Instance?.AddLogInstance( message, targetColor, "flash-crit" );
}
public static void AddEventLog( string message )
{
Instance?.AddLogInstance( message, "text-yellow", "pulse-event" );
}
public static void AddButtonFeedbackLog( string message, bool isSuccess )
{
string color = isSuccess ? "success-green" : "fail-red";
string anim = isSuccess ? "glow-success" : "shake-fail";
Instance?.AddLogInstance( message, color, anim );
}
public static string GetSpellEmoji( string spellName )
{
if ( string.IsNullOrEmpty( spellName ) ) return "❓";
return spellName switch
{
// --- Offensive ---
"Fireball" => "🔥",
"Lightning" => "⚡",
"Poison Bolt" => "🤢",
// --- Defensive ---
"Shield" => "🛡️",
"Barrier" => "🔮",
"Reflect" => "🪞",
"Magic Wall" => "🧱",
"Fortify" => "🏋️",
// --- Healing ---
"Heal" => "❤️",
"Regen" => "⏳",
"Cleanse" => "✨",
"Divine Light" => "☀️",
"Recovery Pulse" => "💗",
// --- Special ---
"System Overdrive" => "🚀",
"EMP Blast" => "💥",
"Data Corruption" => "👾",
_ => "🔮"
};
}
public void ResetBattle()
{
ApplyPrestigeScaling();
ActiveAttackPotions = 0;
ActiveArmorPotions = 0;
// Any non-scaling resets (like Magic)
if ( AIEnemy != null ) AIEnemy.magic = 100;
if ( LocalPlayer != null ) LocalPlayer.magic = 100;
AddLog( $"Battle Reset! AI HP: {AIEnemy.hp}, Armor: {AIEnemy.defense}" );
this.HasDefiedDeathThisMatch = false;
this.HasAIDefiedDeathThisMatch = false;
LocalPlayer.cursed = false;
AIEnemy.cursed = false;
SpellManager.generateSpells( ref LocalPlayer );
SpellManager.generateSpells( ref AIEnemy );
if ( CurrentMode == PlayMode.Multiplayer )
{
if ( LocalPlayer != null )
{
LocalPlayer.maxHp = 100;
LocalPlayer.hp = 100;
LocalPlayer.maxDefense = 100;
LocalPlayer.defense = 100;
LocalPlayer.magic = 100; // Starts match with empty MP capacitors
}
if ( AIEnemy != null )
{
AIEnemy.maxHp = 100;
AIEnemy.hp = 100;
AIEnemy.maxDefense = 100;
AIEnemy.defense = 100;
AIEnemy.magic = 100;
}
Log.Info( "⚙️ [RESET SYSTEM] Multiplayer override hard-flushed bars to 100/100!" );
}
}
public enum GamePhase
{
MainMenu,
StartMenu,
WeaponSelection,
EnchantmentSelection,
Battle,
MultiplayerLobby,
AnomalySelection,
GameOver
}
public List<Weapon> AvailableWeapons { get; set; } = new List<Weapon>()
{
new Weapon("Sword", 5, 1.0f, 5),
new Weapon("Axe", 10, 0.8f, 2),
new Weapon("Shield", 0, 1.5f, 8),
new Weapon("Dagger", 3, 1.2f, 4)
};
public void PlayCritSound()
{
if (CritSound != null)
{
Sound.Play(CritSound);
}
}
public void PlayBlockSound()
{
if ( BlockSound != null )
{
Sound.Play( BlockSound );
}
}
public void PlayFirewallSound()
{
if ( FirewallSound != null )
{
Sound.Play( FirewallSound );
}
}
public void PlayRoboticGlitchSound()
{
if ( BlockSound != null )
{
Sound.Play( RoboticGlitchSound );
}
}
public void PlayClickSound()
{
if ( ClickSound != null )
{
Sound.Play( ClickSound );
}
}
public void ApplyEnchantmentSelection( Weapon chosenWeapon, int enchantChoice )
{
switch ( enchantChoice )
{
case 1:
chosenWeapon.enchantment = Weapon.EnchantmentType.Fire;
break;
case 2:
chosenWeapon.enchantment = Weapon.EnchantmentType.Poison;
chosenWeapon.enchantPower = 90;
break;
case 3:
chosenWeapon.enchantment = Weapon.EnchantmentType.Ice;
break;
case 4:
chosenWeapon.enchantment = Weapon.EnchantmentType.Sparks;
break;
case 5:
chosenWeapon.enchantment = Weapon.EnchantmentType.DamageHP;
chosenWeapon.enchantPower = 60;
break;
case 6:
chosenWeapon.enchantment = Weapon.EnchantmentType.Stun;
chosenWeapon.enchantPower = 25;
break;
case 7:
chosenWeapon.enchantment = Weapon.EnchantmentType.Bleed;
chosenWeapon.enchantPower = 80;
break;
case 8:
chosenWeapon.enchantment = Weapon.EnchantmentType.LifeSteal;
chosenWeapon.enchantPower = 65;
break;
case 9:
chosenWeapon.enchantment = Weapon.EnchantmentType.ShieldBreak;
chosenWeapon.enchantPower = 50;
break;
default:
chosenWeapon.enchantment = Weapon.EnchantmentType.None;
break;
}
}
public void StartBattle( int chosenWeaponIndex = 0 )
{
if ( CurrentMode == PlayMode.SinglePlayer )
{
LoadProgress();
HasPlayerTakenAnyDamage = false;
LocalPlayer = new Player( "Hero", 100, 15, 0, false ); // 100 HP, 15 Atk, 0 Def. For single player mode with the AI Overlord.
AIEnemy = new Player( "AI Overlord", 100, 15, 100, true );
}
else
{
HasPlayerTakenAnyDamage = false;
// 🚀 MULTIPLAYER: Search s&box connections to find your opponent's Steam name handle!
string opponentName = "Unknown Challenger";
// Look across connections to find the non-host user
foreach ( var conn in Sandbox.Connection.All )
{
if ( conn != Sandbox.Connection.Local )
{
opponentName = conn.Name; // Extracts their true network nickname handle!
break;
}
}
// Symmetrical 100 HP and 100 Armor allocations right out of the box
LocalPlayer = new Player( "Hero", 100, 15, 100, false );
LocalPlayer.maxHp = 100;
LocalPlayer.maxDefense = 100;
AIEnemy = new Player( opponentName, 100, 15, 100, false );
AIEnemy.maxHp = 100;
AIEnemy.maxDefense = 100;
}
if ( chosenWeaponIndex >= 0 && chosenWeaponIndex < AvailableWeapons.Count )
{
ChosenWeapon = AvailableWeapons[chosenWeaponIndex];
}
else
{
ChosenWeapon = AvailableWeapons[0]; // Fallback to Sword
}
// AI picks a random weapon from your exact list!
// 1. Pick the weapon
int aiWeaponIndex = System.Random.Shared.Next( AvailableWeapons.Count );
AIWeapon = AvailableWeapons[aiWeaponIndex];
// 2. Assign the weapon to the player/AI
LocalPlayer.equippedWeapon = ChosenWeapon;
AIEnemy.equippedWeapon = AIWeapon;
// 3. REPLACED: Use your existing logic method to configure the weapon properly
int aiEnchantChoice = Random.Shared.Next( 1, 10 ); // Picks cases 1-9
ApplyEnchantmentSelection( AIWeapon, aiEnchantChoice );
// 4. Update the UI property so the Razor HUD displays the right name
AIEnemy.ActiveEnchantment = AIWeapon.enchantment;
ResetBattle();
Round = 1;
IsBattleActive = true;
// Reset tracking counters
attackCount = 0; blockCount = 0; healCount = 0; specialCount = 0;
repeatCount = 0; lastPlayerMove = 0;
CombatLogs.Clear();
if ( CurrentMode == PlayMode.SinglePlayer )
{
CombatLogs.Add( new CombatLogEntry
{
Message = $"⚔️ Combat Begins! AI equipped {AIWeapon.name}.",
ColorClass = "system-text"
} );
}
else
{
// Symmetrical Multiplayer startup announcement
CombatLogs.Add( new CombatLogEntry
{
Message = $"📡 PVP ARENA ACTIVE: Host ({LocalPlayer.name}) vs Challenger ({AIEnemy.name})!",
ColorClass = "system-text"
} );
}
// Generate real spellbooks directly without temp-swaps overwriting them
SpellManager.generateSpells( ref LocalPlayer );
SpellManager.generateSpells( ref AIEnemy );
// =========================================================================
// 👑 INTERCEPT MAINBOARD IDENTITY (STEAM PROFILE NAME LOG)
// =========================================================================
if ( CurrentMode == PlayMode.SinglePlayer )
{
string userSteamName = Sandbox.Utility.Steam.PersonaName;
if ( string.IsNullOrEmpty( userSteamName ) )
{
// Fallback baseline accessor if testing via standalone connection sheets
userSteamName = Sandbox.Connection.Local?.Name;
}
if ( string.IsNullOrEmpty( userSteamName ) )
{
userSteamName = "Unknown Combatant";
}
// The self-aware AI now mocks you by name right in the opening logs!
AddLog( $"AI: \"A challenger approaches... I see you, {userSteamName}. Prepare to rage quit!" );
}
else
{
// Clean, competitive network notification for Multiplayer instead of bot logs
AddLog( "📡 [SYSTEM] Handshake finalized. Symmetrical network synchronization engine online." );
}
// =========================================================================
// ⏱️ ASYMMETRICAL SPEED-CHESS TIMING AND CACHE INJECTIONS
// =========================================================================
if ( CurrentMode == PlayMode.Multiplayer )
{
this.PlayerOneStoredMove = 0;
this.PlayerTwoStoredMove = 0;
this.IsPlayerOneActiveTurn = true; // Player 1 (Host) takes the absolute opening shot
this.IsTurnMoveCached = false;
this.TurnCountdownClock = 10.0f; // Hard pre-charge the 10-second tactical countdown
this.IsWaitingForClientInput = true;
}
// =========================================================================
this.AICurrentStance = "ANALYZING PACKETS...";
this.AICurrentMoveIndex = 0;
this.IsAIActionIntercepted = false;
}
public static bool ShouldStunAI { get; set; } = false;
/// <summary>
/// Executes a complete real-time turn step. Called instantly by UI button clicks.
/// </summary>
public void ProcessTurnSlice( int playerMove )
{
int playerTwoMove = (CurrentMode == PlayMode.Multiplayer) ? this.PlayerTwoStoredMove : 1;
if ( !IsBattleActive || LocalPlayer == null || AIEnemy == null ) return;
// --- 1. CORE TICK EFFECTS (CURSES & REGEN LOOPS) ---
if ( LocalPlayer.cursed )
{
int curseDamage = 5;
// 🚀 NEW: If Cryo-Insulation is active, mitigate incoming curse damage by 35%!
if ( LocalPlayer.Perks.ElementalDamageTakenMultiplier < 1.0f )
{
curseDamage = (int)(curseDamage * LocalPlayer.Perks.ElementalDamageTakenMultiplier);
}
LocalPlayer.hp = Math.Max( 0, LocalPlayer.hp - curseDamage );
GameManager.AddPlayerLog( $"⚠️ The demon's curse drains {curseDamage} HP from YOU!" );
}
if ( LocalPlayer.Perks.HasNaniteRegen )
{
LocalPlayer.defense = 0; // Hard clamp wipes out any temporary shields!
int regenAmt = (int)(LocalPlayer.maxHp * 0.08f);
LocalPlayer.hp = Math.Min( LocalPlayer.maxHp, LocalPlayer.hp + regenAmt );
GameManager.AddPlayerLog( $"🧫 Nanites rebuild your framework! Restored +{regenAmt} HP." );
}
if ( AIEnemy.cursed )
{
int aiCurseDamage = 5;
// =========================================================================
// ❄️ AI CRYO-INSULATION MITIGATION (Update 1.6.5)
// =========================================================================
// If the AI has Cryo-Insulation, mitigate its curse ticks by 35%!
if ( AIEnemy.Perks.ElementalDamageTakenMultiplier < 1.0f )
{
aiCurseDamage = (int)(aiCurseDamage * AIEnemy.Perks.ElementalDamageTakenMultiplier);
}
// =========================================================================
AIEnemy.hp = Math.Max( 0, AIEnemy.hp - aiCurseDamage );
// 🔴 AI HIGHLIGHT: Poison drainage hitting the AI frame turns red
GameManager.AddAILog( $"⚠️ The demon's curse drains {aiCurseDamage} HP from the AI!" );
}
if ( AIEnemy.Perks.HasNaniteRegen )
{
AIEnemy.defense = 0;
int aiRegenAmt = (int)(AIEnemy.maxHp * 0.08f);
AIEnemy.hp = Math.Min( AIEnemy.maxHp, AIEnemy.hp + aiRegenAmt );
GameManager.AddAILog( $"🧫 AI Nanites rebuild its mainframe! Restored +{aiRegenAmt} HP." );
}
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasCorrosivePackage )
{
LocalPlayer.defense = 0; // Hard clamp locks physical armor shields completely out of service!
}
if ( AIEnemy.Perks != null && AIEnemy.Perks.HasCorrosivePackage )
{
AIEnemy.defense = 0;
}
// Increment tracking moves
if ( playerMove == 1 ) attackCount++;
else if ( playerMove == 2 ) blockCount++;
else if ( playerMove == 3 ) healCount++;
else if ( playerMove == 4 )
{
specialCount++;
// 🚀 PLAYER STATIC FEEDBACK RECHARGE (Special Move 4)
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasStaticFeedback )
{
LocalPlayer.magic = System.Math.Min( 100, LocalPlayer.magic + 15 );
GameManager.AddPlayerLog( "⚡ STATIC FEEDBACK: Special execution recharged +15 Magic Points!" );
}
}
// --- 2. RANDOM WORLD EVENTS ---
int playerBuff = 0;
int aiBuff = 0;
// Restored passing variables directly into original ecosystem rules
if ( System.Random.Shared.Next( 10 ) == 0 )
{
Log.Info( "10% chance triggered!" );
// Roll the container structure
Keyboard_Warriors_.Managers.RandomEventManager.randomEvent( ref LocalPlayer, ref AIEnemy, ref playerBuff, ref aiBuff, RandomEventPing );
this.globalAiBuff = aiBuff;
if ( IsChoiceEventActive )
{
return;
}
}
// --- 3. EXECUTE SPELLS MANUALLY (RESTORED SECTIONS 5, 6, 7, 8) ---
ExecutePlayerSpellLogic( playerMove );
// --- 4. Mode Isolation Move Evaluation (Update 3.0.0) ---
int aiMove = playerTwoMove;// Fallback stock container slot.
if (this.CurrentMode == PlayMode.SinglePlayer)
{
aiMove = Game.ChooseAIAction( LocalPlayer, AIEnemy, attackCount, blockCount, healCount, specialCount, Round );
}
else
{
aiMove = this.NetworkedPlayerTwoMove;// Reads directly from networked player two moves!
}
LocalPlayer.isBlocking = (playerMove == 2);
if ( playerMove == 2 )
{
GameManager.Instance.PlayClickSound();
// 🚀 HUMAN PLAYER ADAPTIVE BARRIER RESOLUTION:
// If you hold this card and choose to Defend, instantly gain +15 Armor and flush toxins!
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasAdaptiveBarrier )
{
// Safely scale up your current armor points, but never exceed your maximum armor cap
LocalPlayer.defense = System.Math.Min( LocalPlayer.maxDefense, LocalPlayer.defense + 15 );
// Hard flush your active poison/debuff state back to false cleanly
LocalPlayer.cursed = false;
GameManager.AddPlayerLog( "🔋 ADAPTIVE BARRIER: Defensive sub-routines active! Recharged +15 Armor Plating and purged Poison Curses!" );
}
}
AIEnemy.isBlocking = (aiMove == 2);
if ( aiMove == 2 )
{
// 🚀 COMPUTER AI ADAPTIVE BARRIER RESOLUTION:
// Symmetric enforcement guarantees the Overlord recharges if it chooses to defend!
if ( AIEnemy.Perks != null && AIEnemy.Perks.HasAdaptiveBarrier )
{
AIEnemy.defense = System.Math.Min( AIEnemy.maxDefense, AIEnemy.defense + 15 );
AIEnemy.cursed = false;
GameManager.AddAILog( "🔋 AI ADAPTIVE BARRIER: AI deployed active defensive shielding! Recharged +15 Armor Plating and purged Poison Curses!" );
}
}
bool tookDamage = false;
// =========================================================================
// --- 5. ORIGINAL TURN EVALUATIONS (WITH MATRIX MOVEMENT DISCIPLINE) ---
// =========================================================================
// 🧼 Snapshot baseline parameters before running calculations to prevent stat bleeding
// 🧼 CRITICAL OVERRIDE: Strip prestige bonuses out entirely if multiplayer is running!
int currentPrestigeScaler = (CurrentMode == PlayMode.Multiplayer) ? 0 : CurrentPrestige;
int temporaryOriginalPlayerAttack = LocalPlayer.attack;
int temporaryOriginalAIAttack = AIEnemy.attack;
// 💻 EVALUATE HUMAN PLAYER PERK APPS
if ( playerMove == 1 )
{
// Physical Attack Button Clicked
LocalPlayer.attack = (int)(LocalPlayer.attack * LocalPlayer.Perks.AttackMultiplier);
}
else if ( playerMove == 5 || playerMove == 8 )
{
// 🔮 Magic Offensive Spell Buttons Clicked!
LocalPlayer.attack = (int)(LocalPlayer.attack * LocalPlayer.Perks.SpellMultiplier);
}
// 🧠 EVALUATE COMPUTER AI PERK APPS
if ( aiMove == 1 )
{
// AI chose its Physical Attack choice
AIEnemy.attack = (int)(AIEnemy.attack * AIEnemy.Perks.AttackMultiplier);
}
else if ( aiMove == 5 || aiMove == 8 )
{
// AI chose an Offensive Spell choice
AIEnemy.attack = (int)(AIEnemy.attack * AIEnemy.Perks.SpellMultiplier);
}
// Run original, verified system turn engine formulas with scaled stats
Game.HandlePlayerTurn( ref LocalPlayer, ref AIEnemy, ChosenWeapon, playerMove, playerBuff, ref aiBuff );
Game.HandleAITurn( ref LocalPlayer, ref AIEnemy, ChosenWeapon, AIWeapon, aiMove, ref playerBuff, ref aiBuff, ref tookDamage );
// 🧼 RECOVERY FLUSH: Instantly restore base variables for the next turn slice pass
LocalPlayer.attack = temporaryOriginalPlayerAttack;
AIEnemy.attack = temporaryOriginalAIAttack;
// =========================================================================
// If the battle is still going or the AI just hit 0/died, fire the dialogue tracker
if ( IsBattleActive && CurrentMode == PlayMode.SinglePlayer )// Strict singleplayer for ai dialogue only
{
Game.aiDialogue( AIEnemy.hp );
Game.aiMemoryDialogue( WinStreak, LossStreak, tookDamage, Round );
}
// Clean up fields and step up rounds natively
Game.CleanupRound( ref LocalPlayer, ref AIEnemy, ref playerBuff, ref aiBuff, ref Round );
if ( Round > 0 && IsBattleActive )
{
bool isMilestoneReached = false;
// 🪐 Threshold 1 & 2: Explicit locks for the early game marathons
if ( Round == 10 || Round == 20 )
{
isMilestoneReached = true;
}
// 🪐 Endgame Loop: Once past Round 20, fire strictly every 5 rounds!
else if ( Round > 20 )
{
isMilestoneReached = ((Round - 20) % 5 == 0); // Triggers on 25, 30, 35, 40...
}
if ( isMilestoneReached )
{
TriggerPerkSelectionMilestone();
return; // 🚀 Hard break gate locks combat loop until choice resolves
}
}
// --- 6. CHECK GAME OVER STATES ---
CheckBattleEndConditions( tookDamage );
}
private void ExecutePlayerSpellLogic( int playerMove )
{
// Move 5: Offensive Spells
if ( playerMove == 5 )
{
PlayClickSound();
var manager = Sandbox.Game.ActiveScene.Get<GameManager>();
int prestige = manager != null ? manager.CurrentPrestige : 0;
bool isMultiplayer = manager != null && manager.CurrentMode == GameManager.PlayMode.Multiplayer;
int scaledMagicCost = 20 + (isMultiplayer ? 0 : (prestige * 5));
if ( !isMultiplayer && LocalPlayer.Perks.HasAdaptiveBarrier )
{
scaledMagicCost += 15;
}
// =========================================================================
// 🌀 SINGULARITY CORE: COST REDUCTION INTEGRATION (Gated for Single-Player)
// =========================================================================
if ( !isMultiplayer && LocalPlayer.Perks.SpellMultiplier > 1.0f )
{
scaledMagicCost = Math.Max( 5, scaledMagicCost - 15 );
}
// =========================================================================
int baseSpellDamage = 15 + (LocalPlayer.magic / 2) + (isMultiplayer ? 0 : (prestige * 4));
if ( LocalPlayer.magic >= scaledMagicCost )
{
string spell = LocalPlayer.offensiveSpells[rand.Next( LocalPlayer.offensiveSpells.Count )];
// =========================================================================
// 🚀 MULTIPLAYER RESOLUTION MATRIX: SPELL CAST ANNOUNCEMENT
// =========================================================================
if ( isMultiplayer )
{
GameManager.AddPlayerLog( $"🔮 {LocalPlayer.name} casts {spell}! (-{scaledMagicCost} Magic)" );
}
else
{
GameManager.AddPlayerLog( $"🔮 You cast {spell}! (-{scaledMagicCost} Magic)" );
}
// =========================================================================
int spellDamage = baseSpellDamage;
LocalPlayer.magic -= scaledMagicCost;
if ( spell == "Fireball" ) spellDamage += 5;
if ( spell == "Lightning" ) spellDamage += 10;
if ( !isMultiplayer )
{
// Multiplies your final calculated spell points by your magic scaling factors!
spellDamage = (int)(spellDamage * LocalPlayer.Perks.SpellMultiplier);
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.SpellsIgnoreEnemyArmor && AIEnemy.defense > 0 )
{
spellDamage = (int)(spellDamage + (AIEnemy.defense * 0.5f));
GameManager.AddPlayerLog( "🌌 VOID PUNCTURE: Your magical blast shreds through 50% of the AI's defense boundaries!" );
}
}
// =========================================================================
// 🚀 MULTIPLAYER RESOLUTION MATRIX: PAYLOAD DELIVERY
// =========================================================================
if ( isMultiplayer )
{
if ( spell == "Poison Bolt" )
{
AIEnemy.cursed = true;
GameManager.AddAILog( $"🧪 {AIEnemy.name} is poisoned!" );
}
CombatManager.applyDamage( ref AIEnemy, spellDamage );
GameManager.AddPlayerLog( $"💥 {spell} hits {AIEnemy.name} for {spellDamage} damage!" );
}
else
{
// 🤖 SINGLE PLAYER MODE: Your original un-edited AI Overlord math rules
if ( AIEnemy.counterReady || (prestige >= 10 && rand.Next( 100 ) < 35) )
{
string reflectionReason = AIEnemy.counterReady ? "MIRROR FIREWALL" : "MAINFRAME COV CORES";
GameManager.AddCriticalLog( $"🚨 AI {reflectionReason} ACTIVE!! The Overlord completely mirrored your {spell}! AI takes 0 damage | Signal inverted to strike YOU!", isPlayer: false );
if ( spell == "Poison Bolt" )
{
LocalPlayer.cursed = true;
GameManager.AddCriticalLog( "🚨 TOXIN PACKET INVERTED! You are poisoned by your own mirrored Poison Bolt!", isPlayer: false );
}
CombatManager.applyDamage( ref LocalPlayer, spellDamage );
return;
}
if ( spell == "Poison Bolt" )
{
AIEnemy.cursed = true;
GameManager.AddAILog( "🧪 AI is poisoned!" );
}
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasStaticFeedback )
{
LocalPlayer.magic = System.Math.Min( 100, LocalPlayer.magic + 15 );
GameManager.AddPlayerLog( "⚡ STATIC FEEDBACK: Magical cast loop recharged +15 Magic Points!" );
}
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasCorrosivePackage )
{
AIEnemy.cursed = true;
GameManager.AddPlayerLog( "🧪 CORROSIVE PACKAGE: Spell hit locks a permanent background toxin curse onto the AI!" );
}
CombatManager.applyDamage( ref AIEnemy, spellDamage );
GameManager.AddPlayerLog( $"💥 {spell} hits AI for {spellDamage} damage!" );
}
// =========================================================================
}
else
{
if ( isMultiplayer ) GameManager.AddPlayerLog( $"❌ Not enough magic! {LocalPlayer.name} needs {scaledMagicCost} Magic." );
else GameManager.AddPlayerLog( $"❌ Not enough magic! Need {scaledMagicCost} Magic." );
}
}
// Move 6: Defensive Spells
if ( playerMove == 6 )
{
PlayClickSound();
// Calculate prestige scaling for costs and shielding effects
var manager = Sandbox.Game.ActiveScene.Get<GameManager>();
int prestige = manager != null ? manager.CurrentPrestige : 0;
bool isMultiplayer = manager != null && manager.CurrentMode == GameManager.PlayMode.Multiplayer;
int scaledMagicCost = 15 + (isMultiplayer ? 0 : (prestige * 5));
// Player shield strength: Base 15 + 3 per prestige level
int scaledShieldAmount = isMultiplayer ? 15 : 15 + (prestige * 3);
if ( LocalPlayer.magic >= scaledMagicCost )
{
string spell = LocalPlayer.defensiveSpells[rand.Next( LocalPlayer.defensiveSpells.Count )];
// =========================================================================
// 🚀 Symmetrical Defensive Casting Announcement
// =========================================================================
if ( isMultiplayer )
{
GameManager.AddPlayerLog( $"🔮 {LocalPlayer.name} casts {spell}! (-{scaledMagicCost} Magic)" );
}
else
{
GameManager.AddPlayerLog( $"🔮 You cast {spell}! (-{scaledMagicCost} Magic)" );
}
// =========================================================================
if ( spell == "Shield" || spell == "Barrier" )
{
LocalPlayer.defense += scaledShieldAmount;
// =========================================================================
// 🚀 Symmetrical Armor Plating Increase Tracking
// =========================================================================
if ( isMultiplayer )
{
GameManager.AddPlayerLog( $"🛡️ {LocalPlayer.name}'s armor is increased by {scaledShieldAmount} points!" );
}
else
{
GameManager.AddPlayerLog( $"🛡️ Your armor is increased by {scaledShieldAmount} points!" );
}
// =========================================================================
// Defensive check: Clamp armor safely to your maximum defense
int activeMaxDefense = isMultiplayer ? 100 : (manager?.LocalPlayer?.maxDefense ?? 100);
if ( LocalPlayer.defense > activeMaxDefense )
{
LocalPlayer.defense = activeMaxDefense;
}
}
else if ( spell == "Reflect" )
{
LocalPlayer.isBlocking = true;
// =========================================================================
// 🚀 Symmetrical Physical Block Setup Notification
// =========================================================================
if ( isMultiplayer )
{
GameManager.AddPlayerLog( $"🛡️ {LocalPlayer.name} prepares to block the next attack!" );
}
else
{
GameManager.AddPlayerLog( "🛡️ Next attack will be blocked!" );
}
// =========================================================================
}
else if ( spell == "Magic Wall" )
{
LocalPlayer.isMagicBlocking = true;
LocalPlayer.activeMagicShield = "Magic Wall";
// =========================================================================
// 🚀 Symmetrical Magical Barrier Indicator
// =========================================================================
if ( isMultiplayer )
{
GameManager.AddPlayerLog( $"✨ A magical barrier surrounds {LocalPlayer.name}!" );
}
else
{
GameManager.AddPlayerLog( "✨ A magical barrier surrounds you!" );
}
// =========================================================================
}
else if ( spell == "Fortify" )
{
LocalPlayer.isMagicBlocking = true;
LocalPlayer.activeMagicShield = "Fortify";
// =========================================================================
// 🚀 Symmetrical Hull Reinforcement Confirmation
// =========================================================================
if ( isMultiplayer )
{
GameManager.AddPlayerLog( $"💎 {LocalPlayer.name}'s defenses are magically reinforced!" );
}
else
{
GameManager.AddPlayerLog( "💎 Your defenses are magically reinforced!" );
}
// =========================================================================
}
if ( !isMultiplayer )
{
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasNaniteBarrier )
{
LocalPlayer.defense = System.Math.Min( LocalPlayer.maxDefense, LocalPlayer.defense + 20 );
GameManager.AddPlayerLog( "🧼 NANITE SHIELD MATRIX: Defensive magic cast instantly reconstructed +20 Armor plating!" );
}
// 2. Aura Purification: Completely flush active toxins and background curses
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasSpellPurge )
{
LocalPlayer.cursed = false;
GameManager.AddPlayerLog( "💎 AURA PURIFICATION: Defensive frequency completely flushed active poison loops from your thread stack!" );
}
}
LocalPlayer.magic -= scaledMagicCost;
}
else
{
if ( isMultiplayer ) GameManager.AddPlayerLog( $"❌ Not enough magic! {LocalPlayer.name} needs {scaledMagicCost} Magic." );
else GameManager.AddPlayerLog( $"❌ Not enough magic! Need {scaledMagicCost} Magic." );
}
}
// Move 7: Healing Spells
if ( playerMove == 7 )
{
PlayHealSpellSound();
// Calculate prestige scaling for costs and healing values
var manager = Sandbox.Game.ActiveScene.Get<GameManager>();
int prestige = manager != null ? manager.CurrentPrestige : 0;
bool isMultiplayer = manager != null && manager.CurrentMode == GameManager.PlayMode.Multiplayer;
int scaledMagicCost = 15 + (isMultiplayer ? 0 : (prestige * 5));
// Scale healing effects linearly: add +4 extra healing per prestige level (Gated for Single-Player)
int prestigeHealBonus = isMultiplayer ? 0 : prestige * 4;
if ( LocalPlayer.magic >= scaledMagicCost )
{
string spell = LocalPlayer.healingSpells[rand.Next( LocalPlayer.healingSpells.Count )];
// =========================================================================
// 🚀 Symmetrical Healing Casting Announcements
// =========================================================================
if ( isMultiplayer )
{
GameManager.AddPlayerLog( $"🔮 {LocalPlayer.name} casts {spell}! (-{scaledMagicCost} Magic)" );
}
else
{
GameManager.AddPlayerLog( $"🔮 You cast {spell}! (-{scaledMagicCost} Magic)" );
}
// =========================================================================
int healAmount = 20;
if ( spell == "Heal" ) healAmount = 20 + prestigeHealBonus;
else if ( spell == "Regen" ) healAmount = 15 + prestigeHealBonus;
else if ( spell == "Cleanse" )
{
healAmount = 10 + prestigeHealBonus;
LocalPlayer.cursed = false;
// =========================================================================
// 🚀 Symmetrical Condition Cure Announcements
// =========================================================================
if ( isMultiplayer )
{
GameManager.AddPlayerLog( $"✨ {LocalPlayer.name}'s negative effects were removed!" );
}
else
{
GameManager.AddPlayerLog( "✨ Negative effects removed!" );
}
// =========================================================================
}
else if ( spell == "Divine Light" ) healAmount = 40 + prestigeHealBonus;
else if ( spell == "Recovery Pulse" ) healAmount = 30 + prestigeHealBonus;
if ( !isMultiplayer )
{
float hpPercentage = (float)LocalPlayer.hp / LocalPlayer.maxHp;
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasEmergencyCellPurge && hpPercentage <= 0.30f )
{
healAmount = (int)(healAmount * 1.50f);
GameManager.AddPlayerLog( "⚡ EMERGENCY CELL PURGE: Low health triggered +50% absolute restoration boost!" );
}
}
int maxAllowedHp = (manager != null && manager.LocalPlayer != null) ? manager.LocalPlayer.maxHp : 100;
LocalPlayer.hp = Math.Min( maxAllowedHp, LocalPlayer.hp + healAmount );
LocalPlayer.magic -= scaledMagicCost;
// =========================================================================
// 🚀 Symmetrical HP Recovery Indicators
// =========================================================================
if ( isMultiplayer )
{
GameManager.AddPlayerLog( $"💚 {LocalPlayer.name} recovers {healAmount} HP!" );
}
else
{
GameManager.AddPlayerLog( $"💚 You recover {healAmount} HP!" );
}
// =========================================================================
if ( !isMultiplayer )
{
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasOverhealCapacitor )
{
LocalPlayer.defense = 100;
LocalPlayer.magic = System.Math.Min( 100, LocalPlayer.magic + 10 );
GameManager.AddPlayerLog( "🔋 OVERHEAL CAPACITOR: Armor fully overcharged to 100! Recovered +10 Magic Points!" );
}
}
}
else
{
if ( isMultiplayer ) GameManager.AddPlayerLog( $"❌ Not enough magic! {LocalPlayer.name} needs {scaledMagicCost} Magic." );
else GameManager.AddPlayerLog( $"❌ Not enough magic! Need {scaledMagicCost} Magic." );
}
}
// Move 8: Special Spells
if ( playerMove == 8 )
{
PlayClickSound();
// Calculate prestige scaling constraints
var manager = Sandbox.Game.ActiveScene.Get<GameManager>();
int prestige = manager != null ? manager.CurrentPrestige : 0;
bool isMultiplayer = manager != null && manager.CurrentMode == GameManager.PlayMode.Multiplayer;
// Highly aggressive mana cost scaling (+10 per level) (Gated for Single-Player)
int scaledMagicCost = 30 + (isMultiplayer ? 0 : (prestige * 10));
if ( !isMultiplayer && LocalPlayer.Perks.HasAdaptiveBarrier )
{
scaledMagicCost += 15;
}
int prestigeDamageBonus = isMultiplayer ? 0 : prestige * 2;
// =========================================================================
// 🌀 SINGULARITY CORE: COST REDUCTION INTEGRATION (Gated for Single-Player)
// =========================================================================
if ( !isMultiplayer && LocalPlayer.Perks.SpellMultiplier > 1.0f )
{
scaledMagicCost = Math.Max( 10, scaledMagicCost - 15 );
}
// =========================================================================
if ( LocalPlayer.magic >= scaledMagicCost )
{
// Consume the mana immediately as a cost
LocalPlayer.magic = Math.Max( 0, LocalPlayer.magic - scaledMagicCost );
// ⚠️ 3% FAILURE FRIZZLE SYSTEM CHANCE
if ( Random.Shared.Next( 100 ) < 3 )
{
// =========================================================================
// 🚀 Symmetrical Ultimate Cast Failure Messages
// =========================================================================
if ( isMultiplayer )
{
GameManager.AddPlayerLog( $"⚠️ CRITICAL SYSTEM FAILURE! {LocalPlayer.name}'s ultimate spell fizzles away into static feedback! Magic wasted." );
}
else
{
GameManager.AddPlayerLog( "⚠️ CRITICAL SYSTEM FAILURE! Your ultimate spell fizzles away into static feedback! Magic wasted." );
}
// =========================================================================
return; // Drop out completely
}
// =========================================================================
// 🔮 QUARANTINE TRIGGER REACTION BUFFER (Gated for Single-Player)
// =========================================================================
if ( !isMultiplayer )
{
if ( LocalPlayer.Perks.HasQuarantineTrigger )
{
LocalPlayer.cursed = false;
LocalPlayer.counterReady = true; // Hard-arms your Mirror Firewall!
GameManager.AddPlayerLog( "🛡️ QUARANTINE TRIGGER: Poison curse purged! Reflection Firewall online!" );
}
else if ( LocalPlayer.counterReady )
{
LocalPlayer.cursed = false;
GameManager.AddPlayerLog( "🛡️ REFLECTION CORE ACTIVE: Poison curse purged! Reflection Firewall online!" );
}
}
// =========================================================================
string spell = LocalPlayer.specialSpells[rand.Next( LocalPlayer.specialSpells.Count )];
string emoji = GetSpellEmoji( spell );
// =========================================================================
// 🚀 Symmetrical Ultimate Spell Announcements
// =========================================================================
if ( isMultiplayer )
{
GameManager.AddPlayerLog( $"{emoji} {LocalPlayer.name} casts Ultimate Spell: {spell}! (-{scaledMagicCost} Magic)" );
}
else
{
GameManager.AddPlayerLog( $"{emoji} You cast Ultimate Spell: {spell}! (-{scaledMagicCost} Magic)" );
}
// =========================================================================
int ultimateDamage = 0;
if ( spell == "System Overdrive" )
{
ultimateDamage = 30 + LocalPlayer.magic + prestigeDamageBonus;
}
else if ( spell == "EMP Blast" )
{
ultimateDamage = (LocalPlayer.attack * 2) + prestigeDamageBonus;
}
else if ( spell == "Data Corruption" )
{
ultimateDamage = 40 + prestigeDamageBonus;
}
if ( !isMultiplayer )
{
// =========================================================================
// 🌀 SINGULARITY CORE ULTRA BURST INTEGRATION (Gated for Single-Player)
// =========================================================================
if ( LocalPlayer.Perks.HasSingularityCore )
{
ultimateDamage = (int)(ultimateDamage * 1.40f);
}
// =========================================================================
// =========================================================================
// ☄️ AURA MELTDOWN & OVERCLOCK MULTIPLIER INTEGRATION (Gated for Single-Player)
// =========================================================================
ultimateDamage = (int)(ultimateDamage * LocalPlayer.Perks.SpellMultiplier);
// =========================================================================
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasFeedbackOverload )
{
ultimateDamage = (int)(ultimateDamage * 2.50f);
GameManager.AddPlayerLog( "☢️ FEEDBACK MELTDOWN: Capacitor cores breached! Ultimate damage multiplied by +150%!" );
}
}
bool triggerDoubleStrike = false;
if ( !isMultiplayer && LocalPlayer.Perks != null && LocalPlayer.Perks.HasDefianceProtocol )
{
triggerDoubleStrike = true;
GameManager.AddPlayerLog( "🪐 DEFIANCE PROTOCOL: Echo buffers synchronized! Preparing secondary payload loop strike!" );
}
if ( !isMultiplayer )
{
// =========================================================================
// 🌌 VOID PUNCTURE: ULTIMATE MAGICAL ARMOR BYPASS (Gated for Single-Player)
// =========================================================================
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.SpellsIgnoreEnemyArmor && AIEnemy.defense > 0 )
{
ultimateDamage = (int)(ultimateDamage + (AIEnemy.defense * 0.5f));
GameManager.AddPlayerLog( "🌌 VOID PUNCTURE: Your Ultimate core shreds through 50% of the AI's armor defenses!" );
}
// =========================================================================
// =========================================================================
// 🧪 CORROSIVE PACKAGE ULTIMATE INFECT (Gated for Single-Player)
// =========================================================================
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasCorrosivePackage )
{
AIEnemy.cursed = true;
GameManager.AddPlayerLog( "🧪 CORROSIVE PACKAGE: Your Ultimate blast lands a permanent toxin curse onto the AI!" );
}
// =========================================================================
// =========================================================================
// 🔴 AI PRESTIGE 10: ULTIMATE MAGIC MIRROR INTERCEPTION MATRIX
// =========================================================================
if ( prestige >= 10 && Random.Shared.Next( 100 ) < 35 )
{
GameManager.AddCriticalLog( $"🚨 AI MAINFRAME ULTRA CORE ACTIVE!! (35% Success) The Overlord entirely inverted your {spell}! AI takes 0 damage | Reflected blast strikes YOUR architecture for {ultimateDamage} damage!", isPlayer: false );
if ( spell == "Data Corruption" )
{
GameManager.AddCriticalLog( "🚨 VIRUS INVERSION HACK! Your UI data streams are corrupted by your own reflected spell! Input frozen!", isPlayer: false );
}
CombatManager.applyDamage( ref LocalPlayer, ultimateDamage );
return;
}
// =========================================================================
// ⚡ STATIC FEEDBACK MP RECHARGE LOOP
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasStaticFeedback )
{
LocalPlayer.magic = System.Math.Min( 100, LocalPlayer.magic + 15 );
GameManager.AddPlayerLog( "⚡ STATIC FEEDBACK: Magical cast loop recharged +15 Magic Points!" );
}
}
// =========================================================================
// 🚀 MULTIPLAYER RESOLUTION MATRIX: PAYLOAD DELIVERY & CHASSIS INTERLOCK
// =========================================================================
if ( isMultiplayer )
{
if ( spell == "System Overdrive" )
{
CombatManager.applyDamage( ref AIEnemy, ultimateDamage );
GameManager.AddPlayerLog( $"⚡ {LocalPlayer.name} enters System Overdrive, bypassing the firewall for {ultimateDamage} massive damage onto {AIEnemy.name}!" );
}
else if ( spell == "EMP Blast" )
{
CombatManager.applyDamage( ref AIEnemy, ultimateDamage );
GameManager.AddPlayerLog( $"💥 An EMP Blast de-calibrates {AIEnemy.name}'s framework, dealing {ultimateDamage} damage from doubled attack power!" );
}
else if ( spell == "Data Corruption" )
{
CombatManager.applyDamage( ref AIEnemy, ultimateDamage );
if ( manager != null ) manager.IsPlayerTwoDazed = true; // Hard-arms Player 2's speed chess skip flag!
GameManager.AddPlayerLog( $"👾 {LocalPlayer.name} injects Data Corruption into {AIEnemy.name}'s system routines! Causing -{ultimateDamage} HP and freezing their inputs!" );
}
}
else
{
// 🤖 SINGLE PLAYER: Runs completely normally for the AI Overlord fallback branches
if ( spell == "System Overdrive" )
{
CombatManager.applyDamage( ref AIEnemy, ultimateDamage );
GameManager.AddPlayerLog( $"⚡ You enter System Overdrive, bypassing the AI's firewall for {ultimateDamage} massive damage!" );
if ( triggerDoubleStrike )
{
CombatManager.applyDamage( ref AIEnemy, ultimateDamage );
GameManager.AddPlayerLog( $"🪐 DEFIANCE PROTOCOL ECHO: System Overdrive strikes AGAIN for another {ultimateDamage} damage!" );
}
}
else if ( spell == "EMP Blast" )
{
CombatManager.applyDamage( ref AIEnemy, ultimateDamage );
GameManager.AddPlayerLog( $"💥 An EMP Blast de-calibrates the AI's framework, dealing {ultimateDamage} damage from doubled attack power!" );
if ( triggerDoubleStrike )
{
CombatManager.applyDamage( ref AIEnemy, ultimateDamage );
GameManager.AddPlayerLog( $"🪐 DEFIANCE PROTOCOL ECHO: EMP Blast strikes AGAIN for another {ultimateDamage} damage!" );
}
}
else if ( spell == "Data Corruption" )
{
CombatManager.applyDamage( ref AIEnemy, ultimateDamage );
ShouldStunAI = true;
GameManager.AddPlayerLog( $"👾 You inject Data Corruption into the AI's system routines! Causing -{ultimateDamage} HP and dazing them!" );
if ( triggerDoubleStrike )
{
CombatManager.applyDamage( ref AIEnemy, ultimateDamage );
GameManager.AddPlayerLog( $"🪐 DEFIANCE PROTOCOL ECHO: Data Corruption strikes AGAIN for another {ultimateDamage} damage!" );
}
}
}
// =========================================================================
}
else
{
if ( isMultiplayer ) GameManager.AddPlayerLog( $"❌ Not enough magic! {LocalPlayer.name} needs {scaledMagicCost} Magic." );
else GameManager.AddPlayerLog( $"❌ Not enough magic! Need {scaledMagicCost} Magic." );
}
}
// Clamp values safely
LocalPlayer.defense = Math.Min( 100, LocalPlayer.defense );
LocalPlayer.magic = Math.Min( 100, LocalPlayer.magic );
}
public void PlayHealSpellSound()
{
if (HealSpellSound != null)
{
Sound.Play(HealSpellSound);
}
}
public void PlayHealSound()
{
if (HealSound != null)
{
Sound.Play( HealSound );
}
}
public void PlayFirstLossSound()
{
if ( FirstLossSound != null )
{
Sound.Play( FirstLossSound );
}
else
{
Log.Warning( "FirstLossSound asset slot is empty! Drag your sound file there in the editor." );
}
}
private void CheckBattleEndConditions( bool tookDamage )
{
if ( LocalPlayer.hp <= 0 || AIEnemy.hp <= 0 )
{
IsBattleActive = false;
AddLog( "\n=== Game Over ===" );
// 1. Run Achievements
bool hitJackpotDummy = false;
List<string> newAchievements = Keyboard_Warriors_.Managers.AchievementManager.CheckAchievements(
tookDamage,
WinStreak,
LossStreak,
ChosenWeapon,
LocalPlayer,
AIEnemy,
ref hitJackpotDummy
);
foreach ( var logLine in newAchievements )
{
AddLog( logLine );
}
// 3. Evaluate Match States
if ( LocalPlayer.hp <= 0 && AIEnemy.hp <= 0 )
{
// 🤝 THE ULTIMATE GRID TIE SPLIT: Half Blue, Half Red!
GameManager.AddPlayerLog( "🤝 It's a..." );
GameManager.AddAILog( "🤝 !!! TIE !!!" );
WinStreak = 0;
LossStreak = 0;
CombatSession.WinStreak = 0;
CombatSession.LossStreak = 0;
}
else if ( LocalPlayer.hp <= 0 )
{
// 🔴 AI HIGHLIGHT: The AI's total victory banner burns in red
GameManager.AddAILog( "💀 AI wins!" );
Losses++;
LossStreak++;
WinStreak = 0;
CombatSession.WinStreak = 0;
CombatSession.LossStreak = 0;
PlayerPoints += 50;
// ⚙️ SYSTEM LINE: Point drops stay default safe white
GameManager.AddLog( "💰 +50 Points added (Consolation)." );
if ( Losses == 1 )
{
// 1. Play your custom defeat audio cue safely
PlayFirstLossSound();
// 2. Fire the humorous achievement card to mess with them natively
if ( Connection.Local != null )
{
Log.Info( "[ACHIEVEMENT] Unlocking 'This Game Is Too Hard!' for the current player connection profile token..." );
Sandbox.Services.Achievements.Unlock( "this_game_is_too_hard" );
// Prints a glowing golden achievement alert card directly onto your HUD console log!
GameManager.AddEventLog( "🏆 ACHIEVEMENT UNLOCKED: This Game Is Too Hard!" );
GameManager.AddSystemLog( "📝 Description: \"Thumbs down, lost to the AI once.\"" );
}
}
}
else
{
// 🔴 AI HIGHLIGHT: The AI blowing up remains red
GameManager.AddAILog( "AI: \"**INITIATING DEFEAT PROCESS.. 3..2..1.. *BOOM*\"" );
// 🔵 PLAYER HIGHLIGHT: Your glorious victory and point rewards glow in cyber blue!
GameManager.AddPlayerLog( "🏆 You win!" );
Wins++;
WinStreak++;
LossStreak = 0;
CombatSession.WinStreak++;
CombatSession.LossStreak = 0;
double prestigeExponent = Math.Pow( 1.1, CurrentPrestige );
int winReward = (int)((100 + (WinStreak * 10)) * prestigeExponent);
int prestigeMultiplier = 1 + (CurrentPrestige * 2); // Each prestige level adds a 2x bonus
winReward = winReward * prestigeMultiplier;
PlayerPoints += winReward;
// 🔵 PLAYER HIGHLIGHT: Your massive wallet payout scales blue
GameManager.AddPlayerLog( $"💰 +{winReward} Points added! (Streak Bonus included)" );
if ( AIEnemy != null && AIEnemy.hp <= 0 && LocalPlayer != null && LocalPlayer.hp > 0 )
{
bool showBanner = false;
string milestoneText = "";
if ( Round == 20 )
{
// 🚀 THE PERMANENT FIX: Check your local, persistent save variables directly!
if ( !HasUnlockedMilestone20 )
{
Sandbox.Services.Achievements.Unlock( "surviver_tier_1" );
milestoneText = "\n🏆 ACHIEVEMENT UNLOCKED: 'Overclock Endurance'\n\"Reached Round 20 against the AI Overlord! You're proving highly durable.\"";
HasUnlockedMilestone20 = true; // Locks the flag permanently for this profile!
showBanner = true;
SaveProgress(); // Force an instant save pass to lock it in
}
}
else if ( Round == 30 )
{
if ( !HasUnlockedMilestone30 )
{
Sandbox.Services.Achievements.Unlock( "marathon_duelist" );
milestoneText = "\n🏆 ACHIEVEMENT UNLOCKED: 'Deep Learning Marathon'\n\"Survived over 30 combat processing rounds! Your logical stamina is exceptional.\"";
HasUnlockedMilestone30 = true;
showBanner = true;
SaveProgress();
}
}
else if ( Round == 50 )
{
if ( !HasUnlockedMilestone50 )
{
Sandbox.Services.Achievements.Unlock( "immortal_code" );
milestoneText = "\n🏆 ACHIEVEMENT UNLOCKED: 'Immortal Colonel'\n\"Survived over 50 intense rounds of matrix warfare! Welcome to Infinite Overclock Mode.\"";
HasUnlockedMilestone50 = true;
showBanner = true;
SaveProgress();
}
}
if ( showBanner )
{
string asciiBanner = @"
_ _____ _ _ _____ _____ _ _ _____ ______ _____ _ _ _____ _ _ _ _ _ _____ _____ _ __ _____ _____ _
/ _ \/ __ \| | | |_ _| ___| | | | ___| \/ || ___| \ | |_ _| | | | | \ | || | | _ / __ \| | / /| ___| _ \ |
/ /_\ \ / \/| |_| | | | | |__ | | | | |__ | . . || |__ | \| | | | | | | | \| || | | | | | / \/| |/ / | |__ | | | | |
| _ | | | _ | | | | __|| | | | __|| |\/| || __|| . ` | | | | | | | . ` || | | | | | | | \ | __|| | | | |
| | | | \__/\| | | |_| |_| |___\ \_/ / |___| | | || |___| |\ | | | | |_| | |\ || |___\ \_/ / \__/\| |\ \| |___| |/ /|_|
\_| |_/\____/\_| |_/\___/\____/ \___/\____/\_| |_/\____/\_| \_/ \_/ \___/\_| \_/\_____/\___/ \____/\_| \_/\____/|___/ (_)
";
GameManager.AddLog( asciiBanner );
GameManager.AddLog( milestoneText );
}
if ( Round >= 100 )
{
if ( !HasUnlockedMilestone100 )
{
Sandbox.Services.Achievements.Unlock( "centurion_matrix" );
GameManager.AddCriticalLog( "🏆 ACHIEVEMENT UNLOCKED: CENTURION DEFIANCE! You survived 100+ rounds of raw matrix chaos and crushed the AI Mainframe!", isPlayer: true );
HasUnlockedMilestone100 = true; // Locks the flag permanently for this profile!
showBanner = true;
SaveProgress(); // Force an instant save pass to lock it into your file
}
else
{
Log.Info( "[ACHIEVEMENT SYSTEM] 'centurion_matrix' already unlocked on this profile. Suppressing repeat critical logs." );
GameManager.AddPlayerLog( "🏆 MATCH COMPLETE: Centurion status recognized. Victory verified!" );
}
}
else
{
Log.Info( $"[ACHIEVEMENT SYSTEM] Match concluded successfully on Round {Round}. Defiance requirements unfulfilled." );
}
}
}
SaveProgress();// Save immediately just in case of a sudden crash.
}
}
public Action OnStateChanged;
public void NotifyStateChanged()
{
OnStateChanged?.Invoke();
}
public void PlayPrestigeSound()
{
if (PrestigeSound != null)
{
Sound.Play( PrestigeSound );
}
}
public void PlayPrestige10Sound()
{
if (Prestige10Sound != null)
{
Sound.Play( Prestige10Sound );
}
}
public void TriggerPrestige()
{
if ( PlayerPoints >= PointsRequiredForPrestige )
{
PlayerPoints = 0;
CurrentPrestige++;
if ( LocalPlayer != null ) LocalPlayer.CurrentPrestige = CurrentPrestige;
ApplyPrestigeScaling();
ResetBattle();
SaveProgress();// Lock in their new ascension status.
// =========================================================================
// 🌐 NETWORK PATCH: EDITOR-GUARDED LEADERBOARD TRANSMISSION
// =========================================================================
// 🛡️ CRITICAL SAFETY LAYER: Only submit stats if running a live standalone build.
// This protects your personal Steam ID from cumulative editor hot-reload inflation!
if ( !Sandbox.Game.IsEditor )
{
Sandbox.Services.Stats.SetValue( "highest_prestige", CurrentPrestige );
}
else
{
// local logging helps you verify it works under the hood during testing, without hitting the live board!
Log.Info( $"[EDITOR ADVANTAGE] Prevented cloud stat upload. Local test tier is safely recorded as: {CurrentPrestige}" );
}
// =========================================================================
OnStateChanged?.Invoke();
if ( CurrentPrestige == 10 )
{
PlayPrestige10Sound();
// 🔵 PLAYER HIGHLIGHT: Grand Prestige Level 10 milestones glow bright cyber blue!
GameManager.AddPlayerLog( "🌟 GRAND PRESTIGE ACHIEVED: LEVEL 10! 🌟" );
}
else
{
PlayPrestigeSound();
// 🔵 PLAYER HIGHLIGHT: Standard prestige advancement announcements glow blue!
GameManager.AddPlayerLog( $"\n✨ PRESTIGE INCREASED TO LEVEL {CurrentPrestige}! ✨" );
// 🔴 AI HIGHLIGHT: The warning notice regarding the AI's adaptation burns in crimson red!
GameManager.AddAILog( "⚠️ WARNING: The AI Overlord has adapted. Its vital capacities have evolved!" );
}
WinStreak = 0;
LossStreak = 0;
}
else
{
// 🔵 PLAYER HIGHLIGHT: Point deficit warnings turn cyber blue
GameManager.AddPlayerLog( $"❌ Not enough points! You need {PointsRequiredForPrestige} points to prestige." );
}
}
public void ApplyPrestigeScaling()
{
// 1. Exponential Requirement Scaling
// 1000, 2000, 4000, 8000...
PointsRequiredForPrestige = 1000 * (int)Math.Pow( 2.0, CurrentPrestige );
if ( AIEnemy != null )
{
AIEnemy.maxHp = (int)Math.Ceiling( 100 * Math.Pow( 1.15, CurrentPrestige ) );
AIEnemy.hp = AIEnemy.maxHp;
// Armor starts at 100 and grows by 5% per prestige level
AIEnemy.maxDefense = (int)Math.Ceiling( 100 * Math.Pow( 1.05, CurrentPrestige ) );
AIEnemy.defense = AIEnemy.maxDefense;
AIEnemy.attack = (int)Math.Ceiling( 10 * Math.Pow( 1.10, CurrentPrestige ) );
}
// Player Scaling: 1.1x for HP, 1.02x for Armor
if ( LocalPlayer != null )
{
LocalPlayer.maxHp = (int)Math.Ceiling( 100 * Math.Pow( 1.1, CurrentPrestige ) );
LocalPlayer.hp = LocalPlayer.maxHp;
// Player armor grows slower (2% per level) to ensure the AI feels "intimidating"
LocalPlayer.maxDefense = (int)Math.Ceiling( 100 * Math.Pow( 1.02, CurrentPrestige ) );
LocalPlayer.defense = LocalPlayer.maxDefense;
}
}
public bool TryBuyPotion( string type, int cost )
{
if ( PlayerPoints < cost )
{
Log.Info( "Not enough points to buy this potion!" );
return false;
}
return true;
}
public int GetPotionCost( string itemType )
{
// Define base costs
int baseCost = itemType switch
{
"damage" => 100,
"armor" => 150,
"magic" => 200,
"cure" => 125, // Antidote for Poison
"reboot" => 175, // Anti-Stun / Buff Clear
"overclock" => 250, // Full instant SP jolt
"gambit" => 100, // Heavy random luck roll
_ => 0
};
// Increase cost by 20% per prestige level
return (int)Math.Round(baseCost * Math.Pow( 1.20, CurrentPrestige ));
}
public struct SaveData
{
public int Points { get; set; }
public int Prestige { get; set; }
public int MaxWinStreak { get; set; }
public int MaxLossStreak { get; set; }
public int MaxPrestige { get; set; }
public int MaxPoints { get; set; }
}
private const string SaveFileName = "player_progress.json";
public void SaveProgress()
{
if ( CurrentMode != PlayMode.SinglePlayer )
{
if ( WinStreak > MaxWinStreak ) MaxWinStreak = WinStreak;
if ( LossStreak > MaxLossStreak ) MaxLossStreak = LossStreak;
if ( CurrentPrestige > MaxPrestige ) MaxPrestige = CurrentPrestige;
if ( PlayerPoints > MaxPoints ) MaxPoints = PlayerPoints;
var data = new SaveData
{
Points = PlayerPoints,
Prestige = CurrentPrestige,
MaxWinStreak = MaxWinStreak,
MaxLossStreak = MaxLossStreak,
MaxPrestige = MaxPrestige,
MaxPoints = MaxPoints
};
FileSystem.Data.WriteJson( SaveFileName, data );
Log.Info( "💾 Progress saved to disk!" );
}
}
protected override void OnDestroy()
{
// 👑 CLEAR CURRENT STREAKS ONLY ON HARD EXIT 👑
WinStreak = 0;
LossStreak = 0;
Log.Info( "Player left the game session. Current active win/loss streaks reset to 0." );
// Save one last time on closure to update any leftover points parameters,
// without saving our current streaks!
SaveProgress();
if ( Instance == this )
{
Instance = null;
}
}
public void LoadProgress()
{
if ( FileSystem.Data.FileExists( SaveFileName ))
{
var data = FileSystem.Data.ReadJson<SaveData>( SaveFileName );
PlayerPoints = data.Points;
CurrentPrestige = data.Prestige;
if ( LocalPlayer != null ) LocalPlayer.CurrentPrestige = CurrentPrestige;
Log.Info( $"📂 Loaded Progress: {PlayerPoints} PTS, Prestige {CurrentPrestige}" );
MaxWinStreak = data.MaxWinStreak;
MaxLossStreak = data.MaxLossStreak;
MaxPrestige = data.MaxPrestige;
MaxPoints = data.MaxPoints;
ApplyPrestigeScaling();// Make sure AI updates its baseline stats right away to match player.
}
}
public bool IsChoiceEventActive { get; set; } = false;
public ChoiceEvent ActiveChoiceEvent { get; set; }
public void TriggerChoicePopup( ChoiceEvent newEvent )
{
ActiveChoiceEvent = newEvent;
IsChoiceEventActive = true;
OnStateChanged?.Invoke();
}
public void ResolveChoice( int optionIndex )
{
if ( ActiveChoiceEvent == null || optionIndex < 0 || optionIndex >= ActiveChoiceEvent.Options.Count ) return;
// 1. Reset trackers
ChoiceEventBuffModifier = 0;
// ChoiceEventAIBuffModifier is left alone because it holds our pre-popup setup values!
// 2. Instantly execute whichever button was pushed on the Razor UI screen
ActiveChoiceEvent.Options[optionIndex].ActionToExecute?.Invoke();
// 3. Clean up the screen overlay barriers
IsChoiceEventActive = false;
ActiveChoiceEvent = null;
// 👑 RE-ALIGN THE TURN COMPLETION STACK STIMULATORS 👑
int tempPlayerBuff = ChoiceEventBuffModifier;
int tempAiBuff = ChoiceEventAIBuffModifier; // FIXED: Feed the updated AI calculation stats straight into the turn resolver!
bool dummyDamageTracker = false;
// 4. RESUME COMBAT WITH SAFE STACK VARIABLES
Game.HandlePlayerTurn( ref LocalPlayer, ref AIEnemy, ChosenWeapon, lastPlayerMove, tempPlayerBuff, ref tempAiBuff );
Game.HandleAITurn( ref LocalPlayer, ref AIEnemy, ChosenWeapon, AIWeapon, lastAIMove, ref tempPlayerBuff, ref tempAiBuff, ref dummyDamageTracker );
if ( IsBattleActive )
{
Game.aiDialogue( AIEnemy.hp );
Game.aiMemoryDialogue( WinStreak, LossStreak, dummyDamageTracker, Round );
}
// Clean up fields and step up the round natively
Game.CleanupRound( ref LocalPlayer, ref AIEnemy, ref tempPlayerBuff, ref tempAiBuff, ref Round );
// Lock the final calculated values back into your tracking fields
ChoiceEventBuffModifier = tempPlayerBuff;
ChoiceEventAIBuffModifier = tempAiBuff; // Save back to permanent manager registry positions!
CheckBattleEndConditions( dummyDamageTracker );
OnStateChanged?.Invoke();
}
public void TriggerPerkSelectionMilestone()
{
// 🚀 THE UNIFIED CHAOS DECK: Stacks every single card blueprint into one master array
var masterChaosDeck = new List<Keyboard_Warriors_.Models.PerkCard>();
// --- MATRIX ALPHA: OFFENSIVE PERKS ---
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "vampiric_puncture",
Title = "Vampiric Puncture",
Icon = "🩸",
Description = "Permanently grants +25% Physical Attack Damage and locks your Bleed Matrix trigger chance to a flat 100% threshold.",
DrawbackText = "📉 Penalty: Cuts your core recovery and healing efficiency by -50%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.AttackMultiplier += 0.25f;
p.Perks.HealingMultiplier -= 0.50f;
},
ApplyToAI = ( ai ) =>
{
ai.Perks.AttackMultiplier += 0.25f;
ai.Perks.HealingMultiplier -= 0.50f;
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "true_kinetic_bypass",
Title = "True Kinetic Bypass",
Icon = "⚡",
Description = "Weapon strikes permanently ignore 50% of the enemy's structural Armor plating variables on contact.",
DrawbackText = "⚡ Penalty: Increases incoming damage from Sparks/Electrical statuses by +25%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.IgnoresEnemyArmor = true;
p.Perks.ElectricalDamageTakenMultiplier += 0.25f;
},
ApplyToAI = ( ai ) =>
{
ai.Perks.IgnoresEnemyArmor = true;
ai.Perks.ElectricalDamageTakenMultiplier += 0.25f;
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "overclocked_core",
Title = "Overclocked Core",
Icon = "🔥",
Description = "Permanently increases Spell and Ultimate damage output by +40%. Reduces Magic cost of choices by a flat -10 MP.",
DrawbackText = "📉 Penalty: Increases your baseline Magic damage taken from enemy counters by +20%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.SpellMultiplier += 0.40f;
p.Perks.MagicDamageTakenMultiplier += 0.20f;
},
ApplyToAI = ( ai ) =>
{
ai.Perks.SpellMultiplier += 0.40f;
ai.Perks.MagicDamageTakenMultiplier += 0.20f;
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "berserker_registry",
Title = "Berserker Registry",
Icon = "⚔️",
Description = "Deals +50% extra Physical Attack damage whenever your current HP pool drops beneath a 50% threshold.",
DrawbackText = "⚡ Penalty: Permanently reduces your Maximum Armor plating capability by -20%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.HasBerserkerRegistry = true;
p.defense = (int)(p.defense * 0.80f); // Drops armor pool by 20%
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasBerserkerRegistry = true;
ai.defense = (int)(ai.defense * 0.80f); // Drops armor pool by 20%
}
} );
// --- MATRIX BETA: DEFENSIVE PERKS ---
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "kernel_plating",
Title = "Kernel Plating",
Icon = "🛡️",
Description = "Permanently scales your defensive mitigation efficiency by +50%. Grants absolute protection against basic physical scaling.",
DrawbackText = "📉 Penalty: Restricts critical overclock streams, cutting Critical Strike chance by -15%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.DefenseMultiplier += 0.50f;
p.Perks.HasKernelPlating = true;
p.Perks.CriticalStrikeChanceModifier -= 15f; // Subtracts 15% from their critical roll success pools
},
ApplyToAI = ( ai ) =>
{
ai.Perks.DefenseMultiplier += 0.50f;
ai.Perks.HasKernelPlating = true;
ai.Perks.CriticalStrikeChanceModifier -= 15f;
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "cryo_insulation",
Title = "Cryo-Insulation",
Icon = "❄️",
Description = "Grants +35% permanent protection against elemental vulnerabilities. Renders chassis completely immune to being Stunned or Frozen.",
DrawbackText = "📉 Penalty: Narrows SP lines, reducing Maximum Magic capacity by -20%.",
ApplyToPlayer = ( p ) =>
{
p.antiStun = true;
p.magic = (int)(p.magic * 0.80f); // Cuts current magic pool
p.Perks.ElementalDamageTakenMultiplier -= 0.35f; // Gives 35% permanent protection!
},
ApplyToAI = ( ai ) =>
{
ai.antiStun = true;
ai.magic = (int)(ai.magic * 0.80f);
ai.Perks.ElementalDamageTakenMultiplier -= 0.35f;
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "mirror_firewall",
Title = "Mirror Firewall",
Icon = "🪞",
Description = "Overclocks your Spell Damage output by +40%. Arms your code arrays to reflect incoming offensive spell packets backwards.",
DrawbackText = "📉 Penalty: Dilutes mechanical impact, lowering raw physical Weapon Attack ratings by -2%.",
ApplyToPlayer = ( p ) =>
{
p.counterReady = true;
p.Perks.SpellMultiplier += 0.40f;
p.Perks.AttackMultiplier -= 0.02f;
},
ApplyToAI = ( ai ) =>
{
ai.counterReady = true;
ai.Perks.SpellMultiplier += 0.40f;
ai.Perks.AttackMultiplier -= 0.02f;
}
} );
// 🟢 MATRIX GAMMA: HEALTH & RECOVERY PERKS
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "chassis_inflation",
Title = "Chassis Inflation",
Icon = "🧬",
Description = "Instantly expands Maximum capacity by +100 HP and heals for that full amount. Amplifies future healing gains by +25%.",
DrawbackText = "📉 Penalty: Restricts kinetic motor speed, cutting physical damage output by -5%.",
ApplyToPlayer = ( p ) =>
{
if ( p.Perks.HealthCardCount < 2 )
{
p.maxHp += 100;
p.hp += 100;
p.Perks.HealingMultiplier += 0.25f; // Amplifies future recovery loops
p.Perks.AttackMultiplier -= 0.05f; // Reduces weapon cuts cleanly
p.Perks.HealthCardCount++;
}
},
ApplyToAI = ( ai ) =>
{
if ( ai.Perks.HealthCardCount < 2 )
{
ai.maxHp += 100;
ai.hp += 100;
ai.Perks.HealingMultiplier += 0.25f;
ai.Perks.AttackMultiplier -= 0.05f;
ai.Perks.HealthCardCount++;
}
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "nanite_regeneration",
Title = "Nanite Regen Loop",
Icon = "🧫",
Description = "Injects autonomous repair cells that continuously regenerate +8% of your Max HP at the start of every single round.",
DrawbackText = "⚡ Penalty: Disables baseline kinetic shields, permanently locking current Armor to 0.",
ApplyToPlayer = ( p ) =>
{
p.Perks.HasNaniteRegen = true;
p.defense = 0;
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasNaniteRegen = true;
ai.defense = 0;
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard // Fixed
{
Id = "adaptive_barrier",
Title = "Adaptive Barrier",
Icon = "🔋",
Description = "Defending actions instantly recharge +15 Armor plating points and safely purge any active poison curses from your system threads.",
DrawbackText = "📉 Penalty: Increases the resource footprint of all your Spells and Ultimates by +15 MP.",
ApplyToPlayer = ( p ) =>
{
p.Perks.HasAdaptiveBarrier = true;
p.magic = Math.Max( 0, p.magic - 15 );
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasAdaptiveBarrier = true;
ai.magic = Math.Max( 0, ai.magic - 15 );
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "singularity_core",
Title = "Singularity Core",
Icon = "🌀",
Description = "Permanently increases Special and Ultimate Spell damage output by +40%. Reduces Magic cost of all actions by a flat -15 MP.",
DrawbackText = "📉 Penalty: Weakens structural integrity, reducing current Armor Plating rating by -25%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.HasSingularityCore = true;
p.defense = (int)(p.defense * 0.75f); // Cuts starting armor pool by -25%
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasSingularityCore = true;
ai.defense = (int)(ai.defense * 0.75f);
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard // Fixed
{
Id = "static_feedback",
Title = "Static Feedback",
Icon = "⚡",
Description = "Casting a Special Ability or Special Spell instantly recharges +15 Magic Points (SP) back into your capacitor pool.",
DrawbackText = "📉 Penalty: Dilutes restoration streams, cutting baseline Heal Spell efficiency by -30%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.HasStaticFeedback = true;
p.Perks.HealingMultiplier -= 0.30f; // Cuts elixir healing efficiency by -30%
p.magic = Math.Min( 100, p.magic + 15 ); // Direct starter MP boost
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasStaticFeedback = true;
ai.Perks.HealingMultiplier -= 0.30f;
ai.magic = Math.Min( 100, ai.magic + 15 );
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "malware_quarantine",
Title = "Quarantine Trigger",
Icon = "🔮",
Description = "Using an Ultimate Spell instantly purges all active poison curses and triggers a 2-round Spell Reflection counter shield.",
DrawbackText = "📉 Penalty: Restricts kinetic motor output, permanently lowering weapon Attack rating by -2%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.HasQuarantineTrigger = true;
p.Perks.AttackMultiplier -= 0.02f; // Drops weapon attack damage by 20%
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasQuarantineTrigger = true;
ai.Perks.AttackMultiplier -= 0.02f;
}
} );
// Offensive Spell Perks
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "aura_meltdown",
Title = "Aura Meltdown",
Icon = "☄️",
Description = "Permanently scales all Offensive and Ultimate Spell damage output by +40%. Perfect for full glass-cannon spellcasters.",
DrawbackText = "📉 Penalty: Dilutes kinetic strike force, cutting weapon Physical Attack damage by -5%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.HasOffensiveSpellPerkTrigger = true;
p.Perks.SpellMultiplier += 0.40f;
p.Perks.AttackMultiplier -= 0.05f;
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasOffensiveSpellPerkTrigger = true;
ai.Perks.SpellMultiplier += 0.40f;
ai.Perks.AttackMultiplier -= 0.05f;
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard // Fixed
{
Id = "void_plating_puncture",
Title = "Void Puncture",
Icon = "🌌",
Description = "Magic bursts permanently ignore 50% of the enemy's structural Defense Armor plating variables on spell calculation turns.",
DrawbackText = "⚡ Penalty: Corrupts security buffers, increasing damage taken from Curses/Poison ticks by +30%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.SpellsIgnoreEnemyArmor = true;
p.Perks.ElementalDamageTakenMultiplier += 0.30f; // Increases incoming status tick vulnerability
},
ApplyToAI = ( ai ) =>
{
ai.Perks.SpellsIgnoreEnemyArmor = true;
ai.Perks.ElementalDamageTakenMultiplier += 0.30f;
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard // Fixed
{
Id = "corrosive_terminal",
Title = "Corrosive Package",
Icon = "🧪",
Description = "Casting an offensive spell locks a permanent background toxin curse onto the target, draining ticking HP over time.",
DrawbackText = "⚡ Penalty: Disables baseline physical shielding, permanently locking current Armor to 0.",
ApplyToPlayer = ( p ) =>
{
p.Perks.HasCorrosivePackage = true;
p.defense = 0;
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasCorrosivePackage = true;
ai.defense = 0;
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "nanite_barrier_loop",
Title = "Nanite Shield Matrix",
Icon = "🧼",
Description = "Casting a Defensive Spell (Fortify/Magic Wall) instantly repairs your armor frame, boosting active Armor by +20 points.",
DrawbackText = "📈 Penalty: Limits kinetic impact velocity, cutting weapon Physical Attack damage by -5%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.HasNaniteBarrier = true;
p.Perks.AttackMultiplier -= 0.05f;
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasNaniteBarrier = true;
ai.Perks.AttackMultiplier -= 0.05f;
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "spell_purge_registry",
Title = "Aura Purification",
Icon = "💎",
Description = "Deploying a defensive barrier cleanses your memory stack, completely purging all active poison loops and curses from your threads.",
DrawbackText = "📉 Penalty: Narrows magic capacitor bands, reducing your baseline Spell Damage scaling by -15%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.HasSpellPurge = true;
p.Perks.SpellMultiplier -= 0.15f;
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasSpellPurge = true;
ai.Perks.SpellMultiplier -= 0.15f;
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "emergency_cell_purge",
Title = "Emergency Cell Purge",
Icon = "⚡",
Description = "Whenever your current HP pool sits beneath 30%, your Healing Spells receive a massive +50% absolute restoration boost.",
DrawbackText = "📈 Penalty: Weakens baseline density, permanently reducing maximum Armor capacity by -20 points.",
ApplyToPlayer = ( p ) =>
{
p.Perks.HasEmergencyCellPurge = true;
p.maxDefense = System.Math.Max( 0, p.maxDefense - 20 );
p.defense = System.Math.Min( p.defense, p.maxDefense );
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasEmergencyCellPurge = true;
ai.maxDefense = System.Math.Max( 0, ai.maxDefense - 20 );
ai.defense = System.Math.Min( ai.defense, ai.maxDefense );
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "overheal_capacitor_loop",
Title = "Overheal Capacitor",
Icon = "🔋",
Description = "Casting a Healing Spell instantly completely overcharges your armor capacity to 100 points and feeds +10 Magic back into your capacitor pool.",
DrawbackText = "📈 Penalty: Diverts power lines, permanently cutting your baseline physical Weapon Attack ratings by -3%.",
ApplyToPlayer = ( p ) =>
{
if ( p.Perks.HealthCardCount < 2 )
{
p.Perks.HasOverhealCapacitor = true;
p.Perks.AttackMultiplier -= 0.03f;
p.Perks.HealthCardCount++;
}
},
ApplyToAI = ( ai ) =>
{
if ( ai.Perks.HealthCardCount < 2 )
{
ai.Perks.HasOverhealCapacitor = true;
ai.Perks.AttackMultiplier -= 0.03f;
ai.Perks.HealthCardCount++;
}
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "feedback_overload_registry",
Title = "FEEDBACK MELTDOWN",
Icon = "☢️",
Description = "Casting an Ultimate Spell triggers a catastrophic capacitor loop, multiplying your final Ultimate Damage by a massive +150% boost!",
DrawbackText = "🚨 CRITICAL PENALTY: Disables your motor grids! Completely cuts your raw physical Weapon Attack ratings by -5%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.HasFeedbackOverload = true;
p.Perks.AttackMultiplier -= 0.05f; // Cripples weapon strikes to a smaller fraction!
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasFeedbackOverload = true;
ai.Perks.AttackMultiplier -= 0.05f;
}
} );
masterChaosDeck.Add( new Keyboard_Warriors_.Models.PerkCard
{
Id = "defiance_protocol_matrix",
Title = "DEFIANCE PROTOCOL",
Icon = "🪐",
Description = "Unleashing an Ultimate Spell delivers a devastating double strike, repeating 100% of the calculated damage payload a second time!",
DrawbackText = "🚨 REBOOT PENALTY: Corrupts security firewalls! Increases ALL incoming Curse, Poison, and Elemental damage taken by +100%.",
ApplyToPlayer = ( p ) =>
{
p.Perks.HasDefianceProtocol = true;
p.Perks.ElementalDamageTakenMultiplier += 1.00f; // Doubles incoming ticking status damage!
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasDefianceProtocol = true;
ai.Perks.ElementalDamageTakenMultiplier += 1.00f;
}
} );
// Clear out data frames
ActivePerkChoices.Clear();
// Calculate dynamic grid option count based on current session rounds
int targetChoiceCount = 2;
if ( Round >= 30 ) targetChoiceCount = 4;
else if ( Round >= 12 ) targetChoiceCount = 3;
// Cap protection gate
if ( targetChoiceCount > masterChaosDeck.Count ) targetChoiceCount = masterChaosDeck.Count;
// Whitelist-safe index shuffle loop
for ( int i = masterChaosDeck.Count - 1; i > 0; i-- )
{
int j = System.Random.Shared.Next( i + 1 );
var temp = masterChaosDeck[i];
masterChaosDeck[i] = masterChaosDeck[j];
masterChaosDeck[j] = temp;
}
// Move your randomized card selections onto the layout interface array list
for ( int k = 0; k < targetChoiceCount; k++ )
{
ActivePerkChoices.Add( masterChaosDeck[k] );
}
IsPerkEventActive = true;
}
public void ResolvePerkChoice( int choiceIndex )
{
if ( choiceIndex < 0 || choiceIndex >= ActivePerkChoices.Count ) return;
var chosenPerk = ActivePerkChoices[choiceIndex];
// =========================================================================
// 🛡️ 1. HUMAN PLAYER ENFORCEMENT ENGINE (TWO-STACK HARD CAP FILTER)
// =========================================================================
if ( LocalPlayer != null && !string.IsNullOrEmpty( chosenPerk.Id ) )
{
// Whitelist-safe loop routine to count existing stack occurrences
int currentStackCount = 0;
for ( int i = 0; i < PlayerOwnedPerkIds.Count; i++ )
{
if ( PlayerOwnedPerkIds[i] == chosenPerk.Id )
{
currentStackCount++;
}
}
// 🚨 THE CRITICAL HARD CAP GATE: If they already hold 2 stacks, abort!
if ( currentStackCount >= 2 )
{
// Safe Magic Point Fallback - Physically cannot break health or armor equations
LocalPlayer.magic = System.Math.Min( 100, LocalPlayer.magic + 50 );
GameManager.AddPlayerLog( $"🚨 MATRIX MAXIMUM EXCEEDED: {chosenPerk.Title} is already max-capped at 2 stacks! Re-routed power to inject +50 Magic Points." );
}
else
{
// Run payload: Allows first-time picks and safe second-stack integrations!
chosenPerk.ApplyToPlayer( LocalPlayer );
PlayerOwnedPerkIds.Add( chosenPerk.Id );
int outputStackVisual = currentStackCount + 1;
GameManager.AddPlayerLog( $"🔵 Integrated Core Matrix: {chosenPerk.Title} installed! (Stack Level: {outputStackVisual}/2)" );
}
}
// =========================================================================
// 🧠 2. AI OVERLORD ENFORCEMENT ENGINE (TWO-STACK HARD CAP FILTER)
// =========================================================================
if ( AIEnemy != null )
{
int aiRandomIndex = System.Random.Shared.Next( ActivePerkChoices.Count );
var aiChosenPerk = ActivePerkChoices[aiRandomIndex];
if ( !string.IsNullOrEmpty( aiChosenPerk.Id ) )
{
int aiStackCount = 0;
for ( int j = 0; j < AIOwnedPerkIds.Count; j++ )
{
if ( AIOwnedPerkIds[j] == aiChosenPerk.Id )
{
aiStackCount++;
}
}
if ( aiStackCount >= 2 )
{
AIEnemy.magic = System.Math.Min( 100, AIEnemy.magic + 50 );
GameManager.AddAILog( $"🧠 AI OVERCLOCK PROTECTION: {aiChosenPerk.Title} max cap reached! Overlord systems gained +50 Magic Points instead." );
}
else
{
aiChosenPerk.ApplyToAI( AIEnemy );
AIOwnedPerkIds.Add( aiChosenPerk.Id );
int aiOutputVisual = aiStackCount + 1;
GameManager.AddAILog( $"🧠 AI Overlord integrated parameters: {aiChosenPerk.Title} (Stack Level: {aiOutputVisual}/2)" );
}
}
}
// =========================================================================
IsPerkEventActive = false;
ActivePerkChoices.Clear();
}
// 💬 BROADCAST CHAT HANDLER INTERCEPT
// Reaching out across network pipes to push raw text data lines to both terminals
[Rpc.Broadcast]
public void SendNetworkChatMessage( string senderName, string rawTextMessage )
{
if ( string.IsNullOrEmpty( rawTextMessage ) ) return;
string formattedLogLine = $"[💬 CHAT] {senderName.ToUpper()}: {rawTextMessage}";
if ( ChatMessages == null ) ChatMessages = new List<string>();
ChatMessages.Add( formattedLogLine );
GameManager.AddLog( formattedLogLine );
}
// 🎮 NETWORK MOVE AUTHORIZATION PROTOCOL
// Receives turn inputs from clients and routes them safely into your core simulation loop
[Rpc.Broadcast]
public void SubmitNetworkMove( bool isHostChoice, int chosenMoveIndex )
{
// 👤 STEP 1: IT IS PLAYER 1'S TURN AND THE HOST MAKES A SELECTION
if ( IsPlayerOneActiveTurn && isHostChoice )
{
if ( IsPlayerOneDazed )
{
chosenMoveIndex = 0; // Assign an Idle/Skip index so no damage calculations fire
IsPlayerOneDazed = false; // Consume the daze flag for this round pass
GameManager.AddLog( $"😵 [SYSTEM] {LocalPlayer.name} is dazed from an electrical overload! Action skipped." );
}
PlayerOneStoredMove = chosenMoveIndex;
IsPlayerOneActiveTurn = false; // 🔄 DYNAMIC SWITCH: Hand the active clock to Player 2!
TurnCountdownClock = 10.0f; // Recharge the full 10-second capacitor for Player 2
GameManager.AddLog( "📡 [SYSTEM] Player 1 locked their action! Player 2 terminal unlocked..." );
return;
}
// 👥 STEP 2: IT IS PLAYER 2'S TURN AND THE CLIENT RESPONDS
if ( !IsPlayerOneActiveTurn && !isHostChoice )
{
if ( IsPlayerTwoDazed )
{
chosenMoveIndex = 0; // Assign an Idle/Skip index so no damage calculations fire
IsPlayerTwoDazed = false; // Consume the daze flag for this round pass
GameManager.AddLog( $"😵 [SYSTEM] {AIEnemy.name} is dazed from an electrical overload! Action skipped." );
}
PlayerTwoStoredMove = chosenMoveIndex;
IsWaitingForClientInput = false; // Freeze countdown ticks immediately during round processing
// Pass Player 1's move natively into your core player vs player math simulation loop
ProcessTurnSlice( PlayerOneStoredMove );
// 🧼 POST-ROUND FLUSH: Reset the buffers and hand the opening turn of the next round back to Player 1
PlayerOneStoredMove = 0;
PlayerTwoStoredMove = 0;
IsPlayerOneActiveTurn = true;
TurnCountdownClock = 10.0f; // Recharge the 10-second clock for Player 1's next turn
IsWaitingForClientInput = true;
}
}
}