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