forked from aly/qstbak
296 lines
9.9 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|