punish v6.8.18.0
This commit is contained in:
commit
060278c1b7
317 changed files with 554155 additions and 0 deletions
493
Questionable/Questionable.Controller/MovementController.cs
Normal file
493
Questionable/Questionable.Controller/MovementController.cs
Normal file
|
@ -0,0 +1,493 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin.Ipc.Exceptions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.NavigationOverrides;
|
||||
using Questionable.Data;
|
||||
using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Common.Converter;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller;
|
||||
|
||||
internal sealed class MovementController : IDisposable
|
||||
{
|
||||
public sealed record DestinationData(EMovementType MovementType, uint? DataId, Vector3 Position, float StopDistance, bool IsFlying, bool CanSprint, float VerticalStopDistance, bool Land, bool UseNavmesh)
|
||||
{
|
||||
public int NavmeshCalculations { get; set; }
|
||||
|
||||
public List<Vector3> PartialRoute { get; } = new List<Vector3>();
|
||||
|
||||
public LastWaypointData? LastWaypoint { get; set; }
|
||||
|
||||
public bool ShouldRecalculateNavmesh()
|
||||
{
|
||||
return NavmeshCalculations < 10;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record LastWaypointData(Vector3 Position)
|
||||
{
|
||||
public long UpdatedAt { get; set; }
|
||||
|
||||
public double Distance2DAtLastUpdate { get; set; }
|
||||
}
|
||||
|
||||
public sealed class PathfindingFailedException : Exception
|
||||
{
|
||||
public PathfindingFailedException()
|
||||
{
|
||||
}
|
||||
|
||||
public PathfindingFailedException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public PathfindingFailedException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public const float DefaultVerticalInteractionDistance = 1.95f;
|
||||
|
||||
private readonly NavmeshIpc _navmeshIpc;
|
||||
|
||||
private readonly IClientState _clientState;
|
||||
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
|
||||
private readonly ChatFunctions _chatFunctions;
|
||||
|
||||
private readonly ICondition _condition;
|
||||
|
||||
private readonly MovementOverrideController _movementOverrideController;
|
||||
|
||||
private readonly AetheryteData _aetheryteData;
|
||||
|
||||
private readonly ILogger<MovementController> _logger;
|
||||
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
private Task<List<Vector3>>? _pathfindTask;
|
||||
|
||||
public bool IsNavmeshReady
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return _navmeshIpc.IsReady;
|
||||
}
|
||||
catch (IpcNotReadyError)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPathRunning
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return _navmeshIpc.IsPathRunning;
|
||||
}
|
||||
catch (IpcNotReadyError)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPathfinding
|
||||
{
|
||||
get
|
||||
{
|
||||
Task<List<Vector3>> pathfindTask = _pathfindTask;
|
||||
if (pathfindTask != null)
|
||||
{
|
||||
return !pathfindTask.IsCompleted;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public DestinationData? Destination { get; set; }
|
||||
|
||||
public DateTime MovementStartedAt { get; private set; } = DateTime.Now;
|
||||
|
||||
public int BuiltNavmeshPercent => _navmeshIpc.GetBuildProgress();
|
||||
|
||||
public MovementController(NavmeshIpc navmeshIpc, IClientState clientState, GameFunctions gameFunctions, ChatFunctions chatFunctions, ICondition condition, MovementOverrideController movementOverrideController, AetheryteData aetheryteData, ILogger<MovementController> logger)
|
||||
{
|
||||
_navmeshIpc = navmeshIpc;
|
||||
_clientState = clientState;
|
||||
_gameFunctions = gameFunctions;
|
||||
_chatFunctions = chatFunctions;
|
||||
_condition = condition;
|
||||
_movementOverrideController = movementOverrideController;
|
||||
_aetheryteData = aetheryteData;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
if (_pathfindTask != null && Destination != null)
|
||||
{
|
||||
if (_pathfindTask.IsCompletedSuccessfully)
|
||||
{
|
||||
_logger.LogInformation("Pathfinding complete, got {Count} points", _pathfindTask.Result.Count);
|
||||
if (_pathfindTask.Result.Count == 0)
|
||||
{
|
||||
ResetPathfinding();
|
||||
throw new PathfindingFailedException();
|
||||
}
|
||||
List<Vector3> list = _pathfindTask.Result.Skip(1).ToList();
|
||||
Vector3 p = _clientState.LocalPlayer?.Position ?? list[0];
|
||||
if (Destination.IsFlying && !_condition[ConditionFlag.InFlight] && _condition[ConditionFlag.Mounted] && (IsOnFlightPath(p) || list.Any(IsOnFlightPath)))
|
||||
{
|
||||
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null);
|
||||
}
|
||||
if (!Destination.IsFlying)
|
||||
{
|
||||
(List<Vector3>, bool) tuple = _movementOverrideController.AdjustPath(list);
|
||||
(list, _) = tuple;
|
||||
if (tuple.Item2 && Destination.ShouldRecalculateNavmesh())
|
||||
{
|
||||
Destination.NavmeshCalculations++;
|
||||
Destination.PartialRoute.AddRange(list);
|
||||
_logger.LogInformation("Running navmesh recalculation with fudged point ({From} to {To})", list.Last(), Destination.Position);
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
|
||||
_pathfindTask = _navmeshIpc.Pathfind(list.Last(), Destination.Position, Destination.IsFlying, _cancellationTokenSource.Token);
|
||||
return;
|
||||
}
|
||||
}
|
||||
list = Destination.PartialRoute.Concat(list).ToList();
|
||||
_logger.LogInformation("Navigating via route: [{Route}]", string.Join(" → ", _pathfindTask.Result.Select((Vector3 x) => x.ToString("G", CultureInfo.InvariantCulture))));
|
||||
_navmeshIpc.MoveTo(list, Destination.IsFlying);
|
||||
MovementStartedAt = DateTime.Now;
|
||||
ResetPathfinding();
|
||||
}
|
||||
else if (_pathfindTask.IsCompleted)
|
||||
{
|
||||
_logger.LogWarning("Unable to complete pathfinding task");
|
||||
ResetPathfinding();
|
||||
throw new PathfindingFailedException();
|
||||
}
|
||||
}
|
||||
if (!IsPathRunning || !(Destination != null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_gameFunctions.IsLoadingScreenVisible())
|
||||
{
|
||||
_logger.LogInformation("Stopping movement, loading screen visible");
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
DestinationData destination = Destination;
|
||||
if ((object)destination != null && destination.IsFlying && _condition[ConditionFlag.Swimming])
|
||||
{
|
||||
_logger.LogInformation("Flying but swimming, restarting as non-flying path...");
|
||||
Restart(Destination);
|
||||
return;
|
||||
}
|
||||
destination = Destination;
|
||||
if ((object)destination != null && destination.IsFlying && !_condition[ConditionFlag.Mounted])
|
||||
{
|
||||
_logger.LogInformation("Flying but not mounted, restarting as non-flying path...");
|
||||
Restart(Destination);
|
||||
return;
|
||||
}
|
||||
Vector3 vector = _clientState.LocalPlayer?.Position ?? Vector3.Zero;
|
||||
if (Destination.MovementType == EMovementType.Landing)
|
||||
{
|
||||
if (!_condition[ConditionFlag.InFlight])
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
else if ((vector - Destination.Position).Length() < Destination.StopDistance)
|
||||
{
|
||||
if (vector.Y - Destination.Position.Y <= Destination.VerticalStopDistance)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
else if (Destination.DataId.HasValue)
|
||||
{
|
||||
IGameObject gameObject = _gameFunctions.FindObjectByDataId(Destination.DataId.Value);
|
||||
if ((gameObject is ICharacter || gameObject is IEventObj) ? true : false)
|
||||
{
|
||||
if (Math.Abs(vector.Y - gameObject.Position.Y) < 1.95f)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
else if (gameObject != null && gameObject.ObjectKind == ObjectKind.Aetheryte)
|
||||
{
|
||||
if (AetheryteConverter.IsLargeAetheryte((EAetheryteLocation)Destination.DataId.Value))
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
else if (Math.Abs(vector.Y - gameObject.Position.Y) < 1.95f)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Vector3> waypoints = _navmeshIpc.GetWaypoints();
|
||||
Vector3? vector2 = _clientState.LocalPlayer?.Position;
|
||||
if (vector2.HasValue && (!Destination.ShouldRecalculateNavmesh() || !RecalculateNavmesh(waypoints, vector2.Value)) && !Destination.IsFlying && !_condition[ConditionFlag.Mounted] && !_gameFunctions.HasStatusPreventingSprint() && Destination.CanSprint)
|
||||
{
|
||||
TriggerSprintIfNeeded(waypoints, vector2.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Restart(DestinationData destination)
|
||||
{
|
||||
Stop();
|
||||
if (destination.UseNavmesh)
|
||||
{
|
||||
NavigateTo(EMovementType.None, destination.DataId, destination.Position, fly: false, sprint: false, destination.StopDistance, destination.VerticalStopDistance);
|
||||
return;
|
||||
}
|
||||
uint? dataId = destination.DataId;
|
||||
int num = 1;
|
||||
List<Vector3> list = new List<Vector3>(num);
|
||||
CollectionsMarshal.SetCount(list, num);
|
||||
Span<Vector3> span = CollectionsMarshal.AsSpan(list);
|
||||
int index = 0;
|
||||
span[index] = destination.Position;
|
||||
NavigateTo(EMovementType.None, dataId, list, fly: false, sprint: false, destination.StopDistance, destination.VerticalStopDistance);
|
||||
}
|
||||
|
||||
private bool IsOnFlightPath(Vector3 p)
|
||||
{
|
||||
Vector3? pointOnFloor = _navmeshIpc.GetPointOnFloor(p, unlandable: true);
|
||||
if (pointOnFloor.HasValue)
|
||||
{
|
||||
return Math.Abs(pointOnFloor.Value.Y - p.Y) > 0.5f;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MemberNotNull("Destination")]
|
||||
private void PrepareNavigation(EMovementType type, uint? dataId, Vector3 to, bool fly, bool sprint, float? stopDistance, float verticalStopDistance, bool land, bool useNavmesh)
|
||||
{
|
||||
ResetPathfinding();
|
||||
if (InputManager.IsAutoRunning())
|
||||
{
|
||||
_logger.LogInformation("Turning off auto-move");
|
||||
_chatFunctions.ExecuteCommand("/automove off");
|
||||
}
|
||||
Destination = new DestinationData(type, dataId, to, stopDistance ?? 2.8f, fly, sprint, verticalStopDistance, land, useNavmesh);
|
||||
MovementStartedAt = DateTime.MaxValue;
|
||||
}
|
||||
|
||||
public void NavigateTo(EMovementType type, uint? dataId, Vector3 to, bool fly, bool sprint, float? stopDistance = null, float? verticalStopDistance = null, bool land = false)
|
||||
{
|
||||
fly |= _condition[ConditionFlag.Diving];
|
||||
if (fly && land)
|
||||
{
|
||||
Vector3 vector = to;
|
||||
vector.Y = to.Y + 2.6f;
|
||||
to = vector;
|
||||
}
|
||||
PrepareNavigation(type, dataId, to, fly, sprint, stopDistance, verticalStopDistance ?? 1.95f, land, useNavmesh: true);
|
||||
_logger.LogInformation("Pathfinding to {Destination}", Destination);
|
||||
Destination.NavmeshCalculations++;
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
|
||||
Vector3 vector2 = _clientState.LocalPlayer.Position;
|
||||
if (fly && _aetheryteData.CalculateDistance(vector2, _clientState.TerritoryType, EAetheryteLocation.CoerthasCentralHighlandsCampDragonhead) < 11f)
|
||||
{
|
||||
Vector3 vector = vector2;
|
||||
vector.Y = vector2.Y + 1f;
|
||||
vector2 = vector;
|
||||
_logger.LogInformation("Using modified start position for flying pathfinding: {StartPosition}", vector2.ToString("G", CultureInfo.InvariantCulture));
|
||||
}
|
||||
else if (fly)
|
||||
{
|
||||
Vector3 vector = vector2;
|
||||
vector.Y = vector2.Y + 0.2f;
|
||||
vector2 = vector;
|
||||
}
|
||||
_pathfindTask = _navmeshIpc.Pathfind(vector2, to, fly, _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
public void NavigateTo(EMovementType type, uint? dataId, List<Vector3> to, bool fly, bool sprint, float? stopDistance, float? verticalStopDistance = null, bool land = false)
|
||||
{
|
||||
fly |= _condition[ConditionFlag.Diving];
|
||||
if (fly && land && to.Count > 0)
|
||||
{
|
||||
int index = to.Count - 1;
|
||||
Vector3 value = to[to.Count - 1];
|
||||
value.Y = to[to.Count - 1].Y + 2.6f;
|
||||
to[index] = value;
|
||||
}
|
||||
PrepareNavigation(type, dataId, to.Last(), fly, sprint, stopDistance, verticalStopDistance ?? 1.95f, land, useNavmesh: false);
|
||||
_logger.LogInformation("Moving to {Destination}", Destination);
|
||||
_navmeshIpc.MoveTo(to, fly);
|
||||
MovementStartedAt = DateTime.Now;
|
||||
}
|
||||
|
||||
public void ResetPathfinding()
|
||||
{
|
||||
if (_cancellationTokenSource != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
_cancellationTokenSource.Dispose();
|
||||
}
|
||||
_pathfindTask = null;
|
||||
}
|
||||
|
||||
private unsafe bool RecalculateNavmesh(List<Vector3> navPoints, Vector3 start)
|
||||
{
|
||||
if (Destination == null)
|
||||
{
|
||||
throw new InvalidOperationException("Destination is null");
|
||||
}
|
||||
if (DateTime.Now - MovementStartedAt <= TimeSpan.FromSeconds(5L))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Vector3 vector = navPoints.FirstOrDefault();
|
||||
if (vector == default(Vector3))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
float num = Vector2.Distance(new Vector2(start.X, start.Z), new Vector2(vector.X, vector.Z));
|
||||
if (Destination.LastWaypoint == null || (Destination.LastWaypoint.Position - vector).Length() > 0.1f)
|
||||
{
|
||||
Destination.LastWaypoint = new LastWaypointData(vector)
|
||||
{
|
||||
Distance2DAtLastUpdate = num,
|
||||
UpdatedAt = Environment.TickCount64
|
||||
};
|
||||
return false;
|
||||
}
|
||||
if (Environment.TickCount64 - Destination.LastWaypoint.UpdatedAt > 500)
|
||||
{
|
||||
if (Math.Abs((double)num - Destination.LastWaypoint.Distance2DAtLastUpdate) < 0.5)
|
||||
{
|
||||
int navmeshCalculations = Destination.NavmeshCalculations;
|
||||
if (navmeshCalculations % 6 == 1)
|
||||
{
|
||||
_logger.LogWarning("Jumping to try and resolve navmesh problem (n = {Calculations})", navmeshCalculations);
|
||||
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null);
|
||||
Destination.NavmeshCalculations++;
|
||||
Destination.LastWaypoint.UpdatedAt = Environment.TickCount64;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Recalculating navmesh (n = {Calculations})", navmeshCalculations);
|
||||
Restart(Destination);
|
||||
}
|
||||
Destination.NavmeshCalculations = navmeshCalculations + 1;
|
||||
return true;
|
||||
}
|
||||
Destination.LastWaypoint.Distance2DAtLastUpdate = num;
|
||||
Destination.LastWaypoint.UpdatedAt = Environment.TickCount64;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe void TriggerSprintIfNeeded(IEnumerable<Vector3> navPoints, Vector3 start)
|
||||
{
|
||||
float num = 0f;
|
||||
foreach (Vector3 navPoint in navPoints)
|
||||
{
|
||||
num += (start - navPoint).Length();
|
||||
start = navPoint;
|
||||
}
|
||||
float num2 = 100f;
|
||||
bool flag = !_gameFunctions.HasStatus(EStatus.Jog);
|
||||
if (flag)
|
||||
{
|
||||
bool flag2;
|
||||
switch (GameMain.Instance()->CurrentTerritoryIntendedUseId)
|
||||
{
|
||||
case 0:
|
||||
case 7:
|
||||
case 13:
|
||||
case 14:
|
||||
case 15:
|
||||
case 19:
|
||||
case 23:
|
||||
case 29:
|
||||
flag2 = true;
|
||||
break;
|
||||
default:
|
||||
flag2 = false;
|
||||
break;
|
||||
}
|
||||
flag = flag2;
|
||||
}
|
||||
if (flag)
|
||||
{
|
||||
num2 = 30f;
|
||||
}
|
||||
if (num > num2 && ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 4u, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0)
|
||||
{
|
||||
_logger.LogInformation("Triggering Sprint");
|
||||
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 4u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_navmeshIpc.Stop();
|
||||
ResetPathfinding();
|
||||
Destination = null;
|
||||
if (InputManager.IsAutoRunning())
|
||||
{
|
||||
_logger.LogInformation("Turning off auto-move [stop]");
|
||||
_chatFunctions.ExecuteCommand("/automove off");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue