using System.Collections.Generic; using GameFramework.DataTable; using GeometryTD.CustomEvent; using GeometryTD.DataTable; using GeometryTD.Definition; using GeometryTD.Procedure; using UnityEngine; using UnityGameFramework.Runtime; namespace GeometryTD.CustomComponent { /// /// 鎴樻枟鑺傜偣缁勪欢 /// public class CombatNodeComponent : GameFrameworkComponent { // Level.Id => Level private readonly Dictionary _levelsById = new(); // LevelId => LevelPhases private readonly Dictionary> _phasesByLevelId = new(); // LevelPhase.Id => LevelSpawnEntries private readonly Dictionary> _spawnEntriesByPhaseId = new(); private readonly Dictionary> _selectedSpawnEntriesByPhaseId = new(); private readonly List _levelIdBuffer = new(); private readonly CombatScheduler _combatScheduler = new CombatScheduler(); private bool _runtimeInitialized; private bool _isCombatActive; private bool _lastCombatSucceeded = true; public LevelThemeType CurrentThemeType { get; private set; } public DRLevel CurrentLevel { get; private set; } public int CurrentCoin => _isCombatActive ? _combatScheduler.CurrentCoin : 0; public int CurrentGold => _isCombatActive ? _combatScheduler.CurrentGold : 0; public int CurrentBaseHp => _isCombatActive ? _combatScheduler.CurrentBaseHp : 0; public bool LastCombatSucceeded => _lastCombatSucceeded; public bool CanEndCombat => _combatScheduler.CanEndCombat; public int CurrentBuildTowerCount => _isCombatActive ? _combatScheduler.CurrentBuildTowerCount : 0; public int LastDefeatedEnemyCount { get; private set; } public int LastGainedCoin { get; private set; } public int LastGainedGold { get; private set; } public int CurrentLevelPhaseCount { get { if (CurrentLevel == null) { return 0; } if (!_phasesByLevelId.TryGetValue(CurrentLevel.Id, out List phases) || phases == null) { return 0; } return phases.Count; } } public int CurrentPhaseIndex { get { return _combatScheduler.DisplayPhaseIndex; } } public void OnInit(LevelThemeType themeType) { ShutdownCombatRuntime(); CurrentThemeType = themeType; CurrentLevel = null; _isCombatActive = false; _lastCombatSucceeded = true; LastDefeatedEnemyCount = 0; LastGainedCoin = 0; LastGainedGold = 0; _levelsById.Clear(); _phasesByLevelId.Clear(); _spawnEntriesByPhaseId.Clear(); _selectedSpawnEntriesByPhaseId.Clear(); _levelIdBuffer.Clear(); IDataTable dtLevel = GameEntry.DataTable.GetDataTable(); IDataTable dtLevelPhase = GameEntry.DataTable.GetDataTable(); IDataTable dtSpawnEntry = GameEntry.DataTable.GetDataTable(); if (dtLevel == null || dtLevelPhase == null || dtSpawnEntry == null) { Log.Warning("CombatNodeComponent init failed. Missing data table(s)."); return; } DRLevel[] levels = dtLevel.GetAllDataRows(); for (int i = 0; i < levels.Length; i++) { DRLevel level = levels[i]; if (level.LevelThemeType != themeType) { continue; } _levelsById[level.Id] = level; _phasesByLevelId[level.Id] = new List(); _levelIdBuffer.Add(level.Id); } DRLevelPhase[] levelPhases = dtLevelPhase.GetAllDataRows(); foreach (var phase in levelPhases) { int levelId = phase.Id / 1000; if (!_levelsById.ContainsKey(levelId)) { continue; } if (!_phasesByLevelId.TryGetValue(levelId, out List phases)) { phases = new List(); _phasesByLevelId[levelId] = phases; } phases.Add(phase); _spawnEntriesByPhaseId[phase.Id] = new List(); } DRLevelSpawnEntry[] spawnEntries = dtSpawnEntry.GetAllDataRows(); foreach (var spawnEntry in spawnEntries) { int phaseId = spawnEntry.Id / 1000; int levelId = phaseId / 1000; if (!_levelsById.ContainsKey(levelId)) { continue; } if (!_spawnEntriesByPhaseId.TryGetValue(phaseId, out List entries)) { entries = new List(); _spawnEntriesByPhaseId[phaseId] = entries; } entries.Add(spawnEntry); } foreach (List phaseList in _phasesByLevelId.Values) { phaseList.Sort((left, right) => left.Id.CompareTo(right.Id)); } foreach (List phaseSpawnEntries in _spawnEntriesByPhaseId.Values) { phaseSpawnEntries.Sort((left, right) => { int timeCompare = left.StartTime.CompareTo(right.StartTime); if (timeCompare != 0) { return timeCompare; } return left.Id.CompareTo(right.Id); }); } Log.Info( "CombatNodeComponent initialized. Theme={0}, Levels={1}, Phases={2}, Entries={3}.", themeType, _levelsById.Count, CountPhases(), CountEntries()); EnsureCombatRuntimeInitialized(); } public void StartCombat( int levelId = 0, RunNodeExecutionContext context = null) { if (!EnsureCombatRuntimeInitialized()) { Log.Warning("CombatNodeComponent start failed. Missing scheduler runtime."); return; } bool hasLevel = levelId > 0 ? TryGetLevelById(levelId, out DRLevel selectedLevel) : TrySelectRandomLevel(out selectedLevel); if (!hasLevel) { Log.Warning( "CombatNodeComponent start failed. LevelId={0}. Missing cached level data or invalid level id.", levelId); return; } if (!_phasesByLevelId.TryGetValue(selectedLevel.Id, out List phaseList) || phaseList == null || phaseList.Count <= 0) { Log.Warning("CombatNodeComponent start failed. Level '{0}' has no phase data.", selectedLevel.Id); return; } _selectedSpawnEntriesByPhaseId.Clear(); foreach (var phase in phaseList) { if (_spawnEntriesByPhaseId.TryGetValue(phase.Id, out List entries) && entries != null) { _selectedSpawnEntriesByPhaseId[phase.Id] = entries; } else { _selectedSpawnEntriesByPhaseId[phase.Id] = new List(); } } CurrentLevel = selectedLevel; _isCombatActive = false; _lastCombatSucceeded = true; LastDefeatedEnemyCount = 0; LastGainedCoin = 0; LastGainedGold = 0; if (!_combatScheduler.Start( selectedLevel, phaseList, _selectedSpawnEntriesByPhaseId, context)) { CurrentLevel = null; return; } _isCombatActive = true; } public void EndCombat() { if (!_isCombatActive) { return; } _isCombatActive = false; LastDefeatedEnemyCount = _combatScheduler.DefeatedEnemyCount; LastGainedCoin = _combatScheduler.GainedCoin; LastGainedGold = _combatScheduler.GainedGold; CurrentLevel = null; } public bool TryEndCombatByPlayer() { return _combatScheduler.TryEndCombatByPlayer(); } public void OnUpdate(float elapseSeconds, float realElapseSeconds) { if (!_runtimeInitialized) { return; } _combatScheduler.OnUpdate(elapseSeconds, realElapseSeconds); } public void OnShutdown() { _isCombatActive = false; CurrentLevel = null; _lastCombatSucceeded = true; LastDefeatedEnemyCount = 0; LastGainedCoin = 0; LastGainedGold = 0; ShutdownCombatRuntime(); } public void OnCombatEndedByScheduler(bool succeeded) { _lastCombatSucceeded = succeeded; EndCombat(); } public bool TryConsumeCoin(int coin) { if (Mathf.Max(0, coin) <= 0) { return true; } if (!_isCombatActive) { return false; } return _combatScheduler.TryConsumeCoin(coin); } public bool TryGetBuildTowerStats(int buildIndex, out TowerStatsData stats) { if (!_isCombatActive) { stats = null; return false; } return _combatScheduler.TryGetBuildTowerStats(buildIndex, out stats); } public void AddCoin(int coin) { if (!_isCombatActive) { return; } _combatScheduler.AddCoin(coin); } private void OnDestroy() { OnShutdown(); } private bool EnsureCombatRuntimeInitialized() { if (_runtimeInitialized) { return true; } _combatScheduler.OnInit(OnCombatEndedByScheduler); _runtimeInitialized = true; return true; } private void ShutdownCombatRuntime() { if (!_runtimeInitialized) { return; } _combatScheduler.OnDestroy(); _runtimeInitialized = false; } private bool TrySelectRandomLevel(out DRLevel level) { level = null; if (_levelIdBuffer.Count <= 0) { return false; } int selectedIndex = Random.Range(0, _levelIdBuffer.Count); int selectedLevelId = _levelIdBuffer[selectedIndex]; return _levelsById.TryGetValue(selectedLevelId, out level); } private bool TryGetLevelById(int levelId, out DRLevel level) { level = null; if (levelId <= 0) { return false; } return _levelsById.TryGetValue(levelId, out level); } private int CountPhases() { int count = 0; foreach (List phases in _phasesByLevelId.Values) { count += phases.Count; } return count; } private int CountEntries() { int count = 0; foreach (List list in _spawnEntriesByPhaseId.Values) { count += list.Count; } return count; } } }