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, ILogger logger) { public unsafe MountResult EvaluateMountState(MountTask task, bool dryRun, ref DateTime retryAt) { if (condition[ConditionFlag.Mounted]) { return MountResult.DontMount; } LogLevel logLevel = (dryRun ? LogLevel.None : LogLevel.Information); 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)(clientState.LocalPlayer?.Position ?? ((System.Numerics.Vector3)FFXIVClientStructs.FFXIV.Common.Math.Vector3.Zero)), task.Position.GetValueOrDefault()); if (task.TerritoryId == clientState.TerritoryType && num < 30f && !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); } else { logger.Log(logLevel, "Want to use mount, trying (in territory {Id})...", task.TerritoryId); } if (!condition[ConditionFlag.InCombat]) { if (dryRun) { retryAt = DateTime.Now.AddSeconds(0.5); } return MountResult.Mount; } return MountResult.WhenOutOfCombat; } } internal sealed class MountExecutor(GameFunctions gameFunctions, ICondition condition, MountEvaluator mountEvaluator, ILogger logger) : TaskExecutor() { private bool _mountTriggered; private DateTime _retryAt = DateTime.MinValue; protected override bool Start() { _mountTriggered = false; return mountEvaluator.EvaluateMountState(base.Task, dryRun: false, ref _retryAt) == MountResult.Mount; } public override ETaskResult Update() { if (_mountTriggered && !condition[ConditionFlag.Mounted] && DateTime.Now > _retryAt) { logger.LogInformation("Not mounted, retrying..."); _mountTriggered = false; _retryAt = DateTime.MaxValue; } if (!_mountTriggered) { if (gameFunctions.HasStatusPreventingMount()) { logger.LogInformation("Can't mount due to status preventing sprint or mount"); return ETaskResult.TaskComplete; } base.ProgressContext = InteractionProgressContext.FromActionUse(() => _mountTriggered = gameFunctions.Mount()); _retryAt = DateTime.Now.AddSeconds(5.0); return ETaskResult.StillRunning; } if (!condition[ConditionFlag.Mounted]) { return ETaskResult.StillRunning; } return ETaskResult.TaskComplete; } 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 logger, GameFunctions gameFunctions, IClientState clientState) : TaskExecutor() { private bool _unmountTriggered; private DateTime _continueAt = DateTime.MinValue; protected override bool Start() { if (!condition[ConditionFlag.Mounted]) { return false; } logger.LogInformation("Step explicitly wants no mount, trying to unmount..."); if (condition[ConditionFlag.InFlight]) { gameFunctions.Unmount(); _continueAt = DateTime.Now.AddSeconds(1.0); return true; } _unmountTriggered = gameFunctions.Unmount(); _continueAt = DateTime.Now.AddSeconds(1.0); return true; } public override ETaskResult Update() { if (_continueAt >= DateTime.Now) { return ETaskResult.StillRunning; } if (IsUnmounting()) { return ETaskResult.StillRunning; } if (!_unmountTriggered) { if (condition[ConditionFlag.InFlight]) { gameFunctions.Unmount(); } else { _unmountTriggered = gameFunctions.Unmount(); } _continueAt = DateTime.Now.AddSeconds(1.0); return ETaskResult.StillRunning; } if (condition[ConditionFlag.Mounted] && condition[ConditionFlag.InCombat]) { _unmountTriggered = gameFunctions.Unmount(); _continueAt = DateTime.Now.AddSeconds(1.0); return ETaskResult.StillRunning; } if (!condition[ConditionFlag.Mounted]) { return ETaskResult.TaskComplete; } return ETaskResult.StillRunning; } private unsafe bool IsUnmounting() { IPlayerCharacter localPlayer = clientState.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 } }