780 lines
27 KiB
C#
780 lines
27 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 class CombatScheduler
|
|
{
|
|
private const int RewardSelectDisplayCount = 3;
|
|
private const float FullBaseHpGoldBonusRate = 0.3f;
|
|
private const float HighBaseHpGoldBonusRate = 0.1f;
|
|
private const float HighBaseHpThreshold = 0.8f;
|
|
private const float MidBaseHpThreshold = 0.5f;
|
|
private const float LowBaseHpTowerEndurancePenalty = 10f;
|
|
|
|
private enum SchedulerState : byte
|
|
{
|
|
Idle = 0,
|
|
WaitingForLoading = 1,
|
|
RunningPhase = 2,
|
|
WaitingForFinishReturn = 3,
|
|
Completed = 4,
|
|
Failed = 5
|
|
}
|
|
|
|
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 readonly CombatResourceManager _combatResourceManager = new();
|
|
|
|
private EntityComponent _entity;
|
|
private DRLevel _currentLevel;
|
|
private CombatFinishFormUseCase _combatFinishFormUseCase;
|
|
private RewardSelectFormUseCase _rewardSelectFormUseCase;
|
|
private SchedulerState _state = SchedulerState.Idle;
|
|
private bool _initialized;
|
|
private bool _isFinishAsVictory = true;
|
|
private int _pendingFinishDefeatedEnemyCount;
|
|
private int _pendingFinishGainedGold;
|
|
private BackpackInventoryData _pendingFinishRewardInventory;
|
|
private bool _hasPendingFinishSettlement;
|
|
|
|
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 int PhaseCount => _phaseLoopRuntime.PhaseCount;
|
|
public bool CanEndCombat => _phaseLoopRuntime.CanEndCombat;
|
|
public int DefeatedEnemyCount => _enemyManager.DefeatedEnemyCount;
|
|
public int GainedCoin => _combatResourceManager.GainedCoin;
|
|
public int GainedGold => _combatResourceManager.GainedGold;
|
|
|
|
public void OnInit()
|
|
{
|
|
if (!_initialized)
|
|
{
|
|
_entity = GameEntry.Entity;
|
|
_eventBridge.Bind(
|
|
OnShowEntitySuccess,
|
|
OnShowEntityFailure,
|
|
OnHideEntityComplete,
|
|
OnOpenUIFormSuccess,
|
|
OnOpenUIFormFailure,
|
|
OnCloseUIFormComplete);
|
|
_enemyManager.OnInit(this);
|
|
_loadSession.OnInit(_entity);
|
|
EnsureCombatFinishFormUseCaseBound();
|
|
EnsureRewardSelectFormUseCaseBound();
|
|
_initialized = true;
|
|
}
|
|
|
|
ResetRuntime();
|
|
}
|
|
|
|
public bool Start(
|
|
DRLevel level,
|
|
IReadOnlyList<DRLevelPhase> phases,
|
|
IReadOnlyDictionary<int, IReadOnlyList<DRLevelSpawnEntry>> spawnEntriesByPhaseId)
|
|
{
|
|
if (!_initialized || _entity == null)
|
|
{
|
|
return HandleStartFailure("CombatScheduler start failed. Runtime is not initialized.");
|
|
}
|
|
|
|
if (level == null || phases == null || phases.Count <= 0)
|
|
{
|
|
return HandleStartFailure("CombatScheduler start failed. Invalid level or phase data.");
|
|
}
|
|
|
|
CleanupAllCombatEntities();
|
|
CloseCombatFinishForm();
|
|
CloseRewardSelectForm();
|
|
_enemyManager.EndPhase();
|
|
_enemyManager.ResetCombatStats();
|
|
ResetRuntime();
|
|
_combatResourceManager.Reset();
|
|
_isFinishAsVictory = true;
|
|
|
|
_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)
|
|
{
|
|
return HandleStartFailure($"CombatScheduler start failed. Level '{level.Id}' has no phase data.");
|
|
}
|
|
|
|
if (!_loadSession.StartLoading(level, out string loadError))
|
|
{
|
|
return HandleStartFailure($"CombatScheduler start failed. {loadError}");
|
|
}
|
|
|
|
_state = SchedulerState.WaitingForLoading;
|
|
Log.Info("CombatScheduler started. Level={0}, PhaseCount={1}.", _currentLevel.Id,
|
|
_phaseLoopRuntime.PhaseCount);
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
CleanupAllCombatEntities();
|
|
CloseCombatFinishForm();
|
|
CloseRewardSelectForm();
|
|
_enemyManager.OnDestroy();
|
|
ResetRuntime();
|
|
_eventBridge.Unbind();
|
|
_combatFinishFormUseCase = null;
|
|
_rewardSelectFormUseCase = null;
|
|
|
|
_entity = null;
|
|
_initialized = false;
|
|
}
|
|
|
|
public bool TryEndCombatByPlayer()
|
|
{
|
|
if (_state != SchedulerState.RunningPhase)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!_phaseLoopRuntime.TryRequestEndCombat())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
EnterFinishFlow("Combat ended by player.", true);
|
|
return true;
|
|
}
|
|
|
|
private void UpdateCurrentPhase(float elapseSeconds, float realElapseSeconds)
|
|
{
|
|
DRLevelPhase currentPhase = _phaseLoopRuntime.CurrentPhase;
|
|
if (currentPhase == null)
|
|
{
|
|
EnterFailureFallback("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))
|
|
{
|
|
EnterFinishFlow("Combat ended after loop completion.", true);
|
|
return;
|
|
}
|
|
|
|
_spawnEntriesByPhaseId.TryGetValue(nextPhase.Id, out IReadOnlyList<DRLevelSpawnEntry> spawnEntries);
|
|
_enemyManager.BeginPhase(nextPhase, spawnEntries);
|
|
_state = SchedulerState.RunningPhase;
|
|
GameEntry.Event.Fire(
|
|
this,
|
|
CombatProcessEventArgs.Create(_phaseLoopRuntime.DisplayPhaseIndex, _phaseLoopRuntime.PhaseCount));
|
|
GameEntry.Event.Fire(
|
|
this,
|
|
CombatEnemyHpRateChangedEventArgs.Create(
|
|
ResolveEnemyHpRateMultiplier(_phaseLoopRuntime.DisplayPhaseIndex, _phaseLoopRuntime.PhaseCount)));
|
|
|
|
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 EnterFinishFlow(string reason, bool isVictory)
|
|
{
|
|
int defeatedEnemyCount = _enemyManager.DefeatedEnemyCount;
|
|
|
|
// Step 1: stop runtime and clear enemy entities only.
|
|
_enemyManager.EndPhase();
|
|
_state = SchedulerState.WaitingForFinishReturn;
|
|
_isFinishAsVictory = isVictory;
|
|
_enemyManager.CleanupTrackedEnemies();
|
|
|
|
bool shouldOpenFullBaseHpRewardSelect = false;
|
|
ApplySettlementModifierByBaseHp(isVictory, out shouldOpenFullBaseHpRewardSelect);
|
|
PreparePendingFinishSummary(defeatedEnemyCount);
|
|
|
|
Log.Info(
|
|
"CombatScheduler entered finish flow. Level={0}. Reason={1}",
|
|
_currentLevel != null ? _currentLevel.Id : 0,
|
|
reason);
|
|
|
|
if (shouldOpenFullBaseHpRewardSelect && TryOpenFullBaseHpRewardSelect())
|
|
{
|
|
return;
|
|
}
|
|
|
|
CommitPendingSettlementAndOpenFinishForm();
|
|
}
|
|
|
|
public void OnEnemyReachedBase(int baseDamage)
|
|
{
|
|
if (_state != SchedulerState.RunningPhase)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int resolvedBaseDamage = Mathf.Max(0, baseDamage);
|
|
if (resolvedBaseDamage <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CombatNodeComponent combatNode = GameEntry.CombatNode;
|
|
if (combatNode == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int currentBaseHp = combatNode.ApplyBaseDamage(resolvedBaseDamage);
|
|
if (currentBaseHp > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EnterFinishFlow("Combat ended because base HP reached zero.", false);
|
|
}
|
|
|
|
private void ResetRuntime()
|
|
{
|
|
_phaseBuffer.Clear();
|
|
_spawnEntriesByPhaseId.Clear();
|
|
_phaseLoopRuntime.Reset();
|
|
_loadSession.Reset();
|
|
_combatResourceManager.Reset();
|
|
ClearPendingFinishContext();
|
|
_currentLevel = null;
|
|
_isFinishAsVictory = true;
|
|
_state = SchedulerState.Idle;
|
|
}
|
|
|
|
private void CleanupAllCombatEntities()
|
|
{
|
|
_loadSession.Cleanup();
|
|
_enemyManager.CleanupTrackedEnemies();
|
|
}
|
|
|
|
private void EnsureCombatFinishFormUseCaseBound()
|
|
{
|
|
_combatFinishFormUseCase ??= new CombatFinishFormUseCase(this);
|
|
|
|
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatFinishForm, _combatFinishFormUseCase);
|
|
}
|
|
|
|
private void EnsureRewardSelectFormUseCaseBound()
|
|
{
|
|
_rewardSelectFormUseCase ??= new RewardSelectFormUseCase();
|
|
GameEntry.UIRouter.BindUIUseCase(UIFormType.RewardSelectForm, _rewardSelectFormUseCase);
|
|
}
|
|
|
|
private void OpenCombatFinishForm(int defeatedEnemyCount, int gainedGold, BackpackInventoryData rewardInventory)
|
|
{
|
|
EnsureCombatFinishFormUseCaseBound();
|
|
_combatFinishFormUseCase.SetSummary(
|
|
defeatedEnemyCount,
|
|
gainedGold,
|
|
rewardInventory);
|
|
GameEntry.UIRouter.OpenUI(UIFormType.CombatFinishForm);
|
|
}
|
|
|
|
private void PreparePendingFinishSummary(int defeatedEnemyCount)
|
|
{
|
|
_pendingFinishDefeatedEnemyCount = Mathf.Max(0, defeatedEnemyCount);
|
|
_pendingFinishGainedGold = Mathf.Max(0, _combatResourceManager.GainedGold);
|
|
_pendingFinishRewardInventory = _combatResourceManager.GetRewardInventorySnapshot();
|
|
_hasPendingFinishSettlement = true;
|
|
}
|
|
|
|
private void CommitPendingSettlementAndOpenFinishForm()
|
|
{
|
|
if (!_hasPendingFinishSettlement)
|
|
{
|
|
return;
|
|
}
|
|
|
|
BackpackInventoryData rewardInventory = _pendingFinishRewardInventory ?? new BackpackInventoryData();
|
|
int defeatedEnemyCount = _pendingFinishDefeatedEnemyCount;
|
|
int gainedGold = _pendingFinishGainedGold;
|
|
|
|
GameEntry.PlayerInventory?.MergeInventory(rewardInventory);
|
|
OpenCombatFinishForm(defeatedEnemyCount, gainedGold, rewardInventory);
|
|
ClearPendingFinishContext();
|
|
}
|
|
|
|
private void ClearPendingFinishContext()
|
|
{
|
|
_pendingFinishDefeatedEnemyCount = 0;
|
|
_pendingFinishGainedGold = 0;
|
|
_pendingFinishRewardInventory = null;
|
|
_hasPendingFinishSettlement = false;
|
|
}
|
|
|
|
private void ApplySettlementModifierByBaseHp(bool isVictory, out bool shouldOpenFullBaseHpRewardSelect)
|
|
{
|
|
shouldOpenFullBaseHpRewardSelect = false;
|
|
if (!isVictory)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int levelRewardGold = _currentLevel != null ? Mathf.Max(0, _currentLevel.RewardGold) : 0;
|
|
int currentBaseHp = 0;
|
|
int maxBaseHp = 0;
|
|
float bonusRate = 0f;
|
|
bool appliedLowBaseHpPenalty = false;
|
|
|
|
ResolveBaseHpSnapshot(out currentBaseHp, out maxBaseHp);
|
|
if (maxBaseHp > 0 && currentBaseHp >= maxBaseHp)
|
|
{
|
|
bonusRate = FullBaseHpGoldBonusRate;
|
|
shouldOpenFullBaseHpRewardSelect = true;
|
|
}
|
|
else if (maxBaseHp > 0)
|
|
{
|
|
float hpRate = (float)Mathf.Clamp(currentBaseHp, 0, maxBaseHp) / maxBaseHp;
|
|
if (hpRate >= HighBaseHpThreshold)
|
|
{
|
|
bonusRate = HighBaseHpGoldBonusRate;
|
|
}
|
|
else if (hpRate < MidBaseHpThreshold)
|
|
{
|
|
appliedLowBaseHpPenalty = ApplyLowBaseHpPenalty();
|
|
}
|
|
}
|
|
|
|
int goldForBonusCalculation = Mathf.Max(0, _combatResourceManager.GainedGold) + levelRewardGold;
|
|
int bonusGold = bonusRate > 0f ? Mathf.FloorToInt(goldForBonusCalculation * bonusRate) : 0;
|
|
int settlementGold = levelRewardGold + bonusGold;
|
|
_combatResourceManager.AddSettlementGold(settlementGold);
|
|
|
|
Log.Info(
|
|
"Combat settlement resolved. BaseHp={0}/{1}, LevelReward={2}, BonusRate={3:P0}, BonusGold={4}, FullHpRewardSelect={5}, LowHpPenalty={6}.",
|
|
currentBaseHp,
|
|
maxBaseHp,
|
|
levelRewardGold,
|
|
bonusRate,
|
|
bonusGold,
|
|
shouldOpenFullBaseHpRewardSelect,
|
|
appliedLowBaseHpPenalty);
|
|
}
|
|
|
|
private void ResolveBaseHpSnapshot(out int currentBaseHp, out int maxBaseHp)
|
|
{
|
|
currentBaseHp = 0;
|
|
maxBaseHp = _currentLevel != null ? Mathf.Max(0, _currentLevel.BaseHp) : 0;
|
|
|
|
CombatNodeComponent combatNode = GameEntry.CombatNode;
|
|
if (combatNode != null)
|
|
{
|
|
currentBaseHp = Mathf.Max(0, combatNode.CurrentBaseHp);
|
|
if (maxBaseHp <= 0 && combatNode.CurrentLevel != null)
|
|
{
|
|
maxBaseHp = Mathf.Max(0, combatNode.CurrentLevel.BaseHp);
|
|
}
|
|
}
|
|
|
|
if (maxBaseHp > 0)
|
|
{
|
|
currentBaseHp = Mathf.Clamp(currentBaseHp, 0, maxBaseHp);
|
|
}
|
|
}
|
|
|
|
private bool ApplyLowBaseHpPenalty()
|
|
{
|
|
PlayerInventoryComponent inventory = GameEntry.PlayerInventory;
|
|
if (inventory == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int affectedTowerCount = inventory.ReduceAllTowerEndurance(LowBaseHpTowerEndurancePenalty);
|
|
return affectedTowerCount > 0;
|
|
}
|
|
|
|
private bool TryOpenFullBaseHpRewardSelect()
|
|
{
|
|
IReadOnlyList<TowerCompItemData> candidateItems = _combatResourceManager.RollSettlementRewardCandidates(
|
|
_phaseLoopRuntime.DisplayPhaseIndex,
|
|
ResolveCurrentThemeType(),
|
|
RewardSelectDisplayCount);
|
|
if (candidateItems == null || candidateItems.Count <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
List<RewardSelectItemRawData> rewardPool = new List<RewardSelectItemRawData>(candidateItems.Count);
|
|
for (int i = 0; i < candidateItems.Count; i++)
|
|
{
|
|
TowerCompItemData item = candidateItems[i];
|
|
if (item == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
rewardPool.Add(BuildRewardSelectRawData(item));
|
|
}
|
|
|
|
if (rewardPool.Count <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
EnsureRewardSelectFormUseCaseBound();
|
|
_rewardSelectFormUseCase.SetCallbacks(OnFullBaseHpRewardSelected, OnFullBaseHpRewardGiveUp);
|
|
_rewardSelectFormUseCase.ConfigureRewardPool(
|
|
rewardPool,
|
|
displayCount: RewardSelectDisplayCount,
|
|
refreshCost: 0,
|
|
allowRefreshOnce: false,
|
|
allowGiveUp: false,
|
|
tipText: "基地满血奖励:请选择 1 个组件");
|
|
|
|
RewardSelectFormRawData rawData = _rewardSelectFormUseCase.CreateInitialModel();
|
|
if (rawData == null || rawData.RewardItems == null || rawData.RewardItems.Length <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
GameEntry.UIRouter.OpenUI(UIFormType.RewardSelectForm, rawData);
|
|
return true;
|
|
}
|
|
|
|
private void OnFullBaseHpRewardSelected(RewardSelectItemRawData selectedReward)
|
|
{
|
|
if (_pendingFinishRewardInventory != null && selectedReward?.SourceItem != null)
|
|
{
|
|
TryAppendRewardComponent(_pendingFinishRewardInventory, selectedReward.SourceItem);
|
|
}
|
|
|
|
CommitPendingSettlementAndOpenFinishForm();
|
|
}
|
|
|
|
private void OnFullBaseHpRewardGiveUp()
|
|
{
|
|
CommitPendingSettlementAndOpenFinishForm();
|
|
}
|
|
|
|
private static bool TryAppendRewardComponent(BackpackInventoryData rewardInventory, TowerCompItemData selectedItem)
|
|
{
|
|
if (rewardInventory == null || selectedItem == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (selectedItem is MuzzleCompItemData muzzleComp)
|
|
{
|
|
rewardInventory.MuzzleComponents.Add(muzzleComp);
|
|
return true;
|
|
}
|
|
|
|
if (selectedItem is BearingCompItemData bearingComp)
|
|
{
|
|
rewardInventory.BearingComponents.Add(bearingComp);
|
|
return true;
|
|
}
|
|
|
|
if (selectedItem is BaseCompItemData baseComp)
|
|
{
|
|
rewardInventory.BaseComponents.Add(baseComp);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static RewardSelectItemRawData BuildRewardSelectRawData(TowerCompItemData item)
|
|
{
|
|
return new RewardSelectItemRawData
|
|
{
|
|
RewardId = item.InstanceId,
|
|
SlotType = item.SlotType,
|
|
Title = item.Name,
|
|
TypeText = BuildRewardTypeText(item.SlotType),
|
|
Description = BuildRewardDescription(item),
|
|
Rarity = item.Rarity,
|
|
Tags = item.Tags != null ? (TagType[])item.Tags.Clone() : Array.Empty<TagType>(),
|
|
Icon = null,
|
|
IsSelectable = true,
|
|
SourceItem = item
|
|
};
|
|
}
|
|
|
|
private static string BuildRewardTypeText(TowerCompSlotType slotType)
|
|
{
|
|
return slotType switch
|
|
{
|
|
TowerCompSlotType.Muzzle => "Muzzle Component",
|
|
TowerCompSlotType.Bearing => "Bearing Component",
|
|
TowerCompSlotType.Base => "Base Component",
|
|
TowerCompSlotType.Accessory => "Accessory",
|
|
_ => "Component"
|
|
};
|
|
}
|
|
|
|
private static string BuildRewardDescription(TowerCompItemData item)
|
|
{
|
|
if (item is MuzzleCompItemData muzzle)
|
|
{
|
|
int damage = muzzle.AttackDamage != null && muzzle.AttackDamage.Length > 0 ? muzzle.AttackDamage[0] : 0;
|
|
return $"Damage: {damage}, Spread: {muzzle.DamageRandomRate:P0}";
|
|
}
|
|
|
|
if (item is BearingCompItemData bearing)
|
|
{
|
|
float range = bearing.AttackRange != null && bearing.AttackRange.Length > 0 ? bearing.AttackRange[0] : 0f;
|
|
float rotateSpeed = bearing.RotateSpeed != null && bearing.RotateSpeed.Length > 0 ? bearing.RotateSpeed[0] : 0f;
|
|
return $"Range: {range:0.##}, Rotate Speed: {rotateSpeed:0.##}";
|
|
}
|
|
|
|
if (item is BaseCompItemData baseComp)
|
|
{
|
|
float attackSpeed = baseComp.AttackSpeed != null && baseComp.AttackSpeed.Length > 0 ? baseComp.AttackSpeed[0] : 0f;
|
|
return $"Attack Speed: {attackSpeed:0.##}, Property: {baseComp.AttackPropertyType}";
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
public void OnEnemyDefeatedRewardResolved(int gainedCoin, int gainedGold)
|
|
{
|
|
_combatResourceManager.AddEnemyDefeatedReward(gainedCoin, gainedGold);
|
|
|
|
if (_state != SchedulerState.RunningPhase)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_combatResourceManager.TryRollOutGameItemDrop(
|
|
_phaseLoopRuntime.DisplayPhaseIndex,
|
|
ResolveCurrentThemeType());
|
|
}
|
|
|
|
private LevelThemeType ResolveCurrentThemeType()
|
|
{
|
|
if (_currentLevel != null)
|
|
{
|
|
return _currentLevel.LevelThemeType;
|
|
}
|
|
|
|
if (GameEntry.CombatNode != null)
|
|
{
|
|
return GameEntry.CombatNode.CurrentThemeType;
|
|
}
|
|
|
|
return LevelThemeType.None;
|
|
}
|
|
|
|
private void CloseCombatFinishForm()
|
|
{
|
|
GameEntry.UIRouter.CloseUI(UIFormType.CombatFinishForm);
|
|
}
|
|
|
|
private void CloseRewardSelectForm()
|
|
{
|
|
GameEntry.UIRouter.CloseUI(UIFormType.RewardSelectForm);
|
|
}
|
|
|
|
public bool OnCombatFinishReturnRequested()
|
|
{
|
|
if (_state != SchedulerState.WaitingForFinishReturn)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Step 2: clear remaining map/UI resources and close finish form.
|
|
_loadSession.Cleanup();
|
|
CloseCombatFinishForm();
|
|
CloseRewardSelectForm();
|
|
_state = SchedulerState.Completed;
|
|
GameEntry.CombatNode?.OnCombatEndedByScheduler(_isFinishAsVictory);
|
|
return true;
|
|
}
|
|
|
|
private bool HandleStartFailure(string errorMessage)
|
|
{
|
|
Log.Warning("{0}", errorMessage);
|
|
_state = SchedulerState.Failed;
|
|
_enemyManager.EndPhase();
|
|
CleanupAllCombatEntities();
|
|
CloseCombatFinishForm();
|
|
CloseRewardSelectForm();
|
|
ResetRuntime();
|
|
return false;
|
|
}
|
|
|
|
private void EnterFailureFallback(string errorMessage)
|
|
{
|
|
if (_state == SchedulerState.Failed || _state == SchedulerState.Completed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_state = SchedulerState.Failed;
|
|
Log.Error("CombatScheduler failed. LevelId={0}, {1}", _currentLevel != null ? _currentLevel.Id : 0,
|
|
errorMessage);
|
|
_enemyManager.EndPhase();
|
|
CleanupAllCombatEntities();
|
|
CloseCombatFinishForm();
|
|
CloseRewardSelectForm();
|
|
GameEntry.CombatNode?.OnCombatEndedByScheduler(false);
|
|
}
|
|
|
|
private static int ResolveEnemyHpRateMultiplier(int displayPhaseIndex, int phaseCount)
|
|
{
|
|
if (displayPhaseIndex <= 0 || phaseCount <= 0)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
int completedLoopCount = Mathf.Max(0, (displayPhaseIndex - 1) / phaseCount);
|
|
if (completedLoopCount >= 30)
|
|
{
|
|
return int.MaxValue;
|
|
}
|
|
|
|
return 1 << completedLoopCount;
|
|
}
|
|
|
|
#region Event Handlers
|
|
|
|
private void OnShowEntitySuccess(ShowEntitySuccessEventArgs args)
|
|
{
|
|
var status = _loadSession.HandleShowEntitySuccess(args, out string errorMessage);
|
|
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
|
{
|
|
EnterFailureFallback(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)
|
|
{
|
|
EnterFailureFallback(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)
|
|
{
|
|
EnterFailureFallback(errorMessage);
|
|
}
|
|
}
|
|
|
|
private void OnOpenUIFormFailure(OpenUIFormFailureEventArgs args)
|
|
{
|
|
var status = _loadSession.HandleOpenUIFormFailure(args, out string errorMessage);
|
|
if (status == CombatLoadSession.EventHandleStatus.Failed)
|
|
{
|
|
EnterFailureFallback(errorMessage);
|
|
}
|
|
}
|
|
|
|
private void OnCloseUIFormComplete(CloseUIFormCompleteEventArgs args)
|
|
{
|
|
_loadSession.HandleCloseUIFormComplete(args);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|