using System; using UnityEngine; namespace Cielonos.MainGame { public partial class RunManager { /// /// 管理地图节点选择、进入和完成。 /// 这里负责把 RunMapNode 转换为 RunPhase 与 Zone 加载请求,但不直接处理 CombatRoom 的敌人死亡事件。 /// public class RunNodeFlowSubmodule : SubmoduleBase { public RunNodeFlowSubmodule(RunManager owner) : base(owner) { } public void SelectNode(Vector2Int targetPosition) { if (owner.currentRun == null) { Debug.LogWarning("[RunManager] 当前没有进行中的 Run,忽略节点选择。"); return; } if (!owner.CanSelectMapNodes) { LogMapSelectionBlocked(); return; } if (targetPosition == owner.currentRun.currentPosition) { Debug.Log("[RunManager] 目标即当前位置,忽略。"); return; } if (!IsNodeReachable(targetPosition)) { Debug.LogWarning( $"[RunManager] 节点 {targetPosition} 不可到达" + "(不在已访问节点的1格范围内)。"); return; } if (!owner.currentRun.mapData.nodes.TryGetValue(targetPosition, out RunMapNode targetNode)) { Debug.LogError($"[RunManager] 节点 {targetPosition} 在地图数据中不存在。"); return; } owner.currentRun.currentPosition = targetPosition; bool isFirstVisit = owner.currentRun.visitedNodes.Add(targetPosition); bool isCompleted = owner.currentRun.completedNodes.Contains(targetPosition); owner.RaiseNodeSelected(targetPosition); owner.RaiseZoneEntered(new ZoneEntryContext(targetPosition, targetNode, isFirstVisit, isCompleted)); Debug.Log($"[RunManager] 移动到节点 {targetPosition},类型:{targetNode.nodeType}" + $",首次进入:{isFirstVisit},已完成:{isCompleted}"); CombatManager.AttackAreaSm.DestroyAll(); EnterNode(targetNode); } public void CompleteCurrentNode() { if (owner.currentRun == null) return; Vector2Int pos = owner.currentRun.currentPosition; if (owner.currentRun.completedNodes.Contains(pos)) { Debug.Log($"[RunManager] 节点 {pos} 已完成过,跳过重复结算。但检查是否需要切回 MapSelection。"); if (owner.currentPhase != RunPhase.MapSelection) { owner.phaseSm.TransitionToPhase(RunPhase.MapSelection); } return; } if (!owner.currentRun.mapData.nodes.TryGetValue(pos, out RunMapNode node)) { return; } owner.currentRun.completedNodes.Add(pos); if (RunNodeTypeUtility.ShouldCountAsClearedRoom(node.nodeType)) { owner.currentRun.roomsCleared++; } if (RunNodeTypeUtility.IsSingleUseNode(node.nodeType)) { owner.currentRun.exhaustedNodes.Add(pos); } owner.RaiseNodeCompleted(node); if (owner.currentRun.currentPosition == owner.currentRun.mapData.bossPosition) { owner.currentRun.isCompleted = true; Debug.Log("[RunManager] Boss 已击败,Run 通关!"); owner.phaseSm.TransitionToPhase(RunPhase.Settlement); owner.RaiseRunEnded(owner.currentRun); return; } owner.phaseSm.TransitionToPhase(RunPhase.MapSelection); } private bool IsNodeReachable(Vector2Int targetPosition) { if (owner.currentRun.visitedNodes.Contains(targetPosition)) { return true; } foreach (Vector2Int visitedPos in owner.currentRun.visitedNodes) { if (!owner.currentRun.mapData.nodes.TryGetValue(visitedPos, out RunMapNode node)) { continue; } if (node.connectedPositions.Contains(targetPosition)) { return true; } } return false; } private void LogMapSelectionBlocked() { if (owner.currentRun == null) { Debug.LogWarning("[RunManager] 当前没有进行中的 Run,忽略节点选择。"); return; } if (!owner.phaseSm.IsPhaseAllowingNodeSelection()) { Debug.LogWarning($"[RunManager] 当前阶段为 {owner.currentPhase},不允许选择节点。"); return; } if (owner.HasPendingCombatRewards) { Debug.LogWarning($"[RunManager] 当前仍有 {owner.combatRewardSm.PendingCount} 个 Combat 奖励未领取,不能前往下一个节点。"); } } private void EnterNode(RunMapNode node) { bool isCompleted = owner.currentRun.completedNodes.Contains(owner.currentRun.currentPosition); RunPhase targetPhase = isCompleted ? RunPhase.MapSelection : RunNodeTypeUtility.ToRunPhase(node.nodeType); bool shouldStartBattleSetup = RunNodeTypeUtility.ShouldStartBattleSetup(node, isCompleted); ZoneSetupMode setupMode = shouldStartBattleSetup ? ZoneSetupMode.CombatEncounter : ZoneSetupMode.EnvironmentOnly; if (isCompleted) { Debug.Log($"[RunManager] 节点 {owner.currentRun.currentPosition} 已完成,跳过 {RunNodeTypeUtility.ToRunPhase(node.nodeType)},直接进入 MapSelection。"); } if (node.zoneData == null) { throw new Exception($"[RunManager] 节点 {owner.currentRun.currentPosition} 没有配置 ZoneData,无法进入对应阶段。"); } owner.phaseSm.TransitionToPhase(RunPhase.Transitioning); MapManager.Instance.LoadZone(node.zoneData, onComplete: () => { owner.phaseSm.TransitionToPhase(targetPhase); }, setupMode: setupMode); } } } }