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;
}
}
}