using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using WarGame.Data; using WarGame.Deployment; using WarGame.Units; namespace WarGame.Managers { public enum BattleState { Preparation, BattleInProgress, Victory, Defeat } [System.Serializable] public class EnemyWaveConfig { public string waveName = "Wave 1"; public List enemies = new List(); public int rewardGold = 100; public int waveBonusExp = 40; } [System.Serializable] public class EnemySpawnInfo { public UnitDataSO unitData; public int level = 1; public int count = 1; } public class BattleManager : MonoBehaviour { public static BattleManager Instance { get; private set; } [Header("Deployment Zones")] [SerializeField] private DeploymentZone playerDeploymentZone; [SerializeField] private DeploymentZone enemyDeploymentZone; [Header("Default Unit Prefab (Fallback if UnitData lacks prefab)")] [SerializeField] private GameObject defaultUnitPrefab; [Header("Economy & Progression")] [SerializeField] private int startingGold = 150; [SerializeField] private int baseWaveClearGold = 75; [SerializeField] private int killBountyGold = 10; [SerializeField] private int battleParticipationExp = 30; [Header("Waves Setup")] [SerializeField] private List predefinedWaves = new List(); [SerializeField] private List defaultEnemyUnitTypes = new List(); // Current state public BattleState CurrentBattleState { get; private set; } = BattleState.Preparation; public int CurrentGold { get; private set; } public int CurrentWave { get; private set; } = 1; // Ongoing Live Unit Tracking private readonly List allActiveUnits = new List(); private readonly List playerArmyUnits = new List(); private readonly List enemyArmyUnits = new List(); public List AllActiveUnits => allActiveUnits; public List PlayerArmyUnits => playerArmyUnits; public List EnemyArmyUnits => enemyArmyUnits; public int PlayerAliveCount { get; private set; } public int EnemyAliveCount { get; private set; } public int PlayerTotalCount => playerArmyUnits.Count; public int EnemyTotalCount => enemyArmyUnits.Count; // Events public event Action OnBattleStateChanged; public event Action OnUnitCountsChanged; // playerAlive, enemyAlive public event Action OnGoldChanged; public event Action OnWaveChanged; public event Action OnBattleCompleted; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; } private void Start() { CurrentGold = startingGold; OnGoldChanged?.Invoke(CurrentGold); OnWaveChanged?.Invoke(CurrentWave); SetBattleState(BattleState.Preparation); } public void SetBattleState(BattleState newState) { CurrentBattleState = newState; OnBattleStateChanged?.Invoke(newState); } #region Economy & Unit Buying public bool CanAffordUnit(UnitDataSO unitData) { return unitData != null && CurrentGold >= unitData.goldCost; } public Unit BuyAndDeployUnit(UnitDataSO unitData) { if (!CanAffordUnit(unitData)) { Debug.LogWarning("Not enough gold to buy " + unitData.unitName); return null; } if (CurrentBattleState != BattleState.Preparation) { Debug.LogWarning("Cannot buy units while battle is in progress!"); return null; } // Deduct gold CurrentGold -= unitData.goldCost; OnGoldChanged?.Invoke(CurrentGold); // Spawn unit in player zone Vector3 spawnPos = playerDeploymentZone != null ? playerDeploymentZone.GetNextSpawnPosition() : transform.position; Quaternion spawnRot = Quaternion.identity; GameObject prefabToSpawn = unitData.unitPrefab != null ? unitData.unitPrefab : defaultUnitPrefab; if (prefabToSpawn == null) { Debug.LogError("No prefab found for unit: " + unitData.unitName); return null; } GameObject instance = Instantiate(prefabToSpawn, spawnPos, spawnRot); Unit unit = instance.GetComponent(); if (unit == null) unit = instance.AddComponent(); unit.Initialize(unitData, UnitTeam.Player, 1); playerArmyUnits.Add(unit); allActiveUnits.Add(unit); UpdateUnitCounts(); return unit; } public void AddGold(int amount) { CurrentGold += amount; OnGoldChanged?.Invoke(CurrentGold); } #endregion #region Battle Flow public void StartBattle() { if (CurrentBattleState != BattleState.Preparation) return; if (playerArmyUnits.Count == 0) { Debug.LogWarning("Recruit at least 1 unit before starting battle!"); return; } // Clean up any remaining previous enemies ClearEnemies(); // Spawn Enemy Wave SpawnEnemyWave(CurrentWave); // Reset player positions and heal to full if (playerDeploymentZone != null) playerDeploymentZone.ResetSlots(); for (int i = 0; i < playerArmyUnits.Count; i++) { if (playerArmyUnits[i] != null) { playerArmyUnits[i].gameObject.SetActive(true); Vector3 pos = playerDeploymentZone != null ? playerDeploymentZone.GetNextSpawnPosition() : playerArmyUnits[i].transform.position; playerArmyUnits[i].WarpToPosition(pos, Quaternion.LookRotation(Vector3.forward)); playerArmyUnits[i].HealToFull(); } } UpdateUnitCounts(); SetBattleState(BattleState.BattleInProgress); // Start battle for all units for (int i = 0; i < allActiveUnits.Count; i++) { if (allActiveUnits[i] != null && allActiveUnits[i].gameObject.activeSelf) { allActiveUnits[i].StartBattle(); } } } private void SpawnEnemyWave(int waveNumber) { enemyArmyUnits.Clear(); if (enemyDeploymentZone != null) enemyDeploymentZone.ResetSlots(); // Check if there is a predefined wave configuration EnemyWaveConfig waveConfig = null; if (waveNumber - 1 < predefinedWaves.Count) { waveConfig = predefinedWaves[waveNumber - 1]; } if (waveConfig != null && waveConfig.enemies.Count > 0) { foreach (EnemySpawnInfo spawnInfo in waveConfig.enemies) { for (int i = 0; i < spawnInfo.count; i++) { SpawnSingleEnemy(spawnInfo.unitData, spawnInfo.level); } } } else { // Procedural wave scaling if beyond predefined waves int enemyCount = 2 + waveNumber; int enemyLevel = Mathf.Max(1, 1 + (waveNumber / 2)); for (int i = 0; i < enemyCount; i++) { UnitDataSO randomType = null; if (defaultEnemyUnitTypes.Count > 0) { randomType = defaultEnemyUnitTypes[UnityEngine.Random.Range(0, defaultEnemyUnitTypes.Count)]; } SpawnSingleEnemy(randomType, enemyLevel); } } } private void SpawnSingleEnemy(UnitDataSO unitData, int level) { Vector3 spawnPos = enemyDeploymentZone != null ? enemyDeploymentZone.GetNextSpawnPosition() : transform.position + Vector3.forward * 10f; Quaternion spawnRot = Quaternion.LookRotation(Vector3.back); GameObject prefabToSpawn = (unitData != null && unitData.unitPrefab != null) ? unitData.unitPrefab : defaultUnitPrefab; if (prefabToSpawn == null) return; GameObject instance = Instantiate(prefabToSpawn, spawnPos, spawnRot); Unit enemyUnit = instance.GetComponent(); if (enemyUnit == null) enemyUnit = instance.AddComponent(); enemyUnit.Initialize(unitData, UnitTeam.Enemy, level); enemyArmyUnits.Add(enemyUnit); allActiveUnits.Add(enemyUnit); } private void ClearEnemies() { for (int i = 0; i < enemyArmyUnits.Count; i++) { if (enemyArmyUnits[i] != null) { allActiveUnits.Remove(enemyArmyUnits[i]); Destroy(enemyArmyUnits[i].gameObject); } } enemyArmyUnits.Clear(); } public void OnUnitDefeated(Unit unit) { if (unit.Team == UnitTeam.Enemy) { AddGold(killBountyGold); } UpdateUnitCounts(); if (CurrentBattleState == BattleState.BattleInProgress) { CheckBattleResolution(); } } private void UpdateUnitCounts() { int pAlive = 0; for (int i = 0; i < playerArmyUnits.Count; i++) { if (playerArmyUnits[i] != null && playerArmyUnits[i].IsAlive()) pAlive++; } int eAlive = 0; for (int i = 0; i < enemyArmyUnits.Count; i++) { if (enemyArmyUnits[i] != null && enemyArmyUnits[i].IsAlive()) eAlive++; } PlayerAliveCount = pAlive; EnemyAliveCount = eAlive; OnUnitCountsChanged?.Invoke(PlayerAliveCount, EnemyAliveCount); } private void CheckBattleResolution() { if (EnemyAliveCount <= 0) { ResolveBattle(isVictory: true); } else if (PlayerAliveCount <= 0) { ResolveBattle(isVictory: false); } } private void ResolveBattle(bool isVictory) { SetBattleState(isVictory ? BattleState.Victory : BattleState.Defeat); // Stop battle on surviving units for (int i = 0; i < allActiveUnits.Count; i++) { if (allActiveUnits[i] != null && allActiveUnits[i].IsAlive()) { allActiveUnits[i].EndBattle(restorePosition: false); } } // Calculate post-battle rewards int earnedGold = isVictory ? (baseWaveClearGold + CurrentWave * 20) : 25; AddGold(earnedGold); List levelUps = new List(); // Award participation XP and battle survival to player units for (int i = 0; i < playerArmyUnits.Count; i++) { Unit pUnit = playerArmyUnits[i]; if (pUnit == null || pUnit.Progression == null) continue; int prevLevel = pUnit.Progression.CurrentLevel; int xpGain = battleParticipationExp + (pUnit.IsAlive() ? 20 : 0); pUnit.Progression.AddExperience(xpGain); if (pUnit.IsAlive()) { pUnit.Progression.RecordBattleSurvived(); } if (pUnit.Progression.CurrentLevel > prevLevel) { levelUps.Add(new UnitLevelUpReport { unitName = pUnit.UnitData != null ? pUnit.UnitData.unitName : "Unit", oldLevel = prevLevel, newLevel = pUnit.Progression.CurrentLevel }); } } BattleResultData result = new BattleResultData { isVictory = isVictory, waveNumber = CurrentWave, goldEarned = earnedGold, survivingPlayerUnits = PlayerAliveCount, totalPlayerUnits = PlayerTotalCount, enemyCasualties = EnemyTotalCount, levelUpReports = levelUps }; OnBattleCompleted?.Invoke(result); } public void ContinueToNextRound() { if (CurrentBattleState == BattleState.Victory) { CurrentWave++; OnWaveChanged?.Invoke(CurrentWave); } ClearEnemies(); // Restore player army to deployment positions and heal if (playerDeploymentZone != null) playerDeploymentZone.ResetSlots(); for (int i = 0; i < playerArmyUnits.Count; i++) { if (playerArmyUnits[i] != null) { playerArmyUnits[i].gameObject.SetActive(true); Vector3 pos = playerDeploymentZone != null ? playerDeploymentZone.GetNextSpawnPosition() : playerArmyUnits[i].transform.position; playerArmyUnits[i].WarpToPosition(pos, Quaternion.LookRotation(Vector3.forward)); playerArmyUnits[i].HealToFull(); } } UpdateUnitCounts(); SetBattleState(BattleState.Preparation); } public void SetGameSpeed(float speedMultiplier) { Time.timeScale = Mathf.Clamp(speedMultiplier, 0.5f, 4.0f); } #endregion } [System.Serializable] public class BattleResultData { public bool isVictory; public int waveNumber; public int goldEarned; public int survivingPlayerUnits; public int totalPlayerUnits; public int enemyCasualties; public List levelUpReports; } [System.Serializable] public class UnitLevelUpReport { public string unitName; public int oldLevel; public int newLevel; } }