using System.Collections.Generic; using UnityEngine; namespace Cielonos.MainGame.Map { // ================================================================ // 节点显示状态(三个正交维度) // ================================================================ /// 节点对玩家的可见程度。 public enum MapNodeVisibility { Hidden, // 完全隐藏(雾中,不渲染) Silhouette, // 显示轮廓和"?",不显示具体类型 Revealed, // 完全可见,显示节点类型图标 } /// 节点的交互状态(是否可点击传送)。 public enum MapNodeInteraction { Locked, // 可见但不可点击(超出移动范围,或尚未探索到的区域) Selectable, // 可点击传送(在已访问节点的1格范围内,或本身已被访问) Current, // 玩家当前所在节点 } /// 节点的使用状态(用于视觉区分)。 public enum MapNodeUsage { Fresh, // 未访问过 Visited, // 已访问(战斗已清/已进入过,可再次进入但不触发效果) Exhausted, // 单次使用节点已用完(MedicalStation、MechanicalTable) } /// /// 单个节点的完整显示状态,包含可见性、交互性和使用状态三个维度。 /// public struct NodeDisplayState { public MapNodeVisibility visibility; public MapNodeInteraction interaction; public MapNodeUsage usage; } /// /// 地图迷雾计算器。根据 RunState 计算每个节点的显示状态。 /// 纯静态工具类,不持有状态,UI 层直接调用即可。 /// public static class MapFogCalculator { /// /// 计算地图中所有节点的显示状态。 /// /// 当前 Run 的状态。 /// 每个节点坐标对应的显示状态字典。 public static Dictionary Calculate(RunState state) { Dictionary result = new Dictionary(); RunMapData mapData = state.mapData; int scoutRange = state.scoutRange; // 从所有已访问节点出发,做多源 BFS,计算每个节点到最近已访问节点的距离 // 只需要探测到 scoutRange + 1 深度(Silhouette 需要 +1) Dictionary distFromVisited = MultiSourceBfs(mapData, state.visitedNodes, scoutRange + 1); // 计算可传送集合:所有已访问节点 + 已访问节点的1格邻居 HashSet selectableSet = ComputeSelectableSet(state); foreach (KeyValuePair kvp in mapData.nodes) { Vector2Int pos = kvp.Key; RunMapNode node = kvp.Value; NodeDisplayState displayState = new NodeDisplayState { visibility = ComputeVisibility(pos, node, state, distFromVisited, scoutRange), interaction = ComputeInteraction(pos, state, selectableSet), usage = ComputeUsage(pos, state), }; result[pos] = displayState; } return result; } /// /// 计算可传送节点集合:所有已访问节点 + 已访问节点的1格邻居(不含当前位置)。 /// public static HashSet ComputeSelectableSet(RunState state) { HashSet result = new HashSet(); foreach (Vector2Int visitedPos in state.visitedNodes) { // 已访问节点本身可再次传送 result.Add(visitedPos); // 已访问节点的直接邻居也可传送(新领域扩展) if (state.mapData.nodes.TryGetValue(visitedPos, out RunMapNode node)) { foreach (Vector2Int neighbor in node.connectedPositions) result.Add(neighbor); } } // 当前位置不算"可选"——玩家已经在那了 result.Remove(state.currentPosition); return result; } // ================================================================ // 内部计算方法 // ================================================================ /// 计算单个节点的可见性。 private static MapNodeVisibility ComputeVisibility( Vector2Int pos, RunMapNode node, RunState state, Dictionary distFromVisited, int scoutRange) { // 规则 1:已访问的节点始终完全可见 if (state.visitedNodes.Contains(pos)) return MapNodeVisibility.Revealed; // 规则 2:Boss 节点始终完全可见 if (pos == state.mapData.bossPosition) return MapNodeVisibility.Revealed; // 规则 3:被道具永久揭示的特定节点 if (state.permanentlyRevealedNodes.Contains(pos)) return MapNodeVisibility.Revealed; // 规则 4:被道具永久揭示的节点类型(如"显示所有商店") if (state.permanentlyRevealedTypes.Contains(node.nodeType)) return MapNodeVisibility.Revealed; // 规则 5:在探测范围内(BFS 距离 <= scoutRange)→ Revealed // 在探测范围+1 → Silhouette("?") // 超出 → Hidden if (distFromVisited.TryGetValue(pos, out int dist)) { if (dist <= scoutRange) return MapNodeVisibility.Revealed; if (dist <= scoutRange + 1) return MapNodeVisibility.Silhouette; } // 所有节点始终可见,超出探测范围的显示为 Silhouette("?") return MapNodeVisibility.Silhouette; } /// 计算单个节点的交互状态。 private static MapNodeInteraction ComputeInteraction( Vector2Int pos, RunState state, HashSet selectableSet) { if (pos == state.currentPosition) return MapNodeInteraction.Current; if (selectableSet.Contains(pos)) return MapNodeInteraction.Selectable; return MapNodeInteraction.Locked; } /// 计算单个节点的使用状态。 private static MapNodeUsage ComputeUsage(Vector2Int pos, RunState state) { if (state.exhaustedNodes.Contains(pos)) return MapNodeUsage.Exhausted; if (state.visitedNodes.Contains(pos)) return MapNodeUsage.Visited; return MapNodeUsage.Fresh; } // ================================================================ // 多源 BFS // ================================================================ /// /// 从多个起点同时出发,沿节点连接进行 BFS,返回每个可达节点到最近起点的距离。 /// 仅探索到 maxDepth 深度,超出的节点不包含在结果中。 /// private static Dictionary MultiSourceBfs( RunMapData mapData, HashSet sources, int maxDepth) { Dictionary distances = new Dictionary(); Queue queue = new Queue(); // 所有源节点距离为 0 foreach (Vector2Int src in sources) { if (mapData.nodes.ContainsKey(src)) { distances[src] = 0; queue.Enqueue(src); } } while (queue.Count > 0) { Vector2Int current = queue.Dequeue(); int currentDist = distances[current]; if (currentDist >= maxDepth) continue; if (!mapData.nodes.TryGetValue(current, out RunMapNode node)) continue; foreach (Vector2Int neighbor in node.connectedPositions) { if (!distances.ContainsKey(neighbor) && mapData.nodes.ContainsKey(neighbor)) { distances[neighbor] = currentDist + 1; queue.Enqueue(neighbor); } } } return distances; } } }