193 lines
5.7 KiB
C#
193 lines
5.7 KiB
C#
using System;
|
|
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 Microsoft.Extensions.Logging;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace Questionable.External;
|
|
|
|
internal sealed class WigglyNavIpc
|
|
{
|
|
private readonly ILogger<WigglyNavIpc> _logger;
|
|
|
|
private readonly ICallGateSubscriber<(int, int)> _apiVersion;
|
|
|
|
private readonly ICallGateSubscriber<NavStatus> _status;
|
|
|
|
private readonly ICallGateSubscriber<bool> _rebuild;
|
|
|
|
private readonly ICallGateSubscriber<PathfindQuery, CancellationToken, Task<string>> _pathfind;
|
|
|
|
private readonly ICallGateSubscriber<MoveCommand, bool> _move;
|
|
|
|
private readonly ICallGateSubscriber<object> _stop;
|
|
|
|
private readonly ICallGateSubscriber<bool> _isRunning;
|
|
|
|
private readonly ICallGateSubscriber<List<Vector3>> _waypoints;
|
|
|
|
private readonly ICallGateSubscriber<Vector3, float, bool, Vector3?> _floorPoint;
|
|
|
|
private readonly ICallGateSubscriber<Vector3, float, Vector3?> _nearestPoint;
|
|
|
|
private int _outstandingPathfinds;
|
|
|
|
public bool IsNavmeshAvailable => TryGetApiVersion().HasValue;
|
|
|
|
public bool IsReady => GetStatus()?.Ready ?? false;
|
|
|
|
public bool IsPathRunning => Guarded(() => _isRunning.InvokeFunc(), fallback: false);
|
|
|
|
public bool IsPathfindInProgress => Volatile.Read(in _outstandingPathfinds) > 0;
|
|
|
|
public int NumQueuedPathfindRequests => Math.Max(0, Volatile.Read(in _outstandingPathfinds) - 1);
|
|
|
|
public WigglyNavIpc(IDalamudPluginInterface pluginInterface, ILogger<WigglyNavIpc> logger)
|
|
{
|
|
_logger = logger;
|
|
_apiVersion = pluginInterface.GetIpcSubscriber<(int, int)>("WigglyNav.ApiVersion");
|
|
_status = pluginInterface.GetIpcSubscriber<NavStatus>("WigglyNav.V1.Nav.Status");
|
|
_rebuild = pluginInterface.GetIpcSubscriber<bool>("WigglyNav.V1.Nav.Rebuild");
|
|
_pathfind = pluginInterface.GetIpcSubscriber<PathfindQuery, CancellationToken, Task<string>>("WigglyNav.V1.Pathfind");
|
|
_move = pluginInterface.GetIpcSubscriber<MoveCommand, bool>("WigglyNav.V1.Path.Move");
|
|
_stop = pluginInterface.GetIpcSubscriber<object>("WigglyNav.V1.Path.Stop");
|
|
_isRunning = pluginInterface.GetIpcSubscriber<bool>("WigglyNav.V1.Path.IsRunning");
|
|
_waypoints = pluginInterface.GetIpcSubscriber<List<Vector3>>("WigglyNav.V1.Path.Waypoints");
|
|
_floorPoint = pluginInterface.GetIpcSubscriber<Vector3, float, bool, Vector3?>("WigglyNav.V1.Query.FloorPoint");
|
|
_nearestPoint = pluginInterface.GetIpcSubscriber<Vector3, float, Vector3?>("WigglyNav.V1.Query.NearestPoint");
|
|
}
|
|
|
|
private static T Guarded<T>(Func<T> invoke, T fallback = default(T))
|
|
{
|
|
try
|
|
{
|
|
return invoke();
|
|
}
|
|
catch (IpcError)
|
|
{
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
public (int Major, int Minor)? TryGetApiVersion()
|
|
{
|
|
return Guarded((Func<(int, int)?>)(() => _apiVersion.InvokeFunc()), ((int, int)?)null);
|
|
}
|
|
|
|
public int GetBuildProgress()
|
|
{
|
|
NavStatus status = GetStatus();
|
|
if (status == null)
|
|
{
|
|
return 0;
|
|
}
|
|
if (status.TilesTotal > 0)
|
|
{
|
|
return Math.Clamp(status.TilesBuilt * 100 / status.TilesTotal, 0, 100);
|
|
}
|
|
if (!status.Ready)
|
|
{
|
|
return 0;
|
|
}
|
|
return 100;
|
|
}
|
|
|
|
public async Task<PathfindResult> Pathfind(Vector3 from, Vector3 to, bool fly, float tolerance, CancellationToken cancellationToken)
|
|
{
|
|
Interlocked.Increment(ref _outstandingPathfinds);
|
|
try
|
|
{
|
|
PathfindQuery query = new PathfindQuery(from, to, fly, tolerance);
|
|
Task<string> task = Guarded(() => _pathfind.InvokeFunc(query, cancellationToken));
|
|
if (task == null)
|
|
{
|
|
_logger.LogWarning("Could not pathfind via WigglyNav, the IPC gate is unavailable");
|
|
return EmptyPathfindResult();
|
|
}
|
|
string value = await task.ConfigureAwait(continueOnCapturedContext: false);
|
|
try
|
|
{
|
|
PathfindResult pathfindResult = JsonConvert.DeserializeObject<PathfindResult>(value);
|
|
if ((object)pathfindResult == null || pathfindResult.Waypoints == null)
|
|
{
|
|
_logger.LogWarning("WigglyNav pathfind answered a payload with no waypoint list, treating as no path");
|
|
return EmptyPathfindResult();
|
|
}
|
|
return pathfindResult;
|
|
}
|
|
catch (JsonException exception)
|
|
{
|
|
_logger.LogWarning(exception, "WigglyNav pathfind answered malformed JSON, treating as no path");
|
|
return EmptyPathfindResult();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
Interlocked.Decrement(ref _outstandingPathfinds);
|
|
}
|
|
}
|
|
|
|
public bool MoveTo(List<Vector3> waypoints, bool fly, float arrivalTolerance)
|
|
{
|
|
Stop();
|
|
MoveCommand command = new MoveCommand(waypoints, fly, arrivalTolerance);
|
|
return Guarded(() => _move.InvokeFunc(command), fallback: false);
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
Guarded(delegate
|
|
{
|
|
_stop.InvokeAction();
|
|
return true;
|
|
}, fallback: false);
|
|
}
|
|
|
|
public List<Vector3> GetWaypoints()
|
|
{
|
|
return Guarded(() => _waypoints.InvokeFunc()) ?? new List<Vector3>();
|
|
}
|
|
|
|
public Vector3? GetPointOnFloor(Vector3 position, bool unlandable, float halfExtentXZ)
|
|
{
|
|
return Guarded(() => _floorPoint.InvokeFunc(position, halfExtentXZ, unlandable));
|
|
}
|
|
|
|
public Vector3? FindNearestMeshPoint(Vector3 position, float halfExtentXZ, float halfExtentY)
|
|
{
|
|
return Guarded(() => _nearestPoint.InvokeFunc(position, MathF.Max(halfExtentXZ, halfExtentY)));
|
|
}
|
|
|
|
public Vector3? FindNearestReachableMeshPoint(Vector3 position, float halfExtentXZ, float halfExtentY)
|
|
{
|
|
return FindNearestMeshPoint(position, halfExtentXZ, halfExtentY);
|
|
}
|
|
|
|
public void Reload()
|
|
{
|
|
Rebuild();
|
|
}
|
|
|
|
public void Rebuild()
|
|
{
|
|
if (!Guarded(() => _rebuild.InvokeFunc(), fallback: false))
|
|
{
|
|
_logger.LogWarning("Could not rebuild navmesh via WigglyNav, the request was refused or the gate is unavailable");
|
|
}
|
|
}
|
|
|
|
private NavStatus? GetStatus()
|
|
{
|
|
return Guarded(() => _status.InvokeFunc());
|
|
}
|
|
|
|
private static PathfindResult EmptyPathfindResult()
|
|
{
|
|
return new PathfindResult(new List<Vector3>(), IsPartial: false, "None", 0f);
|
|
}
|
|
}
|