GameManager component for the Keyboard Warriors HUD. Manages game state, player/AI stats, turns, spells, perks, logs, persistence, sounds, achievements and UI choice events; contains core battle loop logic and prestige/progression handling.
using Keyboard_Warriors_.Managers;
using Keyboard_Warriors_.Models;
using Sandbox;
using Sandbox.MainHud;
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using static Sandbox.MainHud.KeyboardWarriorsHUD;
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;
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 your 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();
}
}
}
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 );
}
public enum GamePhase
{
StartMenu,
WeaponSelection,
EnchantmentSelection,
Battle,
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 )
{
LoadProgress();
HasPlayerTakenAnyDamage = false;
// 2. Initialize Objects with the CALCULATED values
LocalPlayer = new Player( "Hero", 100, 15, 0, false ); // 100 HP, 15 Atk, 0 Def
// Use the scaled stats instead of hardcoded 100/15!
AIEnemy = new Player( "AI Overlord", 100, 15, 100, true );
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();
CombatLogs.Add( new CombatLogEntry
{
Message = $"⚔️ Combat Begins! AI equipped {AIWeapon.name}.",
ColorClass = "system-text"
} );
// Generate real spellbooks directly without temp-swaps overwriting them
SpellManager.generateSpells( ref LocalPlayer );
SpellManager.generateSpells( ref AIEnemy );
// =========================================================================
// 👑 UPDATE 2.0.1: INTERCEPT MAINBOARD IDENTITY (STEAM PROFILE NAME LOG)
// =========================================================================
// Securely pulls your account profile string name directly from the s&box wrapper API
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!" );
// =========================================================================
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 )
{
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. AI INTELLIGENT MOVE EVALUATION ---
int aiMove = Game.ChooseAIAction( LocalPlayer, AIEnemy, attackCount, blockCount, healCount, specialCount, Round );
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
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 )
{
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;
int scaledMagicCost = 20 + (prestige * 5);
if ( LocalPlayer.Perks.HasAdaptiveBarrier )
{
scaledMagicCost += 15;
}
// =========================================================================
// 🌀 SINGULARITY CORE: COST REDUCTION INTEGRATION (Update 1.6.5)
// =========================================================================
// If Singularity Core or Overclocked Core is active, drop costs by -15 MP flat!
if ( LocalPlayer.Perks.SpellMultiplier > 1.0f )
{
scaledMagicCost = Math.Max( 5, scaledMagicCost - 15 );
}
// =========================================================================
int baseSpellDamage = 15 + (LocalPlayer.magic / 2) + (prestige * 4);
if ( LocalPlayer.magic >= scaledMagicCost )
{
string spell = LocalPlayer.offensiveSpells[rand.Next( LocalPlayer.offensiveSpells.Count )];
GameManager.AddPlayerLog( $"🔮 You cast {spell}! (-{scaledMagicCost} Magic)" );
int spellDamage = baseSpellDamage;
LocalPlayer.magic -= scaledMagicCost;
if ( spell == "Fireball" ) spellDamage += 5;
if ( spell == "Lightning" ) spellDamage += 10;
// =========================================================================
// ☄️ AURA MELTDOWN & OVERCLOCK MULTIPLIER INTEGRATION (Update 1.6.5)
// =========================================================================
// Multiplies your final calculated spell points by your magic scaling factors!
spellDamage = (int)(spellDamage * LocalPlayer.Perks.SpellMultiplier);
// =========================================================================
// =========================================================================
// 🌌 VOID PUNCTURE: MAGICAL ARMOR BYPASS (Update 2.0.1)
// =========================================================================
// Inline scaling means your magic bypasses 50% of the AI's shield capacity,
// without corrupting the active character attributes or freezing inputs!
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!" );
}
// =========================================================================
if ( AIEnemy.counterReady || (prestige >= 10 && rand.Next( 100 ) < 35) )
{
// Dynamic text prompt updates automatically depending on how the reflect was triggered
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 );
}
// Apply the damage straight back to the player out-of-turn!
CombatManager.applyDamage( ref LocalPlayer, spellDamage );
return; // Abort standard damage delivery to the AI entirely!
}
if ( spell == "Poison Bolt" )
{
AIEnemy.cursed = true;
GameManager.AddAILog( "🧪 AI is poisoned!" );
}
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasStaticFeedback )
{
// Safely re-inject the +15 MP straight into your active capacitor pool
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
{
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;
int scaledMagicCost = 15 + (prestige * 5);
// Player shield strength: Base 15 + 3 per prestige level
int scaledShieldAmount = 15 + (prestige * 3);
if ( LocalPlayer.magic >= scaledMagicCost )
{
string spell = LocalPlayer.defensiveSpells[rand.Next( LocalPlayer.defensiveSpells.Count )];
// 🔵 PLAYER HIGHLIGHT: Defensive casting announcement turns cyber blue
GameManager.AddPlayerLog( $"🔮 You cast {spell}! (-{scaledMagicCost} Magic)" );
if ( spell == "Shield" || spell == "Barrier" )
{
LocalPlayer.defense += scaledShieldAmount;
// 🔵 PLAYER HIGHLIGHT: Shield partition increase tracking turns cyber blue
GameManager.AddPlayerLog( $"🛡️ Your armor is increased by {scaledShieldAmount} points!" );
// Defensive check: Clamp armor safely to your prestige-scaled maximum defense
if ( manager != null && manager.LocalPlayer != null && LocalPlayer.defense > manager.LocalPlayer.maxDefense )
{
LocalPlayer.defense = manager.LocalPlayer.maxDefense;
}
}
else if ( spell == "Reflect" )
{
LocalPlayer.isBlocking = true;
// 🔵 PLAYER HIGHLIGHT: Physical block setup text turns cyber blue
GameManager.AddPlayerLog( "🛡️ Next attack will be blocked!" );
}
else if ( spell == "Magic Wall" )
{
LocalPlayer.isMagicBlocking = true;
LocalPlayer.activeMagicShield = "Magic Wall";
// 🔵 PLAYER HIGHLIGHT: Magical barrier indicators turn cyber blue
GameManager.AddPlayerLog( "✨ A magical barrier surrounds you!" );
}
else if ( spell == "Fortify" )
{
LocalPlayer.isMagicBlocking = true;
LocalPlayer.activeMagicShield = "Fortify";
// 🔵 PLAYER HIGHLIGHT: Hull reinforcement confirmations turn cyber blue
GameManager.AddPlayerLog( "💎 Your defenses are magically reinforced!" );
}
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
{
// 🔵 PLAYER HIGHLIGHT: Manual magic resource failure text turns cyber blue
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;
int scaledMagicCost = 15 + (prestige * 5);
// Scale healing effects linearly: add +4 extra healing per prestige level
int prestigeHealBonus = prestige * 4;
if ( LocalPlayer.magic >= scaledMagicCost )
{
string spell = LocalPlayer.healingSpells[rand.Next( LocalPlayer.healingSpells.Count )];
// 🔵 PLAYER HIGHLIGHT: Your spell casting announcement turns cyber blue
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;
// 🔵 PLAYER HIGHLIGHT: Status removal notices turn cyber blue
GameManager.AddPlayerLog( "✨ Negative effects removed!" );
}
else if ( spell == "Divine Light" ) healAmount = 40 + prestigeHealBonus;
else if ( spell == "Recovery Pulse" ) healAmount = 30 + prestigeHealBonus;
float hpPercentage = (float)LocalPlayer.hp / LocalPlayer.maxHp;
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasEmergencyCellPurge && hpPercentage <= 0.30f )// Emergency Cell Purge Perk.
{
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;
// 🔵 PLAYER HIGHLIGHT: Final HP recovery numbers turn cyber blue
GameManager.AddPlayerLog( $"💚 You recover {healAmount} HP!" );
if ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasOverhealCapacitor )// Overheal Capacitor Perk.
{
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
{
// 🔵 PLAYER HIGHLIGHT: Low-magic resource alerts turn cyber blue
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;
// Highly aggressive mana cost scaling (+10 per level)
int scaledMagicCost = 30 + (prestige * 10);
if ( LocalPlayer.Perks.HasAdaptiveBarrier )
{
scaledMagicCost += 15;
}
int prestigeDamageBonus = prestige * 2;
// =========================================================================
// 🌀 SINGULARITY CORE: COST REDUCTION INTEGRATION (Update 1.6.5)
// =========================================================================
if ( 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 )
{
GameManager.AddPlayerLog( "⚠️ CRITICAL SYSTEM FAILURE! Your ultimate spell fizzles away into static feedback! Magic wasted." );
return; // Drop out completely
}
// =========================================================================
// 🔮 QUARANTINE TRIGGER REACTION BUFFER (Update 1.6.6 - FIXED DUPLICATE)
// =========================================================================
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 );
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;
}
// =========================================================================
// 🌀 SINGULARITY CORE ULTRA BURST INTEGRATION (Update 1.6.5)
// =========================================================================
if ( LocalPlayer.Perks.HasSingularityCore )
{
ultimateDamage = (int)(ultimateDamage * 1.40f);
}
// =========================================================================
// =========================================================================
// ☄️ AURA MELTDOWN & OVERCLOCK MULTIPLIER INTEGRATION (Update 1.6.5)
// =========================================================================
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 ( LocalPlayer.Perks != null && LocalPlayer.Perks.HasDefianceProtocol )
{
triggerDoubleStrike = true;
GameManager.AddPlayerLog( "🪐 DEFIANCE PROTOCOL: Echo buffers synchronized! Preparing secondary payload loop strike!" );
}
// =========================================================================
// 🌌 VOID PUNCTURE: ULTIMATE MAGICAL ARMOR BYPASS (Update 1.6.6 )
// =========================================================================
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 (Update 1.6.6 )
// =========================================================================
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!" );
}
// Standard Fallback: Runs completely normally if you aren't Prestige 10 or the AI's 35% roll misses
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
{
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 ( 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 ) =>
{
p.maxHp += 100;
p.hp += 100;
p.Perks.HealingMultiplier += 0.25f; // Amplifies future recovery loops
p.Perks.AttackMultiplier -= 0.05f; // Reduces weapon cuts cleanly
},
ApplyToAI = ( ai ) =>
{
ai.maxHp += 100;
ai.hp += 100;
ai.Perks.HealingMultiplier += 0.25f;
ai.Perks.AttackMultiplier -= 0.05f;
}
} );
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 ) =>
{
p.Perks.HasOverhealCapacitor = true;
p.Perks.AttackMultiplier -= 0.03f;
},
ApplyToAI = ( ai ) =>
{
ai.Perks.HasOverhealCapacitor = true;
ai.Perks.AttackMultiplier -= 0.03f;
}
} );
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();
}
}