327 lines
11 KiB
C#
327 lines
11 KiB
C#
using System.Collections.Generic;
|
|
using GeometryTD.DataTable;
|
|
using GeometryTD.Entity;
|
|
using UnityGameFramework.Runtime;
|
|
|
|
namespace GeometryTD.CustomComponent
|
|
{
|
|
public class CombatScheduler
|
|
{
|
|
private enum SchedulerState : byte
|
|
{
|
|
Idle = 0,
|
|
WaitingForLoading = 1,
|
|
RunningPhase = 2,
|
|
Completed = 3,
|
|
Failed = 4
|
|
}
|
|
|
|
private readonly List<DRLevelPhase> _phaseBuffer = new();
|
|
private readonly Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>> _spawnEntriesByPhaseId = new();
|
|
private readonly EnemyManager _enemyManager = new();
|
|
private readonly PhaseLoopRuntime _phaseLoopRuntime = new();
|
|
private readonly CombatLoadSession _loadSession = new();
|
|
private readonly CombatEventBridge _eventBridge = new();
|
|
|
|
private EntityComponent _entity;
|
|
private DRLevel _currentLevel;
|
|
private SchedulerState _state = SchedulerState.Idle;
|
|
private bool _initialized;
|
|
|
|
public bool IsRunning => _state == SchedulerState.WaitingForLoading || _state == SchedulerState.RunningPhase;
|
|
public bool IsCompleted => _state == SchedulerState.Completed;
|
|
public DRLevel CurrentLevel => _currentLevel;
|
|
public DRLevelPhase CurrentPhase => _phaseLoopRuntime.CurrentPhase;
|
|
public MapEntity CurrentMap => _loadSession.CurrentMap;
|
|
public int DisplayPhaseIndex => _phaseLoopRuntime.DisplayPhaseIndex;
|
|
public bool CanEndCombat => _phaseLoopRuntime.CanEndCombat;
|
|
|
|
public void OnInit()
|
|
{
|
|
if (!_initialized)
|
|
{
|
|
_entity = GameEntry.Entity;
|
|
_eventBridge.Bind(
|
|
OnShowEntitySuccess,
|
|
OnShowEntityFailure,
|
|
OnHideEntityComplete,
|
|
OnOpenUIFormSuccess,
|
|
OnOpenUIFormFailure,
|
|
OnCloseUIFormComplete);
|
|
_enemyManager.OnInit(this);
|
|
_loadSession.OnInit(_entity);
|
|
_initialized = true;
|
|
}
|
|
|
|
ResetRuntime();
|
|
}
|
|
|
|
public void Start(
|
|
DRLevel level,
|
|
IReadOnlyList<DRLevelPhase> phases,
|
|
IReadOnlyDictionary<int, IReadOnlyList<DRLevelSpawnEntry>> spawnEntriesByPhaseId)
|
|
{
|
|
if (!_initialized || _entity == null)
|
|
{
|
|
_state = SchedulerState.Failed;
|
|
Log.Warning("CombatScheduler start failed. Runtime is not initialized.");
|
|
return;
|
|
}
|
|
|
|
if (level == null || phases == null || phases.Count <= 0)
|
|
{
|
|
_state = SchedulerState.Failed;
|
|
Log.Warning("CombatScheduler start failed. Invalid level or phase data.");
|
|
return;
|
|
}
|
|
|
|
CleanupCombatEntities();
|
|
_enemyManager.EndPhase();
|
|
ResetRuntime();
|
|
|
|
_currentLevel = level;
|
|
for (int i = 0; i < phases.Count; i++)
|
|
{
|
|
DRLevelPhase phase = phases[i];
|
|
if (phase != null)
|
|
{
|
|
_phaseBuffer.Add(phase);
|
|
}
|
|
}
|
|
|
|
if (spawnEntriesByPhaseId != null)
|
|
{
|
|
foreach (var pair in spawnEntriesByPhaseId)
|
|
{
|
|
_spawnEntriesByPhaseId[pair.Key] = pair.Value;
|
|
}
|
|
}
|
|
|
|
_phaseLoopRuntime.SetPhases(_phaseBuffer);
|
|
if (_phaseLoopRuntime.PhaseCount <= 0)
|
|
{
|
|
_state = SchedulerState.Failed;
|
|
Log.Warning("CombatScheduler start failed. Level '{0}' has no phase data.", level.Id);
|
|
return;
|
|
}
|
|
|
|
if (!_loadSession.StartLoading(level, out string loadError))
|
|
{
|
|
_state = SchedulerState.Failed;
|
|
Log.Warning("CombatScheduler start failed. {0}", loadError);
|
|
return;
|
|
}
|
|
|
|
_state = SchedulerState.WaitingForLoading;
|
|
Log.Info("CombatScheduler started. Level={0}, PhaseCount={1}.", _currentLevel.Id, _phaseLoopRuntime.PhaseCount);
|
|
}
|
|
|
|
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
|
{
|
|
switch (_state)
|
|
{
|
|
case SchedulerState.WaitingForLoading:
|
|
if (!_loadSession.IsReady)
|
|
{
|
|
return;
|
|
}
|
|
|
|
BeginNextPhase();
|
|
return;
|
|
case SchedulerState.RunningPhase:
|
|
UpdateCurrentPhase(elapseSeconds, realElapseSeconds);
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
public void OnDestroy()
|
|
{
|
|
if (!_initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CleanupCombatEntities();
|
|
_enemyManager.OnDestroy();
|
|
ResetRuntime();
|
|
_eventBridge.Unbind();
|
|
_entity = null;
|
|
_initialized = false;
|
|
}
|
|
|
|
public bool TryEndCombatByPlayer()
|
|
{
|
|
if (_state == SchedulerState.Completed || _state == SchedulerState.Failed || _state == SchedulerState.Idle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!_phaseLoopRuntime.TryRequestEndCombat())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FinishCombat("Combat ended by player.");
|
|
return true;
|
|
}
|
|
|
|
private void RefreshCombatInfoForm()
|
|
{
|
|
_loadSession.RefreshCombatInfoForm(_currentLevel);
|
|
}
|
|
|
|
private void UpdateCurrentPhase(float elapseSeconds, float realElapseSeconds)
|
|
{
|
|
DRLevelPhase currentPhase = _phaseLoopRuntime.CurrentPhase;
|
|
if (currentPhase == null)
|
|
{
|
|
_state = SchedulerState.Failed;
|
|
Log.Warning("CombatScheduler update failed. Current phase is null.");
|
|
return;
|
|
}
|
|
|
|
_phaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
|
|
_enemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
|
|
|
|
if (!_phaseLoopRuntime.ShouldEndCurrentPhase(_enemyManager.IsPhaseSpawnCompleted, _enemyManager.AliveEnemyCount))
|
|
{
|
|
return;
|
|
}
|
|
|
|
CompleteCurrentPhase();
|
|
}
|
|
|
|
private void CompleteCurrentPhase()
|
|
{
|
|
_enemyManager.EndPhase();
|
|
Log.Info(
|
|
"CombatScheduler phase completed. Level={0}, Phase={1}, Elapsed={2:F2}s.",
|
|
_currentLevel != null ? _currentLevel.Id : 0,
|
|
_phaseLoopRuntime.CurrentPhase != null ? _phaseLoopRuntime.CurrentPhase.Id : 0,
|
|
_phaseLoopRuntime.CurrentPhaseElapsed);
|
|
|
|
BeginNextPhase();
|
|
}
|
|
|
|
private void BeginNextPhase()
|
|
{
|
|
if (!_phaseLoopRuntime.TryEnterNextPhase(out DRLevelPhase nextPhase))
|
|
{
|
|
FinishCombat("Combat ended after loop completion.");
|
|
return;
|
|
}
|
|
|
|
RefreshCombatInfoForm();
|
|
|
|
_spawnEntriesByPhaseId.TryGetValue(nextPhase.Id, out IReadOnlyList<DRLevelSpawnEntry> spawnEntries);
|
|
_enemyManager.BeginPhase(nextPhase, spawnEntries);
|
|
_state = SchedulerState.RunningPhase;
|
|
|
|
Log.Info(
|
|
"CombatScheduler phase started. Level={0}, Phase={1}, EndType={2}, Entries={3}.",
|
|
_currentLevel != null ? _currentLevel.Id : 0,
|
|
_phaseLoopRuntime.DisplayPhaseIndex,
|
|
nextPhase.EndType,
|
|
spawnEntries != null ? spawnEntries.Count : 0);
|
|
}
|
|
|
|
private void FinishCombat(string reason)
|
|
{
|
|
_state = SchedulerState.Completed;
|
|
CleanupCombatEntities();
|
|
_enemyManager.EndPhase();
|
|
|
|
Log.Info(
|
|
"CombatScheduler level completed. Level={0}. Reason={1}",
|
|
_currentLevel != null ? _currentLevel.Id : 0,
|
|
reason);
|
|
GameEntry.CombatNode.EndCombat();
|
|
}
|
|
|
|
private void ResetRuntime()
|
|
{
|
|
_phaseBuffer.Clear();
|
|
_spawnEntriesByPhaseId.Clear();
|
|
_phaseLoopRuntime.Reset();
|
|
_loadSession.Reset();
|
|
_currentLevel = null;
|
|
_state = SchedulerState.Idle;
|
|
}
|
|
|
|
private void CleanupCombatEntities()
|
|
{
|
|
_loadSession.Cleanup();
|
|
_enemyManager.CleanupTrackedEnemies();
|
|
}
|
|
|
|
#region Event Handlers
|
|
|
|
private void OnShowEntitySuccess(ShowEntitySuccessEventArgs args)
|
|
{
|
|
var status = _loadSession.HandleShowEntitySuccess(args, out string errorMessage);
|
|
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
|
{
|
|
_state = SchedulerState.Failed;
|
|
Log.Error("CombatScheduler failed. {0}", errorMessage);
|
|
return;
|
|
}
|
|
|
|
if (status == CombatLoadSession.EventHandleStatus.Succeeded)
|
|
{
|
|
MapEntity map = _loadSession.CurrentMap;
|
|
Log.Info(
|
|
"Map ready. LevelId={0}, PathCells={1}, FoundationCells={2}, Spawners={3}, House={4}.",
|
|
_currentLevel != null ? _currentLevel.Id : 0,
|
|
map.PathCells.Count,
|
|
map.FoundationCells.Count,
|
|
map.Spawners.Length,
|
|
map.House != null ? map.House.name : "None");
|
|
}
|
|
}
|
|
|
|
private void OnShowEntityFailure(ShowEntityFailureEventArgs args)
|
|
{
|
|
var status = _loadSession.HandleShowEntityFailure(args, out string errorMessage);
|
|
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
|
{
|
|
_state = SchedulerState.Failed;
|
|
Log.Error("CombatScheduler failed. LevelId={0}, {1}", _currentLevel != null ? _currentLevel.Id : 0, errorMessage);
|
|
}
|
|
}
|
|
|
|
private void OnHideEntityComplete(HideEntityCompleteEventArgs args)
|
|
{
|
|
_loadSession.HandleHideEntityComplete(args);
|
|
}
|
|
|
|
private void OnOpenUIFormSuccess(OpenUIFormSuccessEventArgs args)
|
|
{
|
|
var status = _loadSession.HandleOpenUIFormSuccess(args, out string errorMessage);
|
|
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
|
{
|
|
_state = SchedulerState.Failed;
|
|
Log.Error("CombatScheduler failed. {0}", errorMessage);
|
|
}
|
|
}
|
|
|
|
private void OnOpenUIFormFailure(OpenUIFormFailureEventArgs args)
|
|
{
|
|
var status = _loadSession.HandleOpenUIFormFailure(args, out string errorMessage);
|
|
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
|
{
|
|
_state = SchedulerState.Failed;
|
|
Log.Error("CombatScheduler failed. {0}", errorMessage);
|
|
}
|
|
}
|
|
|
|
private void OnCloseUIFormComplete(CloseUIFormCompleteEventArgs args)
|
|
{
|
|
_loadSession.HandleCloseUIFormComplete(args);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|