MainHud/GameManager.cs

A GameManager component for a small turn-based combat game. It tracks player/AI state, weapons, spells, rounds, prestige/points, saves/loads progress to disk, runs turn logic, logs combat events, and exposes methods for starting battles and processing player moves.

File AccessNetworking
using Keyboard_Warriors_.Managers;
using Keyboard_Warriors_.Models;
using Sandbox;
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;

	public int PointsRequiredForPrestige { get; set; } = 1000;

	public float AIHealthMultiplier => 1.0f + (CurrentPrestige * 0.25f);

	public GamePhase CurrentPhase { get; set; } = GamePhase.StartMenu;

	// 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;

	// Active UI logs parsed by your Razor log-scroll area
	public List<string> CombatLogs { get; set; } = new List<string>();
	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();
		InitializeGameSetup();

	}

	// Method sits outside OnAwake, but still inside the GameManager class
	

	public static void AddLog( string message )
	{
		
		if ( Instance.CombatLogs == null)
		{
			Instance.CombatLogs = new List<string>();
		}

		Instance.CombatLogs.Add( message );

		Log.Info( message );
	}

	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()
	{

		int nextPrestige = CurrentPrestige + 1;

		float multiplier = 1.0f + (CurrentPrestige * 0.25f);
    
    // AI Scaling
    AIEnemy.maxHp = (int)System.Math.Ceiling( 100 * multiplier );
		AIEnemy.hp = AIEnemy.maxHp;
    AIEnemy.maxDefense = 100 + CurrentPrestige * 10;
		AIEnemy.defense = AIEnemy.maxDefense;
    AIEnemy.magic = 100; // Or whatever your base magic should be

    // Player Scaling (Keep at 100, or scale if you wish)
    LocalPlayer.maxHp = 100 + (CurrentPrestige * 15);
		LocalPlayer.hp = LocalPlayer.maxHp;
    LocalPlayer.maxDefense = 100 + CurrentPrestige * 5; // Or keep as is
		LocalPlayer.defense = LocalPlayer.maxDefense;
    LocalPlayer.magic = 100;
    
    AddLog( $"Battle Reset! AI HP: {AIEnemy.hp}, Armor: {AIEnemy.defense}" );

		LocalPlayer.cursed = false;
		AIEnemy.cursed = false;

		SpellManager.generateSpells( ref LocalPlayer );
		SpellManager.generateSpells( ref AIEnemy );
	}

	public enum GamePhase
	{
		StartMenu,
		WeaponSelection,
		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 ApplyEnchantmentSelection( Weapon chosenWeapon, int enchantChoice )
	{
		switch ( enchantChoice )
		{
			case 1:
				chosenWeapon.enchantment = Weapon.EnchantmentType.Fire;
				break;

			case 2:
				chosenWeapon.enchantment = Weapon.EnchantmentType.Poison;
				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;
				break;

			case 8:
				chosenWeapon.enchantment = Weapon.EnchantmentType.LifeSteal;
				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;

		// 1. Calculate the scaled stats here based on the current prestige
		float multiplier = 1.0f + (CurrentPrestige * 0.25f);
		int scaledAiHp = (int)System.Math.Ceiling( 100 * multiplier );
		int scaledAiArmor = CurrentPrestige * 10;

		// 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", scaledAiHp, 15, scaledAiArmor, true );

		// 🚀 FIX: Pull your real weapon stats from the pool safely instead of hardcoding text strings!
		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( $"⚔️ Combat Begins! You equipped {ChosenWeapon.name}. AI equipped {AIWeapon.name}." );

		// Generate real spellbooks directly without temp-swaps overwriting them
		SpellManager.generateSpells( ref LocalPlayer );
		SpellManager.generateSpells( ref AIEnemy );

		AddLog( $"AI: \"A challenger approaches... I see you have chosen the {ChosenWeapon.name}. Prepare yourself.\"" );
	}

	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) ---
		if ( LocalPlayer.cursed )
		{
			LocalPlayer.hp = Math.Max( 0, LocalPlayer.hp - 5 );
			AddLog( "⚠️ The demon's curse drains 5 HP from YOU!" );
		}
		if ( AIEnemy.cursed )
		{
			AIEnemy.hp = Math.Max( 0, AIEnemy.hp - 5 );
			AddLog( "⚠️ The demon's curse drains 5 HP from the AI!" );
		}

		// Increment tracking moves
		if ( playerMove == 1 ) attackCount++;
		else if ( playerMove == 2 ) blockCount++;
		else if ( playerMove == 3 ) healCount++;
		else if ( playerMove == 4 ) specialCount++;

		// --- 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 )
		{
			// Roll the container structure
			Keyboard_Warriors_.Managers.RandomEventManager.randomEvent( ref LocalPlayer, ref AIEnemy, ref playerBuff, ref aiBuff );
		}

		// --- 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);
		AIEnemy.isBlocking = (aiMove == 2);

		bool tookDamage = false;

		// --- 5. ORIGINAL TURN EVALUATIONS ---
		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 );

		// 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 );

		// --- 6. CHECK GAME OVER STATES ---
		CheckBattleEndConditions( tookDamage );
	}

	private void ExecutePlayerSpellLogic( int playerMove )
	{
		// Move 5: Offensive Spells
		if ( playerMove == 5 )
		{
			if ( LocalPlayer.magic >= 20 )
			{
				string spell = LocalPlayer.offensiveSpells[rand.Next( LocalPlayer.offensiveSpells.Count )];
				AddLog( $"🔮 You cast {spell}!" );
				int spellDamage = 15 + (LocalPlayer.magic / 2);
				LocalPlayer.magic -= 20;

				if ( spell == "Fireball" ) spellDamage += 5;
				if ( spell == "Lightning" ) spellDamage += 10;
				if ( spell == "Poison Bolt" )
				{
					AIEnemy.cursed = true;
					AddLog( "🧪 AI is poisoned!" );
				}

				CombatManager.applyDamage( ref AIEnemy, spellDamage );
				AddLog( $"💥 {spell} hits AI for {spellDamage} damage!" );
			}
			else AddLog( "❌ Not enough magic!" );
		}
		// Move 6: Defensive Spells
		else if ( playerMove == 6 )
		{
			if ( LocalPlayer.magic >= 15 )
			{
				string spell = LocalPlayer.defensiveSpells[rand.Next( LocalPlayer.defensiveSpells.Count )];
				AddLog( $"🔮 You cast {spell}!" );

				if ( spell == "Shield" || spell == "Barrier" ) LocalPlayer.defense += 15;
				else if ( spell == "Reflect" )
				{
					LocalPlayer.isBlocking = true;
					AddLog( "🛡️ Next attack will be blocked!" );
				}
				else if ( spell == "Magic Wall" )
				{
					LocalPlayer.isMagicBlocking = true;
					LocalPlayer.activeMagicShield = "Magic Wall";
					AddLog( "✨ A magical barrier surrounds you!" );
				}
				else if ( spell == "Fortify" )
				{
					LocalPlayer.isMagicBlocking = true;
					LocalPlayer.activeMagicShield = "Fortify";
					AddLog( "💎 Your defenses are magically reinforced!" );
				}

				LocalPlayer.magic -= 15;
			}
			else AddLog( "❌ Not enough magic!" );
		}
		// Move 7: Healing Spells
		else if ( playerMove == 7 )
		{
			if ( LocalPlayer.magic >= 15 )
			{
				string spell = LocalPlayer.healingSpells[rand.Next( LocalPlayer.healingSpells.Count )];
				AddLog( $"🔮 You cast {spell}!" );
				int healAmount = 20;

				if ( spell == "Heal" ) healAmount = 20;
				else if ( spell == "Regen" ) healAmount = 15;
				else if ( spell == "Cleanse" )
				{
					healAmount = 10;
					LocalPlayer.cursed = false;
					AddLog( "✨ Negative effects removed!" );
				}
				else if ( spell == "Divine Light" ) healAmount = 40;
				else if ( spell == "Recovery Pulse" ) healAmount = 30;

				LocalPlayer.hp = Math.Min( 100, LocalPlayer.hp + healAmount );
				LocalPlayer.magic -= 15;
				AddLog( $"💚 You recover {healAmount} HP!" );
			}
			else AddLog( "❌ Not enough magic!" );
		}
		// Move 8: Special Spells
		else if ( playerMove == 8 )
		{
			if ( LocalPlayer.magic >= 30 )
			{
				string spell = LocalPlayer.specialSpells[rand.Next( LocalPlayer.specialSpells.Count )];

				string emoji = GetSpellEmoji( spell );
				AddLog( $"{emoji} You cast Ultimate Spell: {spell}!" );

				LocalPlayer.magic = Math.Max( 0, LocalPlayer.magic - 30 );

				if (spell == "System Overdrive" )
				{
					int damageAmount = 30 + LocalPlayer.magic;// + is the remaining magic points left out of 100

					CombatManager.applyDamage( ref AIEnemy, damageAmount );

					
					AddLog( "⚡ You enter System Overdrive, bypassing the AI's firewall for massive damage!" );

				}
				else if ( spell == "EMP Blast")
				{
					int empDamage = LocalPlayer.attack *= 2;
					CombatManager.applyDamage( ref AIEnemy, empDamage );
					AddLog( "💥 An EMP Blast de-calibrates the AI's framework, doubling your attack power!" );
				}
				else if ( spell == "Data Corruption")
				{
					int corruptionDamage = 40;
					CombatManager.applyDamage( ref AIEnemy, corruptionDamage );
					ShouldStunAI = true;
					AddLog( "👾 You inject Data Corruption into the AI's system routines! Causing -40 HP!" );
				}
			}
			else AddLog( "❌ Not enough magic!" );
		}

		// Clamp values safely
		LocalPlayer.defense = Math.Min( 100, LocalPlayer.defense );
		LocalPlayer.magic = Math.Min( 100, LocalPlayer.magic );
	}

	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 )
			{
				AddLog( "🤝 It's a tie!" );
				WinStreak = 0;
				LossStreak = 0;
			}
			else if ( LocalPlayer.hp <= 0 )
			{
				AddLog( "💀 AI wins!" );
				Losses++;
				LossStreak++;
				WinStreak = 0;

				PlayerPoints += 50;
				AddLog( "💰 +50 Points added (Consolation)." );
			}
			else
			{
				AddLog( "AI: \"**INITIATING DEFEAT PROCESS.. 3..2..1.. *BOOM*\"" );
				AddLog( "🏆 You win!" );
				Wins++;
				WinStreak++;
				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;
				AddLog( $"💰 +{winReward} Points added! (Streak Bonus included)" );

			}
			SaveProgress();// Save immediately just in case of a sudden crash.
		}
	}

	public event Action OnStateChanged;

	public void TriggerPrestige()
	{
		if (PlayerPoints >= PointsRequiredForPrestige)
		{
			PlayerPoints = 0;
			CurrentPrestige++;
			ApplyPrestigeScaling();
			ResetBattle();
			SaveProgress();// Lock in their new ascension status.
			OnStateChanged?.Invoke();

			AddLog( $"\n✨ PRESTIGE INCREASED TO LEVEL {CurrentPrestige}! ✨" );
			AddLog( "⚠️ WARNING: The AI Overlord has adapted. Its vital capacities have evolved!" );

			WinStreak = 0;
			LossStreak = 0;

		}
		else
		{
			AddLog( $"❌ 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 ) 
		{
			// Scale max health based on prestige level (+25% per level)
			float multiplier = 1.0f + (CurrentPrestige * 0.25f);
			int scaledMaxHp = (int)System.Math.Ceiling( 100 * multiplier );

			// If your Player model tracks a MaxHp property, set that too!
			AIEnemy.maxHp = scaledMaxHp;

			// Boost AI start Armor subtly (+10 per prestige level)
			AIEnemy.defense = 100 + CurrentPrestige * 10;
		}
	}

	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!" );
	}

	public void LoadProgress()
	{
		if ( FileSystem.Data.FileExists( SaveFileName ))
		{
			var data = FileSystem.Data.ReadJson<SaveData>( SaveFileName );
			PlayerPoints = data.Points;
			CurrentPrestige = data.Prestige;
			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.
		}
	}
}