qstbak/Questionable/Questionable.Controller.Steps.Movement/MoveExecutor.cs
2026-08-17 20:29:32 +10:00

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);
}
}