361 lines
11 KiB
C#
361 lines
11 KiB
C#
using System.Collections.Generic;
|
|
using GameFramework.DataTable;
|
|
using GeometryTD.CustomEvent;
|
|
using GeometryTD.DataTable;
|
|
using GeometryTD.Definition;
|
|
using UnityEngine;
|
|
using UnityGameFramework.Runtime;
|
|
|
|
namespace GeometryTD.CustomComponent
|
|
{
|
|
/// <summary>
|
|
/// 鎴樻枟鑺傜偣缁勪欢
|
|
/// </summary>
|
|
public class CombatNodeComponent : GameFrameworkComponent
|
|
{
|
|
// Level.Id => Level
|
|
private readonly Dictionary<int, DRLevel> _levelsById = new();
|
|
|
|
// LevelId => LevelPhases
|
|
private readonly Dictionary<int, List<DRLevelPhase>> _phasesByLevelId = new();
|
|
|
|
// LevelPhase.Id => LevelSpawnEntries
|
|
private readonly Dictionary<int, List<DRLevelSpawnEntry>> _spawnEntriesByPhaseId = new();
|
|
private readonly Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>> _selectedSpawnEntriesByPhaseId = new();
|
|
private readonly List<int> _levelIdBuffer = new();
|
|
private readonly CombatScheduler _combatScheduler = new CombatScheduler();
|
|
|
|
private bool _runtimeInitialized;
|
|
private int _currentCoin;
|
|
private int _currentGold;
|
|
|
|
public LevelThemeType CurrentThemeType { get; private set; }
|
|
public DRLevel CurrentLevel { get; private set; }
|
|
public int CurrentCoin => _currentCoin;
|
|
public int CurrentGold => _currentGold;
|
|
public bool CanEndCombat => _combatScheduler.CanEndCombat;
|
|
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<DRLevelPhase> 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;
|
|
_currentCoin = 0;
|
|
_currentGold = 0;
|
|
LastDefeatedEnemyCount = 0;
|
|
LastGainedCoin = 0;
|
|
LastGainedGold = 0;
|
|
_levelsById.Clear();
|
|
_phasesByLevelId.Clear();
|
|
_spawnEntriesByPhaseId.Clear();
|
|
_selectedSpawnEntriesByPhaseId.Clear();
|
|
_levelIdBuffer.Clear();
|
|
|
|
IDataTable<DRLevel> dtLevel = GameEntry.DataTable.GetDataTable<DRLevel>();
|
|
IDataTable<DRLevelPhase> dtLevelPhase = GameEntry.DataTable.GetDataTable<DRLevelPhase>();
|
|
IDataTable<DRLevelSpawnEntry> dtSpawnEntry = GameEntry.DataTable.GetDataTable<DRLevelSpawnEntry>();
|
|
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<DRLevelPhase>();
|
|
_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<DRLevelPhase> phases))
|
|
{
|
|
phases = new List<DRLevelPhase>();
|
|
_phasesByLevelId[levelId] = phases;
|
|
}
|
|
|
|
phases.Add(phase);
|
|
_spawnEntriesByPhaseId[phase.Id] = new List<DRLevelSpawnEntry>();
|
|
}
|
|
|
|
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<DRLevelSpawnEntry> entries))
|
|
{
|
|
entries = new List<DRLevelSpawnEntry>();
|
|
_spawnEntriesByPhaseId[phaseId] = entries;
|
|
}
|
|
|
|
entries.Add(spawnEntry);
|
|
}
|
|
|
|
foreach (List<DRLevelPhase> phaseList in _phasesByLevelId.Values)
|
|
{
|
|
phaseList.Sort((left, right) => left.Id.CompareTo(right.Id));
|
|
}
|
|
|
|
foreach (List<DRLevelSpawnEntry> 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()
|
|
{
|
|
if (!EnsureCombatRuntimeInitialized())
|
|
{
|
|
Log.Warning("CombatNodeComponent start failed. Missing scheduler runtime.");
|
|
return;
|
|
}
|
|
|
|
if (!TrySelectRandomLevel(out DRLevel selectedLevel))
|
|
{
|
|
Log.Warning("CombatNodeComponent has no level cache. Call OnInit(levelThemeType) first.");
|
|
return;
|
|
}
|
|
|
|
if (!_phasesByLevelId.TryGetValue(selectedLevel.Id, out List<DRLevelPhase> 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<DRLevelSpawnEntry> entries) &&
|
|
entries != null)
|
|
{
|
|
_selectedSpawnEntriesByPhaseId[phase.Id] = entries;
|
|
}
|
|
else
|
|
{
|
|
_selectedSpawnEntriesByPhaseId[phase.Id] = new List<DRLevelSpawnEntry>();
|
|
}
|
|
}
|
|
|
|
CurrentLevel = selectedLevel;
|
|
_currentCoin = Mathf.Max(0, selectedLevel.StartCoin);
|
|
_currentGold = 0;
|
|
LastDefeatedEnemyCount = 0;
|
|
LastGainedCoin = 0;
|
|
LastGainedGold = 0;
|
|
_combatScheduler.Start(selectedLevel, phaseList, _selectedSpawnEntriesByPhaseId);
|
|
GameEntry.Event.Fire(this, NodeEnterEventArgs.Create());
|
|
}
|
|
|
|
public void EndCombat()
|
|
{
|
|
LastDefeatedEnemyCount = _combatScheduler.DefeatedEnemyCount;
|
|
LastGainedCoin = _combatScheduler.GainedCoin;
|
|
LastGainedGold = _combatScheduler.GainedGold;
|
|
_currentCoin = 0;
|
|
_currentGold = 0;
|
|
GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create());
|
|
}
|
|
|
|
public bool TryEndCombatByPlayer()
|
|
{
|
|
return _combatScheduler.TryEndCombatByPlayer();
|
|
}
|
|
|
|
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
|
{
|
|
if (!_runtimeInitialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_combatScheduler.OnUpdate(elapseSeconds, realElapseSeconds);
|
|
}
|
|
|
|
public void OnShutdown()
|
|
{
|
|
CurrentLevel = null;
|
|
_currentCoin = 0;
|
|
_currentGold = 0;
|
|
LastDefeatedEnemyCount = 0;
|
|
LastGainedCoin = 0;
|
|
LastGainedGold = 0;
|
|
ShutdownCombatRuntime();
|
|
}
|
|
|
|
public void ApplyEnemyDropReward(int coin, int gold)
|
|
{
|
|
if (coin > 0)
|
|
{
|
|
_currentCoin += coin;
|
|
}
|
|
|
|
if (gold > 0)
|
|
{
|
|
_currentGold += gold;
|
|
}
|
|
}
|
|
|
|
public bool TryConsumeCoin(int coin)
|
|
{
|
|
int requiredCoin = Mathf.Max(0, coin);
|
|
if (requiredCoin <= 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (_currentCoin < requiredCoin)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_currentCoin -= requiredCoin;
|
|
return true;
|
|
}
|
|
|
|
public void AddCoin(int coin)
|
|
{
|
|
int gainCoin = Mathf.Max(0, coin);
|
|
if (gainCoin <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_currentCoin += gainCoin;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
OnShutdown();
|
|
}
|
|
|
|
private bool EnsureCombatRuntimeInitialized()
|
|
{
|
|
if (_runtimeInitialized)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
_combatScheduler.OnInit();
|
|
_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 int CountPhases()
|
|
{
|
|
int count = 0;
|
|
foreach (List<DRLevelPhase> phases in _phasesByLevelId.Values)
|
|
{
|
|
count += phases.Count;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
private int CountEntries()
|
|
{
|
|
int count = 0;
|
|
foreach (List<DRLevelSpawnEntry> list in _spawnEntriesByPhaseId.Values)
|
|
{
|
|
count += list.Count;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
}
|
|
}
|