1
0
Fork 0
forked from aly/qstbak
qstbak/SmartNav/SmartNav/NavGraphBuilder.cs
2026-08-19 13:19:57 +10:00

296 lines
9.9 KiB
C#

using System.Collections.Generic;
using System.Numerics;
using Microsoft.Extensions.Logging;
using SmartNav.Data;
using SmartNav.Graph;
using SmartNav.Model;
using SmartNav.Model.Converter;
using SmartNav.Model.Navigation;
namespace SmartNav;
public sealed class NavGraphBuilder
{
private const float ZoneTransitionSeconds = 2f;
private readonly AetheryteData _aetheryteData;
private readonly WarpDataService _warpDataService;
private readonly ZoneSubRegionService _zoneSubRegionService;
private readonly IZoneBoundarySource _boundarySource;
private readonly ILogger<NavGraphBuilder> _logger;
public NavGraphBuilder(AetheryteData aetheryteData, WarpDataService warpDataService, ZoneSubRegionService zoneSubRegionService, IZoneBoundarySource boundarySource, ILogger<NavGraphBuilder> logger)
{
_aetheryteData = aetheryteData;
_warpDataService = warpDataService;
_zoneSubRegionService = zoneSubRegionService;
_boundarySource = boundarySource;
_logger = logger;
}
public NavGraph Build()
{
NavGraph navGraph = new NavGraph();
AddAetheryteNodes(navGraph);
AddAethernetEdges(navGraph);
AddZoneBoundaryNodes(navGraph);
AddWarpNodes(navGraph);
AddSubRegionConnectorNodes(navGraph);
_logger.LogDebug("NavGraph built: {NodeCount} nodes, {EdgeCount} edges", navGraph.NodeCount, navGraph.EdgeCount);
return navGraph;
}
private void AddAetheryteNodes(NavGraph graph)
{
foreach (var (eAetheryteLocation2, position) in _aetheryteData.Locations)
{
if (_aetheryteData.TerritoryIds.TryGetValue(eAetheryteLocation2, out var value))
{
if (AetheryteConverter.IsLargeAetheryte(eAetheryteLocation2))
{
graph.AddNode(new NavNode.AetheryteNode(eAetheryteLocation2, value, position));
continue;
}
EAetheryteLocation parentAetheryte = FindParentAetheryte(eAetheryteLocation2);
graph.AddNode(new NavNode.AethernetNode(eAetheryteLocation2, value, position, parentAetheryte));
}
}
_logger.LogDebug("Added {Count} aetheryte/aethernet nodes", graph.NodeCount);
}
private EAetheryteLocation FindParentAetheryte(EAetheryteLocation aethernet)
{
if (!_aetheryteData.AethernetGroups.TryGetValue(aethernet, out var value))
{
return EAetheryteLocation.None;
}
foreach (var (eAetheryteLocation2, num2) in _aetheryteData.AethernetGroups)
{
if (num2 == value && AetheryteConverter.IsLargeAetheryte(eAetheryteLocation2))
{
return eAetheryteLocation2;
}
}
return EAetheryteLocation.None;
}
private void AddAethernetEdges(NavGraph graph)
{
Dictionary<ushort, List<NavNode>> dictionary = new Dictionary<ushort, List<NavNode>>();
foreach (NavNode value4 in graph.Nodes.Values)
{
ushort value = 0;
if (value4 is NavNode.AetheryteNode aetheryteNode)
{
_aetheryteData.AethernetGroups.TryGetValue(aetheryteNode.Aetheryte, out value);
}
else if (value4 is NavNode.AethernetNode aethernetNode)
{
_aetheryteData.AethernetGroups.TryGetValue(aethernetNode.Aethernet, out value);
}
if (value != 0)
{
if (!dictionary.TryGetValue(value, out var value2))
{
value2 = (dictionary[value] = new List<NavNode>());
}
value2.Add(value4);
}
}
int num = 0;
foreach (KeyValuePair<ushort, List<NavNode>> item in dictionary)
{
item.Deconstruct(out var _, out var value3);
List<NavNode> list2 = value3;
for (int i = 0; i < list2.Count; i++)
{
for (int j = i + 1; j < list2.Count; j++)
{
EAetheryteLocation? aethernetParticipant = GetAethernetParticipant(list2[i]);
if (aethernetParticipant.HasValue)
{
EAetheryteLocation valueOrDefault = aethernetParticipant.GetValueOrDefault();
if (HasInteractableShard(valueOrDefault))
{
graph.AddEdge(new NavEdge(list2[i].Id, list2[j].Id, NavEdgeType.AethernetHop, 0f, 0));
num++;
}
}
aethernetParticipant = GetAethernetParticipant(list2[j]);
if (aethernetParticipant.HasValue)
{
EAetheryteLocation valueOrDefault2 = aethernetParticipant.GetValueOrDefault();
if (HasInteractableShard(valueOrDefault2))
{
graph.AddEdge(new NavEdge(list2[j].Id, list2[i].Id, NavEdgeType.AethernetHop, 0f, 0));
num++;
}
}
}
}
}
NavNode navNode = null;
foreach (NavNode value5 in graph.Nodes.Values)
{
if (GetAethernetParticipant(value5) == EAetheryteLocation.IshgardFirmament)
{
navNode = value5;
break;
}
}
if (navNode != null)
{
foreach (NavNode value6 in graph.Nodes.Values)
{
EAetheryteLocation? aethernetParticipant2 = GetAethernetParticipant(value6);
if (!(value6.Id == navNode.Id) && aethernetParticipant2.HasValue)
{
EAetheryteLocation valueOrDefault3 = aethernetParticipant2.GetValueOrDefault();
if (valueOrDefault3.IsFirmamentAetheryte())
{
graph.AddEdge(new NavEdge(navNode.Id, value6.Id, NavEdgeType.AethernetHop, 0f, 0));
graph.AddEdge(new NavEdge(value6.Id, navNode.Id, NavEdgeType.AethernetHop, 0f, 0));
num += 2;
}
}
}
}
_logger.LogDebug("Added {Count} aethernet edges across {GroupCount} groups", num, dictionary.Count);
}
private static EAetheryteLocation? GetAethernetParticipant(NavNode node)
{
if (!(node is NavNode.AetheryteNode aetheryteNode))
{
if (node is NavNode.AethernetNode aethernetNode)
{
return aethernetNode.Aethernet;
}
return null;
}
return aetheryteNode.Aetheryte;
}
private bool HasInteractableShard(EAetheryteLocation aetheryte)
{
if (_aetheryteData.IsCityAetheryte(aetheryte) || _aetheryteData.IsGoldSaucerAetheryte(aetheryte))
{
return true;
}
if (_aetheryteData.TerritoryIds.TryGetValue(aetheryte, out var value))
{
return value == 886;
}
return false;
}
private void AddZoneBoundaryNodes(NavGraph graph)
{
IReadOnlyList<ZoneBoundary> boundaries = _boundarySource.GetBoundaries();
int num = 0;
int num2 = 0;
foreach (ZoneBoundary item in boundaries)
{
if (item.Side1.TerritoryId == item.Side2.TerritoryId)
{
_logger.LogWarning("Skipping zone boundary {Id}: both sides are in territory {Territory}", item.Id, item.Side1.TerritoryId);
continue;
}
NavNode.ZoneBoundaryNode zoneBoundaryNode = new NavNode.ZoneBoundaryNode(item.Id, item.Side1.TerritoryId, item.Side1.Position, $"boundary:{item.Id}:{item.Side2.TerritoryId}", item.RequiresFlying, item.RequiredQuest, item.Side1.Trigger);
NavNode.ZoneBoundaryNode zoneBoundaryNode2 = new NavNode.ZoneBoundaryNode(item.Id, item.Side2.TerritoryId, item.Side2.Position, $"boundary:{item.Id}:{item.Side1.TerritoryId}", item.RequiresFlying, item.RequiredQuest, item.Side2.Trigger);
graph.AddNode(zoneBoundaryNode);
graph.AddNode(zoneBoundaryNode2);
num += 2;
graph.AddEdge(new NavEdge(zoneBoundaryNode.Id, zoneBoundaryNode2.Id, NavEdgeType.ZoneBoundary, 2f, 0));
num2++;
if (!item.OneWay)
{
graph.AddEdge(new NavEdge(zoneBoundaryNode2.Id, zoneBoundaryNode.Id, NavEdgeType.ZoneBoundary, 2f, 0));
num2++;
}
}
_logger.LogDebug("Added {NodeCount} boundary nodes and {EdgeCount} boundary edges", num, num2);
}
private void AddWarpNodes(NavGraph graph)
{
int num = 0;
int num2 = 0;
int num3 = 0;
int num4 = 0;
foreach (WarpDataService.WarpInfo warp in _warpDataService.Warps)
{
if (_warpDataService.SkippedWarpIds.Contains(warp.RowId))
{
continue;
}
if (warp.SourceTerritoryId == 0)
{
num4++;
continue;
}
NavNode.WarpNode warpNode = new NavNode.WarpNode(warp.RowId, warp.SourceTerritoryId, warp.SourcePosition, warp.NpcDataId, warp.RequiredQuestIds, warp.GilCost, warp.AvailableDuringMsqIds, warp.RequiredClassLevel, warp.RequireAllQuests);
graph.AddNode(warpNode);
num++;
Vector3? destPosition = warp.DestPosition;
if (destPosition.HasValue)
{
Vector3 valueOrDefault = destPosition.GetValueOrDefault();
NavNode.WarpNode warpNode2 = new NavNode.WarpNode(warp.RowId, warp.DestTerritoryId, valueOrDefault, null, warp.RequiredQuestIds, 0, warp.AvailableDuringMsqIds, warp.RequiredClassLevel, warp.RequireAllQuests);
if (warpNode2.Id == warpNode.Id)
{
_logger.LogWarning("Skipping warp {RowId}: source and destination resolve to the same node {Id}", warp.RowId, warpNode.Id);
num3++;
}
else
{
graph.AddNode(warpNode2);
num++;
graph.AddEdge(new NavEdge(warpNode.Id, warpNode2.Id, NavEdgeType.Warp, 2f, warp.GilCost));
num2++;
}
}
else
{
num3++;
}
}
_logger.LogDebug("Added {NodeCount} warp nodes and {EdgeCount} warp edges ({SkippedDest} missing destination, {SkippedSource} unknown source)", num, num2, num3, num4);
}
private void AddSubRegionConnectorNodes(NavGraph graph)
{
int num = 0;
int num2 = 0;
foreach (KeyValuePair<uint, TerritorySubRegions> allRegion in _zoneSubRegionService.AllRegions)
{
allRegion.Deconstruct(out var key, out var value);
uint territoryId = key;
List<SubRegionConnector> connectors = value.Connectors;
for (int i = 0; i < connectors.Count; i++)
{
SubRegionConnector subRegionConnector = connectors[i];
NavNode.SubRegionConnectorNode subRegionConnectorNode = new NavNode.SubRegionConnectorNode(territoryId, subRegionConnector.SourcePosition, subRegionConnector.SourceSubRegion, i, subRegionConnector.Kind);
graph.AddNode(subRegionConnectorNode);
NavNode.SubRegionConnectorNode subRegionConnectorNode2 = new NavNode.SubRegionConnectorNode(territoryId, subRegionConnector.DestPosition, subRegionConnector.DestSubRegion, i + connectors.Count, subRegionConnector.Kind);
graph.AddNode(subRegionConnectorNode2);
num += 2;
graph.AddEdge(new NavEdge(subRegionConnectorNode.Id, subRegionConnectorNode2.Id, NavEdgeType.SubRegionTransport, subRegionConnector.TimeCostSeconds, 0));
num2++;
if (subRegionConnector.Bidirectional)
{
graph.AddEdge(new NavEdge(subRegionConnectorNode2.Id, subRegionConnectorNode.Id, NavEdgeType.SubRegionTransport, subRegionConnector.TimeCostSeconds, 0));
num2++;
}
}
}
if (num > 0)
{
_logger.LogDebug("Added {NodeCount} sub-region connector nodes and {EdgeCount} transport edges", num, num2);
}
}
}