Files
WarGame/Assets/Scripts/Managers/BattleManager.cs
2026-09-30 23:45:58 +01:00

441 lines
15 KiB
C#

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<EnemySpawnInfo> enemies = new List<EnemySpawnInfo>();
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<EnemyWaveConfig> predefinedWaves = new List<EnemyWaveConfig>();
[SerializeField] private List<UnitDataSO> defaultEnemyUnitTypes = new List<UnitDataSO>();
// 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<Unit> allActiveUnits = new List<Unit>();
private readonly List<Unit> playerArmyUnits = new List<Unit>();
private readonly List<Unit> enemyArmyUnits = new List<Unit>();
public List<Unit> AllActiveUnits => allActiveUnits;
public List<Unit> PlayerArmyUnits => playerArmyUnits;
public List<Unit> 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<BattleState> OnBattleStateChanged;
public event Action<int, int> OnUnitCountsChanged; // playerAlive, enemyAlive
public event Action<int> OnGoldChanged;
public event Action<int> OnWaveChanged;
public event Action<BattleResultData> 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<Unit>();
if (unit == null) unit = instance.AddComponent<Unit>();
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<Unit>();
if (enemyUnit == null) enemyUnit = instance.AddComponent<Unit>();
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<UnitLevelUpReport> levelUps = new List<UnitLevelUpReport>();
// 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<UnitLevelUpReport> levelUpReports;
}
[System.Serializable]
public class UnitLevelUpReport
{
public string unitName;
public int oldLevel;
public int newLevel;
}
}