muffin v7.5.13
This commit is contained in:
parent
911ed68baa
commit
acf3e3cb18
69 changed files with 2731 additions and 1425 deletions
46
SmartNav/SmartNav.Data/WaterMapService.cs
Normal file
46
SmartNav/SmartNav.Data/WaterMapService.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SmartNav.Model.Navigation;
|
||||
|
||||
namespace SmartNav.Data;
|
||||
|
||||
public sealed class WaterMapService
|
||||
{
|
||||
private readonly Lazy<IReadOnlyDictionary<ushort, TerritoryWaterMap>> _maps;
|
||||
|
||||
public WaterMapService(SmartNavDataOptions dataOptions, ILogger<WaterMapService> logger)
|
||||
{
|
||||
_maps = new Lazy<IReadOnlyDictionary<ushort, TerritoryWaterMap>>(delegate
|
||||
{
|
||||
try
|
||||
{
|
||||
var (text, readOnlyDictionary) = AssemblyWaterMapLoader.GetWaterMaps(dataOptions.DevSourceDirectory?.FullName);
|
||||
logger.LogInformation("WaterMapService: {Count} territories with water maps loaded (game version {GameVersion})", readOnlyDictionary.Count, text ?? "<none>");
|
||||
return readOnlyDictionary;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.LogError(exception, "Water cache could not be loaded - routing proceeds without water data");
|
||||
return new Dictionary<ushort, TerritoryWaterMap>();
|
||||
}
|
||||
}, LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
}
|
||||
|
||||
internal WaterMapService(IReadOnlyDictionary<ushort, TerritoryWaterMap> maps)
|
||||
{
|
||||
_maps = new Lazy<IReadOnlyDictionary<ushort, TerritoryWaterMap>>(maps);
|
||||
}
|
||||
|
||||
public bool TryGetMap(uint territoryId, [NotNullWhen(true)] out TerritoryWaterMap? map)
|
||||
{
|
||||
if (territoryId > 65535)
|
||||
{
|
||||
map = null;
|
||||
return false;
|
||||
}
|
||||
return _maps.Value.TryGetValue((ushort)territoryId, out map);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,53 @@ using SmartNav.Model;
|
|||
|
||||
namespace SmartNav;
|
||||
|
||||
public sealed class CostCalculator(ITerritoryInfo territoryInfo)
|
||||
public sealed class CostCalculator(ITerritoryInfo territoryInfo, WaterRoutePlanner waterPlanner)
|
||||
{
|
||||
public float EstimateWithinZoneTravelTime(Vector3 from, Vector3 to, uint territoryId, PlayerNavState playerState)
|
||||
{
|
||||
WaterPlan waterPlan = waterPlanner.Plan(territoryId, from, to);
|
||||
if (waterPlan == null)
|
||||
{
|
||||
return EstimateDry(from, to, territoryId, playerState);
|
||||
}
|
||||
bool flag = playerState.MountUnlocked && playerState.IsFlyingUnlocked(territoryId);
|
||||
switch (waterPlan.Kind)
|
||||
{
|
||||
case WaterPlanKind.Submerged:
|
||||
return Vector3.Distance(from, to) / 6f;
|
||||
case WaterPlanKind.SurfaceOut:
|
||||
return AscentTime(from, waterPlan.SurfacePoint.Value) + EstimateDry(waterPlan.SurfacePoint.Value, to, territoryId, playerState);
|
||||
case WaterPlanKind.DiveIn:
|
||||
if (!flag)
|
||||
{
|
||||
return float.PositiveInfinity;
|
||||
}
|
||||
return EstimateDry(from, waterPlan.EntryPoint.Value, territoryId, playerState) + DescentTime(waterPlan.EntryPoint.Value, to);
|
||||
case WaterPlanKind.SurfaceOutDiveIn:
|
||||
if (!flag)
|
||||
{
|
||||
return float.PositiveInfinity;
|
||||
}
|
||||
return AscentTime(from, waterPlan.SurfacePoint.Value) + EstimateDry(waterPlan.SurfacePoint.Value, waterPlan.EntryPoint.Value, territoryId, playerState) + DescentTime(waterPlan.EntryPoint.Value, to);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("plan", waterPlan.Kind, "unknown water plan kind");
|
||||
}
|
||||
}
|
||||
|
||||
private static float AscentTime(Vector3 from, Vector3 surfacePoint)
|
||||
{
|
||||
return MathF.Max(0f, surfacePoint.Y - from.Y - 20.6f) / 6f + 1f;
|
||||
}
|
||||
|
||||
private static float DescentTime(Vector3 entryPoint, Vector3 to)
|
||||
{
|
||||
Vector3 vector = entryPoint;
|
||||
vector.Y = entryPoint.Y - 25f;
|
||||
Vector3 value = vector;
|
||||
return 4f + Vector3.Distance(value, to) / 6f;
|
||||
}
|
||||
|
||||
private float EstimateDry(Vector3 from, Vector3 to, uint territoryId, PlayerNavState playerState)
|
||||
{
|
||||
if (!territoryInfo.CanUseMount(territoryId))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -225,6 +225,15 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t
|
|||
}
|
||||
}
|
||||
|
||||
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))
|
||||
|
|
@ -234,8 +243,7 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t
|
|||
NavNode node = queryGraph.GetNode(item);
|
||||
if (!(node == null) && AreNodesConnectedWithinZone(origin, node, origin.TerritoryId))
|
||||
{
|
||||
float timeCostSeconds = costCalculator.EstimateWithinZoneTravelTime(origin.Position, node.Position, origin.TerritoryId, playerState);
|
||||
queryGraph.AddEdge(new NavEdge(origin.Id, item, NavEdgeType.WithinZone, timeCostSeconds, 0));
|
||||
AddWithinZoneEdge(queryGraph, origin, node, origin.TerritoryId, playerState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -250,8 +258,7 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t
|
|||
NavNode node = queryGraph.GetNode(item);
|
||||
if (!(node == null) && AreNodesConnectedWithinZone(node, destination, destination.TerritoryId))
|
||||
{
|
||||
float timeCostSeconds = costCalculator.EstimateWithinZoneTravelTime(node.Position, destination.Position, destination.TerritoryId, playerState);
|
||||
queryGraph.AddEdge(new NavEdge(item, destination.Id, NavEdgeType.WithinZone, timeCostSeconds, 0));
|
||||
AddWithinZoneEdge(queryGraph, node, destination, destination.TerritoryId, playerState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -348,10 +355,8 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t
|
|||
NavNode node = queryGraph.GetNode(item);
|
||||
if (!(node == null) && (!(node is NavNode.ZoneBoundaryNode) || string.CompareOrdinal(zoneBoundaryNode.Id, item) < 0) && AreNodesConnectedWithinZone(zoneBoundaryNode, node, zoneBoundaryNode.TerritoryId))
|
||||
{
|
||||
float timeCostSeconds = costCalculator.EstimateWithinZoneTravelTime(node.Position, zoneBoundaryNode.Position, zoneBoundaryNode.TerritoryId, playerState);
|
||||
float timeCostSeconds2 = costCalculator.EstimateWithinZoneTravelTime(zoneBoundaryNode.Position, node.Position, zoneBoundaryNode.TerritoryId, playerState);
|
||||
queryGraph.AddEdge(new NavEdge(item, zoneBoundaryNode.Id, NavEdgeType.WithinZone, timeCostSeconds, 0));
|
||||
queryGraph.AddEdge(new NavEdge(zoneBoundaryNode.Id, item, NavEdgeType.WithinZone, timeCostSeconds2, 0));
|
||||
AddWithinZoneEdge(queryGraph, node, zoneBoundaryNode, zoneBoundaryNode.TerritoryId, playerState);
|
||||
AddWithinZoneEdge(queryGraph, zoneBoundaryNode, node, zoneBoundaryNode.TerritoryId, playerState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -373,10 +378,8 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t
|
|||
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))
|
||||
{
|
||||
float timeCostSeconds = costCalculator.EstimateWithinZoneTravelTime(node.Position, warpNode.Position, warpNode.TerritoryId, playerState);
|
||||
float timeCostSeconds2 = costCalculator.EstimateWithinZoneTravelTime(warpNode.Position, node.Position, warpNode.TerritoryId, playerState);
|
||||
queryGraph.AddEdge(new NavEdge(item, warpNode.Id, NavEdgeType.WithinZone, timeCostSeconds, 0));
|
||||
queryGraph.AddEdge(new NavEdge(warpNode.Id, item, NavEdgeType.WithinZone, timeCostSeconds2, 0));
|
||||
AddWithinZoneEdge(queryGraph, node, warpNode, warpNode.TerritoryId, playerState);
|
||||
AddWithinZoneEdge(queryGraph, warpNode, node, warpNode.TerritoryId, playerState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -403,10 +406,8 @@ public sealed class NavGraphView(CostCalculator costCalculator, ITerritoryInfo t
|
|||
bool flag = ((node is NavNode.ZoneBoundaryNode || node is NavNode.WarpNode) ? true : false);
|
||||
if (!flag && AreNodesConnectedWithinZone(subRegionConnectorNode, node, subRegionConnectorNode.TerritoryId))
|
||||
{
|
||||
float timeCostSeconds = costCalculator.EstimateWithinZoneTravelTime(node.Position, subRegionConnectorNode.Position, subRegionConnectorNode.TerritoryId, playerState);
|
||||
float timeCostSeconds2 = costCalculator.EstimateWithinZoneTravelTime(subRegionConnectorNode.Position, node.Position, subRegionConnectorNode.TerritoryId, playerState);
|
||||
queryGraph.AddEdge(new NavEdge(item, subRegionConnectorNode.Id, NavEdgeType.WithinZone, timeCostSeconds, 0));
|
||||
queryGraph.AddEdge(new NavEdge(subRegionConnectorNode.Id, item, NavEdgeType.WithinZone, timeCostSeconds2, 0));
|
||||
AddWithinZoneEdge(queryGraph, node, subRegionConnectorNode, subRegionConnectorNode.TerritoryId, playerState);
|
||||
AddWithinZoneEdge(queryGraph, subRegionConnectorNode, node, subRegionConnectorNode.TerritoryId, playerState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@ public abstract record NavInstruction
|
|||
|
||||
public sealed record Move(ushort Territory, Vector3 Position, bool Fly, bool LandAtTarget, float? StopDistance, uint? DataId, bool? Mount, bool DisableNavmesh, bool AllowZoneTransition, string? TargetNodeId, string? ApproachNodeId, bool IsFinal) : NavInstruction();
|
||||
|
||||
public sealed record DiveEntry(ushort Territory, Vector3 Position, string? TargetNodeId, string? ApproachNodeId) : NavInstruction();
|
||||
|
||||
public sealed record Swim(ushort Territory, Vector3 Position, float SurfaceY, float? StopDistance, string? TargetNodeId, string? ApproachNodeId, bool IsFinal) : NavInstruction();
|
||||
|
||||
public sealed record Surface(ushort Territory, Vector3 Position, string? TargetNodeId, string? ApproachNodeId) : NavInstruction();
|
||||
|
||||
public sealed record Land : NavInstruction;
|
||||
|
||||
public sealed record Unmount : NavInstruction;
|
||||
|
|
|
|||
|
|
@ -93,4 +93,42 @@ public sealed class PlayerNavState
|
|||
{
|
||||
return QuestSequenceChecker(questRowId);
|
||||
}
|
||||
|
||||
public PlayerNavState WithTravelCapabilities(bool allowMount, bool allowFlight)
|
||||
{
|
||||
PlayerNavState obj = new PlayerNavState
|
||||
{
|
||||
CurrentTerritoryId = CurrentTerritoryId,
|
||||
CurrentPosition = CurrentPosition,
|
||||
Gil = Gil,
|
||||
GilReserve = GilReserve,
|
||||
UnlockedAetherytes = UnlockedAetherytes,
|
||||
HomeAetheryte = HomeAetheryte,
|
||||
FreeAetheryte = FreeAetheryte,
|
||||
TeleportCosts = TeleportCosts
|
||||
};
|
||||
IReadOnlySet<uint> flyingUnlockedTerritories;
|
||||
if (!(allowMount && allowFlight))
|
||||
{
|
||||
IReadOnlySet<uint> readOnlySet = new HashSet<uint>();
|
||||
flyingUnlockedTerritories = readOnlySet;
|
||||
}
|
||||
else
|
||||
{
|
||||
flyingUnlockedTerritories = FlyingUnlockedTerritories;
|
||||
}
|
||||
obj.FlyingUnlockedTerritories = flyingUnlockedTerritories;
|
||||
obj.MountSpeedLevels = MountSpeedLevels;
|
||||
obj.TeleportUnlocked = TeleportUnlocked;
|
||||
obj.ReturnAvailable = ReturnAvailable;
|
||||
obj.MountUnlocked = allowMount && MountUnlocked;
|
||||
obj.MaxExpansion = MaxExpansion;
|
||||
obj.CurrentLevel = CurrentLevel;
|
||||
obj.CurrentMsqQuestId = CurrentMsqQuestId;
|
||||
obj.PreferFreeTravel = PreferFreeTravel;
|
||||
obj.QuestCompleteChecker = QuestCompleteChecker;
|
||||
obj.QuestSequenceChecker = QuestSequenceChecker;
|
||||
obj.AvailableTeleportTickets = AvailableTeleportTickets;
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ public class PlayerNavStateBuilder(IClientState clientState, IObjectTable object
|
|||
return hashSet;
|
||||
}
|
||||
|
||||
private unsafe Dictionary<uint, int> BuildMountSpeedLevels()
|
||||
private Dictionary<uint, int> BuildMountSpeedLevels()
|
||||
{
|
||||
Dictionary<uint, int> dictionary = new Dictionary<uint, int>();
|
||||
ExcelSheet<TerritoryType> excelSheet = dataManager.GetExcelSheet<TerritoryType>();
|
||||
|
|
@ -125,30 +125,51 @@ public class PlayerNavStateBuilder(IClientState clientState, IObjectTable object
|
|||
{
|
||||
return dictionary;
|
||||
}
|
||||
UIState* ptr = UIState.Instance();
|
||||
foreach (TerritoryType item in excelSheet)
|
||||
{
|
||||
if (item.RowId != 0 && item.MountSpeed.RowId != 0)
|
||||
{
|
||||
MountSpeed value = item.MountSpeed.Value;
|
||||
int num = 0;
|
||||
if (value.Quest.RowId != 0 && ptr != null && ptr->IsUnlockLinkUnlockedOrQuestCompleted(value.Quest.RowId, 0))
|
||||
int unlockedMountSpeedLevel = GetUnlockedMountSpeedLevel(item.MountSpeed.Value);
|
||||
if (unlockedMountSpeedLevel > 0)
|
||||
{
|
||||
num++;
|
||||
}
|
||||
if (value.Unknown0 != 0 && ptr != null && ptr->IsUnlockLinkUnlockedOrQuestCompleted(value.Unknown0, 0))
|
||||
{
|
||||
num++;
|
||||
}
|
||||
if (num > 0)
|
||||
{
|
||||
dictionary[item.RowId] = num;
|
||||
dictionary[item.RowId] = unlockedMountSpeedLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
public (int Level, int MaxLevel)? GetTerritoryMountSpeed(uint territoryId)
|
||||
{
|
||||
TerritoryType? territoryType = dataManager.GetExcelSheet<TerritoryType>()?.GetRowOrDefault(territoryId);
|
||||
if (!territoryType.HasValue || territoryType.Value.MountSpeed.RowId == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
MountSpeed value = territoryType.Value.MountSpeed.Value;
|
||||
int item = ((value.Quest.RowId != 0) ? 1 : 0) + ((value.Unknown0 != 0) ? 1 : 0);
|
||||
return (GetUnlockedMountSpeedLevel(value), item);
|
||||
}
|
||||
|
||||
private unsafe static int GetUnlockedMountSpeedLevel(MountSpeed mountSpeed)
|
||||
{
|
||||
UIState* ptr = UIState.Instance();
|
||||
if (ptr == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int num = 0;
|
||||
if (mountSpeed.Quest.RowId != 0 && ptr->IsUnlockLinkUnlockedOrQuestCompleted(mountSpeed.Quest.RowId, 0))
|
||||
{
|
||||
num++;
|
||||
}
|
||||
if (mountSpeed.Unknown0 != 0 && ptr->IsUnlockLinkUnlockedOrQuestCompleted(mountSpeed.Unknown0, 0))
|
||||
{
|
||||
num++;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
private unsafe static int GetCurrentGil()
|
||||
{
|
||||
InventoryManager* ptr = InventoryManager.Instance();
|
||||
|
|
|
|||
|
|
@ -13,17 +13,23 @@ public sealed class ReRoutePolicy(NavRouter navRouter, PlayerNavStateBuilder pla
|
|||
|
||||
private bool _hasDestination;
|
||||
|
||||
private bool _allowMount = true;
|
||||
|
||||
private bool _allowFlight = true;
|
||||
|
||||
private int _reRouteCount;
|
||||
|
||||
private readonly HashSet<string> _penalizedTargets = new HashSet<string>();
|
||||
|
||||
private const int MaxReRoutes = 3;
|
||||
|
||||
public void SetDestination(uint territoryId, Vector3 position)
|
||||
public void SetDestination(uint territoryId, Vector3 position, bool allowMount = true, bool allowFlight = true)
|
||||
{
|
||||
bool num = _hasDestination && _destinationTerritoryId == territoryId && Vector3.DistanceSquared(_destinationPosition, position) < 1f;
|
||||
bool num = _hasDestination && _destinationTerritoryId == territoryId && Vector3.DistanceSquared(_destinationPosition, position) < 1f && _allowMount == allowMount && _allowFlight == allowFlight;
|
||||
_destinationTerritoryId = territoryId;
|
||||
_destinationPosition = position;
|
||||
_allowMount = allowMount;
|
||||
_allowFlight = allowFlight;
|
||||
_hasDestination = true;
|
||||
_reRouteCount = 0;
|
||||
_penalizedTargets.Clear();
|
||||
|
|
@ -68,6 +74,7 @@ public sealed class ReRoutePolicy(NavRouter navRouter, PlayerNavStateBuilder pla
|
|||
logger.LogWarning("Cannot build PlayerNavState for re-routing");
|
||||
return new ReRouteDecision.GiveUp();
|
||||
}
|
||||
playerNavState = playerNavState.WithTravelCapabilities(_allowMount, _allowFlight);
|
||||
if (playerNavState.CurrentTerritoryId != _destinationTerritoryId && hasPreservedTasks)
|
||||
{
|
||||
_reRouteCount++;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -7,7 +8,7 @@ using SmartNav.Model.Navigation;
|
|||
|
||||
namespace SmartNav;
|
||||
|
||||
public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerritoryInfo territoryInfo, ILogger<RouteInstructionBuilder> logger)
|
||||
public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerritoryInfo territoryInfo, WaterRoutePlanner waterPlanner, ILogger<RouteInstructionBuilder> logger)
|
||||
{
|
||||
private const float BoundaryApproachMargin = 5f;
|
||||
|
||||
|
|
@ -17,7 +18,25 @@ public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerrit
|
|||
for (int i = 0; i < route.Segments.Count; i++)
|
||||
{
|
||||
RouteSegment routeSegment = route.Segments[i];
|
||||
if (routeSegment.EdgeType != NavEdgeType.WithinZone || i + 1 >= route.Segments.Count || route.Segments[i + 1].EdgeType != NavEdgeType.ZoneBoundary)
|
||||
if (routeSegment.EdgeType == NavEdgeType.WithinZone && i + 1 < route.Segments.Count && route.Segments[i + 1].EdgeType == NavEdgeType.ZoneBoundary)
|
||||
{
|
||||
WaterPlan waterPlan = waterPlanner.Plan(routeSegment.From.TerritoryId, routeSegment.From.Position, routeSegment.To.Position);
|
||||
if ((object)waterPlan != null && waterPlan.Kind == WaterPlanKind.SurfaceOut)
|
||||
{
|
||||
uint territoryId = routeSegment.From.TerritoryId;
|
||||
list.Add(new NavInstruction.WaitForTerritory(new global::_003C_003Ez__ReadOnlySingleElementList<uint>(territoryId), "Wait(territory: " + territoryInfo.GetNameAndId(territoryId) + ")"));
|
||||
if (!disableNavmesh)
|
||||
{
|
||||
list.Add(new NavInstruction.WaitForNavmesh());
|
||||
}
|
||||
list.Add(new NavInstruction.Surface((ushort)territoryId, waterPlan.SurfacePoint.Value, routeSegment.To.Id, routeSegment.From.Id));
|
||||
}
|
||||
else if (waterPlan != null)
|
||||
{
|
||||
logger.LogWarning("SmartNav: water plan {Kind} on a within-zone segment collapsed into a boundary crossing in {Territory}; only the boundary move is emitted", waterPlan.Kind, territoryInfo.GetNameAndId(routeSegment.From.TerritoryId));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
BuildSegment(list, routeSegment, (i > 0) ? route.Segments[i - 1] : null, i, route.Segments.Count, destinationTerritory, disableNavmesh);
|
||||
}
|
||||
|
|
@ -52,6 +71,39 @@ public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerrit
|
|||
return true;
|
||||
}
|
||||
|
||||
private static NavInstruction.Move BuildWithinZoneMove(RouteSegment seg, uint territory, bool isFinalSegment)
|
||||
{
|
||||
uint? num = ((!(seg.To is NavNode.WarpNode warpNode)) ? ((uint?)null) : warpNode.NpcDataId);
|
||||
uint? dataId = num;
|
||||
return new NavInstruction.Move((ushort)territory, seg.To.Position, seg.Fly, seg.Fly, isFinalSegment ? ((float?)null) : new float?(3f), dataId, null, DisableNavmesh: false, AllowZoneTransition: false, seg.To.Id, seg.From.Id, isFinalSegment);
|
||||
}
|
||||
|
||||
private static void EmitWaterLegs(List<NavInstruction> instructions, WaterPlan plan, RouteSegment seg, uint territory, bool isFinalSegment)
|
||||
{
|
||||
WaterPlanKind kind = plan.Kind;
|
||||
if ((uint)kind > 3u)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("plan", plan.Kind, "unknown water plan kind");
|
||||
}
|
||||
kind = plan.Kind;
|
||||
if ((uint)(kind - 2) <= 1u)
|
||||
{
|
||||
instructions.Add(new NavInstruction.Surface((ushort)territory, plan.SurfacePoint.Value, seg.To.Id, seg.From.Id));
|
||||
}
|
||||
if (plan.Kind == WaterPlanKind.SurfaceOut)
|
||||
{
|
||||
instructions.Add(BuildWithinZoneMove(seg, territory, isFinalSegment));
|
||||
return;
|
||||
}
|
||||
kind = plan.Kind;
|
||||
if ((kind == WaterPlanKind.DiveIn || kind == WaterPlanKind.SurfaceOutDiveIn) ? true : false)
|
||||
{
|
||||
instructions.Add(new NavInstruction.Move((ushort)territory, plan.EntryPoint.Value, Fly: true, LandAtTarget: false, 3f, null, null, DisableNavmesh: false, AllowZoneTransition: false, seg.To.Id, seg.From.Id, IsFinal: false));
|
||||
instructions.Add(new NavInstruction.DiveEntry((ushort)territory, plan.EntryPoint.Value, seg.To.Id, seg.From.Id));
|
||||
}
|
||||
instructions.Add(new NavInstruction.Swim((ushort)territory, seg.To.Position, plan.GoalSurfaceY.Value, isFinalSegment ? ((float?)null) : new float?(3f), seg.To.Id, seg.From.Id, isFinalSegment));
|
||||
}
|
||||
|
||||
private void BuildSegment(List<NavInstruction> instructions, RouteSegment seg, RouteSegment? previous, int index, int segmentCount, uint destinationTerritory, bool disableNavmesh)
|
||||
{
|
||||
uint territoryId4;
|
||||
|
|
@ -79,15 +131,21 @@ public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerrit
|
|||
{
|
||||
uint territoryId = seg.From.TerritoryId;
|
||||
bool flag = index == segmentCount - 1 && seg.To.TerritoryId == destinationTerritory;
|
||||
logger.LogTrace("SmartNav: Move within {Territory}{Final}", territoryInfo.GetNameAndId(territoryId), flag ? " (final)" : "");
|
||||
WaterPlan waterPlan = waterPlanner.Plan(territoryId, seg.From.Position, seg.To.Position);
|
||||
logger.LogTrace("SmartNav: Move within {Territory}{Final}{Water}", territoryInfo.GetNameAndId(territoryId), flag ? " (final)" : "", (waterPlan == null) ? "" : $" (water: {waterPlan.Kind})");
|
||||
instructions.Add(new NavInstruction.WaitForTerritory(new global::_003C_003Ez__ReadOnlySingleElementList<uint>(territoryId), "Wait(territory: " + territoryInfo.GetNameAndId(territoryId) + ")"));
|
||||
if (!disableNavmesh)
|
||||
{
|
||||
instructions.Add(new NavInstruction.WaitForNavmesh());
|
||||
}
|
||||
uint? num = ((!(seg.To is NavNode.WarpNode warpNode)) ? ((uint?)null) : warpNode.NpcDataId);
|
||||
uint? dataId = num;
|
||||
instructions.Add(new NavInstruction.Move((ushort)territoryId, seg.To.Position, seg.Fly, seg.Fly, flag ? ((float?)null) : new float?(3f), dataId, null, DisableNavmesh: false, AllowZoneTransition: false, seg.To.Id, seg.From.Id, flag));
|
||||
if (waterPlan == null)
|
||||
{
|
||||
instructions.Add(BuildWithinZoneMove(seg, territoryId, flag));
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitWaterLegs(instructions, waterPlan, seg, territoryId, flag);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NavEdgeType.ZoneBoundary:
|
||||
|
|
@ -104,20 +162,20 @@ public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerrit
|
|||
{
|
||||
instructions.Add(new NavInstruction.Move((ushort)territoryId3, approach, Fly: true, LandAtTarget: true, 3f, null, null, DisableNavmesh: false, AllowZoneTransition: true, seg.From.Id, null, IsFinal: false));
|
||||
instructions.Add(new NavInstruction.Move((ushort)territoryId3, seg.From.Position, Fly: false, LandAtTarget: false, 0f, null, null, DisableNavmesh: true, AllowZoneTransition: true, seg.From.Id, null, IsFinal: false));
|
||||
goto IL_04b4;
|
||||
goto IL_04bb;
|
||||
}
|
||||
}
|
||||
instructions.Add(new NavInstruction.Move((ushort)territoryId3, seg.From.Position, seg.Fly, seg.Fly, 0f, null, null, DisableNavmesh: false, AllowZoneTransition: true, seg.From.Id, null, IsFinal: false));
|
||||
goto IL_04b4;
|
||||
goto IL_04bb;
|
||||
}
|
||||
case NavEdgeType.Warp:
|
||||
{
|
||||
uint territoryId5 = seg.From.TerritoryId;
|
||||
uint territoryId6 = seg.To.TerritoryId;
|
||||
logger.LogTrace("SmartNav: Warp from {From} to {To}", territoryInfo.GetNameAndId(territoryId5), territoryInfo.GetNameAndId(territoryId6));
|
||||
if (seg.From is NavNode.WarpNode { NpcDataId: { } npcDataId } warpNode2)
|
||||
if (seg.From is NavNode.WarpNode { NpcDataId: { } npcDataId } warpNode)
|
||||
{
|
||||
instructions.Add(new NavInstruction.InteractWarp(npcDataId, warpNode2.WarpRowId, (ushort)territoryId5, (ushort)territoryId6));
|
||||
instructions.Add(new NavInstruction.InteractWarp(npcDataId, warpNode.WarpRowId, (ushort)territoryId5, (ushort)territoryId6));
|
||||
break;
|
||||
}
|
||||
logger.LogWarning("SmartNav: warp segment {From} -> {To} has no interactable source NPC; segment dropped", territoryInfo.GetNameAndId(territoryId5), territoryInfo.GetNameAndId(territoryId6));
|
||||
|
|
@ -156,7 +214,7 @@ public sealed class RouteInstructionBuilder(AetheryteData aetheryteData, ITerrit
|
|||
logger.LogWarning("SmartNav: unhandled edge type {EdgeType}; segment dropped", seg.EdgeType);
|
||||
break;
|
||||
}
|
||||
IL_04b4:
|
||||
IL_04bb:
|
||||
instructions.Add(new NavInstruction.WaitForTerritory(new global::_003C_003Ez__ReadOnlySingleElementList<uint>(territoryId4), "Wait(territory: " + territoryInfo.GetNameAndId(territoryId4) + ")"));
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
5
SmartNav/SmartNav/WaterPlan.cs
Normal file
5
SmartNav/SmartNav/WaterPlan.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace SmartNav;
|
||||
|
||||
public sealed record WaterPlan(WaterPlanKind Kind, Vector3? SurfacePoint, Vector3? EntryPoint, float? GoalSurfaceY);
|
||||
9
SmartNav/SmartNav/WaterPlanKind.cs
Normal file
9
SmartNav/SmartNav/WaterPlanKind.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace SmartNav;
|
||||
|
||||
public enum WaterPlanKind
|
||||
{
|
||||
Submerged,
|
||||
DiveIn,
|
||||
SurfaceOut,
|
||||
SurfaceOutDiveIn
|
||||
}
|
||||
66
SmartNav/SmartNav/WaterRoutePlanner.cs
Normal file
66
SmartNav/SmartNav/WaterRoutePlanner.cs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using SmartNav.Data;
|
||||
using SmartNav.Model.Navigation;
|
||||
|
||||
namespace SmartNav;
|
||||
|
||||
public sealed class WaterRoutePlanner(WaterMapService waterMaps)
|
||||
{
|
||||
private const float SameBodySurfaceTolerance = 0.5f;
|
||||
|
||||
public bool IsSubmerged(uint territoryId, Vector3 point, out float surfaceY)
|
||||
{
|
||||
surfaceY = 0f;
|
||||
if (!waterMaps.TryGetMap(territoryId, out TerritoryWaterMap map))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return IsSubmerged(map, point, out surfaceY);
|
||||
}
|
||||
|
||||
public WaterPlan? Plan(uint territoryId, Vector3 from, Vector3 to)
|
||||
{
|
||||
if (!waterMaps.TryGetMap(territoryId, out TerritoryWaterMap map))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
float surfaceY;
|
||||
bool flag = IsSubmerged(map, from, out surfaceY);
|
||||
float surfaceY2;
|
||||
bool flag2 = IsSubmerged(map, to, out surfaceY2);
|
||||
if (!flag && !flag2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Vector3? surfacePoint = (flag ? new Vector3?(new Vector3(from.X, surfaceY, from.Z)) : ((Vector3?)null));
|
||||
Vector3 value = new Vector3(to.X, surfaceY2, to.Z);
|
||||
if (!flag)
|
||||
{
|
||||
return new WaterPlan(WaterPlanKind.DiveIn, null, value, surfaceY2);
|
||||
}
|
||||
if (!flag2)
|
||||
{
|
||||
return new WaterPlan(WaterPlanKind.SurfaceOut, surfacePoint, null, null);
|
||||
}
|
||||
if (MathF.Abs(surfaceY - surfaceY2) <= 0.5f)
|
||||
{
|
||||
return new WaterPlan(WaterPlanKind.Submerged, null, null, surfaceY2);
|
||||
}
|
||||
return new WaterPlan(WaterPlanKind.SurfaceOutDiveIn, surfacePoint, value, surfaceY2);
|
||||
}
|
||||
|
||||
private static bool IsSubmerged(TerritoryWaterMap map, Vector3 point, out float surfaceY)
|
||||
{
|
||||
surfaceY = 0f;
|
||||
if (map.ClassifyAt(point.X, point.Z) != WaterCellClass.Divable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (map.TryGetSurfaceY(point.X, point.Z, out surfaceY))
|
||||
{
|
||||
return point.Y <= surfaceY - 20.6f;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue