diff --git a/Assets/GameMain/DataTables/Scene.txt b/Assets/GameMain/DataTables/Scene.txt index 6aecfae..06e39b7 100644 --- a/Assets/GameMain/DataTables/Scene.txt +++ b/Assets/GameMain/DataTables/Scene.txt @@ -4,4 +4,4 @@ # 场景编号 备注 资源名称 背景音乐编号 1 菜单场景 Menu 1 2 战斗场景 Main 2 - 3 压力测试场景 StressTest 0 + 3 压力测试场景 StressTest 0 diff --git a/Assets/GameMain/Scripts/CustomComponent/PlayerInventory/PlayerInventoryComponent.cs b/Assets/GameMain/Scripts/CustomComponent/PlayerInventory/PlayerInventoryComponent.cs index a3e4743..4345918 100644 --- a/Assets/GameMain/Scripts/CustomComponent/PlayerInventory/PlayerInventoryComponent.cs +++ b/Assets/GameMain/Scripts/CustomComponent/PlayerInventory/PlayerInventoryComponent.cs @@ -45,6 +45,12 @@ namespace GeometryTD.CustomComponent return _queryModel.GetSnapshot(); } + public void ReplaceInventorySnapshot(BackpackInventoryData inventorySnapshot) + { + EnsureServices(); + _commandModel.ReplaceInventorySnapshot(inventorySnapshot, MaxParticipantTowerCount); + } + public IReadOnlyList GetParticipantTowerSnapshot() { EnsureInitialized(); diff --git a/Assets/GameMain/Scripts/CustomComponent/PlayerInventory/PlayerInventoryStateStore.cs b/Assets/GameMain/Scripts/CustomComponent/PlayerInventory/PlayerInventoryStateStore.cs index f2c433e..e95099f 100644 --- a/Assets/GameMain/Scripts/CustomComponent/PlayerInventory/PlayerInventoryStateStore.cs +++ b/Assets/GameMain/Scripts/CustomComponent/PlayerInventory/PlayerInventoryStateStore.cs @@ -89,6 +89,11 @@ namespace GeometryTD.CustomComponent _state.IsInitialized = true; } + public void ReplaceInventorySnapshot(BackpackInventoryData sourceInventory, int maxParticipantTowerCount) + { + Initialize(sourceInventory, maxParticipantTowerCount); + } + public PlayerInventoryMergeSummary MergeInventory(BackpackInventoryData gainedInventory) { PlayerInventoryMergeSummary summary = default; diff --git a/Assets/GameMain/Scripts/Event/Game/NodeCompleteEventArgs.cs b/Assets/GameMain/Scripts/Event/Game/NodeCompleteEventArgs.cs index 485144c..86c119b 100644 --- a/Assets/GameMain/Scripts/Event/Game/NodeCompleteEventArgs.cs +++ b/Assets/GameMain/Scripts/Event/Game/NodeCompleteEventArgs.cs @@ -1,5 +1,8 @@ using GameFramework; using GameFramework.Event; +using GeometryTD.CustomUtility; +using GeometryTD.Definition; +using GeometryTD.Procedure; namespace GeometryTD.CustomEvent { @@ -9,19 +12,49 @@ namespace GeometryTD.CustomEvent public override int Id => EventId; + public string RunId { get; private set; } + + public int NodeId { get; private set; } + + public RunNodeType NodeType { get; private set; } + + public bool Succeeded { get; private set; } + + public BackpackInventoryData InventorySnapshotAfterNode { get; private set; } + public NodeCompleteEventArgs() { } public static NodeCompleteEventArgs Create() + { + return Create(null, 0, RunNodeType.None, true, null); + } + + public static NodeCompleteEventArgs Create( + string runId, + int nodeId, + RunNodeType nodeType, + bool succeeded, + BackpackInventoryData inventorySnapshotAfterNode) { var args = ReferencePool.Acquire(); + args.RunId = runId; + args.NodeId = nodeId; + args.NodeType = nodeType; + args.Succeeded = succeeded; + args.InventorySnapshotAfterNode = InventoryCloneUtility.CloneInventory(inventorySnapshotAfterNode); return args; } public override void Clear() { + RunId = null; + NodeId = 0; + NodeType = RunNodeType.None; + Succeeded = false; + InventorySnapshotAfterNode = null; } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Event/Game/NodeEnterEventArgs.cs b/Assets/GameMain/Scripts/Event/Game/NodeEnterEventArgs.cs index 7cddf27..04b20e4 100644 --- a/Assets/GameMain/Scripts/Event/Game/NodeEnterEventArgs.cs +++ b/Assets/GameMain/Scripts/Event/Game/NodeEnterEventArgs.cs @@ -1,5 +1,6 @@ using GameFramework; using GameFramework.Event; +using GeometryTD.Procedure; namespace GeometryTD.CustomEvent { @@ -9,19 +10,40 @@ namespace GeometryTD.CustomEvent public override int Id => EventId; + public string RunId { get; private set; } + + public int NodeId { get; private set; } + + public RunNodeType NodeType { get; private set; } + + public int SequenceIndex { get; private set; } + public NodeEnterEventArgs() { } public static NodeEnterEventArgs Create() + { + return Create(null, 0, RunNodeType.None, -1); + } + + public static NodeEnterEventArgs Create(string runId, int nodeId, RunNodeType nodeType, int sequenceIndex) { var args = ReferencePool.Acquire(); + args.RunId = runId; + args.NodeId = nodeId; + args.NodeType = nodeType; + args.SequenceIndex = sequenceIndex; return args; } public override void Clear() { + RunId = null; + NodeId = 0; + NodeType = RunNodeType.None; + SequenceIndex = -1; } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Procedure/RunModel.cs b/Assets/GameMain/Scripts/Procedure/RunModel.cs new file mode 100644 index 0000000..fc8569a --- /dev/null +++ b/Assets/GameMain/Scripts/Procedure/RunModel.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using GeometryTD.CustomUtility; +using GeometryTD.Definition; + +namespace GeometryTD.Procedure +{ + public enum RunNodeType + { + None = 0, + Combat = 1, + Event = 2, + Shop = 3, + BossCombat = 4 + } + + public enum RunNodeStatus + { + Locked = 0, + Available = 1, + Completed = 2, + Failed = 3, + Skipped = 4 + } + + [Serializable] + public sealed class RunNodeSeed + { + public int NodeId { get; set; } + public RunNodeType NodeType { get; set; } + public LevelThemeType ThemeType { get; set; } + public int LinkedLevelId { get; set; } + public int SequenceIndex { get; set; } = -1; + } + + [Serializable] + public sealed class RunNodeState + { + public int NodeId { get; internal set; } + public RunNodeType NodeType { get; internal set; } + public LevelThemeType ThemeType { get; internal set; } + public int LinkedLevelId { get; internal set; } + public RunNodeStatus Status { get; internal set; } + public int SequenceIndex { get; internal set; } + + internal RunNodeState Clone() + { + return new RunNodeState + { + NodeId = NodeId, + NodeType = NodeType, + ThemeType = ThemeType, + LinkedLevelId = LinkedLevelId, + Status = Status, + SequenceIndex = SequenceIndex + }; + } + } + + [Serializable] + public sealed class RunState + { + private readonly List _nodes; + + internal RunState( + string runId, + LevelThemeType themeType, + List nodes, + BackpackInventoryData runInventorySnapshot) + { + RunId = string.IsNullOrWhiteSpace(runId) ? Guid.NewGuid().ToString("N") : runId; + ThemeType = themeType; + _nodes = nodes ?? new List(); + RunInventorySnapshot = InventoryCloneUtility.CloneInventory(runInventorySnapshot); + CurrentNodeIndex = _nodes.Count > 0 ? 0 : -1; + IsCompleted = _nodes.Count <= 0; + } + + public string RunId { get; internal set; } + + public LevelThemeType ThemeType { get; internal set; } + + public int CurrentNodeIndex { get; internal set; } + + public bool IsCompleted { get; internal set; } + + public BackpackInventoryData RunInventorySnapshot { get; internal set; } + + public IReadOnlyList Nodes => _nodes; + + public RunNodeState CurrentNode + { + get + { + if (CurrentNodeIndex < 0 || CurrentNodeIndex >= _nodes.Count) + { + return null; + } + + return _nodes[CurrentNodeIndex]; + } + } + + public bool CanEnterCurrentNode => !IsCompleted && CurrentNode != null && CurrentNode.Status == RunNodeStatus.Available; + + public int CompletedNodeCount + { + get + { + int count = 0; + foreach (var nodeState in _nodes) + { + if (nodeState.Status == RunNodeStatus.Completed) + { + count++; + } + } + + return count; + } + } + + internal void ReplaceInventorySnapshot(BackpackInventoryData inventorySnapshot) + { + RunInventorySnapshot = InventoryCloneUtility.CloneInventory(inventorySnapshot); + } + } +} diff --git a/Assets/GameMain/Scripts/Procedure/RunModel.cs.meta b/Assets/GameMain/Scripts/Procedure/RunModel.cs.meta new file mode 100644 index 0000000..6d9b44e --- /dev/null +++ b/Assets/GameMain/Scripts/Procedure/RunModel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4de7f8a43a9f4be8b0e1a5d6c7f80911 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Procedure/RunStateAdvanceService.cs b/Assets/GameMain/Scripts/Procedure/RunStateAdvanceService.cs new file mode 100644 index 0000000..7625940 --- /dev/null +++ b/Assets/GameMain/Scripts/Procedure/RunStateAdvanceService.cs @@ -0,0 +1,51 @@ +using GeometryTD.Definition; + +namespace GeometryTD.Procedure +{ + public static class RunStateAdvanceService + { + public static bool TryCompleteCurrentNode( + RunState runState, + bool succeeded, + BackpackInventoryData inventorySnapshotAfterNode) + { + if (runState == null || runState.IsCompleted) + { + return false; + } + + RunNodeState currentNode = runState.CurrentNode; + if (currentNode == null || currentNode.Status != RunNodeStatus.Available) + { + return false; + } + + runState.ReplaceInventorySnapshot(inventorySnapshotAfterNode); + + if (!succeeded) + { + currentNode.Status = RunNodeStatus.Failed; + return true; + } + + currentNode.Status = RunNodeStatus.Completed; + + int nextIndex = runState.CurrentNodeIndex + 1; + if (nextIndex >= runState.Nodes.Count) + { + runState.CurrentNodeIndex = runState.Nodes.Count; + runState.IsCompleted = true; + return true; + } + + runState.CurrentNodeIndex = nextIndex; + RunNodeState nextNode = runState.CurrentNode; + if (nextNode != null && nextNode.Status == RunNodeStatus.Locked) + { + nextNode.Status = RunNodeStatus.Available; + } + + return true; + } + } +} diff --git a/Assets/GameMain/Scripts/Procedure/RunStateAdvanceService.cs.meta b/Assets/GameMain/Scripts/Procedure/RunStateAdvanceService.cs.meta new file mode 100644 index 0000000..e2eb35c --- /dev/null +++ b/Assets/GameMain/Scripts/Procedure/RunStateAdvanceService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f1c5db7a94a44b9aa5f7745ca0c2202 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Procedure/RunStateFactory.cs b/Assets/GameMain/Scripts/Procedure/RunStateFactory.cs new file mode 100644 index 0000000..06814f3 --- /dev/null +++ b/Assets/GameMain/Scripts/Procedure/RunStateFactory.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using GeometryTD.Definition; + +namespace GeometryTD.Procedure +{ + public static class RunStateFactory + { + public static RunState Create( + LevelThemeType themeType, + BackpackInventoryData initialInventorySnapshot, + IEnumerable nodeSeeds, + string runId = null) + { + List nodes = new List(); + if (nodeSeeds != null) + { + int sequenceIndex = 0; + foreach (RunNodeSeed seed in nodeSeeds) + { + if (seed == null) + { + continue; + } + + nodes.Add(new RunNodeState + { + NodeId = seed.NodeId > 0 ? seed.NodeId : sequenceIndex + 1, + NodeType = seed.NodeType, + ThemeType = seed.ThemeType == LevelThemeType.None ? themeType : seed.ThemeType, + LinkedLevelId = seed.LinkedLevelId, + SequenceIndex = seed.SequenceIndex >= 0 ? seed.SequenceIndex : sequenceIndex, + Status = sequenceIndex == 0 ? RunNodeStatus.Available : RunNodeStatus.Locked + }); + sequenceIndex++; + } + } + + return new RunState(runId, themeType, nodes, initialInventorySnapshot); + } + } +} diff --git a/Assets/GameMain/Scripts/Procedure/RunStateFactory.cs.meta b/Assets/GameMain/Scripts/Procedure/RunStateFactory.cs.meta new file mode 100644 index 0000000..9180e38 --- /dev/null +++ b/Assets/GameMain/Scripts/Procedure/RunStateFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9b14d5010eb149678d2f4b269d6b8401 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Tests.meta b/Assets/GameMain/Tests.meta new file mode 100644 index 0000000..b9325e7 --- /dev/null +++ b/Assets/GameMain/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c1a8e1a80db2487ea5ef3e8f700c4203 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Tests/Editor.meta b/Assets/GameMain/Tests/Editor.meta new file mode 100644 index 0000000..cce6e4a --- /dev/null +++ b/Assets/GameMain/Tests/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a3d90b95afbf46d1be5e59f90b2f3104 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Tests/Editor/RunStateTests.cs b/Assets/GameMain/Tests/Editor/RunStateTests.cs new file mode 100644 index 0000000..8516438 --- /dev/null +++ b/Assets/GameMain/Tests/Editor/RunStateTests.cs @@ -0,0 +1,131 @@ +using System.Collections.Generic; +using GeometryTD.CustomEvent; +using GeometryTD.Definition; +using GeometryTD.Procedure; +using NUnit.Framework; + +namespace GeometryTD.Tests.Editor +{ + public sealed class RunStateTests + { + [Test] + public void Factory_Creates_RunState_With_First_Node_Available() + { + BackpackInventoryData sourceInventory = new BackpackInventoryData + { + Gold = 120 + }; + sourceInventory.ParticipantTowerInstanceIds.Add(11); + + RunState runState = RunStateFactory.Create( + LevelThemeType.Plain, + sourceInventory, + new List + { + new RunNodeSeed { NodeType = RunNodeType.Combat, LinkedLevelId = 1 }, + new RunNodeSeed { NodeType = RunNodeType.Event }, + new RunNodeSeed { NodeType = RunNodeType.BossCombat, LinkedLevelId = 4 } + }, + "run-test"); + + Assert.That(runState.RunId, Is.EqualTo("run-test")); + Assert.That(runState.ThemeType, Is.EqualTo(LevelThemeType.Plain)); + Assert.That(runState.CurrentNodeIndex, Is.EqualTo(0)); + Assert.That(runState.IsCompleted, Is.False); + Assert.That(runState.Nodes.Count, Is.EqualTo(3)); + Assert.That(runState.Nodes[0].Status, Is.EqualTo(RunNodeStatus.Available)); + Assert.That(runState.Nodes[1].Status, Is.EqualTo(RunNodeStatus.Locked)); + Assert.That(runState.RunInventorySnapshot.Gold, Is.EqualTo(120)); + + sourceInventory.Gold = 1; + sourceInventory.ParticipantTowerInstanceIds[0] = 99; + + Assert.That(runState.RunInventorySnapshot.Gold, Is.EqualTo(120)); + Assert.That(runState.RunInventorySnapshot.ParticipantTowerInstanceIds[0], Is.EqualTo(11)); + } + + [Test] + public void AdvanceService_Completes_Run_And_Unlocks_Next_Node() + { + RunState runState = RunStateFactory.Create( + LevelThemeType.Plain, + new BackpackInventoryData { Gold = 50 }, + new[] + { + new RunNodeSeed { NodeId = 101, NodeType = RunNodeType.Combat, LinkedLevelId = 1 }, + new RunNodeSeed { NodeId = 102, NodeType = RunNodeType.Shop }, + new RunNodeSeed { NodeId = 103, NodeType = RunNodeType.BossCombat, LinkedLevelId = 4 } + }); + + bool firstCompleted = RunStateAdvanceService.TryCompleteCurrentNode( + runState, + true, + new BackpackInventoryData { Gold = 80 }); + + Assert.That(firstCompleted, Is.True); + Assert.That(runState.Nodes[0].Status, Is.EqualTo(RunNodeStatus.Completed)); + Assert.That(runState.CurrentNodeIndex, Is.EqualTo(1)); + Assert.That(runState.Nodes[1].Status, Is.EqualTo(RunNodeStatus.Available)); + Assert.That(runState.RunInventorySnapshot.Gold, Is.EqualTo(80)); + + RunStateAdvanceService.TryCompleteCurrentNode(runState, true, new BackpackInventoryData { Gold = 95 }); + RunStateAdvanceService.TryCompleteCurrentNode(runState, true, new BackpackInventoryData { Gold = 130 }); + + Assert.That(runState.IsCompleted, Is.True); + Assert.That(runState.CurrentNodeIndex, Is.EqualTo(3)); + Assert.That(runState.CompletedNodeCount, Is.EqualTo(3)); + Assert.That(runState.RunInventorySnapshot.Gold, Is.EqualTo(130)); + } + + [Test] + public void AdvanceService_Failure_Marks_Current_Node_Failed_Without_Completing_Run() + { + RunState runState = RunStateFactory.Create( + LevelThemeType.Plain, + new BackpackInventoryData { Gold = 20 }, + new[] + { + new RunNodeSeed { NodeType = RunNodeType.Combat } + }); + + bool result = RunStateAdvanceService.TryCompleteCurrentNode( + runState, + false, + new BackpackInventoryData { Gold = 5 }); + + Assert.That(result, Is.True); + Assert.That(runState.IsCompleted, Is.False); + Assert.That(runState.CurrentNodeIndex, Is.EqualTo(0)); + Assert.That(runState.CurrentNode.Status, Is.EqualTo(RunNodeStatus.Failed)); + Assert.That(runState.RunInventorySnapshot.Gold, Is.EqualTo(5)); + } + + [Test] + public void NodeCompleteEventArgs_Clones_Inventory_And_Clears_State() + { + BackpackInventoryData inventory = new BackpackInventoryData { Gold = 66 }; + NodeCompleteEventArgs eventArgs = NodeCompleteEventArgs.Create( + "run-1", + 7, + RunNodeType.Shop, + true, + inventory); + + inventory.Gold = 1; + + Assert.That(eventArgs.RunId, Is.EqualTo("run-1")); + Assert.That(eventArgs.NodeId, Is.EqualTo(7)); + Assert.That(eventArgs.NodeType, Is.EqualTo(RunNodeType.Shop)); + Assert.That(eventArgs.Succeeded, Is.True); + Assert.That(eventArgs.InventorySnapshotAfterNode.Gold, Is.EqualTo(66)); + + eventArgs.Clear(); + + Assert.That(eventArgs.RunId, Is.Null); + Assert.That(eventArgs.NodeId, Is.EqualTo(0)); + Assert.That(eventArgs.NodeType, Is.EqualTo(RunNodeType.None)); + Assert.That(eventArgs.Succeeded, Is.False); + Assert.That(eventArgs.InventorySnapshotAfterNode, Is.Null); + } + } +} diff --git a/Assets/GameMain/Tests/Editor/RunStateTests.cs.meta b/Assets/GameMain/Tests/Editor/RunStateTests.cs.meta new file mode 100644 index 0000000..0f105cc --- /dev/null +++ b/Assets/GameMain/Tests/Editor/RunStateTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3d3e6fd8ec274118b59f66c6efc41405 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/docs/CodeX-TODO.md b/docs/CodeX-TODO.md index d234959..33c1eab 100644 --- a/docs/CodeX-TODO.md +++ b/docs/CodeX-TODO.md @@ -4,322 +4,206 @@ ## 当前目标 -按 `docs/CombatNodeArchitecture.md` 继续收敛 `CombatNode` 域职责。当前骨架已经基本到位,后续重点是: -- 继续保持 `CombatScheduler` 作为唯一状态机边界,避免把新业务重新堆回本体。 -- 在 `MapData + Event` 已收口的基础上,继续保持 `MapEntity` 不反查 `CombatNode` 域运行时。 -- 稳定 `CombatSettlementContext` 的模型边界,避免流程控制字段和展示摘要继续混杂增长。 -- 补 Unity 编译、PlayMode 和失败路径回归验证,把这轮结构调整真正跑通。 +按 `docs/TODO.md` 的 M1 现状推进最小可玩闭环。当前代码已经具备: +- 基础战斗节点玩法 +- 事件节点基础占位 +- 仓库/组装 UI 骨架 +- 局内掉落与战斗结算的一部分 + +但离“可从开始游戏一路完成一个大关”的 M1 验收还有明显缺口。后续重点是: +- 补真正的单局 Run 流程,而不是继续依赖 `TestMenuForm` 手动点节点。 +- 把 10 节点固定大关、节点推进、Boss 终点串成闭环。 +- 把组装、品质、Tag、耐久从“有数据结构/有 UI”补成“实际影响战斗结果”的规则闭环。 +- 补商店节点真实实现,否则 M1 主流程无法跑通。 ## 已完成 -### 1. 状态类已收口到 CombatScheduler/CombatStates -- 状态类当前位于 `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/`。 -- `CombatStateBase` 已统一改为 `Context + Flow` 双引用模式。 -- 各状态不再直接访问 `CombatScheduler._xxx` 私有字段,而是通过: - - `CombatSchedulerRuntimeContext` - - `CombatSchedulerFlowCoordinator` +### 1. 战斗主链路已经能独立跑通一场 +- `CombatNodeComponent` 能加载关卡、阶段和出怪表,并启动 `CombatScheduler`。 +- `CombatScheduler` 已经具备加载地图、推进 phase、结算与返回的基础流程。 +- 战斗内已有基地血量、建塔花费、敌人到家扣血、胜负结束等基础逻辑。 关键文件: -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatStateBase.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/*.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerRuntimeContext.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs` - -### 2. 第一轮目标命名、骨架与接线已建立 -- `CombatResourceManager` 已重命名为 `CombatInRunResourceManager`。 -- 已新增掉落解析骨架: - - `EnemyDropResolveContext` - - `EnemyDropResolveResult` - - `EnemyDropResolver` -- 已新增 phase end 骨架并接入等待退出状态: - - `IPhaseEndCondition` - - `PhaseEndConditionContext` - - `PhaseEndConditionFactory` - - `NonePhaseEndCondition` - - `TimeElapsedPhaseEndCondition` - - `EnemiesClearedPhaseEndCondition` - - `BossDeadPhaseEndCondition` - -关键文件: -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatInRunResourceManager.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/EnemyDropResolver.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseEndConditions/` - -### 3. 局内资源真值已迁到 CombatInRunResourceManager -目前 `CombatInRunResourceManager` 已经接管: -- `CurrentCoin` -- `CurrentBaseHp` -- `MaxBaseHp` -- `GainedCoin` -- `GainedGold` -- 本局 `BuildTowerStats` 快照 -- 奖励背包快照 - -已实现的资源接口: -- `InitializeForCombat(DRLevel level)` -- `MarkCombatEnded()` -- `Reset()` -- `TryConsumeCoin(int coin)` -- `AddCoin(int coin)` -- `ApplyBaseDamage(int damage)` -- `TryGetBuildTowerStats(int buildIndex, out TowerStatsData stats)` -- `AddEnemyDefeatedReward(int gainedCoin, int gainedGold)` -- `AddSettlementGold(int gainedGold)` -- `GetRewardInventorySnapshot()` - -附带完成: -- `CombatCoinChangedEventArgs` 增加了 `DeltaCoin` -- `CombatBaseHpChangedEventArgs` 增加了 `DeltaBaseHp` -- coin/baseHp 变化事件现在由 `CombatInRunResourceManager` 发布,而不是 `CombatNodeComponent` -- `Reset()` 现在会清理 `ParticipantTowerInstanceIds` - -关键文件: -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatInRunResourceManager.cs` -- `Assets/GameMain/Scripts/Event/Combat/CombatCoinChangedEventArgs.cs` -- `Assets/GameMain/Scripts/Event/Combat/CombatBaseHpChangedEventArgs.cs` - -### 4. CombatScheduler / CombatNodeComponent 已做一轮收口 -#### CombatScheduler -- 启动时会先初始化 `CombatInRunResourceManager` -- 提供资源转发属性: - - `CurrentCoin` - - `CurrentGold` - - `CurrentBaseHp` - - `CurrentBuildTowerCount` -- 提供资源转发方法: - - `TryConsumeCoin(...)` - - `AddCoin(...)` - - `TryGetBuildTowerStats(...)` -- 失败调试入口: - - `TryDebugFail(...)` -- 不再通过 `CombatNodeComponent` 读写 baseHp/coin 真值 -- 当前主职责已收紧为: - - 生命周期入口 - - 状态切换入口 - - 对外公开查询/操作接口 - - 敌人事件公共入口 - - 事件桥回调入口 - -#### CombatScheduler 内部实现细化 -- 已新增 `CombatSchedulerRuntimeContext` - - 承载共享运行时字段与共享服务引用 -- 已新增 `CombatSchedulerFlowCoordinator` - - 承载多个状态共用的流程辅助方法 -- 当前 `CombatScheduler` 本体不再直接堆所有共享字段和公用流程辅助 - -#### CombatNodeComponent -- 不再持有这些字段: - - `_currentCoin` - - `_currentGold` - - `_currentBaseHp` - - `_currentBuildTowerStats` -- 当前只保留: - - 关卡数据缓存 - - `CurrentLevel / CurrentThemeType` - - 战斗启动/结束协调 - - 最终结算摘要字段(`LastDefeatedEnemyCount / LastGainedCoin / LastGainedGold`) - - 对 `CombatScheduler` 的只读/操作转发 - -关键文件: -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerRuntimeContext.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs` - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs` - -### 5. EnemyManager 事件边界已改为只上报敌人事实 -- `EnemyManager` 现在只向 `CombatScheduler` 上报: - - `OnEnemyDefeated(DREnemy enemy)` - - `OnEnemyReachedBase(DREnemy enemy)` -- coin/gold/baseDamage 的公共副作用已统一收口到 `CombatScheduler` -- `EnemyDropResolver + CombatInRunResourceManager` 已接到公共敌人事件入口上 - -关键文件: -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyManager.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/EnemyDropResolver.cs` - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs` - -### 6. PhaseEndCondition 已正式接入 WaitingForPhaseEnd -- `CombatWaitingForPhaseEndState` 已改为通过: - - `PhaseEndConditionFactory.Create(...)` - - `IPhaseEndCondition.ShouldExit(...)` - 判定 phase 结束 -- `PhaseLoopRuntime` 当前只保留 phase runtime 数据与 phase 进入逻辑,不再负责 `PhaseEndType` 判定 - -关键文件: -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatWaitingForPhaseEndState.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseEndConditions/` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseLoopRuntime.cs` - -### 7. 结束链与异常失败链已做协议收口 -- 正常结束: - - `NodeCompleteEventArgs` 只在 `WaitingForReturn` 完成后发布 -- 异常失败: - - 使用 `CombatFailedState` - - 弹出单按钮 `DialogForm` - - 点击 `Return Menu` 后发布 `CombatFailureReturnEventArgs` -- `ProcedureMenu` 已区分正常结束与异常失败两条返回协议 - -关键文件: -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatWaitingForReturnState.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatFailedState.cs` -- `Assets/GameMain/Scripts/Event/Combat/CombatFailureReturnEventArgs.cs` -- `Assets/GameMain/Scripts/Procedure/ProcedureMenu.cs` - -### 8. CombatSettlementContext 已收紧为结束链共享上下文 -- 当前由 `Settlement -> RewardSelection -> FinishForm -> WaitingForReturn` 共享 -- 已显式承载: - - 结算事实 - - 奖励背包 - - 奖励选择相关流程标记 - - 低血惩罚相关事实 -- 低血惩罚已改为“先记录事实,提交阶段再统一落库”,不再在结算构造期直接写库存 -- 结算背包合并当前发生在 `WaitingForReturn` 的真正退出点,而不是 `FinishForm` -- `CombatFinishForm` 当前只消费它真正展示需要的摘要,不再把额外结算事实继续灌进 UI Context - -关键文件: -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementContext.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementFlowService.cs` -- `Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs` - -### 9. CombatInfoForm 已补手动失败测试入口 -- `CombatInfoForm.OnFailButtonClick()` 可手动触发异常失败链 -- 新增 `CombatDebugFailEventArgs` -- 当前可直接用于测试: - - `CombatFailedState` - - 失败 Dialog - - `CombatFailureReturnEventArgs` - -关键文件: -- `Assets/GameMain/Scripts/UI/Combat/View/CombatInfoForm.cs` -- `Assets/GameMain/Scripts/UI/Combat/Controller/CombatInfoFormController.cs` -- `Assets/GameMain/Scripts/UI/Combat/UseCase/CombatInfoFormUseCase.cs` -- `Assets/GameMain/Scripts/Event/Combat/CombatDebugFailEventArgs.cs` - -### 10. MapData + Event 解耦已完成一轮收口 -- `MapData` 已收口为纯初始化快照,不再承载 coin 写接口委托 -- 已新增 `MapEntityLoadContext` - - 用于把 `MapData` 快照与 coin 命令通道拆开传给地图加载 -- `CombatLoadingState` 现在会组装: - - `MapData` - - `MapEntityLoadContext` -- `CombatLoadSession` / `EntityExtension.ShowMap(...)` 已切到 `MapEntityLoadContext` -- `MapEntity` 当前通过: - - `MapEntityLoadContext` 获取初始快照与 coin 命令通道 - - `CombatCoinChangedEventArgs` 同步后续 coin 变化 -- 已新增 `MapCombatRuntimeBridge` - - 收口地图侧 coin 当前值、命令调用与事件订阅 -- `MapEntity` 不再自己维护 `_currentCoin` 和 coin 事件订阅样板 - -当前结论: -- 地图侧已经完成“`MapData` 初始快照 + Event 同步 + 独立命令桥接”的接线 -- 当前未发现 `MapEntity` / 地图侧服务对 `CombatNodeComponent` 或 `CombatScheduler` 的运行时反查 - -关键文件: -- `Assets/GameMain/Scripts/Entity/EntityData/MapData.cs` -- `Assets/GameMain/Scripts/Entity/EntityData/MapEntityLoadContext.cs` -- `Assets/GameMain/Scripts/Scene/Map/MapCombatRuntimeBridge.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/` - `Assets/GameMain/Scripts/Entity/EntityLogic/MapEntity.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatLoadingState.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatLoadSession.cs` -- `Assets/GameMain/Scripts/Entity/EntityExtension.cs` +- `Assets/GameMain/Scripts/Scene/Map/` -### 11. FlowCoordinator 已切到极小宿主接口 -- 已新增 `ICombatSchedulerHost` -- `CombatSchedulerFlowCoordinator` 不再直接依赖 `CombatScheduler` 具体类,而是只依赖: - - 状态切换入口 - - coin 命令转发 - - CombatInfo / FinishForm 所需的只读查询与回调 -- `CombatLoadSession` 与 `CombatFinishFormUseCase` 也已切到 `ICombatSchedulerHost` -- 状态类当前通过 `Flow.ChangeState(...)` 和 `Flow` 上的轻量转发访问宿主,不再持有 `CombatScheduler` 具体类型引用 +### 2. 战斗结算、掉落与奖励选择已有基础实现 +- 战斗内敌人被击败后,已能发放 coin / gold / 组件掉落。 +- `CombatInRunResourceManager` 已维护本场奖励背包快照。 +- 结算链已经包含奖励计算、奖励选择 UI、FinishForm 返回等基础骨架。 关键文件: -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/ICombatSchedulerHost.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatLoadSession.cs` -- `Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs` - -### 12. CombatSettlementContext 已拆成 Flow / Result / Summary 分层 -- `CombatSettlementContext` 当前明确区分: - - `Flow` - - `Result` - - `Summary` -- 流程控制字段现在集中在 `Flow` -- 结算事实与惩罚事实现在集中在 `Result` -- `CombatFinishFormUseCase` 当前只消费 `Summary` -- 已去掉单独的结束原因字段,结束链不再依赖 `CombatEndReason` 风格数据 -- 奖励选择与延迟提交惩罚已同步切到分层后的上下文访问路径 - -关键文件: -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementContext.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatInRunResourceManager.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/EnemyDrop/EnemyDropResolver.cs` - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementFlowService.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatSettlementState.cs` -- `Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs` +- `Assets/GameMain/Scripts/UI/Combat/` + +### 3. 背包与组装基础能力已经存在 +- `PlayerInventoryComponent` 已有库存快照、金币读写、参战塔名单、组装塔入口。 +- `RepoForm` 已能展示组件、塔、参战区和组装区。 +- `CombineArea` 已有三槽约束,只有枪口/轴承/底座齐备时才会发起组合请求。 +- `PlayerInventoryTowerAssemblyService` 已能基于三组件生成 `TowerItemData` 与 `TowerStatsData`。 + +关键文件: +- `Assets/GameMain/Scripts/CustomComponent/PlayerInventory/PlayerInventoryComponent.cs` +- `Assets/GameMain/Scripts/CustomComponent/PlayerInventory/PlayerInventoryTowerAssemblyService.cs` +- `Assets/GameMain/Scripts/UI/Game/View/RepoForm.cs` +- `Assets/GameMain/Scripts/UI/Game/View/CombineArea.cs` +- `Assets/GameMain/Scripts/UI/Game/Controller/RepoFormController.cs` + +### 4. 事件节点有基础占位实现 +- `EventNodeComponent` 已能读取 `Event.txt`,解析事件选项并随机打开一个事件。 +- 事件完成后会发出 `NodeCompleteEventArgs` 返回上层流程。 + +关键文件: +- `Assets/GameMain/Scripts/CustomComponent/EventNodeComponent.cs` +- `Assets/GameMain/Scripts/UI/Game/UseCase/EventFormUseCase.cs` +- `Assets/GameMain/Scripts/UI/Game/Controller/EventFormController.cs` ## 还没完成 -### 1. 需要补实际运行验证 -当前改动已覆盖: -- 状态机结构 -- 结束链/失败链协议 -- 手动失败测试入口 -- `CombatSchedulerRuntimeContext + CombatSchedulerFlowCoordinator + ICombatSchedulerHost` -- `CombatSettlementContext` 分层与延迟提交惩罚 -- `MapData + Event + MapCombatRuntimeBridge` +### 1. M1 真正的 Run 流程还不存在 +- 当前入口仍是 `ProcedureMenu` 里打开 `MainForm + TestMenuForm`。 +- 玩家目前是手动点击 `战斗 / 事件 / 商店` 按钮进入节点,不是通过单局大关顺序推进。 +- 代码里没有明确的“当前节点 / 节点列表 / 节点索引 / 大关完成”运行时模型。 +- `docs/TODO.md` 的 `P0-04 / P0-05 / P0-06` 从验收标准看都还没有完整落地。 -但仍缺: -- Unity 编译验证 -- PlayMode/手点验证 -- 失败路径回收验证 -- 新开局后无残留状态验证 +直接证据: +- `ProcedureMenu` 进入后直接打开测试菜单。 +- `TestMenuFormController` 直接转发到 `StartCombat / StartEvent / StartShop`。 + +关键文件: +- `Assets/GameMain/Scripts/Procedure/ProcedureMenu.cs` +- `Assets/GameMain/Scripts/UI/Menu/Controller/TestMenuFormController.cs` + +### 2. 10 节点地图生成与 Boss 终点未实现 +- 当前没有发现“固定 10 节点”或“第 10 节点固定 Boss”的节点生成器。 +- `CombatNodeComponent.StartCombat()` 仍是从当前主题关卡池里随机抽一个 `DRLevel` 开战。 +- 现有 Boss 相关逻辑只存在于战斗内部的 `EntryType.Boss / BossDead`,不是大关节点层面的第 10 节点约束。 + +关键文件: +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemySpawnDirector.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseEndConditions/BossDeadPhaseEndCondition.cs` + +### 3. 商店节点基本未实现,当前是主流程硬缺口 +- `ShopNodeComponent.StartShop()` 仍然是空方法。 +- 旧版 `ShopFormUseCase / ShopFormController / ShopForm` 基本都是整文件注释状态,不能视为可用实现。 +- 因此 `战斗 -> 事件 -> 商店 -> 组装 -> 下一节点` 这条 M1 主链路实际上卡在商店节点。 + +关键文件: +- `Assets/GameMain/Scripts/CustomComponent/ShopNodeComponent.cs` +- `Assets/GameMain/Scripts/UI/Templates/GameScene/UseCase/ShopFormUseCase.cs` +- `Assets/GameMain/Scripts/UI/Templates/GameScene/Controller/ShopFormController.cs` +- `Assets/GameMain/Scripts/UI/Templates/GameScene/View/ShopForm.cs` + +### 4. 三组件约束只做到了“组装时”,还没做到“出战时” +- `CombineArea` 已经要求枪口/轴承/底座三槽都填满才能发起组装。 +- 但战斗入口并没有校验“玩家是否拥有可参战的完整塔”。 +- 当前可以在没有任何参战塔时直接调用 `StartCombat()`,不符合 `P0-10` 的“未满足三组件时禁止出战”。 + +关键文件: +- `Assets/GameMain/Scripts/UI/Game/View/CombineArea.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs` + +### 5. 品质/槽位/Tag 规则只做了最浅一层 +- 塔品质目前只按三组件平均品质四舍五入得到。 +- `TowerItemData` 里没有“配件槽位数量”之类的落地字段,槽位规则没有真正实现。 +- Tag 当前不是按品质概率、数量、等级生成,而是: + - 掉落时直接复制 `PossibleTag` + - 组塔时直接把三组件 Tag 做并集 +- `DRTag` 虽然存在,但没有看到被用于实际掉落/组装规则计算。 +- `RarityType` 仍包含 `Red`,而 `docs/MVP-Scope.md.md` 里写的是 M1 不做红色,这里也还没统一。 + +关键文件: +- `Assets/GameMain/Scripts/CustomComponent/PlayerInventory/PlayerInventoryTowerAssemblyService.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/EnemyDrop/EnemyDropResolver.cs` +- `Assets/GameMain/Scripts/DataTable/DRTag.cs` +- `Assets/GameMain/Scripts/Definition/DataStruct/TowerItemData.cs` +- `Assets/GameMain/Scripts/Definition/Enum/RarityType.cs` + +### 6. 耐久只停留在展示和扣数值,没有真正生效 +- 当前组件耐久会显示在仓库描述和 UI 颜色上。 +- 低血通关后会调用 `ReduceAllTowerEndurance(...)` 扣耐久。 +- 但塔的真实战斗属性是组装时固化到 `TowerStatsData` 的,不会随着耐久变化动态衰减。 +- 组件耐久归 0 后没有销毁、移除库存、拆塔或失效处理。 +- 所以 `P0-12` 的“耐久影响属性,归零后物品移除”目前未完成。 + +关键文件: +- `Assets/GameMain/Scripts/Utility/ItemDescUtility.cs` +- `Assets/GameMain/Scripts/CustomComponent/PlayerInventory/PlayerInventoryTowerRosterService.cs` +- `Assets/GameMain/Scripts/Definition/DataStruct/TowerItemData.cs` +- `Assets/GameMain/Scripts/Definition/DataStruct/TowerCompItemData.cs` + +### 7. M1 文档与当前实现还有几处范围不一致 +- `docs/MVP-Scope.md.md` 写的是固定 10 节点与固定顺序,但当前代码没有对应系统。 +- 文档写的是商店节点可购买/出售组件,但当前商店未实现。 +- 文档写的是 M1 不做耐久系统,但 `docs/TODO.md` 又把耐久列在 M1 的 `P0-12`;当前代码只做了半套,需要先统一目标再继续推进。 +- 文档写的是 M1 不做红色品质,但数据与枚举仍保留 `Red`。 + +关键文件: +- `docs/TODO.md` +- `docs/MVP-Scope.md.md` +- `docs/GameDesign.md` ## 推荐的后续执行顺序 -1. 补 Unity 编译与手动回归验证 -2. 重点验证正常结束链、异常失败链、新开局无残留状态 -3. 若验证中暴露边界问题,再继续做小步收口 +1. 先补单局 Run 模型 +- 明确 `当前节点索引 / 节点列表 / 当前主题 / 大关完成状态` +- 把 `ProcedureMenu + TestMenuForm` 临时入口替换成真实节点推进入口 + +2. 再补 10 节点固定大关 +- 先做最小版本:固定顺序 10 节点 +- 最后一个节点固定 Boss 战斗 +- 节点完成后推进到下一节点 + +3. 立刻补商店节点最小实现 +- 先只做 3 个随机组件、购买、返回 +- 不提前做复杂定价与出售扩展 + +4. 收口组装到出战闭环 +- 没有完整塔时禁止开始战斗 +- 只有参战塔列表合法时才允许进入战斗节点 + +5. 最后补品质/Tag/耐久规则 +- 先把 M1 真实要做的规则重新对齐 +- 再决定是否保留红色品质、耐久和槽位到本阶段 ## 当前做变更时要记住的约束 -- 状态切换只能通过 `CombatScheduler.ChangeState(...)` -- 不要把新业务继续堆回 `CombatScheduler.cs`,优先考虑: - - 状态私有逻辑 - - `CombatSchedulerFlowCoordinator` - - 现有独立服务 -- `CombatNodeComponent` 现在应该保持轻量 facade,不要再把 coin/baseHp/build snapshot 回流到它 -- coin/baseHp 变化事件应继续由 `CombatInRunResourceManager` 发布 -- `Failed` 只处理异常失败,不处理“基地血量归零”的正常失败路径 -- 状态类应继续只通过 `CombatSchedulerRuntimeContext + CombatSchedulerFlowCoordinator` 访问共享状态与共享流程 +- 不要继续把 `TestMenuForm` 当作正式节点流程。 +- 优先补“流程闭环”,不要先做 M2 的深度系统。 +- 商店、节点推进、出战校验属于当前阻塞项,优先级高于数值打磨。 +- 如果 M1 最终决定不做完整耐久/红色品质,要先同步更新文档,再改代码目标。 +- 若继续保留 `PlayerInventory` 作为局内外共用库存入口,需要避免把单局 Run 状态也硬塞进去。 ## 当前关键入口文件速查 -- 状态机宿主: - - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs` -- 共享运行时承载: - - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerRuntimeContext.cs` -- 共享流程协调: - - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs` -- 局内资源真值: - - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatInRunResourceManager.cs` -- 状态类: - - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/` -- 加载服务: - - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatLoadSession.cs` -- phase runtime: - - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseLoopRuntime.cs` -- phase end 条件骨架: - - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseEndConditions/` -- 敌人域 facade: - - `Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyManager.cs` -- CombatNode 入口 facade: +- 流程入口: + - `Assets/GameMain/Scripts/Procedure/ProcedureMenu.cs` +- 临时节点菜单: + - `Assets/GameMain/Scripts/UI/Menu/Controller/TestMenuFormController.cs` +- 战斗节点 facade: - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs` -- 结束链共享上下文: - - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementContext.cs` -- 异常失败返回事件: - - `Assets/GameMain/Scripts/Event/Combat/CombatFailureReturnEventArgs.cs` +- 战斗状态机: + - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs` +- 事件节点: + - `Assets/GameMain/Scripts/CustomComponent/EventNodeComponent.cs` +- 商店节点: + - `Assets/GameMain/Scripts/CustomComponent/ShopNodeComponent.cs` +- 背包与组装: + - `Assets/GameMain/Scripts/CustomComponent/PlayerInventory/` +- 仓库 UI: + - `Assets/GameMain/Scripts/UI/Game/` +- 战斗 UI: + - `Assets/GameMain/Scripts/UI/Combat/` ## 备注 -- 当前环境没有可用的本地 C# 编译器(`dotnet / mcs / csc / msbuild / xbuild` 都不可用),所以本轮改动主要依赖文本级检查。 -- 仓库目前有一些与本任务无关的脏文件,继续改动时只聚焦 `CombatNode` 相关文件即可,不要顺手回滚无关改动。 +- 这份清单按“当前仓库真实实现状态”整理,不沿用旧版 `CodeX-TODO.md` 的 CombatNode 收口主题。 +- 当前判断主要基于代码静态检查,没有跑 Unity Editor / PlayMode。 +- 如果下一步要继续推进,最值得先做的是:`RunState + 10 节点流程 + ShopNode 最小可用版`。