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 IsNavReady; public readonly ICallGateSubscriber>> NavPathfind; public readonly ICallGateSubscriber>> NavPathfindWithTolerance; public readonly ICallGateSubscriber NavPathfindInProgress; public readonly ICallGateSubscriber NavNumQueuedRequests; public readonly ICallGateSubscriber, bool, object> PathMoveTo; public readonly ICallGateSubscriber PathStop; public readonly ICallGateSubscriber PathIsRunning; public readonly ICallGateSubscriber> PathListWaypoints; public readonly ICallGateSubscriber PathSetTolerance; public readonly ICallGateSubscriber QueryPointOnFloor; public readonly ICallGateSubscriber QueryNearestPoint; public readonly ICallGateSubscriber QueryNearestPointReachable; public readonly ICallGateSubscriber BuildProgress; public readonly ICallGateSubscriber NavReload; public readonly ICallGateSubscriber NavRebuild; public Subscribers(IDalamudPluginInterface pluginInterface, Configuration.ENavigationProvider provider) { string text = ((provider == Configuration.ENavigationProvider.WigglyNavShim) ? "WigglyNav" : "vnavmesh"); IsNavReady = pluginInterface.GetIpcSubscriber(text + ".Nav.IsReady"); NavPathfind = pluginInterface.GetIpcSubscriber>>(text + ".Nav.PathfindCancelable"); NavPathfindWithTolerance = pluginInterface.GetIpcSubscriber>>(text + ".Nav.PathfindWithTolerance"); NavPathfindInProgress = pluginInterface.GetIpcSubscriber(text + ".Nav.PathfindInProgress"); NavNumQueuedRequests = pluginInterface.GetIpcSubscriber(text + ".Nav.PathfindNumQueued"); PathMoveTo = pluginInterface.GetIpcSubscriber, bool, object>(text + ".Path.MoveTo"); PathStop = pluginInterface.GetIpcSubscriber(text + ".Path.Stop"); PathIsRunning = pluginInterface.GetIpcSubscriber(text + ".Path.IsRunning"); PathListWaypoints = pluginInterface.GetIpcSubscriber>(text + ".Path.ListWaypoints"); PathSetTolerance = pluginInterface.GetIpcSubscriber(text + ".Path.SetTolerance"); QueryPointOnFloor = pluginInterface.GetIpcSubscriber(text + ".Query.Mesh.PointOnFloor"); QueryNearestPoint = pluginInterface.GetIpcSubscriber(text + ".Query.Mesh.NearestPoint"); QueryNearestPointReachable = pluginInterface.GetIpcSubscriber(text + ".Query.Mesh.NearestPointReachable"); BuildProgress = pluginInterface.GetIpcSubscriber(text + ".Nav.BuildProgress"); NavReload = pluginInterface.GetIpcSubscriber(text + ".Nav.Reload"); NavRebuild = pluginInterface.GetIpcSubscriber(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 _logger; private readonly ICallGateSubscriber _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 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("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 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 WrapLegacyPathfind(Task> 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 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(exception); } } public Task 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(exception); } } public void MoveTo(List 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 GetWaypoints() { if (UseNative) { return _wigglyNavIpc.GetWaypoints(); } if (IsPathRunning) { try { return S.PathListWaypoints.InvokeFunc(); } catch (IpcError) { return new List(); } } return new List(); } 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(), HasPartialInfo: false, IsPartial: false, 0f); } }