punish v6.8.18.0
This commit is contained in:
commit
e786325cda
322 changed files with 554232 additions and 0 deletions
|
@ -0,0 +1,277 @@
|
|||
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>();
|
||||
}
|
||||
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<UseOnObject> logger) : TaskExecutor<UseOnObject>()
|
||||
{
|
||||
private bool _usedAction;
|
||||
|
||||
private DateTime _continueAt = DateTime.MinValue;
|
||||
|
||||
protected override bool Start()
|
||||
{
|
||||
if (base.Task.DataId.HasValue)
|
||||
{
|
||||
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId.Value);
|
||||
if (gameObject == null)
|
||||
{
|
||||
logger.LogWarning("No game object with dataId {DataId}", base.Task.DataId);
|
||||
return false;
|
||||
}
|
||||
if (gameObject.IsTargetable)
|
||||
{
|
||||
if (base.Task.Action == EAction.Diagnosis && gameFunctions.HasStatus(EStatus.Eukrasia) && GameFunctions.RemoveStatus(EStatus.Eukrasia))
|
||||
{
|
||||
_continueAt = DateTime.Now.AddSeconds(2.0);
|
||||
return true;
|
||||
}
|
||||
_usedAction = gameFunctions.UseAction(gameObject, base.Task.Action);
|
||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
_usedAction = gameFunctions.UseAction(base.Task.Action);
|
||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (DateTime.Now <= _continueAt)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
if (!_usedAction)
|
||||
{
|
||||
if (base.Task.DataId.HasValue)
|
||||
{
|
||||
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId.Value);
|
||||
if (gameObject == null || !gameObject.IsTargetable)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
_usedAction = gameFunctions.UseAction(gameObject, base.Task.Action);
|
||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||
}
|
||||
else
|
||||
{
|
||||
_usedAction = gameFunctions.UseAction(base.Task.Action);
|
||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||
}
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
if (base.Task.Quest != null && base.Task.CompletionQuestVariablesFlags != null && QuestWorkUtils.HasCompletionFlags(base.Task.CompletionQuestVariablesFlags))
|
||||
{
|
||||
QuestProgressInfo questProgressInfo = questFunctions.GetQuestProgressInfo(base.Task.Quest.Id);
|
||||
if (questProgressInfo == null || !QuestWorkUtils.MatchesQuestWork(base.Task.CompletionQuestVariablesFlags, questProgressInfo))
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
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<UseMudraOnObject> 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 DateTime _continueAt = DateTime.MinValue;
|
||||
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public unsafe override ETaskResult Update()
|
||||
{
|
||||
if (DateTime.Now < _continueAt)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
EAction adjustedActionId = (EAction)ActionManager.Instance()->GetAdjustedActionId(2260u);
|
||||
if (adjustedActionId == EAction.RabbitMedium)
|
||||
{
|
||||
_continueAt = DateTime.Now.AddSeconds(1.0);
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.DataId);
|
||||
if (gameObject == null || !gameObject.IsTargetable)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
if (adjustedActionId == base.Task.Action)
|
||||
{
|
||||
_continueAt = DateTime.Now.AddSeconds(0.25);
|
||||
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 = DateTime.Now.AddSeconds(0.25);
|
||||
gameFunctions.UseAction(value2);
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
_continueAt = DateTime.Now.AddSeconds(0.25);
|
||||
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>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
if (gameFunctions.HasStatus(base.Task.Status))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
gameFunctions.UseAction(base.Task.Action);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (!gameFunctions.HasStatus(base.Task.Status))
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class AetherCurrent
|
||||
{
|
||||
internal sealed class Factory(AetherCurrentData aetherCurrentData, IChatGui chatGui) : SimpleTaskFactory()
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.AttuneAetherCurrent)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.DataId, "step.DataId");
|
||||
ArgumentNullException.ThrowIfNull(step.AetherCurrentId, "step.AetherCurrentId");
|
||||
if (!aetherCurrentData.IsValidAetherCurrent(step.TerritoryId, step.AetherCurrentId.Value))
|
||||
{
|
||||
chatGui.PrintError($"Aether current with id {step.AetherCurrentId} is referencing an invalid aether current, will skip attunement", "Questionable", 576);
|
||||
return null;
|
||||
}
|
||||
return new Attune(step.DataId.Value, step.AetherCurrentId.Value);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record Attune(uint DataId, uint AetherCurrentId) : ITask
|
||||
{
|
||||
public bool ShouldRedoOnInterrupt()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"AttuneAetherCurrent({AetherCurrentId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DoAttune(GameFunctions gameFunctions, ILogger<DoAttune> logger) : TaskExecutor<Attune>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
if (!gameFunctions.IsAetherCurrentUnlocked(base.Task.AetherCurrentId))
|
||||
{
|
||||
logger.LogInformation("Attuning to aether current {AetherCurrentId} / {DataId}", base.Task.AetherCurrentId, base.Task.DataId);
|
||||
base.ProgressContext = InteractionProgressContext.FromActionUseOrDefault(() => gameFunctions.InteractWith(base.Task.DataId, ObjectKind.EventObj));
|
||||
return true;
|
||||
}
|
||||
logger.LogInformation("Already attuned to aether current {AetherCurrentId} / {DataId}", base.Task.AetherCurrentId, base.Task.DataId);
|
||||
return false;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (!gameFunctions.IsAetherCurrentUnlocked(base.Task.AetherCurrentId))
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class AethernetShard
|
||||
{
|
||||
internal sealed class Factory : SimpleTaskFactory
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.AttuneAethernetShard)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.AethernetShard, "step.AethernetShard");
|
||||
return new Attune(step.AethernetShard.Value);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record Attune(EAetheryteLocation AetheryteLocation) : ITask
|
||||
{
|
||||
public bool ShouldRedoOnInterrupt()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"AttuneAethernetShard({AetheryteLocation})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DoAttune(AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, ILogger<DoAttune> logger) : TaskExecutor<Attune>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
if (!aetheryteFunctions.IsAetheryteUnlocked(base.Task.AetheryteLocation))
|
||||
{
|
||||
logger.LogInformation("Attuning to aethernet shard {AethernetShard}", base.Task.AetheryteLocation);
|
||||
base.ProgressContext = InteractionProgressContext.FromActionUseOrDefault(() => gameFunctions.InteractWith((uint)base.Task.AetheryteLocation, ObjectKind.Aetheryte));
|
||||
return true;
|
||||
}
|
||||
logger.LogInformation("Already attuned to aethernet shard {AethernetShard}", base.Task.AetheryteLocation);
|
||||
return false;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (!aetheryteFunctions.IsAetheryteUnlocked(base.Task.AetheryteLocation))
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class Aetheryte
|
||||
{
|
||||
internal sealed class Factory : SimpleTaskFactory
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.AttuneAetheryte)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.Aetheryte, "step.Aetheryte");
|
||||
return new Attune(step.Aetheryte.Value);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record Attune(EAetheryteLocation AetheryteLocation) : ITask
|
||||
{
|
||||
public bool ShouldRedoOnInterrupt()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"AttuneAetheryte({AetheryteLocation})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DoAttune(AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, ILogger<DoAttune> logger) : TaskExecutor<Attune>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
if (!aetheryteFunctions.IsAetheryteUnlocked(base.Task.AetheryteLocation))
|
||||
{
|
||||
logger.LogInformation("Attuning to aetheryte {Aetheryte}", base.Task.AetheryteLocation);
|
||||
base.ProgressContext = InteractionProgressContext.FromActionUseOrDefault(() => gameFunctions.InteractWith((uint)base.Task.AetheryteLocation, ObjectKind.Aetheryte));
|
||||
return true;
|
||||
}
|
||||
logger.LogInformation("Already attuned to aetheryte {Aetheryte}", base.Task.AetheryteLocation);
|
||||
return false;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (!aetheryteFunctions.IsAetheryteUnlocked(base.Task.AetheryteLocation))
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class AetheryteFreeOrFavored
|
||||
{
|
||||
internal sealed class Factory : SimpleTaskFactory
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.RegisterFreeOrFavoredAetheryte)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.Aetheryte, "step.Aetheryte");
|
||||
return new Register(step.Aetheryte.Value);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record Register(EAetheryteLocation AetheryteLocation) : ITask
|
||||
{
|
||||
public bool ShouldRedoOnInterrupt()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"RegisterFreeOrFavoredAetheryte({AetheryteLocation})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DoRegister(AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, ILogger<DoRegister> logger) : TaskExecutor<Register>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
if (!aetheryteFunctions.IsAetheryteUnlocked(base.Task.AetheryteLocation))
|
||||
{
|
||||
throw new TaskException($"Aetheryte {base.Task.AetheryteLocation} is not attuned");
|
||||
}
|
||||
if (aetheryteFunctions.CanRegisterFreeOrFavoriteAetheryte(base.Task.AetheryteLocation) == AetheryteRegistrationResult.NotPossible)
|
||||
{
|
||||
logger.LogInformation("Could not register aetheryte {AetheryteLocation} as free or favored", base.Task.AetheryteLocation);
|
||||
return false;
|
||||
}
|
||||
base.ProgressContext = InteractionProgressContext.FromActionUseOrDefault(() => gameFunctions.InteractWith((uint)base.Task.AetheryteLocation, ObjectKind.Aetheryte));
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (aetheryteFunctions.CanRegisterFreeOrFavoriteAetheryte(base.Task.AetheryteLocation) != AetheryteRegistrationResult.NotPossible)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Controller.Utils;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class Combat
|
||||
{
|
||||
internal sealed class Factory(GameFunctions gameFunctions) : ITaskFactory
|
||||
{
|
||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.Combat)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.EnemySpawnType, "step.EnemySpawnType");
|
||||
if (gameFunctions.GetMountId() != 128 && gameFunctions.GetMountId() != 147)
|
||||
{
|
||||
yield return new Mount.UnmountTask();
|
||||
}
|
||||
if (step.CombatDelaySecondsAtStart.HasValue)
|
||||
{
|
||||
yield return new WaitAtStart.WaitDelay(TimeSpan.FromSeconds(step.CombatDelaySecondsAtStart.Value));
|
||||
}
|
||||
switch (step.EnemySpawnType)
|
||||
{
|
||||
case EEnemySpawnType.AfterInteraction:
|
||||
ArgumentNullException.ThrowIfNull(step.DataId, "step.DataId");
|
||||
yield return new Interact.Task(step.DataId.Value, quest, EInteractionType.None, SkipMarkerCheck: true);
|
||||
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1L));
|
||||
yield return CreateTask(quest, sequence, step);
|
||||
break;
|
||||
case EEnemySpawnType.AfterItemUse:
|
||||
ArgumentNullException.ThrowIfNull(step.ItemId, "step.ItemId");
|
||||
if (step.GroundTarget == true)
|
||||
{
|
||||
if (step.DataId.HasValue)
|
||||
{
|
||||
yield return new UseItem.UseOnGround(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags, StartingCombat: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(step.Position, "step.Position");
|
||||
yield return new UseItem.UseOnPosition(quest.Id, step.Position.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags, StartingCombat: true);
|
||||
}
|
||||
}
|
||||
else if (step.DataId.HasValue)
|
||||
{
|
||||
yield return new UseItem.UseOnObject(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags, StartingCombat: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new UseItem.UseOnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags, StartingCombat: true);
|
||||
}
|
||||
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1L));
|
||||
yield return CreateTask(quest, sequence, step);
|
||||
break;
|
||||
case EEnemySpawnType.AfterAction:
|
||||
ArgumentNullException.ThrowIfNull(step.DataId, "step.DataId");
|
||||
ArgumentNullException.ThrowIfNull(step.Action, "step.Action");
|
||||
if (!step.Action.Value.RequiresMount())
|
||||
{
|
||||
yield return new Mount.UnmountTask();
|
||||
}
|
||||
yield return new Action.UseOnObject(step.DataId.Value, null, step.Action.Value, null);
|
||||
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1L));
|
||||
yield return CreateTask(quest, sequence, step);
|
||||
break;
|
||||
case EEnemySpawnType.AfterEmote:
|
||||
ArgumentNullException.ThrowIfNull(step.Emote, "step.Emote");
|
||||
yield return new Mount.UnmountTask();
|
||||
if (step.DataId.HasValue)
|
||||
{
|
||||
yield return new Emote.UseOnObject(step.Emote.Value, step.DataId.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new Emote.UseOnSelf(step.Emote.Value);
|
||||
}
|
||||
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1L));
|
||||
yield return CreateTask(quest, sequence, step);
|
||||
break;
|
||||
case EEnemySpawnType.AutoOnEnterArea:
|
||||
if (!step.CombatDelaySecondsAtStart.HasValue)
|
||||
{
|
||||
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1L));
|
||||
}
|
||||
yield return CreateTask(quest, sequence, step);
|
||||
break;
|
||||
case EEnemySpawnType.OverworldEnemies:
|
||||
case EEnemySpawnType.FateEnemies:
|
||||
case EEnemySpawnType.FinishCombatIfAny:
|
||||
yield return CreateTask(quest, sequence, step);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("step", $"Unknown spawn type {step.EnemySpawnType}");
|
||||
}
|
||||
}
|
||||
|
||||
private static Task CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(step.EnemySpawnType, "step.EnemySpawnType");
|
||||
bool isLastStep = sequence.Steps.Last() == step;
|
||||
return CreateTask(quest.Id, sequence.Sequence, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds, step.CompletionQuestVariablesFlags, step.ComplexCombatData, step.CombatItemUse);
|
||||
}
|
||||
|
||||
internal static Task CreateTask(ElementId? elementId, int sequence, bool isLastStep, EEnemySpawnType enemySpawnType, IList<uint> killEnemyDataIds, IList<QuestWorkValue?> completionQuestVariablesFlags, IList<ComplexCombatData> complexCombatData, CombatItemUse? combatItemUse)
|
||||
{
|
||||
return new Task(new CombatController.CombatData
|
||||
{
|
||||
ElementId = elementId,
|
||||
Sequence = sequence,
|
||||
CompletionQuestVariablesFlags = completionQuestVariablesFlags,
|
||||
SpawnType = enemySpawnType,
|
||||
KillEnemyDataIds = killEnemyDataIds.ToList(),
|
||||
ComplexCombatDatas = complexCombatData.ToList(),
|
||||
CombatItemUse = combatItemUse
|
||||
}, completionQuestVariablesFlags, isLastStep);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record Task(CombatController.CombatData CombatData, IList<QuestWorkValue?> CompletionQuestVariableFlags, bool IsLastStep) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
if (CombatData.SpawnType == EEnemySpawnType.FinishCombatIfAny)
|
||||
{
|
||||
return "HandleCombat(wait: not in combat, optional)";
|
||||
}
|
||||
if (QuestWorkUtils.HasCompletionFlags(CompletionQuestVariableFlags))
|
||||
{
|
||||
return "HandleCombat(wait: QW flags)";
|
||||
}
|
||||
if (IsLastStep)
|
||||
{
|
||||
return "HandleCombat(wait: next sequence)";
|
||||
}
|
||||
return "HandleCombat(wait: not in combat)";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HandleCombat(CombatController combatController, QuestFunctions questFunctions) : TaskExecutor<Task>()
|
||||
{
|
||||
private CombatController.EStatus _status;
|
||||
|
||||
protected override bool Start()
|
||||
{
|
||||
return combatController.Start(base.Task.CombatData);
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
_status = combatController.Update();
|
||||
if (_status != CombatController.EStatus.Complete)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
if (QuestWorkUtils.HasCompletionFlags(base.Task.CompletionQuestVariableFlags) && base.Task.CombatData.ElementId is QuestId elementId)
|
||||
{
|
||||
QuestProgressInfo questProgressInfo = questFunctions.GetQuestProgressInfo(elementId);
|
||||
if (questProgressInfo == null)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
if (QuestWorkUtils.MatchesQuestWork(base.Task.CompletionQuestVariableFlags, questProgressInfo))
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
if (base.Task.IsLastStep)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
combatController.Stop("Combat task complete");
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
168
Questionable/Questionable.Controller.Steps.Interactions/Dive.cs
Normal file
168
Questionable/Questionable.Controller.Steps.Interactions/Dive.cs
Normal file
|
@ -0,0 +1,168 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Input;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class Dive
|
||||
{
|
||||
internal sealed class Factory : SimpleTaskFactory
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.Dive)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new Task();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class Task : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "Dive";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DoDive(ICondition condition, ILogger<DoDive> logger) : AbstractDelayedTaskExecutor<Task>(TimeSpan.FromSeconds(5L))
|
||||
{
|
||||
private readonly Queue<(uint Type, nint Key)> _keysToPress = new Queue<(uint, nint)>();
|
||||
|
||||
private int _attempts;
|
||||
|
||||
protected override bool StartInternal()
|
||||
{
|
||||
if (condition[ConditionFlag.Diving])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (condition[ConditionFlag.Mounted] || condition[ConditionFlag.Swimming])
|
||||
{
|
||||
Descend();
|
||||
return true;
|
||||
}
|
||||
throw new TaskException("You aren't swimming, so we can't dive.");
|
||||
}
|
||||
|
||||
public unsafe override ETaskResult Update()
|
||||
{
|
||||
if (_keysToPress.TryDequeue(out (uint, nint) result))
|
||||
{
|
||||
if (result.Item1 == 0)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
logger.LogDebug("{Action} key {KeyCode:X2}", (result.Item1 == 256) ? "Pressing" : "Releasing", result.Item2);
|
||||
NativeMethods.SendMessage((nint)Device.Instance()->hWnd, result.Item1, result.Item2, IntPtr.Zero);
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return base.Update();
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override ETaskResult UpdateInternal()
|
||||
{
|
||||
if (condition[ConditionFlag.Diving])
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
if (_attempts >= 3)
|
||||
{
|
||||
throw new TaskException("Please dive manually.");
|
||||
}
|
||||
Descend();
|
||||
_attempts++;
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
|
||||
private unsafe void Descend()
|
||||
{
|
||||
UIInputData.Keybind keybind = default(UIInputData.Keybind);
|
||||
Utf8String* name = Utf8String.FromString("MOVE_DESCENT");
|
||||
UIInputData.Instance()->GetKeybindByName(name, (Keybind*)(&keybind));
|
||||
logger.LogInformation("Dive keybind: {Key1} + {Modifier1}, {Key2} + {Modifier2}", keybind.Key, keybind.Modifier, keybind.AltKey, keybind.AltModifier);
|
||||
int num = 2;
|
||||
List<List<nint>> list = new List<List<nint>>(num);
|
||||
CollectionsMarshal.SetCount(list, num);
|
||||
Span<List<nint>> span = CollectionsMarshal.AsSpan(list);
|
||||
int num2 = 0;
|
||||
span[num2] = GetKeysToPress(keybind.Key, keybind.Modifier);
|
||||
num2++;
|
||||
span[num2] = GetKeysToPress(keybind.AltKey, keybind.AltModifier);
|
||||
List<nint> list2 = (from x in list
|
||||
where x != null
|
||||
select (x)).MinBy((List<nint> x) => x.Count);
|
||||
if (list2 == null || list2.Count == 0)
|
||||
{
|
||||
throw new TaskException("No useable keybind found for diving");
|
||||
}
|
||||
foreach (nint item in list2)
|
||||
{
|
||||
_keysToPress.Enqueue((256u, item));
|
||||
_keysToPress.Enqueue((0u, 0));
|
||||
_keysToPress.Enqueue((0u, 0));
|
||||
}
|
||||
for (int num3 = 0; num3 < 5; num3++)
|
||||
{
|
||||
_keysToPress.Enqueue((0u, 0));
|
||||
}
|
||||
list2.Reverse();
|
||||
foreach (nint item2 in list2)
|
||||
{
|
||||
_keysToPress.Enqueue((257u, item2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class NativeMethods
|
||||
{
|
||||
public const uint WM_KEYUP = 257u;
|
||||
|
||||
public const uint WM_KEYDOWN = 256u;
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
public static extern nint SendMessage(nint hWnd, uint Msg, nint wParam, nint lParam);
|
||||
}
|
||||
|
||||
private static List<nint>? GetKeysToPress(SeVirtualKey key, ModifierFlag modifier)
|
||||
{
|
||||
List<nint> list = new List<nint>();
|
||||
if (modifier.HasFlag(ModifierFlag.Ctrl))
|
||||
{
|
||||
list.Add(17);
|
||||
}
|
||||
if (modifier.HasFlag(ModifierFlag.Shift))
|
||||
{
|
||||
list.Add(16);
|
||||
}
|
||||
if (modifier.HasFlag(ModifierFlag.Alt))
|
||||
{
|
||||
list.Add(18);
|
||||
}
|
||||
nint num = (nint)key;
|
||||
if (num == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
list.Add(num);
|
||||
return list;
|
||||
}
|
||||
}
|
195
Questionable/Questionable.Controller.Steps.Interactions/Duty.cs
Normal file
195
Questionable/Questionable.Controller.Steps.Interactions/Duty.cs
Normal file
|
@ -0,0 +1,195 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using LLib.Gear;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Controller.Utils;
|
||||
using Questionable.Data;
|
||||
using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class Duty
|
||||
{
|
||||
internal sealed class Factory(AutoDutyIpc autoDutyIpc) : ITaskFactory
|
||||
{
|
||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.Duty)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.DutyOptions, "step.DutyOptions");
|
||||
uint contentFinderConditionId;
|
||||
int dutyMode;
|
||||
if (autoDutyIpc.IsConfiguredToRunContent(step.DutyOptions))
|
||||
{
|
||||
contentFinderConditionId = step.DutyOptions.ContentFinderConditionId;
|
||||
ElementId id = quest.Id;
|
||||
if (id is QuestId)
|
||||
{
|
||||
ushort value = id.Value;
|
||||
if (value >= 357 && value <= 360)
|
||||
{
|
||||
dutyMode = 2;
|
||||
goto IL_00b2;
|
||||
}
|
||||
}
|
||||
dutyMode = 1;
|
||||
goto IL_00b2;
|
||||
}
|
||||
if (!step.DutyOptions.LowPriority)
|
||||
{
|
||||
yield return new OpenDutyFinderTask(step.DutyOptions.ContentFinderConditionId);
|
||||
}
|
||||
yield break;
|
||||
IL_00b2:
|
||||
yield return new StartAutoDutyTask(contentFinderConditionId, (AutoDutyIpc.DutyMode)dutyMode);
|
||||
yield return new WaitAutoDutyTask(step.DutyOptions.ContentFinderConditionId);
|
||||
if (!QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
|
||||
{
|
||||
yield return new WaitAtEnd.WaitNextStepOrSequence();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record StartAutoDutyTask(uint ContentFinderConditionId, AutoDutyIpc.DutyMode DutyMode) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"StartAutoDuty({ContentFinderConditionId}, {DutyMode})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class StartAutoDutyExecutor(GearStatsCalculator gearStatsCalculator, AutoDutyIpc autoDutyIpc, TerritoryData territoryData, IClientState clientState, IChatGui chatGui, SendNotification.Executor sendNotificationExecutor) : TaskExecutor<StartAutoDutyTask>(), IStoppableTaskExecutor, ITaskExecutor
|
||||
{
|
||||
protected unsafe override bool Start()
|
||||
{
|
||||
if (!territoryData.TryGetContentFinderCondition(base.Task.ContentFinderConditionId, out TerritoryData.ContentFinderConditionData contentFinderConditionData))
|
||||
{
|
||||
throw new TaskException("Failed to get territory ID for content finder condition");
|
||||
}
|
||||
InventoryManager* intPtr = InventoryManager.Instance();
|
||||
if (intPtr == null)
|
||||
{
|
||||
throw new TaskException("Inventory unavailable");
|
||||
}
|
||||
InventoryContainer* inventoryContainer = intPtr->GetInventoryContainer(InventoryType.EquippedItems);
|
||||
if (inventoryContainer == null)
|
||||
{
|
||||
throw new TaskException("Equipped items unavailable");
|
||||
}
|
||||
short num = gearStatsCalculator.CalculateAverageItemLevel(inventoryContainer);
|
||||
if (contentFinderConditionData.RequiredItemLevel > num)
|
||||
{
|
||||
string text = $"Could not use AutoDuty to queue for {contentFinderConditionData.Name}, required item level: {contentFinderConditionData.RequiredItemLevel}, current item level: {num}.";
|
||||
if (!sendNotificationExecutor.Start(new SendNotification.Task(EInteractionType.Duty, text)))
|
||||
{
|
||||
chatGui.PrintError(text, "Questionable", 576);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
autoDutyIpc.StartInstance(base.Task.ContentFinderConditionId, base.Task.DutyMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (!territoryData.TryGetContentFinderCondition(base.Task.ContentFinderConditionId, out TerritoryData.ContentFinderConditionData contentFinderConditionData))
|
||||
{
|
||||
throw new TaskException("Failed to get territory ID for content finder condition");
|
||||
}
|
||||
if (clientState.TerritoryType != contentFinderConditionData.TerritoryId)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public void StopNow()
|
||||
{
|
||||
autoDutyIpc.Stop();
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record WaitAutoDutyTask(uint ContentFinderConditionId) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Wait(AutoDuty, left instance {ContentFinderConditionId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class WaitAutoDutyExecutor(AutoDutyIpc autoDutyIpc, TerritoryData territoryData, IClientState clientState) : TaskExecutor<WaitAutoDutyTask>(), IStoppableTaskExecutor, ITaskExecutor
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (!territoryData.TryGetContentFinderCondition(base.Task.ContentFinderConditionId, out TerritoryData.ContentFinderConditionData contentFinderConditionData))
|
||||
{
|
||||
throw new TaskException("Failed to get territory ID for content finder condition");
|
||||
}
|
||||
if (clientState.TerritoryType == contentFinderConditionData.TerritoryId || !autoDutyIpc.IsStopped())
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public void StopNow()
|
||||
{
|
||||
autoDutyIpc.Stop();
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record OpenDutyFinderTask(uint ContentFinderConditionId) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"OpenDutyFinder({ContentFinderConditionId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class OpenDutyFinderExecutor(GameFunctions gameFunctions, ICondition condition) : TaskExecutor<OpenDutyFinderTask>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
if (condition[ConditionFlag.InDutyQueue])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
gameFunctions.OpenDutyFinder(base.Task.ContentFinderConditionId);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class Emote
|
||||
{
|
||||
internal sealed class Factory : 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.Emote.HasValue)
|
||||
{
|
||||
return Array.Empty<ITask>();
|
||||
}
|
||||
}
|
||||
else if (step.InteractionType != EInteractionType.Emote)
|
||||
{
|
||||
return Array.Empty<ITask>();
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.Emote, "step.Emote");
|
||||
Mount.UnmountTask unmountTask = new Mount.UnmountTask();
|
||||
if (step.DataId.HasValue)
|
||||
{
|
||||
UseOnObject useOnObject = new UseOnObject(step.Emote.Value, step.DataId.Value);
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2] { unmountTask, useOnObject });
|
||||
}
|
||||
UseOnSelf useOnSelf = new UseOnSelf(step.Emote.Value);
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2] { unmountTask, useOnSelf });
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record UseOnObject(EEmote Emote, uint DataId) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Emote({Emote} on {DataId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UseOnObjectExecutor(ChatFunctions chatFunctions) : AbstractDelayedTaskExecutor<UseOnObject>()
|
||||
{
|
||||
protected override bool StartInternal()
|
||||
{
|
||||
chatFunctions.UseEmote(base.Task.DataId, base.Task.Emote);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record UseOnSelf(EEmote Emote) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Emote({Emote})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UseOnSelfExecutor(ChatFunctions chatFunctions) : AbstractDelayedTaskExecutor<UseOnSelf>()
|
||||
{
|
||||
protected override bool StartInternal()
|
||||
{
|
||||
chatFunctions.UseEmote(base.Task.Emote);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using LLib;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class EquipItem
|
||||
{
|
||||
internal sealed class Factory : SimpleTaskFactory
|
||||
{
|
||||
public override ITask? CreateTask(Questionable.Model.Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.EquipItem)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.ItemId, "step.ItemId");
|
||||
return new Task(step.ItemId.Value);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record Task(uint ItemId) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Equip({ItemId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DoEquip(IDataManager dataManager, ILogger<DoEquip> logger) : TaskExecutor<Task>(), IToastAware, ITaskExecutor
|
||||
{
|
||||
private const int MaxAttempts = 3;
|
||||
|
||||
private static readonly IReadOnlyList<InventoryType> SourceInventoryTypes = new global::_003C_003Ez__ReadOnlyArray<InventoryType>(new InventoryType[16]
|
||||
{
|
||||
InventoryType.ArmoryMainHand,
|
||||
InventoryType.ArmoryOffHand,
|
||||
InventoryType.ArmoryHead,
|
||||
InventoryType.ArmoryBody,
|
||||
InventoryType.ArmoryHands,
|
||||
InventoryType.ArmoryLegs,
|
||||
InventoryType.ArmoryFeets,
|
||||
InventoryType.ArmoryEar,
|
||||
InventoryType.ArmoryNeck,
|
||||
InventoryType.ArmoryWrist,
|
||||
InventoryType.ArmoryRings,
|
||||
InventoryType.ArmorySoulCrystal,
|
||||
InventoryType.Inventory1,
|
||||
InventoryType.Inventory2,
|
||||
InventoryType.Inventory3,
|
||||
InventoryType.Inventory4
|
||||
});
|
||||
|
||||
private int _attempts;
|
||||
|
||||
private Item? _item;
|
||||
|
||||
private List<ushort> _targetSlots;
|
||||
|
||||
private DateTime _continueAt = DateTime.MaxValue;
|
||||
|
||||
protected override bool Start()
|
||||
{
|
||||
_item = dataManager.GetExcelSheet<Item>().GetRowOrDefault(base.Task.ItemId) ?? throw new ArgumentOutOfRangeException("ItemId");
|
||||
_targetSlots = GetEquipSlot(_item) ?? throw new InvalidOperationException("Not a piece of equipment");
|
||||
Equip();
|
||||
_continueAt = DateTime.Now.AddSeconds(1.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
public unsafe override ETaskResult Update()
|
||||
{
|
||||
if (DateTime.Now < _continueAt)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
InventoryManager* ptr = InventoryManager.Instance();
|
||||
if (ptr == null)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
foreach (ushort targetSlot in _targetSlots)
|
||||
{
|
||||
InventoryItem* inventorySlot = ptr->GetInventorySlot(InventoryType.EquippedItems, targetSlot);
|
||||
if (inventorySlot != null && inventorySlot->ItemId == base.Task.ItemId)
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
}
|
||||
Equip();
|
||||
_continueAt = DateTime.Now.AddSeconds(1.0);
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
|
||||
private unsafe void Equip()
|
||||
{
|
||||
_attempts++;
|
||||
if (_attempts > 3)
|
||||
{
|
||||
throw new TaskException("Unable to equip gear.");
|
||||
}
|
||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||
if (inventoryManager == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
InventoryContainer* inventoryContainer = inventoryManager->GetInventoryContainer(InventoryType.EquippedItems);
|
||||
if (inventoryContainer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (ushort targetSlot in _targetSlots)
|
||||
{
|
||||
InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(targetSlot);
|
||||
if (inventorySlot != null && inventorySlot->ItemId == base.Task.ItemId)
|
||||
{
|
||||
logger.LogInformation("Already equipped {Item}, skipping step", _item?.Name.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
foreach (InventoryType sourceInventoryType in SourceInventoryTypes)
|
||||
{
|
||||
InventoryContainer* inventoryContainer2 = inventoryManager->GetInventoryContainer(sourceInventoryType);
|
||||
if (inventoryContainer2 == null || (inventoryManager->GetItemCountInContainer(base.Task.ItemId, sourceInventoryType, isHq: true, 0) == 0 && inventoryManager->GetItemCountInContainer(base.Task.ItemId, sourceInventoryType, isHq: false, 0) == 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (ushort num = 0; num < inventoryContainer2->Size; num++)
|
||||
{
|
||||
InventoryItem* inventorySlot2 = inventoryContainer2->GetInventorySlot(num);
|
||||
if (inventorySlot2 != null && inventorySlot2->ItemId == base.Task.ItemId)
|
||||
{
|
||||
ushort num2 = _targetSlots.Where(delegate(ushort x)
|
||||
{
|
||||
InventoryItem* inventorySlot3 = inventoryManager->GetInventorySlot(InventoryType.EquippedItems, x);
|
||||
return inventorySlot3 == null || inventorySlot3->ItemId == 0;
|
||||
}).Concat(_targetSlots).First();
|
||||
logger.LogInformation("Equipping item from {SourceInventory}, {SourceSlot} to {TargetInventory}, {TargetSlot}", sourceInventoryType, num, InventoryType.EquippedItems, num2);
|
||||
int num3 = inventoryManager->MoveItemSlot(sourceInventoryType, num, InventoryType.EquippedItems, num2, a6: true);
|
||||
logger.LogInformation("MoveItemSlot result: {Result}", num3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new TaskException($"Could not equip item {base.Task.ItemId}.");
|
||||
}
|
||||
|
||||
private static List<ushort>? GetEquipSlot(Item? item)
|
||||
{
|
||||
if (!item.HasValue)
|
||||
{
|
||||
return new List<ushort>();
|
||||
}
|
||||
Span<ushort> span;
|
||||
switch (item.Value.EquipSlotCategory.RowId)
|
||||
{
|
||||
case 1u:
|
||||
case 2u:
|
||||
case 3u:
|
||||
case 4u:
|
||||
case 5u:
|
||||
case 6u:
|
||||
case 7u:
|
||||
case 8u:
|
||||
case 9u:
|
||||
case 10u:
|
||||
case 11u:
|
||||
{
|
||||
int index = 1;
|
||||
List<ushort> list4 = new List<ushort>(index);
|
||||
CollectionsMarshal.SetCount(list4, index);
|
||||
span = CollectionsMarshal.AsSpan(list4);
|
||||
int num = 0;
|
||||
span[num] = (ushort)(item.Value.EquipSlotCategory.RowId - 1);
|
||||
return list4;
|
||||
}
|
||||
case 12u:
|
||||
{
|
||||
int num = 2;
|
||||
List<ushort> list3 = new List<ushort>(num);
|
||||
CollectionsMarshal.SetCount(list3, num);
|
||||
span = CollectionsMarshal.AsSpan(list3);
|
||||
int index = 0;
|
||||
span[index] = 11;
|
||||
index++;
|
||||
span[index] = 12;
|
||||
return list3;
|
||||
}
|
||||
case 13u:
|
||||
{
|
||||
int index = 1;
|
||||
List<ushort> list2 = new List<ushort>(index);
|
||||
CollectionsMarshal.SetCount(list2, index);
|
||||
span = CollectionsMarshal.AsSpan(list2);
|
||||
int num = 0;
|
||||
span[num] = 0;
|
||||
return list2;
|
||||
}
|
||||
case 17u:
|
||||
{
|
||||
int num = 1;
|
||||
List<ushort> list = new List<ushort>(num);
|
||||
CollectionsMarshal.SetCount(list, num);
|
||||
span = CollectionsMarshal.AsSpan(list);
|
||||
int index = 0;
|
||||
span[index] = 13;
|
||||
return list;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OnErrorToast(SeString message)
|
||||
{
|
||||
string b = dataManager.GetString(709u, (LogMessage x) => x.Text);
|
||||
if (GameFunctions.GameStringEquals(message.TextValue, b))
|
||||
{
|
||||
_attempts = 3;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using FFXIVClientStructs.Interop;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class EquipRecommended
|
||||
{
|
||||
internal sealed class Factory : SimpleTaskFactory
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.EquipRecommended)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new EquipTask();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class BeforeDutyOrInstance : SimpleTaskFactory
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.Duty && step.InteractionType != EInteractionType.SinglePlayerDuty && step.InteractionType != EInteractionType.Combat)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new EquipTask();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class EquipTask : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "EquipRecommended";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DoEquipRecommended(IClientState clientState, IChatGui chatGui, ICondition condition) : TaskExecutor<EquipTask>()
|
||||
{
|
||||
private bool _checkedOrTriggeredEquipmentUpdate;
|
||||
|
||||
private DateTime _continueAt = DateTime.MinValue;
|
||||
|
||||
protected unsafe override bool Start()
|
||||
{
|
||||
if (condition[ConditionFlag.InCombat])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
RecommendEquipModule.Instance()->SetupForClassJob((byte)clientState.LocalPlayer.ClassJob.RowId);
|
||||
return true;
|
||||
}
|
||||
|
||||
public unsafe override ETaskResult Update()
|
||||
{
|
||||
RecommendEquipModule* ptr = RecommendEquipModule.Instance();
|
||||
if (ptr->IsUpdating)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
if (!_checkedOrTriggeredEquipmentUpdate)
|
||||
{
|
||||
if (!IsAllRecommendeGearEquipped())
|
||||
{
|
||||
chatGui.Print("Equipping recommended gear.", "Questionable", 576);
|
||||
ptr->EquipRecommendedGear();
|
||||
_continueAt = DateTime.Now.AddSeconds(1.0);
|
||||
}
|
||||
_checkedOrTriggeredEquipmentUpdate = true;
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
if (!(DateTime.Now >= _continueAt))
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
private unsafe bool IsAllRecommendeGearEquipped()
|
||||
{
|
||||
RecommendEquipModule* intPtr = RecommendEquipModule.Instance();
|
||||
InventoryContainer* inventoryContainer = InventoryManager.Instance()->GetInventoryContainer(InventoryType.EquippedItems);
|
||||
bool result = true;
|
||||
Span<Pointer<InventoryItem>> recommendedItems = intPtr->RecommendedItems;
|
||||
for (int i = 0; i < recommendedItems.Length; i++)
|
||||
{
|
||||
Pointer<InventoryItem> pointer = recommendedItems[i];
|
||||
InventoryItem* value = pointer.Value;
|
||||
if (value == null || value->ItemId == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bool flag = false;
|
||||
for (int j = 0; j < inventoryContainer->Size; j++)
|
||||
{
|
||||
InventoryItem inventoryItem = inventoryContainer->Items[j];
|
||||
if (inventoryItem.ItemId != 0 && inventoryItem.ItemId == value->ItemId)
|
||||
{
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!flag)
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
158
Questionable/Questionable.Controller.Steps.Interactions/Jump.cs
Normal file
158
Questionable/Questionable.Controller.Steps.Interactions/Jump.cs
Normal file
|
@ -0,0 +1,158 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class Jump
|
||||
{
|
||||
internal sealed class Factory : SimpleTaskFactory
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.Jump)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.JumpDestination, "step.JumpDestination");
|
||||
if (step.JumpDestination.Type == EJumpType.SingleJump)
|
||||
{
|
||||
return new SingleJumpTask(step.DataId, step.JumpDestination, step.Comment);
|
||||
}
|
||||
return new RepeatedJumpTask(step.DataId, step.JumpDestination, step.Comment);
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IJumpTask : ITask
|
||||
{
|
||||
uint? DataId { get; }
|
||||
|
||||
JumpDestination JumpDestination { get; }
|
||||
|
||||
string? Comment { get; }
|
||||
}
|
||||
|
||||
internal sealed record SingleJumpTask(uint? DataId, JumpDestination JumpDestination, string? Comment) : IJumpTask, ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "Jump(" + Comment + ")";
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class JumpBase<T>(MovementController movementController, IClientState clientState, IFramework framework) : TaskExecutor<T>() where T : class, IJumpTask
|
||||
{
|
||||
protected unsafe override bool Start()
|
||||
{
|
||||
float num = base.Task.JumpDestination.CalculateStopDistance();
|
||||
if ((_003CclientState_003EP.LocalPlayer.Position - base.Task.JumpDestination.Position).Length() <= num)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
MovementController movementController = _003CmovementController_003EP;
|
||||
uint? dataId = base.Task.DataId;
|
||||
int num2 = 1;
|
||||
List<Vector3> list = new List<Vector3>(num2);
|
||||
CollectionsMarshal.SetCount(list, num2);
|
||||
Span<Vector3> span = CollectionsMarshal.AsSpan(list);
|
||||
int index = 0;
|
||||
span[index] = base.Task.JumpDestination.Position;
|
||||
movementController.NavigateTo(EMovementType.Quest, dataId, list, fly: false, sprint: false, base.Task.JumpDestination.StopDistance ?? num);
|
||||
_003Cframework_003EP.RunOnTick(delegate
|
||||
{
|
||||
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null);
|
||||
}, TimeSpan.FromSeconds(base.Task.JumpDestination.DelaySeconds ?? 0.5f));
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (_003CmovementController_003EP.IsPathfinding || _003CmovementController_003EP.IsPathRunning)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
DateTime movementStartedAt = _003CmovementController_003EP.MovementStartedAt;
|
||||
if (movementStartedAt == DateTime.MaxValue || movementStartedAt.AddSeconds(1.0) >= DateTime.Now)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DoSingleJump : JumpBase<SingleJumpTask>
|
||||
{
|
||||
public DoSingleJump(MovementController movementController, IClientState clientState, IFramework framework)
|
||||
: base(movementController, clientState, framework)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record RepeatedJumpTask(uint? DataId, JumpDestination JumpDestination, string? Comment) : IJumpTask, ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "RepeatedJump(" + Comment + ")";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DoRepeatedJumps : JumpBase<RepeatedJumpTask>
|
||||
{
|
||||
private readonly IClientState _clientState;
|
||||
|
||||
private DateTime _continueAt;
|
||||
|
||||
private int _attempts;
|
||||
|
||||
public DoRepeatedJumps(MovementController movementController, IClientState clientState, IFramework framework, ICondition condition, ILogger<DoRepeatedJumps> logger)
|
||||
{
|
||||
_003Ccondition_003EP = condition;
|
||||
_003Clogger_003EP = logger;
|
||||
_clientState = clientState;
|
||||
_continueAt = DateTime.MinValue;
|
||||
base._002Ector(movementController, clientState, framework);
|
||||
}
|
||||
|
||||
protected override bool Start()
|
||||
{
|
||||
_continueAt = DateTime.Now + TimeSpan.FromSeconds(2f * (base.Task.JumpDestination.DelaySeconds ?? 0.5f));
|
||||
return base.Start();
|
||||
}
|
||||
|
||||
public unsafe override ETaskResult Update()
|
||||
{
|
||||
if (DateTime.Now < _continueAt || _003Ccondition_003EP[ConditionFlag.Jumping])
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
float num = base.Task.JumpDestination.CalculateStopDistance();
|
||||
if ((_clientState.LocalPlayer.Position - base.Task.JumpDestination.Position).Length() <= num || _clientState.LocalPlayer.Position.Y >= base.Task.JumpDestination.Position.Y - 0.5f)
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
_003Clogger_003EP.LogTrace("Y-Heights for jumps: player={A}, target={B}", _clientState.LocalPlayer.Position.Y, base.Task.JumpDestination.Position.Y - 0.5f);
|
||||
if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null))
|
||||
{
|
||||
_attempts++;
|
||||
}
|
||||
if (_attempts >= 50)
|
||||
{
|
||||
throw new TaskException("Tried to jump too many times, didn't reach the target");
|
||||
}
|
||||
_continueAt = DateTime.Now + TimeSpan.FromSeconds(base.Task.JumpDestination.DelaySeconds ?? 0.5f);
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class PurchaseItem
|
||||
{
|
||||
internal sealed class Factory : SimpleTaskFactory
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.PurchaseItem)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PurchaseRequest
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class Say
|
||||
{
|
||||
internal sealed class Factory(ExcelFunctions excelFunctions) : ITaskFactory
|
||||
{
|
||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
EInteractionType interactionType = step.InteractionType;
|
||||
if ((uint)(interactionType - 28) <= 1u)
|
||||
{
|
||||
if (step.ChatMessage == null)
|
||||
{
|
||||
return Array.Empty<ITask>();
|
||||
}
|
||||
}
|
||||
else if (step.InteractionType != EInteractionType.Say)
|
||||
{
|
||||
return Array.Empty<ITask>();
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.ChatMessage, "step.ChatMessage");
|
||||
string? text = excelFunctions.GetDialogueText(quest, step.ChatMessage.ExcelSheet, step.ChatMessage.Key, isRegex: false).GetString();
|
||||
ArgumentNullException.ThrowIfNull(text, "excelString");
|
||||
Mount.UnmountTask unmountTask = new Mount.UnmountTask();
|
||||
Task task = new Task(text);
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2] { unmountTask, task });
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record Task(string ChatMessage) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "Say(" + ChatMessage + ")";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UseChat(ChatFunctions chatFunctions) : AbstractDelayedTaskExecutor<Task>()
|
||||
{
|
||||
protected override bool StartInternal()
|
||||
{
|
||||
chatFunctions.ExecuteCommand("/say " + base.Task.ChatMessage);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,268 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Data;
|
||||
using Questionable.External;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class SinglePlayerDuty
|
||||
{
|
||||
internal static class SpecialTerritories
|
||||
{
|
||||
public const ushort Lahabrea = 1052;
|
||||
|
||||
public const ushort ItsProbablyATrap = 665;
|
||||
|
||||
public const ushort Naadam = 688;
|
||||
}
|
||||
|
||||
internal sealed class Factory(BossModIpc bossModIpc, TerritoryData territoryData, ICondition condition, IClientState clientState) : ITaskFactory
|
||||
{
|
||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.SinglePlayerDuty || !bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyOptions))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
if (!territoryData.TryGetContentFinderConditionForSoloInstance(quest.Id, step.SinglePlayerDutyIndex, out TerritoryData.ContentFinderConditionData cfcData))
|
||||
{
|
||||
throw new TaskException("Failed to get content finder condition for solo instance");
|
||||
}
|
||||
yield return new Mount.UnmountTask();
|
||||
yield return new StartSinglePlayerDuty(cfcData.ContentFinderConditionId);
|
||||
yield return new WaitAtStart.WaitDelay(TimeSpan.FromSeconds(2L));
|
||||
yield return new EnableAi(cfcData.TerritoryId == 688);
|
||||
if (cfcData.TerritoryId == 1052)
|
||||
{
|
||||
yield return new SetTarget(14643u);
|
||||
yield return new WaitCondition.Task(() => condition[ConditionFlag.Unconscious] || clientState.TerritoryType != 1052, "Wait(death)");
|
||||
yield return new DisableAi();
|
||||
yield return new WaitCondition.Task(() => !condition[ConditionFlag.Unconscious] || clientState.TerritoryType != 1052, "Wait(resurrection)");
|
||||
yield return new EnableAi();
|
||||
}
|
||||
else if (cfcData.TerritoryId == 665)
|
||||
{
|
||||
yield return new WaitCondition.Task(() => DutyActionsAvailable() || clientState.TerritoryType != 665, "Wait(Phase 2)");
|
||||
yield return new EnableAi(Passive: true);
|
||||
}
|
||||
else if (cfcData.TerritoryId == 688)
|
||||
{
|
||||
yield return new WaitCondition.Task(delegate
|
||||
{
|
||||
if (clientState.TerritoryType != 688)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Vector3 vector = clientState.LocalPlayer?.Position ?? default(Vector3);
|
||||
return (new Vector3(352.01f, -1.45f, 288.59f) - vector).Length() < 10f;
|
||||
}, "Wait(moving to Ovoo)");
|
||||
yield return new Mount.UnmountTask();
|
||||
yield return new EnableAi();
|
||||
}
|
||||
yield return new WaitSinglePlayerDuty(cfcData.ContentFinderConditionId);
|
||||
yield return new DisableAi();
|
||||
yield return new WaitAtEnd.WaitNextStepOrSequence();
|
||||
}
|
||||
|
||||
private unsafe bool DutyActionsAvailable()
|
||||
{
|
||||
ContentDirector* contentDirector = EventFramework.Instance()->GetContentDirector();
|
||||
if (contentDirector != null)
|
||||
{
|
||||
return contentDirector->DutyActionManager.ActionsPresent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record StartSinglePlayerDuty(uint ContentFinderConditionId) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Wait(BossMod, entered instance {ContentFinderConditionId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class StartSinglePlayerDutyExecutor(ICondition condition) : TaskExecutor<StartSinglePlayerDuty>()
|
||||
{
|
||||
private DateTime _enteredAt = DateTime.MinValue;
|
||||
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public unsafe override ETaskResult Update()
|
||||
{
|
||||
if (GameMain.Instance()->CurrentContentFinderConditionId != base.Task.ContentFinderConditionId)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
if (!condition[ConditionFlag.BoundByDuty])
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
if (_enteredAt == DateTime.MinValue)
|
||||
{
|
||||
_enteredAt = DateTime.Now;
|
||||
}
|
||||
if (!(DateTime.Now - _enteredAt >= TimeSpan.FromSeconds(2L)))
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record EnableAi(bool Passive = false) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "BossMod.EnableAi(" + (Passive ? "Passive" : "AutoPull") + ")";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class EnableAiExecutor(BossModIpc bossModIpc) : TaskExecutor<EnableAi>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
bossModIpc.EnableAi(base.Task.Passive);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record WaitSinglePlayerDuty(uint ContentFinderConditionId) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Wait(BossMod, left instance {ContentFinderConditionId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class WaitSinglePlayerDutyExecutor(BossModIpc bossModIpc, MovementController movementController) : TaskExecutor<WaitSinglePlayerDuty>(), IStoppableTaskExecutor, ITaskExecutor, IDebugStateProvider
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public unsafe override ETaskResult Update()
|
||||
{
|
||||
if (GameMain.Instance()->CurrentContentFinderConditionId == base.Task.ContentFinderConditionId)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public void StopNow()
|
||||
{
|
||||
bossModIpc.DisableAi();
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public string? GetDebugState()
|
||||
{
|
||||
if (!movementController.IsNavmeshReady)
|
||||
{
|
||||
return $"Navmesh: {movementController.BuiltNavmeshPercent}%";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record DisableAi : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "BossMod.DisableAi";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DisableAiExecutor(BossModIpc bossModIpc) : TaskExecutor<DisableAi>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
bossModIpc.DisableAi();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record SetTarget(uint DataId) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"SetTarget({DataId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SetTargetExecutor(ITargetManager targetManager, IObjectTable objectTable) : TaskExecutor<SetTarget>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (targetManager.Target?.DataId == base.Task.DataId)
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
IGameObject gameObject = objectTable.FirstOrDefault((IGameObject x) => x.DataId == base.Task.DataId);
|
||||
if (gameObject == null)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
targetManager.Target = gameObject;
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class StatusOff
|
||||
{
|
||||
internal sealed class Factory : SimpleTaskFactory
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.StatusOff)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.Status, "step.Status");
|
||||
return new Task(step.Status.Value);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record Task(EStatus Status) : ITask
|
||||
{
|
||||
public bool ShouldRedoOnInterrupt()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"StatusOff({Status})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DoStatusOff(GameFunctions gameFunctions) : AbstractDelayedTaskExecutor<Task>()
|
||||
{
|
||||
protected override bool StartInternal()
|
||||
{
|
||||
if (gameFunctions.HasStatus(base.Task.Status))
|
||||
{
|
||||
return GameFunctions.RemoveStatus(base.Task.Status);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (!gameFunctions.HasStatus(base.Task.Status))
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Controller.Steps.Movement;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Controller.Utils;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class UseItem
|
||||
{
|
||||
internal sealed class Factory(IClientState clientState, TerritoryData territoryData, ILogger<Factory> logger) : ITaskFactory
|
||||
{
|
||||
public unsafe IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
EInteractionType interactionType = step.InteractionType;
|
||||
if ((interactionType == EInteractionType.SinglePlayerDuty || interactionType == EInteractionType.CompleteQuest) ? true : false)
|
||||
{
|
||||
if (!step.ItemId.HasValue)
|
||||
{
|
||||
return Array.Empty<ITask>();
|
||||
}
|
||||
}
|
||||
else if (step.InteractionType != EInteractionType.UseItem)
|
||||
{
|
||||
return Array.Empty<ITask>();
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.ItemId, "step.ItemId");
|
||||
if (step.ItemId == 30362)
|
||||
{
|
||||
if (InventoryManager.Instance()->GetInventoryItemCount(step.ItemId.Value, isHq: false, checkEquipped: true, checkArmory: true, 0) == 0)
|
||||
{
|
||||
return CreateVesperBayFallbackTask();
|
||||
}
|
||||
UseOnSelf useOnSelf = new UseOnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||
int num = sequence.Steps.IndexOf(step);
|
||||
Vector3? position = (sequence.Steps.Skip(num + 1).FirstOrDefault() ?? step).Position;
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[4]
|
||||
{
|
||||
useOnSelf,
|
||||
new WaitCondition.Task(() => clientState.TerritoryType == 140, "Wait(territory: " + territoryData.GetNameAndId(140) + ")"),
|
||||
new Mount.MountTask(140, position.HasValue ? Mount.EMountIf.AwayFromPosition : Mount.EMountIf.Always, position),
|
||||
new MoveTask(140, new Vector3(-408.92343f, 23.167036f, -351.16223f), null, 0.25f, null, DisableNavmesh: true, false, Fly: false, Land: false, IgnoreDistanceToObject: false, RestartNavigation: true, EInteractionType.WalkTo)
|
||||
});
|
||||
}
|
||||
Mount.UnmountTask unmountTask = new Mount.UnmountTask();
|
||||
if (step.GroundTarget == true)
|
||||
{
|
||||
ITask task;
|
||||
if (step.DataId.HasValue)
|
||||
{
|
||||
task = new UseOnGround(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||
}
|
||||
else
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(step.Position, "step.Position");
|
||||
task = new UseOnPosition(quest.Id, step.Position.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||
}
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[3]
|
||||
{
|
||||
unmountTask,
|
||||
new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(0.5)),
|
||||
task
|
||||
});
|
||||
}
|
||||
if (step.DataId.HasValue)
|
||||
{
|
||||
UseOnObject useOnObject = new UseOnObject(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2] { unmountTask, useOnObject });
|
||||
}
|
||||
UseOnSelf useOnSelf2 = new UseOnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2] { unmountTask, useOnSelf2 });
|
||||
}
|
||||
|
||||
private IEnumerable<ITask> CreateVesperBayFallbackTask()
|
||||
{
|
||||
logger.LogWarning("No vesper bay aetheryte tickets in inventory, navigating via ferry in Limsa instead");
|
||||
uint npcId = 1003540u;
|
||||
ushort territoryId = 129;
|
||||
Vector3 destination = new Vector3(-360.9217f, 8f, 38.92566f);
|
||||
yield return new AetheryteShortcut.Task(null, null, EAetheryteLocation.Limsa, territoryId);
|
||||
yield return new Questionable.Controller.Steps.Shared.AethernetShortcut.Task(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist);
|
||||
yield return new WaitAtEnd.WaitDelay();
|
||||
uint? dataId = npcId;
|
||||
bool? sprint = false;
|
||||
yield return new MoveTask(territoryId, destination, null, null, dataId, DisableNavmesh: false, sprint, Fly: false, Land: false, IgnoreDistanceToObject: false, RestartNavigation: true, EInteractionType.WalkTo);
|
||||
yield return new Interact.Task(npcId, null, EInteractionType.None, SkipMarkerCheck: true);
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IUseItemBase : ITask
|
||||
{
|
||||
ElementId? QuestId { get; }
|
||||
|
||||
uint ItemId { get; }
|
||||
|
||||
IList<QuestWorkValue?> CompletionQuestVariablesFlags { get; }
|
||||
|
||||
bool StartingCombat { get; }
|
||||
}
|
||||
|
||||
internal abstract class UseItemExecutorBase<T>(QuestFunctions questFunctions, ICondition condition, ILogger logger) : TaskExecutor<T>() where T : class, IUseItemBase
|
||||
{
|
||||
private bool _usedItem;
|
||||
|
||||
private DateTime _continueAt;
|
||||
|
||||
private int _itemCount;
|
||||
|
||||
private ElementId? QuestId => base.Task.QuestId;
|
||||
|
||||
protected uint ItemId => base.Task.ItemId;
|
||||
|
||||
private IList<QuestWorkValue?> CompletionQuestVariablesFlags => base.Task.CompletionQuestVariablesFlags;
|
||||
|
||||
private bool StartingCombat => base.Task.StartingCombat;
|
||||
|
||||
protected abstract bool UseItem();
|
||||
|
||||
protected unsafe override bool Start()
|
||||
{
|
||||
InventoryManager* ptr = InventoryManager.Instance();
|
||||
if (ptr == null)
|
||||
{
|
||||
throw new TaskException("No InventoryManager");
|
||||
}
|
||||
_itemCount = ptr->GetInventoryItemCount(ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0);
|
||||
if (_itemCount == 0)
|
||||
{
|
||||
throw new TaskException($"Don't have any {ItemId} in inventory (checks NQ only)");
|
||||
}
|
||||
base.ProgressContext = InteractionProgressContext.FromActionUseOrDefault(() => _usedItem = UseItem());
|
||||
_continueAt = DateTime.Now.Add(GetRetryDelay());
|
||||
return true;
|
||||
}
|
||||
|
||||
public unsafe override ETaskResult Update()
|
||||
{
|
||||
if (QuestId is QuestId elementId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
|
||||
{
|
||||
QuestProgressInfo questProgressInfo = _003CquestFunctions_003EP.GetQuestProgressInfo(elementId);
|
||||
if (questProgressInfo != null && QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questProgressInfo))
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
}
|
||||
if (DateTime.Now <= _continueAt)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
if (StartingCombat && _003Ccondition_003EP[ConditionFlag.InCombat])
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
if (ItemId == 30362 && _usedItem)
|
||||
{
|
||||
InventoryManager* ptr = InventoryManager.Instance();
|
||||
if (ptr == null)
|
||||
{
|
||||
_003Clogger_003EP.LogWarning("InventoryManager is not available");
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
if (ptr->GetInventoryItemCount(ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) == _itemCount)
|
||||
{
|
||||
_003Clogger_003EP.LogInformation("Attempted to use vesper bay aetheryte ticket, but it didn't consume an item - reattempting next frame");
|
||||
_usedItem = false;
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
}
|
||||
if (!_usedItem)
|
||||
{
|
||||
base.ProgressContext = InteractionProgressContext.FromActionUseOrDefault(() => _usedItem = UseItem());
|
||||
_continueAt = DateTime.Now.Add(GetRetryDelay());
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
private TimeSpan GetRetryDelay()
|
||||
{
|
||||
if (ItemId == 30362)
|
||||
{
|
||||
return TimeSpan.FromSeconds(11L);
|
||||
}
|
||||
return TimeSpan.FromSeconds(5L);
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record UseOnGround(ElementId? QuestId, uint DataId, uint ItemId, IList<QuestWorkValue?> CompletionQuestVariablesFlags, bool StartingCombat = false) : IUseItemBase, ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"UseItem({ItemId} on ground at {DataId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UseOnGroundExecutor : UseItemExecutorBase<UseOnGround>
|
||||
{
|
||||
public UseOnGroundExecutor(GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger<UseOnGroundExecutor> logger)
|
||||
{
|
||||
_003CgameFunctions_003EP = gameFunctions;
|
||||
base._002Ector(questFunctions, condition, (ILogger)logger);
|
||||
}
|
||||
|
||||
protected override bool UseItem()
|
||||
{
|
||||
return _003CgameFunctions_003EP.UseItemOnGround(base.Task.DataId, base.ItemId);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record UseOnPosition(ElementId? QuestId, Vector3 Position, uint ItemId, IList<QuestWorkValue?> CompletionQuestVariablesFlags, bool StartingCombat = false) : IUseItemBase, ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"UseItem({ItemId} on ground at {Position.ToString("G", CultureInfo.InvariantCulture)})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UseOnPositionExecutor : UseItemExecutorBase<UseOnPosition>
|
||||
{
|
||||
public UseOnPositionExecutor(GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger<UseOnPosition> logger)
|
||||
{
|
||||
_003CgameFunctions_003EP = gameFunctions;
|
||||
base._002Ector(questFunctions, condition, (ILogger)logger);
|
||||
}
|
||||
|
||||
protected override bool UseItem()
|
||||
{
|
||||
return _003CgameFunctions_003EP.UseItemOnPosition(base.Task.Position, base.ItemId);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record UseOnObject(ElementId? QuestId, uint DataId, uint ItemId, IList<QuestWorkValue?> CompletionQuestVariablesFlags, bool StartingCombat = false) : IUseItemBase, ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"UseItem({ItemId} on {DataId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UseOnObjectExecutor : UseItemExecutorBase<UseOnObject>
|
||||
{
|
||||
public UseOnObjectExecutor(QuestFunctions questFunctions, GameFunctions gameFunctions, ICondition condition, ILogger<UseOnObject> logger)
|
||||
{
|
||||
_003CgameFunctions_003EP = gameFunctions;
|
||||
base._002Ector(questFunctions, condition, (ILogger)logger);
|
||||
}
|
||||
|
||||
protected override bool UseItem()
|
||||
{
|
||||
return _003CgameFunctions_003EP.UseItem(base.Task.DataId, base.ItemId);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record UseOnSelf(ElementId? QuestId, uint ItemId, IList<QuestWorkValue?> CompletionQuestVariablesFlags, bool StartingCombat = false) : IUseItemBase, ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"UseItem({ItemId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UseOnSelfExecutor : UseItemExecutorBase<UseOnSelf>
|
||||
{
|
||||
public UseOnSelfExecutor(GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger<UseOnSelf> logger)
|
||||
{
|
||||
_003CgameFunctions_003EP = gameFunctions;
|
||||
base._002Ector(questFunctions, condition, (ILogger)logger);
|
||||
}
|
||||
|
||||
protected override bool UseItem()
|
||||
{
|
||||
return _003CgameFunctions_003EP.UseItem(base.ItemId);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue