qstbak/SmartNav/SmartNav/NavGraphView.cs
2026-08-22 10:25:22 +10:00

436 lines
16 KiB
C#

using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Microsoft.Extensions.Logging;
using SmartNav.Data;
using SmartNav.Graph;
using SmartNav.Model;
namespace SmartNav;
public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo territoryInfo, ZoneSubRegionService zoneSubRegionService, WarpFailureTracker warpFailureTracker, TeleportTicketService teleportTicketService, ILogger<NavGraphView> logger)
{
public (NavGraph EffectiveGraph, string OriginId, string DestinationId) CreateQueryGraph(NavGraph staticGraph, PlayerNavState playerState, uint destinationTerritoryId, Vector3 destinationPosition)
{
if (playerState.CurrentTerritoryId == destinationTerritoryId)
{
return CreateSameZoneQueryGraph(staticGraph, playerState, destinationTerritoryId, destinationPosition);
}
return CreateCrossZoneQueryGraph(staticGraph, playerState, destinationTerritoryId, destinationPosition);
}
private (NavGraph EffectiveGraph, string OriginId, string DestinationId) CreateSameZoneQueryGraph(NavGraph staticGraph, PlayerNavState playerState, uint territoryId, Vector3 destinationPosition)
{
NavGraph navGraph = new NavGraph();
HashSet<string> hashSet = new HashSet<string>();
foreach (NavNode value in staticGraph.Nodes.Values)
{
if (value.TerritoryId != territoryId)
{
continue;
}
foreach (NavEdge edge in staticGraph.GetEdges(value.Id))
{
if (edge.EdgeType == NavEdgeType.Warp)
{
NavNode? node = staticGraph.GetNode(edge.ToNodeId);
if ((object)node != null && node.TerritoryId == territoryId)
{
hashSet.Add(edge.FromNodeId);
hashSet.Add(edge.ToNodeId);
}
}
}
}
List<NavNode.AetheryteNode> list = new List<NavNode.AetheryteNode>();
foreach (NavNode value2 in staticGraph.Nodes.Values)
{
if (value2.TerritoryId != territoryId || value2 is NavNode.ZoneBoundaryNode)
{
continue;
}
if (value2 is NavNode.AetheryteNode aetheryteNode)
{
if (playerState.IsAetheryteUnlocked(aetheryteNode.Aetheryte))
{
navGraph.AddNode(value2);
list.Add(aetheryteNode);
}
}
else if (value2 is NavNode.AethernetNode aethernetNode)
{
if (playerState.IsAetheryteUnlocked(aethernetNode.Aethernet))
{
navGraph.AddNode(value2);
}
}
else if (value2 is NavNode.WarpNode warpNode)
{
if (!warpFailureTracker.IsBlacklisted(warpNode.WarpRowId) && warpNode.RequiredClassLevel <= playerState.CurrentLevel && WarpQuestGateSatisfied(warpNode, playerState))
{
uint[] availableDuringMsqIds = warpNode.AvailableDuringMsqIds;
if ((availableDuringMsqIds == null || availableDuringMsqIds.Length <= 0 || (playerState.CurrentMsqQuestId.HasValue && availableDuringMsqIds.Contains(playerState.CurrentMsqQuestId.Value))) && hashSet.Contains(warpNode.Id))
{
navGraph.AddNode(value2);
}
}
}
else
{
navGraph.AddNode(value2);
}
}
CopyStaticEdges(navGraph, staticGraph);
RecomputeAethernetCosts(navGraph);
NavNode.PositionNode positionNode = new NavNode.PositionNode(territoryId, playerState.CurrentPosition);
navGraph.AddNode(positionNode);
NavNode.PositionNode positionNode2 = new NavNode.PositionNode(territoryId, destinationPosition);
if (positionNode2.Id != positionNode.Id)
{
navGraph.AddNode(positionNode2);
}
AddWithinZoneEdgesFrom(navGraph, positionNode, playerState);
AddWithinZoneEdgesTo(navGraph, positionNode2, playerState);
if (playerState.TeleportUnlocked)
{
AddTeleportEdgesFromOrigin(navGraph, positionNode, list, playerState);
}
if (playerState != null && playerState.TeleportUnlocked && playerState.ReturnAvailable)
{
AddReturnEdge(navGraph, positionNode, playerState);
}
AddTicketTeleportEdges(navGraph, positionNode, playerState, territoryId);
AddWarpWithinZoneEdges(navGraph, playerState);
AddConnectorWithinZoneEdges(navGraph, playerState);
logger.LogDebug("Same-zone query graph: {NodeCount} nodes, {EdgeCount} edges", navGraph.NodeCount, navGraph.EdgeCount);
return (EffectiveGraph: navGraph, OriginId: positionNode.Id, DestinationId: positionNode2.Id);
}
private (NavGraph EffectiveGraph, string OriginId, string DestinationId) CreateCrossZoneQueryGraph(NavGraph staticGraph, PlayerNavState playerState, uint destinationTerritoryId, Vector3 destinationPosition)
{
NavGraph navGraph = new NavGraph();
List<NavNode.AetheryteNode> list = new List<NavNode.AetheryteNode>();
foreach (NavNode value in staticGraph.Nodes.Values)
{
if (territoryInfo.GetExVersion(value.TerritoryId) > playerState.MaxExpansion)
{
continue;
}
if (value is NavNode.AetheryteNode aetheryteNode)
{
if (playerState.IsAetheryteUnlocked(aetheryteNode.Aetheryte))
{
navGraph.AddNode(value);
list.Add(aetheryteNode);
}
}
else if (value is NavNode.AethernetNode aethernetNode)
{
if (playerState.IsAetheryteUnlocked(aethernetNode.Aethernet))
{
navGraph.AddNode(value);
}
}
else if (value is NavNode.ZoneBoundaryNode { RequiredQuestId: var requiredQuestId })
{
if (requiredQuestId.HasValue)
{
uint valueOrDefault = requiredQuestId.GetValueOrDefault();
if (!playerState.IsQuestComplete(valueOrDefault))
{
continue;
}
}
navGraph.AddNode(value);
}
else if (value is NavNode.WarpNode warpNode)
{
if (!warpFailureTracker.IsBlacklisted(warpNode.WarpRowId) && warpNode.RequiredClassLevel <= playerState.CurrentLevel && WarpQuestGateSatisfied(warpNode, playerState))
{
uint[] availableDuringMsqIds = warpNode.AvailableDuringMsqIds;
if (availableDuringMsqIds == null || availableDuringMsqIds.Length <= 0 || (playerState.CurrentMsqQuestId.HasValue && availableDuringMsqIds.Contains(playerState.CurrentMsqQuestId.Value)))
{
navGraph.AddNode(value);
}
}
}
else
{
navGraph.AddNode(value);
}
}
CopyStaticEdges(navGraph, staticGraph);
PruneUncrossableFlyingBoundaries(navGraph, playerState);
NavNode.PositionNode positionNode = new NavNode.PositionNode(playerState.CurrentTerritoryId, playerState.CurrentPosition);
navGraph.AddNode(positionNode);
NavNode.PositionNode positionNode2 = new NavNode.PositionNode(destinationTerritoryId, destinationPosition);
if (positionNode2.Id != positionNode.Id)
{
navGraph.AddNode(positionNode2);
}
AddWithinZoneEdgesFrom(navGraph, positionNode, playerState);
AddWithinZoneEdgesTo(navGraph, positionNode2, playerState);
if (playerState.TeleportUnlocked)
{
AddTeleportEdgesFromOrigin(navGraph, positionNode, list, playerState);
}
if (playerState != null && playerState.TeleportUnlocked && playerState.ReturnAvailable)
{
AddReturnEdge(navGraph, positionNode, playerState);
}
AddTicketTeleportEdges(navGraph, positionNode, playerState, null);
RecomputeAethernetCosts(navGraph);
AddBoundaryWithinZoneEdges(navGraph, playerState);
AddWarpWithinZoneEdges(navGraph, playerState);
AddConnectorWithinZoneEdges(navGraph, playerState);
logger.LogDebug("Cross-zone query graph: {NodeCount} nodes, {EdgeCount} edges", navGraph.NodeCount, navGraph.EdgeCount);
return (EffectiveGraph: navGraph, OriginId: positionNode.Id, DestinationId: positionNode2.Id);
}
private static bool WarpQuestGateSatisfied(NavNode.WarpNode warpNode, PlayerNavState playerState)
{
if (warpNode.RequiredQuestIds.Length == 0)
{
return true;
}
if (!warpNode.RequireAllQuests)
{
return warpNode.RequiredQuestIds.Any(playerState.IsQuestComplete);
}
return warpNode.RequiredQuestIds.All(playerState.IsQuestComplete);
}
private static void PruneUncrossableFlyingBoundaries(NavGraph queryGraph, PlayerNavState playerState)
{
foreach (NavNode value in queryGraph.Nodes.Values)
{
if (value is NavNode.ZoneBoundaryNode { RequiresFlying: not false } zoneBoundaryNode && !playerState.IsFlyingUnlocked(zoneBoundaryNode.TerritoryId))
{
queryGraph.RemoveEdges(zoneBoundaryNode.Id, (NavEdge e) => e.EdgeType == NavEdgeType.ZoneBoundary);
}
}
}
private static void CopyStaticEdges(NavGraph queryGraph, NavGraph staticGraph)
{
foreach (NavNode value in queryGraph.Nodes.Values)
{
foreach (NavEdge edge in staticGraph.GetEdges(value.Id))
{
if (queryGraph.GetNode(edge.ToNodeId) != null)
{
queryGraph.AddEdge(edge);
}
}
}
}
private void AddWithinZoneEdge(NavGraph queryGraph, NavNode from, NavNode to, uint territoryId, PlayerNavState playerState)
{
float num = costCalculator.EstimateWithinZoneTravelTime(from.Position, to.Position, territoryId, playerState);
if (float.IsFinite(num))
{
queryGraph.AddEdge(new NavEdge(from.Id, to.Id, NavEdgeType.WithinZone, num, 0));
}
}
private void AddWithinZoneEdgesFrom(NavGraph queryGraph, NavNode origin, PlayerNavState playerState)
{
foreach (string item in queryGraph.GetNodesInTerritory(origin.TerritoryId))
{
if (!(item == origin.Id))
{
NavNode node = queryGraph.GetNode(item);
if (!(node == null) && AreNodesConnectedWithinZone(origin, node, origin.TerritoryId))
{
AddWithinZoneEdge(queryGraph, origin, node, origin.TerritoryId, playerState);
}
}
}
}
private void AddWithinZoneEdgesTo(NavGraph queryGraph, NavNode destination, PlayerNavState playerState)
{
foreach (string item in queryGraph.GetNodesInTerritory(destination.TerritoryId))
{
if (!(item == destination.Id))
{
NavNode node = queryGraph.GetNode(item);
if (!(node == null) && AreNodesConnectedWithinZone(node, destination, destination.TerritoryId))
{
AddWithinZoneEdge(queryGraph, node, destination, destination.TerritoryId, playerState);
}
}
}
}
private void AddTeleportEdgesFromOrigin(NavGraph queryGraph, NavNode origin, List<NavNode.AetheryteNode> largeAetherytes, PlayerNavState playerState)
{
float num = CostCalculator.EstimateTeleportTime();
bool flag = playerState.PreferFreeTravel && !territoryInfo.CanUseMount(playerState.CurrentTerritoryId);
foreach (NavNode.AetheryteNode largeAetheryte in largeAetherytes)
{
if (largeAetheryte.Aetheryte == playerState.HomeAetheryte && playerState.ReturnAvailable)
{
continue;
}
int num2 = EstimateTeleportGilCost(largeAetheryte.Aetheryte, playerState);
if (playerState.CanAffordTeleport(num2))
{
float num3 = num;
if (flag && num2 > 0)
{
num3 *= 10f;
}
queryGraph.AddEdge(new NavEdge(origin.Id, largeAetheryte.Id, NavEdgeType.Teleport, num3, num2));
}
}
}
private void AddTicketTeleportEdges(NavGraph queryGraph, NavNode origin, PlayerNavState playerState, uint? restrictToTerritory)
{
if (playerState.AvailableTeleportTickets.Count == 0)
{
return;
}
foreach (TeleportTicketService.TeleportTicket ticket in teleportTicketService.Tickets)
{
if (playerState.AvailableTeleportTickets.Contains(ticket.ItemId) && (!restrictToTerritory.HasValue || ticket.TerritoryId == restrictToTerritory) && territoryInfo.GetExVersion(ticket.TerritoryId) <= playerState.MaxExpansion)
{
NavNode.TicketNode ticketNode = new NavNode.TicketNode(ticket.ItemId, ticket.TerritoryId, ticket.Position);
if (queryGraph.GetNode(ticketNode.Id) == null)
{
queryGraph.AddNode(ticketNode);
}
queryGraph.AddEdge(new NavEdge(origin.Id, ticketNode.Id, NavEdgeType.TicketTeleport, CostCalculator.EstimateTeleportTime(), 0));
AddWithinZoneEdgesFrom(queryGraph, ticketNode, playerState);
}
}
}
private static void AddReturnEdge(NavGraph queryGraph, NavNode origin, PlayerNavState playerState)
{
if (playerState.HomeAetheryte != EAetheryteLocation.None)
{
string text = $"aetheryte:{playerState.HomeAetheryte}";
if (!(queryGraph.GetNode(text) == null))
{
queryGraph.AddEdge(new NavEdge(origin.Id, text, NavEdgeType.Return, CostCalculator.EstimateTeleportTime(), 0));
}
}
}
private static void RecomputeAethernetCosts(NavGraph queryGraph)
{
List<NavEdge> list = new List<NavEdge>();
foreach (var (nodeId, _) in queryGraph.Nodes)
{
foreach (NavEdge edge in queryGraph.GetEdges(nodeId))
{
if (edge.EdgeType == NavEdgeType.AethernetHop && edge.TimeCostSeconds == 0f)
{
float timeCostSeconds = CostCalculator.EstimateAethernetHopTime();
list.Add(new NavEdge(edge.FromNodeId, edge.ToNodeId, NavEdgeType.AethernetHop, timeCostSeconds, 0));
}
}
}
foreach (NavEdge item in list)
{
queryGraph.ReplaceEdge(item);
}
}
private void AddBoundaryWithinZoneEdges(NavGraph queryGraph, PlayerNavState playerState)
{
foreach (NavNode value in queryGraph.Nodes.Values)
{
if (!(value is NavNode.ZoneBoundaryNode zoneBoundaryNode))
{
continue;
}
foreach (string item in queryGraph.GetNodesInTerritory(zoneBoundaryNode.TerritoryId))
{
if (!(item == zoneBoundaryNode.Id))
{
NavNode node = queryGraph.GetNode(item);
if (!(node == null) && (!(node is NavNode.ZoneBoundaryNode) || string.CompareOrdinal(zoneBoundaryNode.Id, item) < 0) && AreNodesConnectedWithinZone(zoneBoundaryNode, node, zoneBoundaryNode.TerritoryId))
{
AddWithinZoneEdge(queryGraph, node, zoneBoundaryNode, zoneBoundaryNode.TerritoryId, playerState);
AddWithinZoneEdge(queryGraph, zoneBoundaryNode, node, zoneBoundaryNode.TerritoryId, playerState);
}
}
}
}
}
private void AddWarpWithinZoneEdges(NavGraph queryGraph, PlayerNavState playerState)
{
foreach (NavNode value in queryGraph.Nodes.Values)
{
if (!(value is NavNode.WarpNode warpNode))
{
continue;
}
foreach (string item in queryGraph.GetNodesInTerritory(warpNode.TerritoryId))
{
if (!(item == warpNode.Id))
{
NavNode node = queryGraph.GetNode(item);
if (!(node == null) && (!(node is NavNode.WarpNode) || string.CompareOrdinal(warpNode.Id, item) < 0) && !(node is NavNode.ZoneBoundaryNode) && AreNodesConnectedWithinZone(warpNode, node, warpNode.TerritoryId))
{
AddWithinZoneEdge(queryGraph, node, warpNode, warpNode.TerritoryId, playerState);
AddWithinZoneEdge(queryGraph, warpNode, node, warpNode.TerritoryId, playerState);
}
}
}
}
}
private void AddConnectorWithinZoneEdges(NavGraph queryGraph, PlayerNavState playerState)
{
foreach (NavNode value in queryGraph.Nodes.Values)
{
if (!(value is NavNode.SubRegionConnectorNode subRegionConnectorNode))
{
continue;
}
foreach (string item in queryGraph.GetNodesInTerritory(subRegionConnectorNode.TerritoryId))
{
if (item == subRegionConnectorNode.Id)
{
continue;
}
NavNode node = queryGraph.GetNode(item);
if (!(node == null) && (!(node is NavNode.SubRegionConnectorNode) || string.CompareOrdinal(subRegionConnectorNode.Id, item) < 0))
{
bool flag = ((node is NavNode.ZoneBoundaryNode || node is NavNode.WarpNode) ? true : false);
if (!flag && AreNodesConnectedWithinZone(subRegionConnectorNode, node, subRegionConnectorNode.TerritoryId))
{
AddWithinZoneEdge(queryGraph, node, subRegionConnectorNode, subRegionConnectorNode.TerritoryId, playerState);
AddWithinZoneEdge(queryGraph, subRegionConnectorNode, node, subRegionConnectorNode.TerritoryId, playerState);
}
}
}
}
}
private bool AreNodesConnectedWithinZone(NavNode a, NavNode b, uint territoryId)
{
if (!zoneSubRegionService.HasSubRegions(territoryId))
{
return true;
}
string text = ((a is NavNode.SubRegionConnectorNode subRegionConnectorNode) ? subRegionConnectorNode.SubRegionId : zoneSubRegionService.GetSubRegionId(territoryId, a.Position.X, a.Position.Y, a.Position.Z));
string text2 = ((b is NavNode.SubRegionConnectorNode subRegionConnectorNode2) ? subRegionConnectorNode2.SubRegionId : zoneSubRegionService.GetSubRegionId(territoryId, b.Position.X, b.Position.Y, b.Position.Z));
if (text == null || text2 == null)
{
return true;
}
return text == text2;
}
private static int EstimateTeleportGilCost(EAetheryteLocation aetheryte, PlayerNavState playerState)
{
return playerState.GetTeleportCost(aetheryte);
}
}