qstbak/Questionable/Questionable.Controller.Steps.Common/Mount.cs
2025-10-09 07:47:19 +10:00

244 lines
6.7 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, ILogger<MountEvaluator> 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<MountExecutor> logger) : TaskExecutor<MountTask>()
{
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<UnmountTask> logger, GameFunctions gameFunctions, IClientState clientState) : TaskExecutor<UnmountTask>()
{
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
}
}