qstbak/Questionable/Questionable.Controller.Steps.Interactions/Action.cs
2026-08-19 13:19:57 +10:00

323 lines
8.4 KiB
C#

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<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.Action)
{
return Array.Empty<ITask>();
}
if (step.DataIds.Count > 0)
{
return Array.Empty<ITask>();
}
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<ITask>(task);
}
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2]
{
new Mount.UnmountTask(),
task
});
}
public static ITask OnObject(uint? dataId, Quest quest, EAction action, List<QuestWorkValue?>? 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<QuestWorkValue?>? CompletionQuestVariablesFlags) : ITask
{
public bool ShouldRedoOnInterrupt()
{
return true;
}
public override string ToString()
{
return $"Action({Action})";
}
}
internal sealed class UseOnObjectExecutor(GameFunctions gameFunctions, QuestFunctions questFunctions, ILogger<UseOnObjectExecutor> logger) : TaskExecutor<UseOnObject>()
{
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<UseMudraOnObjectExecutor> logger) : TaskExecutor<UseMudraOnObject>()
{
private static readonly ReadOnlyDictionary<EAction, Dictionary<EAction, EAction>> Combos = new Dictionary<EAction, Dictionary<EAction, EAction>>
{
{
EAction.FumaShuriken,
new Dictionary<EAction, EAction> {
{
EAction.Ninjutsu,
EAction.Ten
} }
},
{
EAction.Raiton,
new Dictionary<EAction, EAction>
{
{
EAction.Ninjutsu,
EAction.Ten
},
{
EAction.FumaShuriken,
EAction.Chi
}
}
},
{
EAction.Katon,
new Dictionary<EAction, EAction>
{
{
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<EAction, EAction> 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<TriggerStatusIfMissing>()
{
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;
}
}
}