337 lines
10 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|