- 添加 CombatFailedState 执行路径

- CombatInfoForm 测试异常方法
This commit is contained in:
SepComet 2026-03-07 18:45:25 +08:00
parent 3badbe9212
commit eb818e6295
12 changed files with 338 additions and 18 deletions

View File

@ -260,7 +260,8 @@ namespace GeometryTD.CustomComponent
_combatInfoFormUseCase.Configure(
() => BuildCombatInfoFormRawData(scheduler),
() => scheduler != null && scheduler.CanEndCombat && scheduler.TryEndCombatByPlayer());
() => scheduler != null && scheduler.CanEndCombat && scheduler.TryEndCombatByPlayer(),
() => scheduler != null && scheduler.TryDebugFail("Manual debug fail from CombatInfoForm."));
int? serialId = GameEntry.UIRouter.OpenUI(UIFormType.CombatInfoForm);
if (!serialId.HasValue)

View File

@ -233,6 +233,19 @@ namespace GeometryTD.CustomComponent
return _combatInRunResourceManager.TryGetBuildTowerStats(buildIndex, out stats);
}
public bool TryDebugFail(string errorMessage)
{
if (_isCompleted || _currentState == null || _currentState is CombatFailedState)
{
return false;
}
EnterFailureFallback(string.IsNullOrWhiteSpace(errorMessage)
? "Manual debug fail."
: errorMessage);
return _currentState is CombatFailedState;
}
private void ResetRuntime()
{
_currentState = null;
@ -266,6 +279,20 @@ namespace GeometryTD.CustomComponent
GameEntry.UIRouter.BindUIUseCase(UIFormType.RewardSelectForm, _rewardSelectFormUseCase);
}
private void OpenCombatFailureDialog(string errorMessage)
{
CloseDialogForm();
GameEntry.UIRouter.OpenUI(UIFormType.DialogForm, new DialogFormRawData
{
Mode = 1,
Title = "Combat Error",
Message = string.IsNullOrWhiteSpace(errorMessage) ? "Combat failed unexpectedly." : errorMessage,
PauseGame = false,
ConfirmText = "Return Menu",
OnClickConfirm = OnCombatFailureDialogConfirmed
});
}
private void ChangeState(CombatStateBase nextState)
{
if (ReferenceEquals(_currentState, nextState))
@ -387,6 +414,11 @@ namespace GeometryTD.CustomComponent
GameEntry.UIRouter.CloseUI(UIFormType.RewardSelectForm);
}
private void CloseDialogForm()
{
GameEntry.UIRouter.CloseUI(UIFormType.DialogForm);
}
private void CompleteCombat(bool succeeded)
{
_isCompleted = true;
@ -401,6 +433,16 @@ namespace GeometryTD.CustomComponent
GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create());
}
private void CompleteFailureCombatAndNotify()
{
CleanupAllCombatEntities();
CloseCombatFinishForm();
CloseRewardSelectForm();
CloseDialogForm();
CompleteCombat(false);
GameEntry.Event.Fire(this, CombatFailureReturnEventArgs.Create());
}
private bool HandleStartFailure(string errorMessage)
{
Log.Warning("{0}", errorMessage);
@ -419,7 +461,7 @@ namespace GeometryTD.CustomComponent
return;
}
ChangeState(new CombatFailedState(this, errorMessage, true));
ChangeState(new CombatFailedState(this, errorMessage));
}
private static int ResolveEnemyHpRateMultiplier(int displayPhaseIndex, int phaseCount)
@ -438,6 +480,17 @@ namespace GeometryTD.CustomComponent
return 1 << completedLoopCount;
}
private void OnCombatFailureDialogConfirmed(object userData)
{
_ = userData;
if (_currentState is not CombatFailedState || _isCompleted)
{
return;
}
CompleteFailureCombatAndNotify();
}
#region Event Handlers
private void OnShowEntitySuccess(ShowEntitySuccessEventArgs args)
@ -501,10 +554,5 @@ namespace GeometryTD.CustomComponent
#endregion
private void GameOverByFailure()
{
CompleteCombat(false);
}
}
}

View File

@ -7,12 +7,10 @@ namespace GeometryTD.CustomComponent
private sealed class CombatFailedState : CombatStateBase
{
private readonly string _errorMessage;
private readonly bool _notifyCombatNode;
public CombatFailedState(CombatScheduler scheduler, string errorMessage, bool notifyCombatNode) : base(scheduler)
public CombatFailedState(CombatScheduler scheduler, string errorMessage) : base(scheduler)
{
_errorMessage = errorMessage;
_notifyCombatNode = notifyCombatNode;
}
public override void OnEnter()
@ -22,13 +20,14 @@ namespace GeometryTD.CustomComponent
Scheduler._currentLevel != null ? Scheduler._currentLevel.Id : 0,
_errorMessage);
Scheduler._enemyManager.EndPhase();
Scheduler.CleanupAllCombatEntities();
Scheduler.CloseCombatFinishForm();
Scheduler.CloseRewardSelectForm();
if (_notifyCombatNode)
Scheduler.OpenCombatFailureDialog(_errorMessage);
}
public override void OnExit()
{
Scheduler.GameOverByFailure();
}
Scheduler.CloseDialogForm();
}
}
}

View File

@ -0,0 +1,21 @@
using GameFramework;
using GameFramework.Event;
namespace GeometryTD.CustomEvent
{
public class CombatDebugFailEventArgs : GameEventArgs
{
public static int EventId => typeof(CombatDebugFailEventArgs).GetHashCode();
public override int Id => EventId;
public static CombatDebugFailEventArgs Create()
{
return ReferencePool.Acquire<CombatDebugFailEventArgs>();
}
public override void Clear()
{
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2b2c9f455d7b43e8ac4d31ecf1d3ec8d
timeCreated: 1772863800

View File

@ -0,0 +1,21 @@
using GameFramework;
using GameFramework.Event;
namespace GeometryTD.CustomEvent
{
public class CombatFailureReturnEventArgs : GameEventArgs
{
public static int EventId => typeof(CombatFailureReturnEventArgs).GetHashCode();
public override int Id => EventId;
public static CombatFailureReturnEventArgs Create()
{
return ReferencePool.Acquire<CombatFailureReturnEventArgs>();
}
public override void Clear()
{
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7db1bf4f6f5f435d8d3086510e178d1d
timeCreated: 1772863200

View File

@ -27,6 +27,7 @@ namespace GeometryTD.Procedure
{
base.OnEnter(procedureOwner);
GameEntry.Event.Subscribe(CombatFailureReturnEventArgs.EventId, OnCombatFailureReturn);
GameEntry.Event.Subscribe(NodeCompleteEventArgs.EventId, OnNodeComplete);
GameEntry.Event.Subscribe(NodeEnterEventArgs.EventId, OnNodeEnter);
@ -62,6 +63,7 @@ namespace GeometryTD.Procedure
protected override void OnLeave(IFsm<IProcedureManager> procedureOwner, bool isShutdown)
{
GameEntry.CombatNode.OnShutdown();
GameEntry.Event.Unsubscribe(CombatFailureReturnEventArgs.EventId, OnCombatFailureReturn);
GameEntry.Event.Unsubscribe(NodeEnterEventArgs.EventId, OnNodeEnter);
GameEntry.Event.Unsubscribe(NodeCompleteEventArgs.EventId, OnNodeComplete);
_repoFormUseCase = null;
@ -86,5 +88,13 @@ namespace GeometryTD.Procedure
GameEntry.UIRouter.OpenUI(UIFormType.TestMenuForm);
GameEntry.UIRouter.OpenUI(UIFormType.MainForm);
}
private void OnCombatFailureReturn(object sender, GameEventArgs e)
{
if (!(e is CombatFailureReturnEventArgs)) return;
GameEntry.UIRouter.OpenUI(UIFormType.TestMenuForm);
GameEntry.UIRouter.OpenUI(UIFormType.MainForm);
}
}
}

View File

@ -20,6 +20,7 @@ namespace GeometryTD.UI
{
GameEntry.Event.Subscribe(CombatPauseEventArgs.EventId, OnCombatPauseButtonClicked);
GameEntry.Event.Subscribe(CombatEndEventArgs.EventId, OnCombatEndButtonClicked);
GameEntry.Event.Subscribe(CombatDebugFailEventArgs.EventId, OnCombatDebugFailButtonClicked);
GameEntry.Event.Subscribe(CombatProcessEventArgs.EventId, OnCombatProcessChanged);
GameEntry.Event.Subscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
GameEntry.Event.Subscribe(CombatBaseHpChangedEventArgs.EventId, OnCombatBaseHpChanged);
@ -30,6 +31,7 @@ namespace GeometryTD.UI
{
GameEntry.Event.Unsubscribe(CombatPauseEventArgs.EventId, OnCombatPauseButtonClicked);
GameEntry.Event.Unsubscribe(CombatEndEventArgs.EventId, OnCombatEndButtonClicked);
GameEntry.Event.Unsubscribe(CombatDebugFailEventArgs.EventId, OnCombatDebugFailButtonClicked);
GameEntry.Event.Unsubscribe(CombatProcessEventArgs.EventId, OnCombatProcessChanged);
GameEntry.Event.Unsubscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
GameEntry.Event.Unsubscribe(CombatBaseHpChangedEventArgs.EventId, OnCombatBaseHpChanged);
@ -176,6 +178,16 @@ namespace GeometryTD.UI
_useCase?.TryEndCombat();
}
private void OnCombatDebugFailButtonClicked(object sender, GameEventArgs e)
{
if (!(sender is CombatInfoForm) || !(e is CombatDebugFailEventArgs))
{
return;
}
_useCase?.TryDebugFail();
}
private void OnCombatProcessChanged(object sender, GameEventArgs e)
{
if (!(e is CombatProcessEventArgs))

View File

@ -8,6 +8,7 @@ namespace GeometryTD.UI
{
private Func<CombatInfoFormRawData> _modelProvider;
private Func<bool> _tryEndCombat;
private Func<bool> _tryDebugFail;
public CombatInfoFormRawData CreateInitialModel()
{
@ -19,10 +20,11 @@ namespace GeometryTD.UI
return BuildModel();
}
public void Configure(Func<CombatInfoFormRawData> modelProvider, Func<bool> tryEndCombat)
public void Configure(Func<CombatInfoFormRawData> modelProvider, Func<bool> tryEndCombat, Func<bool> tryDebugFail)
{
_modelProvider = modelProvider;
_tryEndCombat = tryEndCombat;
_tryDebugFail = tryDebugFail;
}
public bool TryEndCombat()
@ -35,6 +37,16 @@ namespace GeometryTD.UI
return _tryEndCombat.Invoke();
}
public bool TryDebugFail()
{
if (_tryDebugFail == null)
{
return false;
}
return _tryDebugFail.Invoke();
}
private CombatInfoFormRawData BuildModel()
{
return _modelProvider != null ? _modelProvider.Invoke() : null;

View File

@ -83,6 +83,11 @@ namespace GeometryTD.UI
GameEntry.Event.Fire(this, CombatEndEventArgs.Create());
}
public void OnFailButtonClick()
{
GameEntry.Event.Fire(this, CombatDebugFailEventArgs.Create());
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);

View File

@ -828,14 +828,15 @@ RectTransform:
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 5669756128883443685}
- {fileID: 5403492755541954141}
- {fileID: 7007208554693322137}
m_Father: {fileID: 8136671500363545760}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -350, y: -175}
m_SizeDelta: {x: 500, y: 200}
m_AnchoredPosition: {x: -415.65576, y: -175}
m_SizeDelta: {x: 631.3114, y: 200}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &910542505150000154
MonoBehaviour:
@ -1059,6 +1060,190 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: a5079836f95c2a44b96fa331487ebb70, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1001 &8141247492847649042
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 6325121653268560776}
m_Modifications:
- target: {fileID: 770565994539545022, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_Name
value: FailButton
objectReference: {fileID: 0}
- target: {fileID: 1920576543566152029, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_text
value: Fail
objectReference: {fileID: 0}
- target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_Mode
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target
value:
objectReference: {fileID: 389500655449199117}
- target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_Target
value:
objectReference: {fileID: 389500655449199117}
- target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: _onHover.m_PersistentCalls.m_Calls.Array.data[0].m_Target
value:
objectReference: {fileID: 389500655449199117}
- target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_MethodName
value: OnFailButtonClick
objectReference: {fileID: 0}
- target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_TargetAssemblyTypeName
value: GeometryTD.UI.CombatInfoForm, Assembly-CSharp
objectReference: {fileID: 0}
- target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_Arguments.m_ObjectArgumentAssemblyTypeName
value: UnityEngine.Object, UnityEngine
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_Pivot.x
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_Pivot.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchorMax.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchorMax.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchorMin.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_SizeDelta.x
value: 150
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_SizeDelta.y
value: 150
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_SizeDelta.x
value: -20
objectReference: {fileID: 0}
- target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_SizeDelta.y
value: -20
objectReference: {fileID: 0}
- target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 2307f223279813546a43b221ddd496cc, type: 3}
--- !u!224 &5669756128883443685 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
m_PrefabInstance: {fileID: 8141247492847649042}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &8406408876340136106
PrefabInstance:
m_ObjectHideFlags: 0