forked from aly/qstbak
426 lines
17 KiB
C#
426 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Runtime.InteropServices;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
|
using Dalamud.Game.ClientState.Objects.Types;
|
|
using Dalamud.Game.Text.SeStringHandling;
|
|
using Dalamud.Plugin.Services;
|
|
using LLib;
|
|
using Lumina.Excel.Sheets;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Controller.Steps.Common;
|
|
using Questionable.Functions;
|
|
using Questionable.Model;
|
|
using Questionable.Model.Questing;
|
|
using SmartNav.Data;
|
|
using SmartNav.Model;
|
|
using SmartNav.Model.Converter;
|
|
|
|
namespace Questionable.Controller.Steps.Movement;
|
|
|
|
internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware, ITaskExecutor
|
|
{
|
|
private const float DescendDrop = 100f;
|
|
|
|
private readonly string _cannotExecuteAtThisTime;
|
|
|
|
private readonly MovementController _movementController;
|
|
|
|
private readonly GameFunctions _gameFunctions;
|
|
|
|
private readonly ILogger<MoveExecutor> _logger;
|
|
|
|
private readonly IClientState _clientState;
|
|
|
|
private readonly IObjectTable _objectTable;
|
|
|
|
private readonly ICondition _condition;
|
|
|
|
private readonly Questionable.Controller.Steps.Common.Mount.MountEvaluator _mountEvaluator;
|
|
|
|
private readonly IServiceProvider _serviceProvider;
|
|
|
|
private readonly AetheryteData _aetheryteData;
|
|
|
|
private readonly Configuration _configuration;
|
|
|
|
private System.Action? _startAction;
|
|
|
|
private Vector3 _destination;
|
|
|
|
private bool _canRestart;
|
|
|
|
private bool _descendAttempted;
|
|
|
|
private bool _adjustedToObject;
|
|
|
|
private (Questionable.Controller.Steps.Common.Mount.MountExecutor Executor, Questionable.Controller.Steps.Common.Mount.MountTask Task)? _mountBeforeMovement;
|
|
|
|
private (Questionable.Controller.Steps.Common.Mount.UnmountExecutor Executor, Questionable.Controller.Steps.Common.Mount.UnmountTask Task)? _unmountBeforeMovement;
|
|
|
|
private (Questionable.Controller.Steps.Common.Mount.MountExecutor Executor, Questionable.Controller.Steps.Common.Mount.MountTask Task)? _mountDuringMovement;
|
|
|
|
public MoveExecutor(MovementController movementController, GameFunctions gameFunctions, AetheryteData aetheryteData, Configuration configuration, ILogger<MoveExecutor> logger, IClientState clientState, IObjectTable objectTable, ICondition condition, IDataManager dataManager, Questionable.Controller.Steps.Common.Mount.MountEvaluator mountEvaluator, IServiceProvider serviceProvider)
|
|
{
|
|
_movementController = movementController;
|
|
_gameFunctions = gameFunctions;
|
|
_aetheryteData = aetheryteData;
|
|
_configuration = configuration;
|
|
_logger = logger;
|
|
_clientState = clientState;
|
|
_objectTable = objectTable;
|
|
_condition = condition;
|
|
_serviceProvider = serviceProvider;
|
|
_mountEvaluator = mountEvaluator;
|
|
_cannotExecuteAtThisTime = dataManager.GetString(579u, (LogMessage x) => x.Text);
|
|
}
|
|
|
|
private void PrepareMovementIfNeeded()
|
|
{
|
|
if (!_gameFunctions.IsFlyingUnlocked(base.Task.TerritoryId))
|
|
{
|
|
base.Task = base.Task with
|
|
{
|
|
Fly = false,
|
|
Land = false
|
|
};
|
|
}
|
|
if (!base.Task.DisableNavmesh)
|
|
{
|
|
_startAction = delegate
|
|
{
|
|
_movementController.NavigateTo(EMovementType.Quest, base.Task.DataId, _destination, base.Task.Fly, base.Task.Sprint ?? (!_mountDuringMovement.HasValue), base.Task.StopDistance, base.Task.IgnoreDistanceToObject ? new float?(float.MaxValue) : ((float?)null), base.Task.Land, base.Task.SmartNavRouted);
|
|
};
|
|
return;
|
|
}
|
|
_startAction = delegate
|
|
{
|
|
MovementController movementController = _movementController;
|
|
uint? dataId = base.Task.DataId;
|
|
int num = 1;
|
|
List<Vector3> list = new List<Vector3>(num);
|
|
CollectionsMarshal.SetCount(list, num);
|
|
CollectionsMarshal.AsSpan(list)[0] = _destination;
|
|
movementController.NavigateTo(EMovementType.Quest, dataId, list, base.Task.Fly, base.Task.Sprint ?? (!_mountDuringMovement.HasValue), base.Task.StopDistance, base.Task.IgnoreDistanceToObject ? new float?(float.MaxValue) : ((float?)null), base.Task.Land);
|
|
};
|
|
}
|
|
|
|
protected override bool Start()
|
|
{
|
|
if (_clientState.TerritoryType != base.Task.TerritoryId)
|
|
{
|
|
_logger.LogDebug("Skipping movement - already in territory {Current} (task expects {Expected})", _clientState.TerritoryType, base.Task.TerritoryId);
|
|
return false;
|
|
}
|
|
_canRestart = base.Task.RestartNavigation;
|
|
_destination = base.Task.Destination;
|
|
if (_aetheryteData.Locations.Any((KeyValuePair<EAetheryteLocation, Vector3> kvp) => AetheryteConverter.IsLargeAetheryte(kvp.Key) && Vector3.Distance(kvp.Value, _destination) < 1f))
|
|
{
|
|
float num = 7f;
|
|
if ((base.Task.StopDistance ?? 3f) < num)
|
|
{
|
|
base.Task = base.Task with
|
|
{
|
|
StopDistance = num
|
|
};
|
|
}
|
|
}
|
|
float num2 = base.Task.StopDistance ?? 3f;
|
|
Vector3? vector = _objectTable.LocalPlayer?.Position;
|
|
float num3 = ((!vector.HasValue) ? float.MaxValue : Vector3.Distance(vector.Value, _destination));
|
|
if (num3 > num2)
|
|
{
|
|
PrepareMovementIfNeeded();
|
|
}
|
|
if (base.Task.MountRequired)
|
|
{
|
|
Questionable.Controller.Steps.Common.Mount.MountTask mountTask = new Questionable.Controller.Steps.Common.Mount.MountTask(base.Task.TerritoryId, Questionable.Controller.Steps.Common.Mount.EMountIf.Always);
|
|
_mountBeforeMovement = (_serviceProvider.GetRequiredService<Questionable.Controller.Steps.Common.Mount.MountExecutor>(), mountTask);
|
|
if (!_mountBeforeMovement.Value.Executor.Start(mountTask))
|
|
{
|
|
_mountBeforeMovement = null;
|
|
}
|
|
}
|
|
else if (base.Task.DismountRequired)
|
|
{
|
|
Questionable.Controller.Steps.Common.Mount.UnmountTask unmountTask = new Questionable.Controller.Steps.Common.Mount.UnmountTask();
|
|
_unmountBeforeMovement = (_serviceProvider.GetRequiredService<Questionable.Controller.Steps.Common.Mount.UnmountExecutor>(), unmountTask);
|
|
if (!_unmountBeforeMovement.Value.Executor.Start(unmountTask))
|
|
{
|
|
_unmountBeforeMovement = null;
|
|
}
|
|
}
|
|
else if (base.Task.Mount == true)
|
|
{
|
|
Questionable.Controller.Steps.Common.Mount.MountTask mountTask2 = new Questionable.Controller.Steps.Common.Mount.MountTask(base.Task.TerritoryId, Questionable.Controller.Steps.Common.Mount.EMountIf.Always);
|
|
_mountBeforeMovement = (_serviceProvider.GetRequiredService<Questionable.Controller.Steps.Common.Mount.MountExecutor>(), mountTask2);
|
|
if (!_mountBeforeMovement.Value.Executor.Start(mountTask2))
|
|
{
|
|
_mountBeforeMovement = null;
|
|
}
|
|
}
|
|
else if (base.Task.Mount == false)
|
|
{
|
|
Questionable.Controller.Steps.Common.Mount.UnmountTask unmountTask2 = new Questionable.Controller.Steps.Common.Mount.UnmountTask();
|
|
_unmountBeforeMovement = (_serviceProvider.GetRequiredService<Questionable.Controller.Steps.Common.Mount.UnmountExecutor>(), unmountTask2);
|
|
if (!_unmountBeforeMovement.Value.Executor.Start(unmountTask2))
|
|
{
|
|
_unmountBeforeMovement = null;
|
|
}
|
|
}
|
|
else if (!base.Task.DisableNavmesh)
|
|
{
|
|
bool flag = _gameFunctions.IsFlyingUnlocked(base.Task.TerritoryId);
|
|
if (!base.Task.SmartNavRouted && !base.Task.Fly && flag && num3 > _configuration.Navigation.MountFlyDistance)
|
|
{
|
|
base.Task = base.Task with
|
|
{
|
|
Fly = true,
|
|
Land = true
|
|
};
|
|
}
|
|
float num4 = (base.Task.Fly ? _configuration.Navigation.MountFlyDistance : _configuration.Navigation.MountGroundDistance);
|
|
Questionable.Controller.Steps.Common.Mount.EMountIf mountIf = ((!(num3 > num4)) ? Questionable.Controller.Steps.Common.Mount.EMountIf.AwayFromPosition : Questionable.Controller.Steps.Common.Mount.EMountIf.Always);
|
|
Questionable.Controller.Steps.Common.Mount.MountTask mountTask3 = new Questionable.Controller.Steps.Common.Mount.MountTask(base.Task.TerritoryId, mountIf, _destination);
|
|
long retryAtMs = 0L;
|
|
(Questionable.Controller.Steps.Common.Mount.MountExecutor, Questionable.Controller.Steps.Common.Mount.MountTask)? tuple;
|
|
if (!_condition[ConditionFlag.Mounted] && _mountEvaluator.EvaluateMountState(mountTask3, dryRun: true, ref retryAtMs) != Questionable.Controller.Steps.Common.Mount.MountResult.DontMount)
|
|
{
|
|
tuple = (_serviceProvider.GetRequiredService<Questionable.Controller.Steps.Common.Mount.MountExecutor>(), mountTask3);
|
|
tuple.Value.Item1.Start(mountTask3);
|
|
}
|
|
else
|
|
{
|
|
tuple = null;
|
|
}
|
|
if (base.Task.Fly)
|
|
{
|
|
_mountBeforeMovement = tuple;
|
|
if (!tuple.HasValue && !_condition[ConditionFlag.Mounted])
|
|
{
|
|
_logger.LogDebug("Not mounting for short flight movement; navigating on foot instead");
|
|
base.Task = base.Task with
|
|
{
|
|
Fly = false,
|
|
Land = false
|
|
};
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_mountDuringMovement = tuple;
|
|
}
|
|
}
|
|
if (!_mountBeforeMovement.HasValue && !_unmountBeforeMovement.HasValue && _startAction != null)
|
|
{
|
|
_startAction();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
ETaskResult? eTaskResult = UpdateMountState();
|
|
if (eTaskResult.HasValue)
|
|
{
|
|
return eTaskResult.GetValueOrDefault();
|
|
}
|
|
if (_startAction == null)
|
|
{
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
if (_movementController.IsPathfinding || _movementController.IsPathRunning)
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (!_movementController.HasBeenMovingForAtLeast(2000))
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
IPlayerCharacter localPlayer = _objectTable.LocalPlayer;
|
|
float num = base.Task.StopDistance ?? 3f;
|
|
if (base.Task.Fly && base.Task.Land && _canRestart && localPlayer != null && _clientState.TerritoryType == base.Task.TerritoryId && Vector3.Distance(localPlayer.Position, _destination) > num)
|
|
{
|
|
if (_condition[ConditionFlag.InFlight])
|
|
{
|
|
if (_descendAttempted)
|
|
{
|
|
_logger.LogWarning("Still airborne after descending, treating as unreachable");
|
|
throw new MovementController.PathfindingFailedException("Still airborne after descending, cannot close gap on foot");
|
|
}
|
|
if (!_movementController.IsNavmeshReady)
|
|
{
|
|
_logger.LogDebug("Airborne outside stop distance, waiting for navmesh to descend");
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
Vector3 position = localPlayer.Position;
|
|
position.Y = localPlayer.Position.Y - 100f;
|
|
Vector3 vector = position;
|
|
_descendAttempted = true;
|
|
_logger.LogDebug("Outside stop distance but still airborne, descending to {Target}", vector.ToString("G", CultureInfo.InvariantCulture));
|
|
MovementController movementController = _movementController;
|
|
uint? dataId = base.Task.DataId;
|
|
int num2 = 1;
|
|
List<Vector3> list = new List<Vector3>(num2);
|
|
CollectionsMarshal.SetCount(list, num2);
|
|
CollectionsMarshal.AsSpan(list)[0] = vector;
|
|
movementController.NavigateTo(EMovementType.Landing, dataId, list, fly: true, sprint: false, null);
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
_canRestart = false;
|
|
_logger.LogDebug("Landed outside stop distance, closing gap on foot");
|
|
MovementController movementController2 = _movementController;
|
|
uint? dataId2 = base.Task.DataId;
|
|
Vector3 destination = _destination;
|
|
float? stopDistance = base.Task.StopDistance;
|
|
bool smartNavRouted = base.Task.SmartNavRouted;
|
|
movementController2.NavigateTo(EMovementType.Quest, dataId2, destination, fly: false, sprint: false, stopDistance, null, land: false, smartNavRouted);
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (base.Task.DataId.HasValue && !_adjustedToObject && localPlayer != null && !_condition[ConditionFlag.InFlight] && _clientState.TerritoryType == base.Task.TerritoryId)
|
|
{
|
|
IGameObject gameObject = _gameFunctions.FindObjectByDataId(base.Task.DataId.Value, null, warnIfMissing: false);
|
|
if (gameObject != null)
|
|
{
|
|
float num3 = Vector3.Distance(localPlayer.Position, gameObject.Position);
|
|
if (num3 > num)
|
|
{
|
|
_adjustedToObject = true;
|
|
_logger.LogDebug("Adjusting to actual object position ({Distance:F1}y away)", num3);
|
|
_movementController.NavigateTo(EMovementType.Quest, base.Task.DataId, gameObject.Position, fly: false, sprint: false, base.Task.StopDistance);
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
}
|
|
}
|
|
if (_canRestart && localPlayer != null && Vector3.Distance(localPlayer.Position, _destination) > num + 5f)
|
|
{
|
|
_canRestart = false;
|
|
if (_clientState.TerritoryType == base.Task.TerritoryId)
|
|
{
|
|
_logger.LogInformation("Looks like movement was interrupted, re-attempting to move");
|
|
_startAction();
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (base.Task.AllowZoneTransition)
|
|
{
|
|
_logger.LogDebug("Zone transition during boundary crossing (now in {Current}, expected {Expected}), completing task", _clientState.TerritoryType, base.Task.TerritoryId);
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
_logger.LogWarning("Movement interrupted by zone transition (now in {Current}, task expected {Expected})", _clientState.TerritoryType, base.Task.TerritoryId);
|
|
throw new MovementController.PathfindingFailedException($"Zone transition during movement: now in territory {_clientState.TerritoryType}, expected {base.Task.TerritoryId}");
|
|
}
|
|
if (base.Task.SmartNavRouted && !_adjustedToObject && localPlayer != null && _clientState.TerritoryType == base.Task.TerritoryId && Vector3.Distance(localPlayer.Position, _destination) > num + 5f)
|
|
{
|
|
float num4 = Vector3.Distance(localPlayer.Position, _destination);
|
|
_logger.LogWarning("SmartNav movement ended {Distance:F1}y from target (stop distance {StopDistance:F1}y), treating as unreachable", num4, num);
|
|
throw new MovementController.PathfindingFailedException($"SmartNav movement ended {num4:F1}y from target (stop distance {num:F1}y)");
|
|
}
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
|
|
private ETaskResult? UpdateMountState()
|
|
{
|
|
(Questionable.Controller.Steps.Common.Mount.MountExecutor, Questionable.Controller.Steps.Common.Mount.MountTask)? mountBeforeMovement = _mountBeforeMovement;
|
|
if (mountBeforeMovement.HasValue)
|
|
{
|
|
Questionable.Controller.Steps.Common.Mount.MountExecutor item = mountBeforeMovement.GetValueOrDefault().Item1;
|
|
if (item != null)
|
|
{
|
|
if (item.Update() == ETaskResult.TaskComplete)
|
|
{
|
|
_logger.LogDebug("MountBeforeMovement complete");
|
|
_mountBeforeMovement = null;
|
|
_startAction?.Invoke();
|
|
return null;
|
|
}
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
}
|
|
(Questionable.Controller.Steps.Common.Mount.UnmountExecutor, Questionable.Controller.Steps.Common.Mount.UnmountTask)? unmountBeforeMovement = _unmountBeforeMovement;
|
|
if (unmountBeforeMovement.HasValue)
|
|
{
|
|
Questionable.Controller.Steps.Common.Mount.UnmountExecutor item2 = unmountBeforeMovement.GetValueOrDefault().Item1;
|
|
if (item2 != null)
|
|
{
|
|
if (item2.Update() == ETaskResult.TaskComplete)
|
|
{
|
|
_logger.LogDebug("UnmountBeforeMovement complete");
|
|
_unmountBeforeMovement = null;
|
|
_startAction?.Invoke();
|
|
return null;
|
|
}
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
}
|
|
mountBeforeMovement = _mountDuringMovement;
|
|
if (mountBeforeMovement.HasValue)
|
|
{
|
|
(Questionable.Controller.Steps.Common.Mount.MountExecutor, Questionable.Controller.Steps.Common.Mount.MountTask) valueOrDefault = mountBeforeMovement.GetValueOrDefault();
|
|
var (mountExecutor, _) = valueOrDefault;
|
|
if (mountExecutor != null)
|
|
{
|
|
Questionable.Controller.Steps.Common.Mount.MountTask item3 = valueOrDefault.Item2;
|
|
if ((object)item3 != null)
|
|
{
|
|
if (mountExecutor.Update() == ETaskResult.TaskComplete)
|
|
{
|
|
_logger.LogDebug("MountDuringMovement complete (mounted)");
|
|
_mountDuringMovement = null;
|
|
return null;
|
|
}
|
|
long retryAtMs = 0L;
|
|
if (_mountEvaluator.EvaluateMountState(item3, dryRun: true, ref retryAtMs) == Questionable.Controller.Steps.Common.Mount.MountResult.DontMount)
|
|
{
|
|
_logger.LogDebug("MountDuringMovement implicitly complete (shouldn't mount anymore)");
|
|
_mountDuringMovement = null;
|
|
return null;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public override bool WasInterrupted()
|
|
{
|
|
long retryAtMs = 0L;
|
|
if (base.Task.Fly && _condition[ConditionFlag.InCombat] && !_condition[ConditionFlag.Mounted])
|
|
{
|
|
(Questionable.Controller.Steps.Common.Mount.MountExecutor, Questionable.Controller.Steps.Common.Mount.MountTask)? mountBeforeMovement = _mountBeforeMovement;
|
|
if (mountBeforeMovement.HasValue)
|
|
{
|
|
Questionable.Controller.Steps.Common.Mount.MountTask item = mountBeforeMovement.GetValueOrDefault().Item2;
|
|
if ((object)item != null && _mountEvaluator.EvaluateMountState(item, dryRun: true, ref retryAtMs) == Questionable.Controller.Steps.Common.Mount.MountResult.WhenOutOfCombat)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return base.WasInterrupted();
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
if (!_mountBeforeMovement.HasValue)
|
|
{
|
|
return ShouldResolveCombatBeforeNextInteraction();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private bool ShouldResolveCombatBeforeNextInteraction()
|
|
{
|
|
return base.Task.InteractionType == EInteractionType.Jump;
|
|
}
|
|
|
|
public bool OnErrorToast(SeString message)
|
|
{
|
|
return GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue);
|
|
}
|
|
}
|