using System; using System.Collections.Generic; using Dalamud.Game.ClientState.Objects.Types; using Microsoft.Extensions.Logging; using Questionable.Functions; using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; internal static class FateFarming { internal sealed record WaitForFateTargets(IReadOnlyList Targets) : ITask { public override string ToString() { return $"WaitForFateTargets({Targets.Count} targets)"; } } internal sealed class WaitForFateTargetsExecutor(GameFunctions gameFunctions, ILogger logger) : TaskExecutor() { private long _nextPollAt; private bool _loggedWaitingForFate; protected override bool Start() { logger.LogDebug("Waiting for FATE targets to appear ({Count} targets)", base.Task.Targets.Count); _loggedWaitingForFate = false; return true; } public override ETaskResult Update() { if (Environment.TickCount64 < _nextPollAt) { return ETaskResult.StillRunning; } ushort currentFateId = gameFunctions.GetCurrentFateId(); if (currentFateId == 0) { if (!_loggedWaitingForFate) { logger.LogDebug("No active FATE yet, waiting for FATE to start before checking targets"); _loggedWaitingForFate = true; } _nextPollAt = Environment.TickCount64 + 1000; return ETaskResult.StillRunning; } _loggedWaitingForFate = false; foreach (FateActionTarget target in base.Task.Targets) { IGameObject gameObject = gameFunctions.FindObjectByDataId(target.DataId, null, warnIfMissing: false); if (gameObject != null && gameObject.IsTargetable) { logger.LogDebug("FATE {FateId} active and target {DataId} is targetable", currentFateId, target.DataId); return ETaskResult.TaskComplete; } } _nextPollAt = Environment.TickCount64 + 1000; return ETaskResult.StillRunning; } public override bool ShouldInterruptOnDamage() { return false; } } internal sealed record SyncFateLevel : ITask { public override string ToString() { return "SyncFateLevel"; } } internal sealed class SyncFateLevelExecutor(GameFunctions gameFunctions, ILogger logger) : TaskExecutor() { protected override bool Start() { logger.LogDebug("Syncing to FATE level"); return true; } public override ETaskResult Update() { ushort currentFateId = gameFunctions.GetCurrentFateId(); if (currentFateId == 0) { logger.LogDebug("No active FATE to sync to, skipping"); return ETaskResult.TaskComplete; } gameFunctions.SyncToFate(currentFateId); return ETaskResult.TaskComplete; } public override bool ShouldInterruptOnDamage() { return false; } } internal sealed record FateActionLoop(IReadOnlyList Targets, EStatus? RequiredStatusId = null) : ITask { public bool ShouldRedoOnInterrupt() { return true; } public override string ToString() { return $"FateActionLoop({Targets.Count} targets)"; } } internal sealed class FateActionLoopExecutor(GameFunctions gameFunctions, ILogger logger) : TaskExecutor() { private long _nextActionAt; private ushort _trackedFateId; private bool _fateWasActive; protected override bool Start() { _trackedFateId = gameFunctions.GetCurrentFateId(); logger.LogDebug("Starting FATE action loop with {Count} targets, tracking FATE {FateId}", base.Task.Targets.Count, _trackedFateId); return true; } public override ETaskResult Update() { if (Environment.TickCount64 < _nextActionAt) { return ETaskResult.StillRunning; } if (base.Task.RequiredStatusId.HasValue && !gameFunctions.HasStatus(base.Task.RequiredStatusId.Value)) { logger.LogDebug("Required status {StatusId} lost during FATE action loop, ending cycle to re-apply", base.Task.RequiredStatusId.Value); return ETaskResult.TaskComplete; } ushort currentFateId = gameFunctions.GetCurrentFateId(); bool flag = currentFateId != 0; if (_fateWasActive && !flag) { logger.LogDebug("FATE {FateId} is no longer active, cycle complete", _trackedFateId); return ETaskResult.TaskComplete; } _fateWasActive = flag; if (_trackedFateId == 0) { _trackedFateId = currentFateId; if (_trackedFateId != 0) { logger.LogDebug("Now tracking FATE {FateId}", _trackedFateId); } } if (_trackedFateId != 0 && !gameFunctions.IsFateStillActive(_trackedFateId)) { logger.LogDebug("FATE {FateId} is no longer running, cycle complete", _trackedFateId); return ETaskResult.TaskComplete; } foreach (FateActionTarget target in base.Task.Targets) { IGameObject gameObject = gameFunctions.FindObjectByDataId(target.DataId, null, warnIfMissing: false); if (gameObject != null && gameObject.IsTargetable) { bool flag2 = gameFunctions.UseAction(gameObject, target.Action); _nextActionAt = (flag2 ? (Environment.TickCount64 + 2500) : (Environment.TickCount64 + 500)); return ETaskResult.StillRunning; } } _nextActionAt = Environment.TickCount64 + 250; return ETaskResult.StillRunning; } public override bool ShouldInterruptOnDamage() { return false; } } }