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

337 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Shared;
using Questionable.Controller.Utils;
using Questionable.External;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Interactions;
internal static class Interact
{
internal sealed class Factory(AutomatonIpc automatonIpc, Configuration configuration) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
EInteractionType interactionType = step.InteractionType;
if ((interactionType == EInteractionType.SinglePlayerDuty || (uint)(interactionType - 28) <= 1u) ? true : false)
{
if ((step.InteractionType == EInteractionType.CompleteQuest && configuration.Advanced.PreventQuestCompletion) || step.Emote.HasValue || step.ChatMessage != null || step.ItemId.HasValue || !step.DataId.HasValue)
{
yield break;
}
}
else if (step.InteractionType == EInteractionType.PurchaseItem)
{
if (!step.DataId.HasValue)
{
yield break;
}
}
else if (step.InteractionType == EInteractionType.Snipe)
{
if (!automatonIpc.IsAutoSnipeEnabled)
{
yield break;
}
}
else if (step.InteractionType == EInteractionType.UnlockTaxiStand)
{
if (!step.TaxiStandId.HasValue)
{
yield break;
}
}
else if (step.InteractionType != EInteractionType.Interact)
{
yield break;
}
ArgumentNullException.ThrowIfNull(step.DataId, "step.DataId");
if (sequence.Sequence == 0 && sequence.Steps.IndexOf(step) == 0)
{
yield return new WaitAtEnd.WaitDelay();
}
uint value = step.DataId.Value;
EInteractionType interactionType2 = step.InteractionType;
int skipMarkerCheck;
if (!step.TargetTerritoryId.HasValue && !(quest.Id is SatisfactionSupplyNpcId))
{
SkipConditions skipConditions = step.SkipConditions;
if (skipConditions != null)
{
SkipStepConditions stepIf = skipConditions.StepIf;
if (stepIf != null && stepIf.Never)
{
goto IL_0247;
}
}
if (step.InteractionType != EInteractionType.PurchaseItem)
{
skipMarkerCheck = ((step.DataId == 1052475) ? 1 : 0);
goto IL_0248;
}
}
goto IL_0247;
IL_0247:
skipMarkerCheck = 1;
goto IL_0248;
IL_0248:
yield return new Task(value, quest, interactionType2, (byte)skipMarkerCheck != 0, step.PickUpItemId, step.TaxiStandId, step.SkipConditions?.StepIf, step.CompletionQuestVariablesFlags);
}
}
internal sealed record Task : ITask
{
public uint DataId { get; init; }
public Quest? Quest { get; init; }
public EInteractionType InteractionType { get; init; }
public bool SkipMarkerCheck { get; init; }
public uint? PickUpItemId { get; init; }
public byte? TaxiStandId { get; init; }
public SkipStepConditions? SkipConditions { get; init; }
public List<QuestWorkValue?> CompletionQuestVariablesFlags { get; }
public bool HasCompletionQuestVariablesFlags { get; }
public Task(uint DataId, Quest? Quest, EInteractionType InteractionType, bool SkipMarkerCheck = false, uint? PickUpItemId = null, byte? TaxiStandId = null, SkipStepConditions? SkipConditions = null, List<QuestWorkValue?>? CompletionQuestVariablesFlags = null)
{
this.DataId = DataId;
this.Quest = Quest;
this.InteractionType = InteractionType;
this.SkipMarkerCheck = SkipMarkerCheck;
this.PickUpItemId = PickUpItemId;
this.TaxiStandId = TaxiStandId;
this.SkipConditions = SkipConditions;
this.CompletionQuestVariablesFlags = CompletionQuestVariablesFlags ?? new List<QuestWorkValue>();
HasCompletionQuestVariablesFlags = Quest != null && CompletionQuestVariablesFlags != null && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags);
base._002Ector();
}
public bool ShouldRedoOnInterrupt()
{
return true;
}
public override string ToString()
{
return $"Interact{(HasCompletionQuestVariablesFlags ? "*" : "")}({DataId})";
}
[CompilerGenerated]
public void Deconstruct(out uint DataId, out Quest? Quest, out EInteractionType InteractionType, out bool SkipMarkerCheck, out uint? PickUpItemId, out byte? TaxiStandId, out SkipStepConditions? SkipConditions, out List<QuestWorkValue?>? CompletionQuestVariablesFlags)
{
DataId = this.DataId;
Quest = this.Quest;
InteractionType = this.InteractionType;
SkipMarkerCheck = this.SkipMarkerCheck;
PickUpItemId = this.PickUpItemId;
TaxiStandId = this.TaxiStandId;
SkipConditions = this.SkipConditions;
CompletionQuestVariablesFlags = this.CompletionQuestVariablesFlags;
}
}
internal sealed class DoInteract(GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger<DoInteract> logger) : TaskExecutor<Task>(), IConditionChangeAware, ITaskExecutor
{
private enum EInteractionState
{
None,
InteractionTriggered,
InteractionConfirmed
}
private bool _needsUnmount;
private EInteractionState _interactionState;
private DateTime _continueAt = DateTime.MinValue;
private bool delayedFinalCheck;
public Quest? Quest => base.Task.Quest;
public EInteractionType InteractionType { get; set; }
protected override bool Start()
{
InteractionType = base.Task.InteractionType;
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId);
if (gameObject == null)
{
logger.LogWarning("No game object with dataId {DataId}", base.Task.DataId);
return false;
}
if (!gameObject.IsTargetable)
{
SkipStepConditions skipConditions = base.Task.SkipConditions;
if (skipConditions != null && !skipConditions.Never && skipConditions.NotTargetable)
{
logger.LogInformation("Not interacting with {DataId} because it is not targetable (but skippable)", base.Task.DataId);
return false;
}
}
if (!gameObject.IsTargetable && condition[ConditionFlag.Mounted] && gameObject.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.GatheringPoint)
{
logger.LogInformation("Preparing interaction for {DataId} by unmounting", base.Task.DataId);
_needsUnmount = true;
gameFunctions.Unmount();
_continueAt = DateTime.Now.AddSeconds(1.0);
return true;
}
if (gameObject.IsTargetable && HasAnyMarker(gameObject))
{
TriggerInteraction(gameObject);
return true;
}
return true;
}
public unsafe override ETaskResult Update()
{
if (DateTime.Now <= _continueAt)
{
return ETaskResult.StillRunning;
}
if (_needsUnmount)
{
if (condition[ConditionFlag.Mounted])
{
gameFunctions.Unmount();
_continueAt = DateTime.Now.AddSeconds(1.0);
return ETaskResult.StillRunning;
}
_needsUnmount = false;
}
uint? pickUpItemId = base.Task.PickUpItemId;
if (pickUpItemId.HasValue)
{
uint valueOrDefault = pickUpItemId.GetValueOrDefault();
if (InventoryManager.Instance()->GetInventoryItemCount(valueOrDefault, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0)
{
return ETaskResult.TaskComplete;
}
}
else
{
byte? taxiStandId = base.Task.TaxiStandId;
if (taxiStandId.HasValue)
{
byte valueOrDefault2 = taxiStandId.GetValueOrDefault();
if (UIState.Instance()->IsChocoboTaxiStandUnlocked(valueOrDefault2))
{
return ETaskResult.TaskComplete;
}
}
else
{
if (InteractionType == EInteractionType.Gather && condition[ConditionFlag.Gathering])
{
return ETaskResult.TaskComplete;
}
if (Quest != null && base.Task.HasCompletionQuestVariablesFlags)
{
QuestProgressInfo questProgressInfo = questFunctions.GetQuestProgressInfo(Quest.Id);
if (questProgressInfo == null || !QuestWorkUtils.MatchesQuestWork(base.Task.CompletionQuestVariablesFlags, questProgressInfo))
{
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}
if (base.ProgressContext != null)
{
if (base.ProgressContext.WasInterrupted())
{
return ETaskResult.StillRunning;
}
if (base.ProgressContext.WasSuccessful() || _interactionState == EInteractionState.InteractionConfirmed)
{
if (delayedFinalCheck)
{
return ETaskResult.TaskComplete;
}
_continueAt = DateTime.Now.AddSeconds(0.2);
delayedFinalCheck = true;
return ETaskResult.StillRunning;
}
}
}
}
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId);
if (gameObject == null || !gameObject.IsTargetable || !HasAnyMarker(gameObject))
{
return ETaskResult.StillRunning;
}
TriggerInteraction(gameObject);
return ETaskResult.StillRunning;
}
private void TriggerInteraction(IGameObject gameObject)
{
base.ProgressContext = InteractionProgressContext.FromActionUseOrDefault(delegate
{
if (gameFunctions.InteractWith(gameObject))
{
_interactionState = EInteractionState.InteractionTriggered;
}
else
{
_interactionState = EInteractionState.None;
}
return _interactionState != EInteractionState.None;
});
_continueAt = DateTime.Now.AddSeconds(0.5);
}
private unsafe bool HasAnyMarker(IGameObject gameObject)
{
if (base.Task.SkipMarkerCheck || gameObject.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.EventNpc)
{
return true;
}
GameObject* address = (GameObject*)gameObject.Address;
return address->NamePlateIconId != 0;
}
public void OnConditionChange(ConditionFlag flag, bool value)
{
if (base.ProgressContext == null || (!base.ProgressContext.WasInterrupted() && !base.ProgressContext.WasSuccessful()))
{
logger.LogDebug("Condition change: {Flag} = {Value}", flag, value);
bool flag2 = _interactionState == EInteractionState.InteractionTriggered;
if (flag2)
{
bool flag3 = (uint)(flag - 31) <= 1u;
flag2 = flag3;
}
if (flag2 && value)
{
logger.LogInformation("Interaction was most likely triggered");
_interactionState = EInteractionState.InteractionConfirmed;
}
}
}
public override bool ShouldInterruptOnDamage()
{
return true;
}
}
}