502 lines
12 KiB
C#
502 lines
12 KiB
C#
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Dalamud.Plugin;
|
|
using Dalamud.Plugin.Ipc;
|
|
using Dalamud.Plugin.Ipc.Exceptions;
|
|
using Dalamud.Plugin.Services;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Questionable.External;
|
|
|
|
internal sealed class NavmeshIpc
|
|
{
|
|
private sealed class Subscribers
|
|
{
|
|
public readonly ICallGateSubscriber<bool> IsNavReady;
|
|
|
|
public readonly ICallGateSubscriber<Vector3, Vector3, bool, CancellationToken, Task<List<Vector3>>> NavPathfind;
|
|
|
|
public readonly ICallGateSubscriber<Vector3, Vector3, bool, float, Task<List<Vector3>>> NavPathfindWithTolerance;
|
|
|
|
public readonly ICallGateSubscriber<bool> NavPathfindInProgress;
|
|
|
|
public readonly ICallGateSubscriber<int> NavNumQueuedRequests;
|
|
|
|
public readonly ICallGateSubscriber<List<Vector3>, bool, object> PathMoveTo;
|
|
|
|
public readonly ICallGateSubscriber<object> PathStop;
|
|
|
|
public readonly ICallGateSubscriber<bool> PathIsRunning;
|
|
|
|
public readonly ICallGateSubscriber<List<Vector3>> PathListWaypoints;
|
|
|
|
public readonly ICallGateSubscriber<float, object> PathSetTolerance;
|
|
|
|
public readonly ICallGateSubscriber<Vector3, bool, float, Vector3?> QueryPointOnFloor;
|
|
|
|
public readonly ICallGateSubscriber<Vector3, float, float, Vector3?> QueryNearestPoint;
|
|
|
|
public readonly ICallGateSubscriber<Vector3, float, float, Vector3?> QueryNearestPointReachable;
|
|
|
|
public readonly ICallGateSubscriber<float> BuildProgress;
|
|
|
|
public readonly ICallGateSubscriber<bool> NavReload;
|
|
|
|
public readonly ICallGateSubscriber<bool> NavRebuild;
|
|
|
|
public Subscribers(IDalamudPluginInterface pluginInterface, Configuration.ENavigationProvider provider)
|
|
{
|
|
string text = ((provider == Configuration.ENavigationProvider.WigglyNavShim) ? "WigglyNav" : "vnavmesh");
|
|
IsNavReady = pluginInterface.GetIpcSubscriber<bool>(text + ".Nav.IsReady");
|
|
NavPathfind = pluginInterface.GetIpcSubscriber<Vector3, Vector3, bool, CancellationToken, Task<List<Vector3>>>(text + ".Nav.PathfindCancelable");
|
|
NavPathfindWithTolerance = pluginInterface.GetIpcSubscriber<Vector3, Vector3, bool, float, Task<List<Vector3>>>(text + ".Nav.PathfindWithTolerance");
|
|
NavPathfindInProgress = pluginInterface.GetIpcSubscriber<bool>(text + ".Nav.PathfindInProgress");
|
|
NavNumQueuedRequests = pluginInterface.GetIpcSubscriber<int>(text + ".Nav.PathfindNumQueued");
|
|
PathMoveTo = pluginInterface.GetIpcSubscriber<List<Vector3>, bool, object>(text + ".Path.MoveTo");
|
|
PathStop = pluginInterface.GetIpcSubscriber<object>(text + ".Path.Stop");
|
|
PathIsRunning = pluginInterface.GetIpcSubscriber<bool>(text + ".Path.IsRunning");
|
|
PathListWaypoints = pluginInterface.GetIpcSubscriber<List<Vector3>>(text + ".Path.ListWaypoints");
|
|
PathSetTolerance = pluginInterface.GetIpcSubscriber<float, object>(text + ".Path.SetTolerance");
|
|
QueryPointOnFloor = pluginInterface.GetIpcSubscriber<Vector3, bool, float, Vector3?>(text + ".Query.Mesh.PointOnFloor");
|
|
QueryNearestPoint = pluginInterface.GetIpcSubscriber<Vector3, float, float, Vector3?>(text + ".Query.Mesh.NearestPoint");
|
|
QueryNearestPointReachable = pluginInterface.GetIpcSubscriber<Vector3, float, float, Vector3?>(text + ".Query.Mesh.NearestPointReachable");
|
|
BuildProgress = pluginInterface.GetIpcSubscriber<float>(text + ".Nav.BuildProgress");
|
|
NavReload = pluginInterface.GetIpcSubscriber<bool>(text + ".Nav.Reload");
|
|
NavRebuild = pluginInterface.GetIpcSubscriber<bool>(text + ".Nav.Rebuild");
|
|
}
|
|
}
|
|
|
|
private readonly IDalamudPluginInterface _pluginInterface;
|
|
|
|
private readonly Configuration _configuration;
|
|
|
|
private readonly WigglyNavIpc _wigglyNavIpc;
|
|
|
|
private readonly WigglyNavAvailability _wigglyNavAvailability;
|
|
|
|
private readonly IChatGui _chatGui;
|
|
|
|
private readonly ILogger<NavmeshIpc> _logger;
|
|
|
|
private readonly ICallGateSubscriber<object> _vnavmeshStop;
|
|
|
|
private Configuration.ENavigationProvider _activeProvider;
|
|
|
|
private Subscribers _subscribers;
|
|
|
|
private bool _fallbackNoticed;
|
|
|
|
private bool UseNative
|
|
{
|
|
get
|
|
{
|
|
if (_configuration.General.NavigationProvider == Configuration.ENavigationProvider.WigglyNav)
|
|
{
|
|
return _wigglyNavAvailability.IsCompatible;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private Subscribers S
|
|
{
|
|
get
|
|
{
|
|
Configuration.ENavigationProvider navigationProvider = _configuration.General.NavigationProvider;
|
|
if (navigationProvider != _activeProvider)
|
|
{
|
|
_subscribers = new Subscribers(_pluginInterface, navigationProvider);
|
|
_activeProvider = navigationProvider;
|
|
}
|
|
return _subscribers;
|
|
}
|
|
}
|
|
|
|
public bool IsNavmeshAvailable
|
|
{
|
|
get
|
|
{
|
|
if (UseNative)
|
|
{
|
|
return _wigglyNavIpc.IsNavmeshAvailable;
|
|
}
|
|
try
|
|
{
|
|
S.IsNavReady.InvokeFunc();
|
|
return true;
|
|
}
|
|
catch (IpcNotReadyError)
|
|
{
|
|
return false;
|
|
}
|
|
catch (IpcError)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsReady
|
|
{
|
|
get
|
|
{
|
|
if (UseNative)
|
|
{
|
|
return _wigglyNavIpc.IsReady;
|
|
}
|
|
try
|
|
{
|
|
return S.IsNavReady.InvokeFunc();
|
|
}
|
|
catch (IpcError)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsPathRunning
|
|
{
|
|
get
|
|
{
|
|
if (UseNative)
|
|
{
|
|
return _wigglyNavIpc.IsPathRunning;
|
|
}
|
|
try
|
|
{
|
|
return S.PathIsRunning.InvokeFunc();
|
|
}
|
|
catch (IpcError)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsPathfindInProgress
|
|
{
|
|
get
|
|
{
|
|
if (UseNative)
|
|
{
|
|
return _wigglyNavIpc.IsPathfindInProgress;
|
|
}
|
|
try
|
|
{
|
|
return S.NavPathfindInProgress.InvokeFunc();
|
|
}
|
|
catch (IpcError)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public int NumQueuedPathfindRequests
|
|
{
|
|
get
|
|
{
|
|
if (UseNative)
|
|
{
|
|
return _wigglyNavIpc.NumQueuedPathfindRequests;
|
|
}
|
|
try
|
|
{
|
|
return S.NavNumQueuedRequests.InvokeFunc();
|
|
}
|
|
catch (IpcError)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
public NavmeshIpc(IDalamudPluginInterface pluginInterface, Configuration configuration, WigglyNavIpc wigglyNavIpc, WigglyNavAvailability wigglyNavAvailability, IChatGui chatGui, ILogger<NavmeshIpc> logger)
|
|
{
|
|
_pluginInterface = pluginInterface;
|
|
_configuration = configuration;
|
|
_wigglyNavIpc = wigglyNavIpc;
|
|
_wigglyNavAvailability = wigglyNavAvailability;
|
|
_chatGui = chatGui;
|
|
_logger = logger;
|
|
_activeProvider = configuration.General.NavigationProvider;
|
|
_subscribers = new Subscribers(pluginInterface, _activeProvider);
|
|
_vnavmeshStop = pluginInterface.GetIpcSubscriber<object>("vnavmesh.Path.Stop");
|
|
}
|
|
|
|
internal void StopAll()
|
|
{
|
|
_wigglyNavIpc.Stop();
|
|
try
|
|
{
|
|
_vnavmeshStop.InvokeAction();
|
|
}
|
|
catch (IpcError)
|
|
{
|
|
}
|
|
}
|
|
|
|
private void NoticeFallbackToVnavmesh()
|
|
{
|
|
if (UseNative || _configuration.General.NavigationProvider != Configuration.ENavigationProvider.WigglyNav)
|
|
{
|
|
_fallbackNoticed = false;
|
|
}
|
|
else if (!_fallbackNoticed)
|
|
{
|
|
_fallbackNoticed = true;
|
|
_chatGui.Print("WigglyNav is unavailable - navigation is using vnavmesh until it returns.", "Questionable", 576);
|
|
}
|
|
}
|
|
|
|
private async Task<NavPathfindResult> PathfindNative(Vector3 localPlayerPosition, Vector3 targetPosition, bool fly, float tolerance, CancellationToken cancellationToken)
|
|
{
|
|
PathfindResult pathfindResult = await _wigglyNavIpc.Pathfind(localPlayerPosition, targetPosition, fly, tolerance, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
|
|
return new NavPathfindResult(pathfindResult.Waypoints, HasPartialInfo: true, pathfindResult.IsPartial, pathfindResult.EndDistance);
|
|
}
|
|
|
|
private static async Task<NavPathfindResult> WrapLegacyPathfind(Task<List<Vector3>> pathfind)
|
|
{
|
|
return new NavPathfindResult(await pathfind.ConfigureAwait(continueOnCapturedContext: false), HasPartialInfo: false, IsPartial: false, 0f);
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
if (UseNative)
|
|
{
|
|
_wigglyNavIpc.Stop();
|
|
}
|
|
else if (IsNavmeshAvailable)
|
|
{
|
|
try
|
|
{
|
|
S.PathStop.InvokeAction();
|
|
}
|
|
catch (IpcError exception)
|
|
{
|
|
_logger.LogWarning(exception, "Could not stop navigating via navmesh");
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Reload()
|
|
{
|
|
if (UseNative)
|
|
{
|
|
_wigglyNavIpc.Reload();
|
|
}
|
|
else if (IsNavmeshAvailable)
|
|
{
|
|
try
|
|
{
|
|
S.NavReload.InvokeFunc();
|
|
}
|
|
catch (IpcError exception)
|
|
{
|
|
_logger.LogWarning(exception, "Could not reload navmesh");
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Rebuild()
|
|
{
|
|
if (UseNative)
|
|
{
|
|
_wigglyNavIpc.Rebuild();
|
|
}
|
|
else if (IsNavmeshAvailable)
|
|
{
|
|
try
|
|
{
|
|
S.NavRebuild.InvokeFunc();
|
|
}
|
|
catch (IpcError exception)
|
|
{
|
|
_logger.LogWarning(exception, "Could not rebuild navmesh");
|
|
}
|
|
}
|
|
}
|
|
|
|
public Task<NavPathfindResult> Pathfind(Vector3 localPlayerPosition, Vector3 targetPosition, bool fly, CancellationToken cancellationToken)
|
|
{
|
|
NoticeFallbackToVnavmesh();
|
|
if (UseNative)
|
|
{
|
|
return PathfindNative(localPlayerPosition, targetPosition, fly, 0f, cancellationToken);
|
|
}
|
|
if (!IsNavmeshAvailable)
|
|
{
|
|
return Task.FromResult(EmptyPathfindResult());
|
|
}
|
|
try
|
|
{
|
|
S.PathSetTolerance.InvokeAction(0.25f);
|
|
return WrapLegacyPathfind(S.NavPathfind.InvokeFunc(localPlayerPosition, targetPosition, fly, cancellationToken));
|
|
}
|
|
catch (IpcError exception)
|
|
{
|
|
_logger.LogWarning(exception, "Could not pathfind via navmesh");
|
|
return Task.FromException<NavPathfindResult>(exception);
|
|
}
|
|
}
|
|
|
|
public Task<NavPathfindResult> PathfindWithTolerance(Vector3 localPlayerPosition, Vector3 targetPosition, bool fly, float tolerance)
|
|
{
|
|
NoticeFallbackToVnavmesh();
|
|
if (UseNative)
|
|
{
|
|
return PathfindNative(localPlayerPosition, targetPosition, fly, tolerance, CancellationToken.None);
|
|
}
|
|
if (!IsNavmeshAvailable)
|
|
{
|
|
return Task.FromResult(EmptyPathfindResult());
|
|
}
|
|
try
|
|
{
|
|
S.PathSetTolerance.InvokeAction(tolerance);
|
|
return WrapLegacyPathfind(S.NavPathfindWithTolerance.InvokeFunc(localPlayerPosition, targetPosition, fly, tolerance));
|
|
}
|
|
catch (IpcError exception)
|
|
{
|
|
_logger.LogWarning(exception, "Could not pathfind with tolerance via navmesh");
|
|
return Task.FromException<NavPathfindResult>(exception);
|
|
}
|
|
}
|
|
|
|
public void MoveTo(List<Vector3> position, bool fly, float arrivalTolerance)
|
|
{
|
|
NoticeFallbackToVnavmesh();
|
|
if (UseNative)
|
|
{
|
|
if (!_wigglyNavIpc.MoveTo(position, fly, arrivalTolerance))
|
|
{
|
|
_logger.LogWarning("Could not move via WigglyNav, the command was refused");
|
|
}
|
|
}
|
|
else if (IsNavmeshAvailable)
|
|
{
|
|
Stop();
|
|
try
|
|
{
|
|
S.PathMoveTo.InvokeAction(position, fly);
|
|
}
|
|
catch (IpcError exception)
|
|
{
|
|
_logger.LogWarning(exception, "Could not move via navmesh");
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector3? GetPointOnFloor(Vector3 position, bool unlandable)
|
|
{
|
|
if (UseNative)
|
|
{
|
|
return _wigglyNavIpc.GetPointOnFloor(position, unlandable, 0.2f);
|
|
}
|
|
try
|
|
{
|
|
return S.QueryPointOnFloor.InvokeFunc(position, unlandable, 0.2f);
|
|
}
|
|
catch (IpcError)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public Vector3? GetPointOnFloor(Vector3 position, bool unlandable, float halfExtentXZ)
|
|
{
|
|
if (UseNative)
|
|
{
|
|
return _wigglyNavIpc.GetPointOnFloor(position, unlandable, halfExtentXZ);
|
|
}
|
|
try
|
|
{
|
|
return S.QueryPointOnFloor.InvokeFunc(position, unlandable, halfExtentXZ);
|
|
}
|
|
catch (IpcError)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public Vector3? FindNearestMeshPoint(Vector3 position, float halfExtentXZ, float halfExtentY)
|
|
{
|
|
if (UseNative)
|
|
{
|
|
return _wigglyNavIpc.FindNearestMeshPoint(position, halfExtentXZ, halfExtentY);
|
|
}
|
|
try
|
|
{
|
|
return S.QueryNearestPoint.InvokeFunc(position, halfExtentXZ, halfExtentY);
|
|
}
|
|
catch (IpcError)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public Vector3? FindNearestReachableMeshPoint(Vector3 position, float halfExtentXZ, float halfExtentY)
|
|
{
|
|
if (UseNative)
|
|
{
|
|
return _wigglyNavIpc.FindNearestReachableMeshPoint(position, halfExtentXZ, halfExtentY);
|
|
}
|
|
try
|
|
{
|
|
return S.QueryNearestPointReachable.InvokeFunc(position, halfExtentXZ, halfExtentY);
|
|
}
|
|
catch (IpcError)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public List<Vector3> GetWaypoints()
|
|
{
|
|
if (UseNative)
|
|
{
|
|
return _wigglyNavIpc.GetWaypoints();
|
|
}
|
|
if (IsPathRunning)
|
|
{
|
|
try
|
|
{
|
|
return S.PathListWaypoints.InvokeFunc();
|
|
}
|
|
catch (IpcError)
|
|
{
|
|
return new List<Vector3>();
|
|
}
|
|
}
|
|
return new List<Vector3>();
|
|
}
|
|
|
|
public int GetBuildProgress()
|
|
{
|
|
if (UseNative)
|
|
{
|
|
return _wigglyNavIpc.GetBuildProgress();
|
|
}
|
|
try
|
|
{
|
|
float num = S.BuildProgress.InvokeFunc();
|
|
if (num < 0f)
|
|
{
|
|
return 100;
|
|
}
|
|
return (int)(num * 100f);
|
|
}
|
|
catch (IpcError)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private static NavPathfindResult EmptyPathfindResult()
|
|
{
|
|
return new NavPathfindResult(new List<Vector3>(), HasPartialInfo: false, IsPartial: false, 0f);
|
|
}
|
|
}
|