256 lines
7.1 KiB
C#
256 lines
7.1 KiB
C#
using System;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
|
using FFXIVClientStructs.FFXIV.Common.Math;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Data;
|
|
using Questionable.Functions;
|
|
|
|
namespace Questionable.Controller.Steps.Common;
|
|
|
|
internal static class Mount
|
|
{
|
|
internal sealed record MountTask : ITask
|
|
{
|
|
public ushort TerritoryId { get; init; }
|
|
|
|
public EMountIf MountIf { get; init; }
|
|
|
|
public FFXIVClientStructs.FFXIV.Common.Math.Vector3? Position { get; }
|
|
|
|
public MountTask(ushort TerritoryId, EMountIf MountIf, FFXIVClientStructs.FFXIV.Common.Math.Vector3? Position = null)
|
|
{
|
|
this.TerritoryId = TerritoryId;
|
|
this.MountIf = MountIf;
|
|
this.Position = ((MountIf == EMountIf.AwayFromPosition) ? new FFXIVClientStructs.FFXIV.Common.Math.Vector3?(Position ?? throw new ArgumentNullException("Position")) : ((FFXIVClientStructs.FFXIV.Common.Math.Vector3?)null));
|
|
base._002Ector();
|
|
}
|
|
|
|
public bool ShouldRedoOnInterrupt()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "Mount";
|
|
}
|
|
|
|
[CompilerGenerated]
|
|
public void Deconstruct(out ushort TerritoryId, out EMountIf MountIf, out FFXIVClientStructs.FFXIV.Common.Math.Vector3? Position)
|
|
{
|
|
TerritoryId = this.TerritoryId;
|
|
MountIf = this.MountIf;
|
|
Position = this.Position;
|
|
}
|
|
}
|
|
|
|
internal sealed class MountEvaluator(GameFunctions gameFunctions, ICondition condition, TerritoryData territoryData, IClientState clientState, IObjectTable objectTable, ILogger<MountEvaluator> logger)
|
|
{
|
|
public unsafe MountResult EvaluateMountState(MountTask task, bool dryRun, ref long retryAtMs)
|
|
{
|
|
if (condition[ConditionFlag.Mounted])
|
|
{
|
|
return MountResult.DontMount;
|
|
}
|
|
LogLevel logLevel = ((!dryRun) ? LogLevel.Debug : LogLevel.None);
|
|
if (!territoryData.CanUseMount(task.TerritoryId))
|
|
{
|
|
logger.Log(logLevel, "Can't use mount in current territory {Id}", task.TerritoryId);
|
|
return MountResult.DontMount;
|
|
}
|
|
if (gameFunctions.HasStatusPreventingMount())
|
|
{
|
|
logger.Log(logLevel, "Can't mount due to status preventing sprint or mount");
|
|
return MountResult.DontMount;
|
|
}
|
|
if (task.MountIf == EMountIf.AwayFromPosition)
|
|
{
|
|
float num = System.Numerics.Vector3.Distance((FFXIVClientStructs.FFXIV.Common.Math.Vector3)(objectTable.LocalPlayer?.Position ?? ((System.Numerics.Vector3)FFXIVClientStructs.FFXIV.Common.Math.Vector3.Zero)), task.Position.GetValueOrDefault());
|
|
if (task.TerritoryId == clientState.TerritoryType && num < 30f && !FFXIVClientStructs.FFXIV.Client.Game.Conditions.Instance()->Diving)
|
|
{
|
|
logger.Log(logLevel, "Not using mount, as we're close to the target");
|
|
return MountResult.DontMount;
|
|
}
|
|
logger.Log(logLevel, "Want to use mount if away from destination ({Distance} yalms), trying (in territory {Id})...", num, task.TerritoryId);
|
|
}
|
|
logger.Log(logLevel, "Want to use mount, trying (in territory {Id})...", task.TerritoryId);
|
|
if (!condition[ConditionFlag.InCombat])
|
|
{
|
|
if (dryRun)
|
|
{
|
|
retryAtMs = Environment.TickCount64 + 500;
|
|
}
|
|
return MountResult.Mount;
|
|
}
|
|
return MountResult.WhenOutOfCombat;
|
|
}
|
|
}
|
|
|
|
internal sealed class MountExecutor(GameFunctions gameFunctions, ICondition condition, MountEvaluator mountEvaluator, ILogger<MountExecutor> logger) : TaskExecutor<MountTask>()
|
|
{
|
|
private bool _mountTriggered;
|
|
|
|
private long _retryAtMs;
|
|
|
|
protected override bool Start()
|
|
{
|
|
_mountTriggered = false;
|
|
return mountEvaluator.EvaluateMountState(base.Task, dryRun: false, ref _retryAtMs) == MountResult.Mount;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
if (_mountTriggered && !condition[ConditionFlag.Mounted] && Environment.TickCount64 > _retryAtMs)
|
|
{
|
|
logger.LogDebug("Not mounted, retrying...");
|
|
_mountTriggered = false;
|
|
_retryAtMs = long.MaxValue;
|
|
}
|
|
if (!_mountTriggered)
|
|
{
|
|
if (gameFunctions.HasStatusPreventingMount())
|
|
{
|
|
logger.LogDebug("Can't mount due to status preventing sprint or mount");
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
base.ProgressContext = InteractionProgressContext.FromActionUse(() => _mountTriggered = gameFunctions.Mount());
|
|
ResetProgressTimer();
|
|
_retryAtMs = Environment.TickCount64 + 5000;
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (condition[ConditionFlag.Mounted])
|
|
{
|
|
ResetProgressTimer();
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
if (base.ProgressContext != null)
|
|
{
|
|
if (base.ProgressContext.WasInterrupted())
|
|
{
|
|
logger.LogDebug("Mount was interrupted");
|
|
_mountTriggered = false;
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (base.ProgressContext.WasSuccessful())
|
|
{
|
|
ResetProgressTimer();
|
|
}
|
|
}
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal enum MountResult
|
|
{
|
|
DontMount,
|
|
Mount,
|
|
WhenOutOfCombat
|
|
}
|
|
|
|
internal sealed record UnmountTask : ITask
|
|
{
|
|
public bool ShouldRedoOnInterrupt()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "Unmount";
|
|
}
|
|
}
|
|
|
|
internal sealed class UnmountExecutor(ICondition condition, ILogger<UnmountExecutor> logger, GameFunctions gameFunctions, IObjectTable objectTable) : TaskExecutor<UnmountTask>()
|
|
{
|
|
private bool _unmountTriggered;
|
|
|
|
private long _continueAtMs;
|
|
|
|
protected override bool Start()
|
|
{
|
|
if (!condition[ConditionFlag.Mounted])
|
|
{
|
|
return false;
|
|
}
|
|
logger.LogDebug("Step explicitly wants no mount, trying to unmount...");
|
|
if (condition[ConditionFlag.InFlight])
|
|
{
|
|
gameFunctions.Unmount();
|
|
_continueAtMs = Environment.TickCount64 + 1000;
|
|
return true;
|
|
}
|
|
_unmountTriggered = gameFunctions.Unmount();
|
|
_continueAtMs = Environment.TickCount64 + 1000;
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
if (Environment.TickCount64 < _continueAtMs)
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (IsUnmounting())
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (!_unmountTriggered)
|
|
{
|
|
if (condition[ConditionFlag.InFlight])
|
|
{
|
|
gameFunctions.Unmount();
|
|
}
|
|
else
|
|
{
|
|
_unmountTriggered = gameFunctions.Unmount();
|
|
}
|
|
_continueAtMs = Environment.TickCount64 + 1000;
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (condition[ConditionFlag.Mounted] && condition[ConditionFlag.InCombat])
|
|
{
|
|
_unmountTriggered = gameFunctions.Unmount();
|
|
_continueAtMs = Environment.TickCount64 + 1000;
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (!condition[ConditionFlag.Mounted])
|
|
{
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
|
|
private unsafe bool IsUnmounting()
|
|
{
|
|
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
|
|
if (localPlayer != null)
|
|
{
|
|
BattleChara* address = (BattleChara*)localPlayer.Address;
|
|
return (address->Mount.Flags & 1) == 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public enum EMountIf
|
|
{
|
|
Always,
|
|
AwayFromPosition
|
|
}
|
|
}
|