using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Dalamud.Game.ClientState.Objects.Types; using FFXIVClientStructs.FFXIV.Client.Game; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Common; using Questionable.Controller.Utils; using Questionable.Functions; using Questionable.Model; using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; internal static class Action { internal sealed class Factory : ITaskFactory { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { if (step.InteractionType != EInteractionType.Action) { return Array.Empty(); } if (step.DataIds.Count > 0) { return Array.Empty(); } ArgumentNullException.ThrowIfNull(step.Action, "step.Action"); ITask task = OnObject(step.DataId, quest, step.Action.Value, step.CompletionQuestVariablesFlags); if (step.Action.Value.RequiresMount()) { return new global::_003C_003Ez__ReadOnlySingleElementList(task); } return new global::_003C_003Ez__ReadOnlyArray(new ITask[2] { new Mount.UnmountTask(), task }); } public static ITask OnObject(uint? dataId, Quest quest, EAction action, List? completionQuestVariablesFlags) { if ((uint)(action - 2265) <= 2u) { ArgumentNullException.ThrowIfNull(dataId, "dataId"); return new UseMudraOnObject(dataId.Value, action); } return new UseOnObject(dataId, quest, action, completionQuestVariablesFlags); } } internal sealed record UseOnObject(uint? DataId, Quest? Quest, EAction Action, List? CompletionQuestVariablesFlags) : ITask { public bool ShouldRedoOnInterrupt() { return true; } public override string ToString() { return $"Action({Action})"; } } internal sealed class UseOnObjectExecutor(GameFunctions gameFunctions, QuestFunctions questFunctions, ILogger logger) : TaskExecutor() { private bool _usedAction; private long _continueAt; private bool _waitingForQuestFlags; private long _questFlagWaitStarted; public override bool HasTimedOut(float timeoutSeconds) { if (!_waitingForQuestFlags) { return false; } if (base.ProgressContext != null) { return base.HasTimedOut(timeoutSeconds); } return Environment.TickCount64 - _questFlagWaitStarted > (long)(timeoutSeconds * 1000f); } public override void ResetTimeout() { base.ResetTimeout(); _questFlagWaitStarted = Environment.TickCount64; } protected override bool Start() { if (base.Task.DataId.HasValue) { IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId.Value); if (gameObject == null) { logger.LogDebug("Object with dataId {DataId} not found yet, waiting", base.Task.DataId); return true; } if (gameObject.IsTargetable) { if (base.Task.Action == EAction.Diagnosis && gameFunctions.HasStatus(EStatus.Eukrasia) && GameFunctions.RemoveStatus(EStatus.Eukrasia)) { _continueAt = Environment.TickCount64 + 2000; return true; } _usedAction = gameFunctions.UseAction(gameObject, base.Task.Action); _continueAt = Environment.TickCount64 + 500; return true; } logger.LogDebug("Object {DataId} is untargetable, using action {Action} without target", base.Task.DataId, base.Task.Action); } _usedAction = gameFunctions.UseAction(base.Task.Action); _continueAt = Environment.TickCount64 + 500; return true; } public override ETaskResult Update() { if (Environment.TickCount64 <= _continueAt) { return ETaskResult.StillRunning; } if (!_usedAction) { if (base.Task.DataId.HasValue) { IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId.Value); if (gameObject == null) { return ETaskResult.StillRunning; } _usedAction = (gameObject.IsTargetable ? gameFunctions.UseAction(gameObject, base.Task.Action) : gameFunctions.UseAction(base.Task.Action)); } else { _usedAction = gameFunctions.UseAction(base.Task.Action); } _continueAt = Environment.TickCount64 + 500; return ETaskResult.StillRunning; } if (base.Task.Quest != null && base.Task.CompletionQuestVariablesFlags != null && QuestWorkUtils.HasCompletionFlags(base.Task.CompletionQuestVariablesFlags)) { if (!_waitingForQuestFlags) { _waitingForQuestFlags = true; _questFlagWaitStarted = Environment.TickCount64; } QuestProgressInfo questProgressInfo = questFunctions.GetQuestProgressInfo(base.Task.Quest.Id); if (questProgressInfo != null && QuestWorkUtils.MatchesQuestWork(base.Task.CompletionQuestVariablesFlags, questProgressInfo)) { ResetProgressTimer(); return ETaskResult.TaskComplete; } return ETaskResult.StillRunning; } return ETaskResult.TaskComplete; } public override bool ShouldInterruptOnDamage() { return true; } } internal sealed record UseMudraOnObject(uint DataId, EAction Action) : ITask { public override string ToString() { return $"Mudra({Action})"; } } internal sealed class UseMudraOnObjectExecutor(GameFunctions gameFunctions, ILogger logger) : TaskExecutor() { private static readonly ReadOnlyDictionary> Combos = new Dictionary> { { EAction.FumaShuriken, new Dictionary { { EAction.Ninjutsu, EAction.Ten } } }, { EAction.Raiton, new Dictionary { { EAction.Ninjutsu, EAction.Ten }, { EAction.FumaShuriken, EAction.Chi } } }, { EAction.Katon, new Dictionary { { EAction.Ninjutsu, EAction.Chi }, { EAction.FumaShuriken, EAction.Ten } } } }.AsReadOnly(); private long _continueAt; protected override bool Start() { return true; } public unsafe override ETaskResult Update() { if (Environment.TickCount64 < _continueAt) { return ETaskResult.StillRunning; } EAction adjustedActionId = (EAction)ActionManager.Instance()->GetAdjustedActionId(2260u); if (adjustedActionId == EAction.RabbitMedium) { _continueAt = Environment.TickCount64 + 1000; return ETaskResult.StillRunning; } IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId); if (gameObject == null || !gameObject.IsTargetable) { return ETaskResult.StillRunning; } if (adjustedActionId == base.Task.Action) { _continueAt = Environment.TickCount64 + 250; if (!gameFunctions.UseAction(gameObject, base.Task.Action)) { return ETaskResult.StillRunning; } return ETaskResult.TaskComplete; } if (Combos.TryGetValue(base.Task.Action, out Dictionary value)) { if (value.TryGetValue(adjustedActionId, out var value2)) { _continueAt = Environment.TickCount64 + 250; gameFunctions.UseAction(value2); return ETaskResult.StillRunning; } _continueAt = Environment.TickCount64 + 250; return ETaskResult.StillRunning; } logger.LogError("Unable to find relevant combo for {Action}", base.Task.Action); return ETaskResult.TaskComplete; } public override bool ShouldInterruptOnDamage() { return false; } } internal sealed record TriggerStatusIfMissing(EStatus Status, EAction Action) : ITask { public override string ToString() { return $"TriggerStatus({Status})"; } } internal sealed class TriggerStatusIfMissingExecutor(GameFunctions gameFunctions) : TaskExecutor() { private long _startedAt; public override bool HasTimedOut(float timeoutSeconds) { return Environment.TickCount64 - _startedAt > (long)(timeoutSeconds * 1000f); } public override void ResetTimeout() { base.ResetTimeout(); _startedAt = Environment.TickCount64; } protected override bool Start() { if (gameFunctions.HasStatus(base.Task.Status)) { return false; } gameFunctions.UseAction(base.Task.Action); _startedAt = Environment.TickCount64; return true; } public override ETaskResult Update() { if (!gameFunctions.HasStatus(base.Task.Status)) { return ETaskResult.StillRunning; } return ETaskResult.TaskComplete; } public override bool ShouldInterruptOnDamage() { return false; } } }