320 lines
11 KiB
C#
320 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using GeometryTD.CustomEvent;
|
|
using GeometryTD.DataTable;
|
|
using GeometryTD.Definition;
|
|
using GeometryTD.Entity;
|
|
using GeometryTD.UI;
|
|
using UnityEngine;
|
|
using UnityGameFramework.Runtime;
|
|
|
|
namespace GeometryTD.CustomComponent
|
|
{
|
|
public partial class CombatScheduler : ICombatSchedulerHost
|
|
{
|
|
private readonly CombatSchedulerRuntimeContext _context = new();
|
|
private readonly CombatSchedulerFlowCoordinator _flowCoordinator;
|
|
|
|
private bool _initialized;
|
|
|
|
public CombatScheduler()
|
|
{
|
|
_flowCoordinator = new CombatSchedulerFlowCoordinator(this, _context);
|
|
}
|
|
|
|
public bool IsRunning =>
|
|
_context.CurrentState is CombatLoadingState or CombatRunningPhaseState or CombatWaitingForPhaseEndState;
|
|
|
|
public bool IsCompleted => _context.IsCompleted;
|
|
public DRLevel CurrentLevel => _context.CurrentLevel;
|
|
public DRLevelPhase CurrentPhase => _context.PhaseLoopRuntime.CurrentPhase;
|
|
public MapEntity CurrentMap => _context.LoadSession.CurrentMap;
|
|
public int DisplayPhaseIndex => _context.PhaseLoopRuntime.DisplayPhaseIndex;
|
|
public int PhaseCount => _context.PhaseLoopRuntime.PhaseCount;
|
|
public bool CanEndCombat => _context.PhaseLoopRuntime.CanEndCombat;
|
|
public int CurrentCoin => _context.CombatInRunResourceManager.CurrentCoin;
|
|
public int CurrentGold => _context.CombatInRunResourceManager.CurrentGold;
|
|
public int CurrentBaseHp => _context.CombatInRunResourceManager.CurrentBaseHp;
|
|
public int CurrentBuildTowerCount => _context.CombatInRunResourceManager.CurrentBuildTowerCount;
|
|
public int DefeatedEnemyCount => _context.EnemyManager.DefeatedEnemyCount;
|
|
public int GainedCoin => _context.CombatInRunResourceManager.GainedCoin;
|
|
public int GainedGold => _context.CombatInRunResourceManager.GainedGold;
|
|
|
|
public void OnInit(Action<bool> combatEndedCallback)
|
|
{
|
|
_context.CombatEndedCallback = combatEndedCallback;
|
|
|
|
if (!_initialized)
|
|
{
|
|
_context.Entity = GameEntry.Entity;
|
|
_context.EventBridge.Bind(
|
|
OnShowEntitySuccess,
|
|
OnShowEntityFailure,
|
|
OnHideEntityComplete,
|
|
OnOpenUIFormSuccess,
|
|
OnOpenUIFormFailure,
|
|
OnCloseUIFormComplete);
|
|
_context.EnemyManager.OnInit(this);
|
|
_context.LoadSession.OnInit(_context.Entity);
|
|
_flowCoordinator.EnsureCombatFinishFormUseCaseBound();
|
|
_flowCoordinator.EnsureRewardSelectFormUseCaseBound();
|
|
_initialized = true;
|
|
}
|
|
|
|
_flowCoordinator.ResetRuntime();
|
|
}
|
|
|
|
public bool Start(
|
|
DRLevel level,
|
|
IReadOnlyList<DRLevelPhase> phases,
|
|
IReadOnlyDictionary<int, IReadOnlyList<DRLevelSpawnEntry>> spawnEntriesByPhaseId)
|
|
{
|
|
if (!_initialized || _context.Entity == null)
|
|
{
|
|
return _flowCoordinator.HandleStartFailure("CombatScheduler start failed. Runtime is not initialized.");
|
|
}
|
|
|
|
if (level == null || phases == null || phases.Count <= 0)
|
|
{
|
|
return _flowCoordinator.HandleStartFailure("CombatScheduler start failed. Invalid level or phase data.");
|
|
}
|
|
|
|
_flowCoordinator.CleanupAllCombatEntities();
|
|
_flowCoordinator.CloseCombatFinishForm();
|
|
_flowCoordinator.CloseRewardSelectForm();
|
|
_flowCoordinator.CloseDialogForm();
|
|
_context.EnemyManager.EndPhase();
|
|
_context.EnemyManager.ResetCombatStats();
|
|
_flowCoordinator.ResetRuntime();
|
|
_context.IsFinishAsVictory = true;
|
|
|
|
_context.CurrentLevel = level;
|
|
_context.CombatInRunResourceManager.InitializeForCombat(level);
|
|
for (int i = 0; i < phases.Count; i++)
|
|
{
|
|
DRLevelPhase phase = phases[i];
|
|
if (phase != null)
|
|
{
|
|
_context.PhaseBuffer.Add(phase);
|
|
}
|
|
}
|
|
|
|
if (spawnEntriesByPhaseId != null)
|
|
{
|
|
foreach (var pair in spawnEntriesByPhaseId)
|
|
{
|
|
_context.SpawnEntriesByPhaseId[pair.Key] = pair.Value;
|
|
}
|
|
}
|
|
|
|
_context.PhaseLoopRuntime.SetPhases(_context.PhaseBuffer);
|
|
if (_context.PhaseLoopRuntime.PhaseCount <= 0)
|
|
{
|
|
return _flowCoordinator.HandleStartFailure($"CombatScheduler start failed. Level '{level.Id}' has no phase data.");
|
|
}
|
|
|
|
ChangeState(new CombatLoadingState(_context, _flowCoordinator));
|
|
Log.Info(
|
|
"CombatScheduler started. Level={0}, PhaseCount={1}.",
|
|
_context.CurrentLevel.Id,
|
|
_context.PhaseLoopRuntime.PhaseCount);
|
|
return true;
|
|
}
|
|
|
|
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
|
{
|
|
_context.CurrentState?.OnUpdate(elapseSeconds, realElapseSeconds);
|
|
}
|
|
|
|
public void OnDestroy()
|
|
{
|
|
if (!_initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_context.CurrentState?.OnExit();
|
|
_context.CurrentState?.OnDestroy();
|
|
_context.CurrentState = null;
|
|
_flowCoordinator.CleanupAllCombatEntities();
|
|
_flowCoordinator.CloseCombatFinishForm();
|
|
_flowCoordinator.CloseRewardSelectForm();
|
|
_flowCoordinator.CloseDialogForm();
|
|
_context.EnemyManager.OnDestroy();
|
|
_flowCoordinator.ResetRuntime();
|
|
_context.EventBridge.Unbind();
|
|
_context.CombatFinishFormUseCase = null;
|
|
_context.RewardSelectFormUseCase = null;
|
|
_context.CombatEndedCallback = null;
|
|
|
|
_context.Entity = null;
|
|
_initialized = false;
|
|
}
|
|
|
|
public bool TryEndCombatByPlayer()
|
|
{
|
|
if (_context.CurrentState is not CombatRunningPhaseState &&
|
|
_context.CurrentState is not CombatWaitingForPhaseEndState)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return _context.PhaseLoopRuntime.TryRequestEndCombat();
|
|
}
|
|
|
|
public void OnEnemyReachedBase(DREnemy enemy)
|
|
{
|
|
if (!IsRunning)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int resolvedBaseDamage = enemy != null ? Mathf.Max(0, enemy.BaseDamage) : 0;
|
|
if (resolvedBaseDamage <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_flowCoordinator.ApplyBaseDamage(resolvedBaseDamage);
|
|
}
|
|
|
|
public void OnEnemyDefeated(DREnemy enemy)
|
|
{
|
|
if (!IsRunning)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EnemyDropResolveContext context = new(
|
|
enemy,
|
|
_context.PhaseLoopRuntime.DisplayPhaseIndex,
|
|
_flowCoordinator.ResolveCurrentThemeType());
|
|
EnemyDropResolveResult result = _context.EnemyDropResolver.Resolve(context);
|
|
_context.CombatInRunResourceManager.AddEnemyDefeatedReward(result.Coin, result.Gold);
|
|
_context.CombatInRunResourceManager.AddEnemyDefeatedLoot(result.LootItem);
|
|
}
|
|
|
|
public bool OnCombatFinishReturnRequested()
|
|
{
|
|
if (_context.CurrentState is not CombatWaitingForReturnState waitingForReturnState)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
waitingForReturnState.RequestReturn();
|
|
return true;
|
|
}
|
|
|
|
public bool TryConsumeCoin(int coin)
|
|
{
|
|
return _context.CombatInRunResourceManager.TryConsumeCoin(coin);
|
|
}
|
|
|
|
public void AddCoin(int coin)
|
|
{
|
|
_context.CombatInRunResourceManager.AddCoin(coin);
|
|
}
|
|
|
|
public bool TryGetBuildTowerStats(int buildIndex, out TowerStatsData stats)
|
|
{
|
|
return _context.CombatInRunResourceManager.TryGetBuildTowerStats(buildIndex, out stats);
|
|
}
|
|
|
|
public bool TryDebugFail(string errorMessage)
|
|
{
|
|
if (_context.IsCompleted || _context.CurrentState == null || _context.CurrentState is CombatFailedState)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_flowCoordinator.EnterFailureFallback(string.IsNullOrWhiteSpace(errorMessage)
|
|
? "Manual debug fail."
|
|
: errorMessage);
|
|
return _context.CurrentState is CombatFailedState;
|
|
}
|
|
|
|
void ICombatSchedulerHost.ChangeState(CombatStateBase nextState)
|
|
{
|
|
ChangeState(nextState);
|
|
}
|
|
|
|
internal void ChangeState(CombatStateBase nextState)
|
|
{
|
|
if (ReferenceEquals(_context.CurrentState, nextState))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_context.CurrentState?.OnExit();
|
|
_context.CurrentState?.OnDestroy();
|
|
_context.CurrentState = nextState;
|
|
_context.CurrentState?.OnInit();
|
|
_context.CurrentState?.OnEnter();
|
|
}
|
|
|
|
#region Event Handlers
|
|
|
|
private void OnShowEntitySuccess(ShowEntitySuccessEventArgs args)
|
|
{
|
|
var status = _context.LoadSession.HandleShowEntitySuccess(args, out string errorMessage);
|
|
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
|
{
|
|
_flowCoordinator.EnterFailureFallback(errorMessage);
|
|
return;
|
|
}
|
|
|
|
if (status == CombatLoadSession.EventHandleStatus.Succeeded)
|
|
{
|
|
MapEntity map = _context.LoadSession.CurrentMap;
|
|
Log.Info(
|
|
"Map ready. LevelId={0}, PathCells={1}, FoundationCells={2}, Spawners={3}, House={4}.",
|
|
_context.CurrentLevel != null ? _context.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 = _context.LoadSession.HandleShowEntityFailure(args, out string errorMessage);
|
|
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
|
{
|
|
_flowCoordinator.EnterFailureFallback(errorMessage);
|
|
}
|
|
}
|
|
|
|
private void OnHideEntityComplete(HideEntityCompleteEventArgs args)
|
|
{
|
|
_context.LoadSession.HandleHideEntityComplete(args);
|
|
}
|
|
|
|
private void OnOpenUIFormSuccess(OpenUIFormSuccessEventArgs args)
|
|
{
|
|
var status = _context.LoadSession.HandleOpenUIFormSuccess(args, out string errorMessage);
|
|
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
|
{
|
|
_flowCoordinator.EnterFailureFallback(errorMessage);
|
|
}
|
|
}
|
|
|
|
private void OnOpenUIFormFailure(OpenUIFormFailureEventArgs args)
|
|
{
|
|
var status = _context.LoadSession.HandleOpenUIFormFailure(args, out string errorMessage);
|
|
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
|
{
|
|
_flowCoordinator.EnterFailureFallback(errorMessage);
|
|
}
|
|
}
|
|
|
|
private void OnCloseUIFormComplete(CloseUIFormCompleteEventArgs args)
|
|
{
|
|
_context.LoadSession.HandleCloseUIFormComplete(args);
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
}
|