punish v6.8.18.0

This commit is contained in:
alydev 2025-10-09 07:47:19 +10:00
commit cfb4dea47e
316 changed files with 554088 additions and 0 deletions

View file

@ -0,0 +1,236 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
using Questionable.Data;
using Questionable.External;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Common;
using Questionable.Model.Common.Converter;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Shared;
internal static class AethernetShortcut
{
internal sealed class Factory(AetheryteData aetheryteData, TerritoryData territoryData, IClientState clientState) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.AethernetShortcut == null)
{
yield break;
}
yield return new WaitNavmesh.Task();
yield return new Task(step.AethernetShortcut.From, step.AethernetShortcut.To, step.SkipConditions?.AethernetShortcutIf ?? new SkipAetheryteCondition());
if (AetheryteShortcut.MoveAwayFromAetheryteExecutor.AppliesTo(step.AethernetShortcut.To))
{
yield return new WaitCondition.Task(() => clientState.TerritoryType == aetheryteData.TerritoryIds[step.AethernetShortcut.To], "Wait(territory: " + territoryData.GetNameAndId(aetheryteData.TerritoryIds[step.AethernetShortcut.To]) + ")");
yield return new AetheryteShortcut.MoveAwayFromAetheryte(step.AethernetShortcut.To);
}
}
}
internal sealed record Task(EAetheryteLocation From, EAetheryteLocation To, SkipAetheryteCondition SkipConditions) : ISkippableTask, ITask
{
public Task(EAetheryteLocation from, EAetheryteLocation to)
: this(from, to, new SkipAetheryteCondition())
{
}
public override string ToString()
{
return $"UseAethernet({From} -> {To})";
}
}
internal sealed class UseAethernetShortcut(ILogger<UseAethernetShortcut> logger, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, QuestFunctions questFunctions, IClientState clientState, AetheryteData aetheryteData, TerritoryData territoryData, LifestreamIpc lifestreamIpc, MovementController movementController, ICondition condition) : TaskExecutor<Task>()
{
private bool _moving;
private bool _teleported;
private bool _triedMounting;
private DateTime _continueAt = DateTime.MinValue;
protected override bool Start()
{
if (!base.Task.SkipConditions.Never)
{
if (base.Task.SkipConditions.InSameTerritory && clientState.TerritoryType == aetheryteData.TerritoryIds[base.Task.To])
{
logger.LogInformation("Skipping aethernet shortcut because the target is in the same territory");
return false;
}
if (base.Task.SkipConditions.InTerritory.Contains(clientState.TerritoryType))
{
logger.LogInformation("Skipping aethernet shortcut because the target is in the specified territory");
return false;
}
if (base.Task.SkipConditions.QuestsCompleted.Count > 0 && base.Task.SkipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete))
{
logger.LogInformation("Skipping aethernet shortcut, all prequisite quests are complete");
return true;
}
if (base.Task.SkipConditions.QuestsAccepted.Count > 0 && base.Task.SkipConditions.QuestsAccepted.All(questFunctions.IsQuestAccepted))
{
logger.LogInformation("Skipping aethernet shortcut, all prequisite quests are accepted");
return true;
}
if (base.Task.SkipConditions.AetheryteLocked.HasValue && !aetheryteFunctions.IsAetheryteUnlocked(base.Task.SkipConditions.AetheryteLocked.Value))
{
logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is locked");
return false;
}
if (base.Task.SkipConditions.AetheryteUnlocked.HasValue && aetheryteFunctions.IsAetheryteUnlocked(base.Task.SkipConditions.AetheryteUnlocked.Value))
{
logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is unlocked");
return false;
}
}
if (aetheryteFunctions.IsAetheryteUnlocked(base.Task.From) && aetheryteFunctions.IsAetheryteUnlocked(base.Task.To))
{
ushort territoryType = clientState.TerritoryType;
Vector3 playerPosition = clientState.LocalPlayer.Position;
if (aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.From) < aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.To))
{
if (aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.From) < (base.Task.From.IsFirmamentAetheryte() ? 11f : 4f))
{
DoTeleport();
return true;
}
if (base.Task.From == EAetheryteLocation.SolutionNine)
{
logger.LogInformation("Moving to S9 aetheryte");
int num = 4;
List<Vector3> list = new List<Vector3>(num);
CollectionsMarshal.SetCount(list, num);
Span<Vector3> span = CollectionsMarshal.AsSpan(list);
int num2 = 0;
span[num2] = new Vector3(0f, 8.442986f, 9f);
num2++;
span[num2] = new Vector3(9f, 8.442986f, 0f);
num2++;
span[num2] = new Vector3(-9f, 8.442986f, 0f);
num2++;
span[num2] = new Vector3(0f, 8.442986f, -9f);
Vector3 to = list.MinBy((Vector3 x) => Vector3.Distance(playerPosition, x));
_moving = true;
movementController.NavigateTo(EMovementType.Quest, (uint)base.Task.From, to, fly: false, sprint: true, 0.25f);
return true;
}
if (territoryData.CanUseMount(territoryType) && aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.From) > 30f && !gameFunctions.HasStatusPreventingMount())
{
_triedMounting = gameFunctions.Mount();
if (_triedMounting)
{
_continueAt = DateTime.Now.AddSeconds(0.5);
return true;
}
}
MoveTo();
return true;
}
}
else
{
if (clientState.TerritoryType != aetheryteData.TerritoryIds[base.Task.To])
{
throw new TaskException($"Aethernet shortcut not unlocked (from: {base.Task.From}, to: {base.Task.To})");
}
logger.LogWarning("Aethernet shortcut not unlocked (from: {FromAetheryte}, to: {ToAetheryte}), skipping as we are already in the destination territory", base.Task.From, base.Task.To);
}
return false;
}
private void MoveTo()
{
logger.LogInformation("Moving to aethernet shortcut");
_moving = true;
EAetheryteLocation eAetheryteLocation = base.Task.From;
float num = (base.Task.From.IsFirmamentAetheryte() ? 4.4f : ((eAetheryteLocation != EAetheryteLocation.UldahChamberOfRule) ? ((!AetheryteConverter.IsLargeAetheryte(base.Task.From)) ? 6.9f : 10.9f) : 5f));
float value = num;
bool flag = aetheryteData.IsGoldSaucerAetheryte(base.Task.From) && !AetheryteConverter.IsLargeAetheryte(base.Task.From);
movementController.NavigateTo(EMovementType.Quest, (uint)base.Task.From, aetheryteData.Locations[base.Task.From], fly: false, sprint: true, value, flag ? new float?(5f) : ((float?)null));
}
private void DoTeleport()
{
logger.LogInformation("Using lifestream to teleport to {Destination}", base.Task.To);
lifestreamIpc.Teleport(base.Task.To);
_teleported = true;
}
public override ETaskResult Update()
{
if (DateTime.Now < _continueAt)
{
return ETaskResult.StillRunning;
}
if (_triedMounting)
{
if (condition[ConditionFlag.Mounted])
{
_triedMounting = false;
MoveTo();
return ETaskResult.StillRunning;
}
return ETaskResult.StillRunning;
}
if (_moving)
{
DateTime movementStartedAt = movementController.MovementStartedAt;
if (movementStartedAt == DateTime.MaxValue || movementStartedAt.AddSeconds(2.0) >= DateTime.Now)
{
return ETaskResult.StillRunning;
}
if (!movementController.IsPathfinding && !movementController.IsPathRunning)
{
_moving = false;
}
return ETaskResult.StillRunning;
}
if (!_teleported)
{
DoTeleport();
return ETaskResult.StillRunning;
}
Vector3? vector = clientState.LocalPlayer?.Position;
if (!vector.HasValue)
{
return ETaskResult.StillRunning;
}
if (aetheryteData.IsAirshipLanding(base.Task.To))
{
if (aetheryteData.CalculateAirshipLandingDistance(vector.Value, clientState.TerritoryType, base.Task.To) > 5f)
{
return ETaskResult.StillRunning;
}
}
else if (aetheryteData.IsCityAetheryte(base.Task.To) || aetheryteData.IsGoldSaucerAetheryte(base.Task.To))
{
if (aetheryteData.CalculateDistance(vector.Value, clientState.TerritoryType, base.Task.To) > 20f)
{
return ETaskResult.StillRunning;
}
}
else if (clientState.TerritoryType != aetheryteData.TerritoryIds[base.Task.To])
{
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}
public override bool ShouldInterruptOnDamage()
{
return true;
}
}
}

View file

@ -0,0 +1,261 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Movement;
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.Shared;
internal static class AetheryteShortcut
{
internal sealed class Factory(AetheryteData aetheryteData, TerritoryData territoryData, IClientState clientState) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (!step.AetheryteShortcut.HasValue)
{
yield break;
}
yield return new Task(step, quest.Id, step.AetheryteShortcut.Value, aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]);
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(0.5));
if (MoveAwayFromAetheryteExecutor.AppliesTo(step.AetheryteShortcut.Value) && step.AethernetShortcut?.From != step.AetheryteShortcut.Value)
{
yield return new WaitCondition.Task(() => clientState.TerritoryType == aetheryteData.TerritoryIds[step.AetheryteShortcut.Value], "Wait(territory: " + territoryData.GetNameAndId(aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]) + ")");
yield return new MoveAwayFromAetheryte(step.AetheryteShortcut.Value);
}
}
}
internal sealed record Task(QuestStep? Step, ElementId? ElementId, EAetheryteLocation TargetAetheryte, ushort ExpectedTerritoryId) : ISkippableTask, ITask
{
public override string ToString()
{
return $"UseAetheryte({TargetAetheryte})";
}
}
internal sealed class UseAetheryteShortcut(ILogger<UseAetheryteShortcut> logger, AetheryteFunctions aetheryteFunctions, QuestFunctions questFunctions, IClientState clientState, IChatGui chatGui, ICondition condition, AetheryteData aetheryteData, ExtraConditionUtils extraConditionUtils) : TaskExecutor<Task>()
{
private bool _teleported;
private DateTime _continueAt;
protected override bool Start()
{
return !ShouldSkipTeleport();
}
public override ETaskResult Update()
{
if (DateTime.Now < _continueAt)
{
return ETaskResult.StillRunning;
}
if (!_teleported)
{
_teleported = DoTeleport();
return ETaskResult.StillRunning;
}
if (clientState.TerritoryType == base.Task.ExpectedTerritoryId)
{
return ETaskResult.TaskComplete;
}
return ETaskResult.StillRunning;
}
private bool ShouldSkipTeleport()
{
ushort territoryType = clientState.TerritoryType;
if (base.Task.Step != null)
{
SkipAetheryteCondition skipAetheryteCondition = base.Task.Step.SkipConditions?.AetheryteShortcutIf ?? new SkipAetheryteCondition();
if (skipAetheryteCondition != null && !skipAetheryteCondition.Never)
{
if (skipAetheryteCondition.InTerritory.Contains(territoryType))
{
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (InTerritory)");
return true;
}
if (skipAetheryteCondition.QuestsCompleted.Count > 0 && skipAetheryteCondition.QuestsCompleted.All(questFunctions.IsQuestComplete))
{
logger.LogInformation("Skipping aetheryte, all prequisite quests are complete");
return true;
}
if (skipAetheryteCondition.QuestsAccepted.Count > 0 && skipAetheryteCondition.QuestsAccepted.All(questFunctions.IsQuestAccepted))
{
logger.LogInformation("Skipping aetheryte, all prequisite quests are accepted");
return true;
}
if (skipAetheryteCondition.AetheryteLocked.HasValue && !aetheryteFunctions.IsAetheryteUnlocked(skipAetheryteCondition.AetheryteLocked.Value))
{
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (AetheryteLocked)");
return true;
}
if (skipAetheryteCondition.AetheryteUnlocked.HasValue && aetheryteFunctions.IsAetheryteUnlocked(skipAetheryteCondition.AetheryteUnlocked.Value))
{
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (AetheryteUnlocked)");
return true;
}
if (base.Task.ElementId != null)
{
QuestProgressInfo questProgressInfo = questFunctions.GetQuestProgressInfo(base.Task.ElementId);
if (skipAetheryteCondition.RequiredQuestVariablesNotMet && questProgressInfo != null && !QuestWorkUtils.MatchesRequiredQuestWorkConfig(base.Task.Step.RequiredQuestVariables, questProgressInfo, logger))
{
logger.LogInformation("Skipping aetheryte teleport, as required variables do not match");
return true;
}
}
NearPositionCondition nearPosition = skipAetheryteCondition.NearPosition;
if (nearPosition != null && clientState.TerritoryType == nearPosition.TerritoryId && Vector3.Distance(nearPosition.Position, clientState.LocalPlayer.Position) <= nearPosition.MaximumDistance)
{
logger.LogInformation("Skipping aetheryte shortcut, as we're near the position");
return true;
}
NearPositionCondition notNearPosition = skipAetheryteCondition.NotNearPosition;
if (notNearPosition != null && clientState.TerritoryType == notNearPosition.TerritoryId && notNearPosition.MaximumDistance <= Vector3.Distance(notNearPosition.Position, clientState.LocalPlayer.Position))
{
logger.LogInformation("Skipping aetheryte shortcut, as we're not near the position");
return true;
}
if (skipAetheryteCondition.ExtraCondition.HasValue && skipAetheryteCondition.ExtraCondition != EExtraSkipCondition.None && extraConditionUtils.MatchesExtraCondition(skipAetheryteCondition.ExtraCondition.Value))
{
logger.LogInformation("Skipping step, extra condition {} matches", skipAetheryteCondition.ExtraCondition);
return true;
}
}
if (base.Task.ExpectedTerritoryId == territoryType && !skipAetheryteCondition.Never)
{
if (skipAetheryteCondition != null && skipAetheryteCondition.InSameTerritory)
{
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (InSameTerritory)");
return true;
}
Vector3 position = clientState.LocalPlayer.Position;
if (base.Task.Step.Position.HasValue && (position - base.Task.Step.Position.Value).Length() < base.Task.Step.CalculateActualStopDistance())
{
logger.LogInformation("Skipping aetheryte teleport, we're near the target");
return true;
}
if (aetheryteData.CalculateDistance(position, territoryType, base.Task.TargetAetheryte) < 20f || (base.Task.Step.AethernetShortcut != null && (aetheryteData.CalculateDistance(position, territoryType, base.Task.Step.AethernetShortcut.From) < 20f || aetheryteData.CalculateDistance(position, territoryType, base.Task.Step.AethernetShortcut.To) < 20f)))
{
logger.LogInformation("Skipping aetheryte teleport");
return true;
}
}
}
return false;
}
private bool DoTeleport()
{
if (!aetheryteFunctions.CanTeleport(base.Task.TargetAetheryte))
{
if (!aetheryteFunctions.IsTeleportUnlocked())
{
throw new TaskException("Teleport is not unlocked, attune to any aetheryte first.");
}
_continueAt = DateTime.Now.AddSeconds(1.0);
logger.LogTrace("Waiting for teleport cooldown...");
return false;
}
_continueAt = DateTime.Now.AddSeconds(8.0);
if (!aetheryteFunctions.IsAetheryteUnlocked(base.Task.TargetAetheryte))
{
chatGui.PrintError($"Aetheryte {base.Task.TargetAetheryte} is not unlocked.", "Questionable", 576);
throw new TaskException("Aetheryte is not unlocked");
}
base.ProgressContext = InteractionProgressContext.FromActionUseOrDefault(() => aetheryteFunctions.TeleportAetheryte(base.Task.TargetAetheryte));
if (base.ProgressContext != null)
{
logger.LogInformation("Travelling via aetheryte...");
return true;
}
chatGui.Print("Unable to teleport to aetheryte.", "Questionable", 576);
throw new TaskException("Unable to teleport to aetheryte");
}
public override bool WasInterrupted()
{
if (!condition[ConditionFlag.InCombat])
{
return base.WasInterrupted();
}
return true;
}
public override bool ShouldInterruptOnDamage()
{
return true;
}
}
internal sealed record MoveAwayFromAetheryte(EAetheryteLocation TargetAetheryte) : ITask
{
public override string ToString()
{
return $"MoveAway({TargetAetheryte})";
}
}
internal sealed class MoveAwayFromAetheryteExecutor(MoveExecutor moveExecutor, AetheryteData aetheryteData, IClientState clientState) : TaskExecutor<MoveAwayFromAetheryte>()
{
private static readonly Dictionary<EAetheryteLocation, List<Vector3>> AetherytesToMoveFrom;
public static bool AppliesTo(EAetheryteLocation location)
{
return AetherytesToMoveFrom.ContainsKey(location);
}
protected override bool Start()
{
Vector3 playerPosition = clientState.LocalPlayer.Position;
if (aetheryteData.CalculateDistance(playerPosition, clientState.TerritoryType, base.Task.TargetAetheryte) >= 20f)
{
return false;
}
Vector3 destination = AetherytesToMoveFrom[base.Task.TargetAetheryte].MinBy((Vector3 x) => Vector3.Distance(x, playerPosition));
MoveTask task = new MoveTask(aetheryteData.TerritoryIds[base.Task.TargetAetheryte], destination, false, 0.25f, null, DisableNavmesh: true, null, Fly: false, Land: false, IgnoreDistanceToObject: false, RestartNavigation: false);
return moveExecutor.Start(task);
}
public override ETaskResult Update()
{
return moveExecutor.Update();
}
public override bool ShouldInterruptOnDamage()
{
return true;
}
static MoveAwayFromAetheryteExecutor()
{
Dictionary<EAetheryteLocation, List<Vector3>> dictionary = new Dictionary<EAetheryteLocation, List<Vector3>>();
int num = 4;
List<Vector3> list = new List<Vector3>(num);
CollectionsMarshal.SetCount(list, num);
Span<Vector3> span = CollectionsMarshal.AsSpan(list);
int num2 = 0;
span[num2] = new Vector3(0f, 8.8f, 15.5f);
num2++;
span[num2] = new Vector3(0f, 8.8f, -15.5f);
num2++;
span[num2] = new Vector3(15.5f, 8.8f, 0f);
num2++;
span[num2] = new Vector3(-15.5f, 8.8f, 0f);
dictionary.Add(EAetheryteLocation.SolutionNine, list);
AetherytesToMoveFrom = dictionary;
}
}
}

View file

@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameData;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
using Questionable.External;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Shared;
internal static class Craft
{
internal sealed class Factory : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Questionable.Model.Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.Craft)
{
return Array.Empty<ITask>();
}
ArgumentNullException.ThrowIfNull(step.ItemId, "step.ItemId");
ArgumentNullException.ThrowIfNull(step.ItemCount, "step.ItemCount");
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2]
{
new Questionable.Controller.Steps.Common.Mount.UnmountTask(),
new CraftTask(step.ItemId.Value, step.ItemCount.Value)
});
}
}
internal sealed record CraftTask(uint ItemId, int ItemCount) : ITask
{
public override string ToString()
{
return $"Craft {ItemCount}x {ItemId} (with Artisan)";
}
}
internal sealed class DoCraft(IDataManager dataManager, IClientState clientState, ArtisanIpc artisanIpc, ILogger<DoCraft> logger) : TaskExecutor<CraftTask>()
{
protected override bool Start()
{
if (HasRequestedItems())
{
logger.LogInformation("Already own {ItemCount}x {ItemId}", base.Task.ItemCount, base.Task.ItemId);
return false;
}
RecipeLookup? rowOrDefault = dataManager.GetExcelSheet<RecipeLookup>().GetRowOrDefault(base.Task.ItemId);
if (!rowOrDefault.HasValue)
{
throw new TaskException($"Item {base.Task.ItemId} is not craftable");
}
uint num = (EClassJob)clientState.LocalPlayer.ClassJob.RowId switch
{
EClassJob.Carpenter => rowOrDefault.Value.CRP.RowId,
EClassJob.Blacksmith => rowOrDefault.Value.BSM.RowId,
EClassJob.Armorer => rowOrDefault.Value.ARM.RowId,
EClassJob.Goldsmith => rowOrDefault.Value.GSM.RowId,
EClassJob.Leatherworker => rowOrDefault.Value.LTW.RowId,
EClassJob.Weaver => rowOrDefault.Value.WVR.RowId,
EClassJob.Alchemist => rowOrDefault.Value.ALC.RowId,
EClassJob.Culinarian => rowOrDefault.Value.CUL.RowId,
_ => 0u,
};
if (num == 0)
{
num = new uint[8]
{
rowOrDefault.Value.CRP.RowId,
rowOrDefault.Value.BSM.RowId,
rowOrDefault.Value.ARM.RowId,
rowOrDefault.Value.GSM.RowId,
rowOrDefault.Value.LTW.RowId,
rowOrDefault.Value.WVR.RowId,
rowOrDefault.Value.ALC.RowId,
rowOrDefault.Value.WVR.RowId
}.FirstOrDefault((uint x) => x != 0);
}
if (num == 0)
{
throw new TaskException($"Unable to determine recipe for item {base.Task.ItemId}");
}
int num2 = base.Task.ItemCount - GetOwnedItemCount();
logger.LogInformation("Starting craft for item {ItemId} with recipe {RecipeId} for {RemainingItemCount} items", base.Task.ItemId, num, num2);
if (!artisanIpc.CraftItem((ushort)num, num2))
{
throw new TaskException($"Failed to start Artisan craft for recipe {num}");
}
return true;
}
public unsafe override ETaskResult Update()
{
if (HasRequestedItems() && !artisanIpc.IsCrafting())
{
AgentRecipeNote* ptr = AgentRecipeNote.Instance();
if (ptr != null && ptr->IsAgentActive())
{
uint addonId = ptr->GetAddonId();
if (addonId == 0)
{
return ETaskResult.StillRunning;
}
AtkUnitBase* addonById = AtkStage.Instance()->RaptureAtkUnitManager->GetAddonById((ushort)addonId);
if (addonById != null)
{
logger.LogInformation("Closing crafting window");
addonById->FireCallbackInt(-1);
return ETaskResult.TaskComplete;
}
}
}
return ETaskResult.StillRunning;
}
private bool HasRequestedItems()
{
return GetOwnedItemCount() >= base.Task.ItemCount;
}
private unsafe int GetOwnedItemCount()
{
InventoryManager* ptr = InventoryManager.Instance();
return ptr->GetInventoryItemCount(base.Task.ItemId, isHq: false, checkEquipped: false, checkArmory: true, 0) + ptr->GetInventoryItemCount(base.Task.ItemId, isHq: true, checkEquipped: false, checkArmory: true, 0);
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}

View file

@ -0,0 +1,40 @@
using System;
using System.Numerics;
using Dalamud.Plugin.Services;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Shared;
internal sealed class ExtraConditionUtils
{
private readonly IClientState _clientState;
public ExtraConditionUtils(IClientState clientState)
{
_clientState = clientState;
}
public bool MatchesExtraCondition(EExtraSkipCondition skipCondition)
{
Vector3? vector = _clientState.LocalPlayer?.Position;
if (vector.HasValue && _clientState.TerritoryType != 0)
{
return MatchesExtraCondition(skipCondition, vector.Value, _clientState.TerritoryType);
}
return false;
}
public static bool MatchesExtraCondition(EExtraSkipCondition skipCondition, Vector3 position, ushort territoryType)
{
return skipCondition switch
{
EExtraSkipCondition.WakingSandsMainArea => territoryType == 212 && position.X < 24f,
EExtraSkipCondition.WakingSandsSolar => territoryType == 212 && position.X >= 24f,
EExtraSkipCondition.RisingStonesSolar => territoryType == 351 && position.Z <= -28f,
EExtraSkipCondition.RoguesGuild => territoryType == 129 && position.Y <= -115f,
EExtraSkipCondition.NotRoguesGuild => territoryType == 129 && position.Y > -115f,
EExtraSkipCondition.DockStorehouse => territoryType == 137 && position.Y <= -20f,
_ => throw new ArgumentOutOfRangeException("skipCondition", skipCondition, null),
};
}
}

View file

@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using LLib.GameData;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Interactions;
using Questionable.Data;
using Questionable.Model;
using Questionable.Model.Gathering;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Shared;
internal static class Gather
{
internal sealed class Factory : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.Gather)
{
yield break;
}
foreach (GatheredItem item in step.ItemsToGather)
{
yield return new DelayedGatheringTask(item, quest, sequence.Sequence);
}
}
}
internal sealed record DelayedGatheringTask(GatheredItem GatheredItem, Quest Quest, byte Sequence) : ITask
{
public override string ToString()
{
return $"Gathering(pending for {GatheredItem.ItemId})";
}
}
internal sealed class DelayedGatheringExecutor(GatheringPointRegistry gatheringPointRegistry, TerritoryData territoryData, IClientState clientState, IServiceProvider serviceProvider, ILogger<DelayedGatheringExecutor> logger) : TaskExecutor<DelayedGatheringTask>(), IExtraTaskCreator, ITaskExecutor
{
protected override bool Start()
{
return true;
}
public override ETaskResult Update()
{
return ETaskResult.CreateNewTasks;
}
public IEnumerable<ITask> CreateExtraTasks()
{
EClassJob rowId = (EClassJob)clientState.LocalPlayer.ClassJob.RowId;
if (!gatheringPointRegistry.TryGetGatheringPointId(base.Task.GatheredItem.ItemId, rowId, out GatheringPointId gatheringPointId))
{
throw new TaskException($"No gathering point found for item {base.Task.GatheredItem.ItemId}");
}
if (!gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot gatheringRoot))
{
throw new TaskException($"No path found for gathering point {gatheringPointId}");
}
if (HasRequiredItems(base.Task.GatheredItem))
{
yield break;
}
switch (rowId)
{
case EClassJob.Miner:
yield return new Questionable.Controller.Steps.Interactions.Action.TriggerStatusIfMissing(EStatus.Prospect, EAction.Prospect);
break;
case EClassJob.Botanist:
yield return new Questionable.Controller.Steps.Interactions.Action.TriggerStatusIfMissing(EStatus.Triangulate, EAction.Triangulate);
break;
}
using (logger.BeginScope("Gathering(inner)"))
{
QuestSequence gatheringSequence = new QuestSequence
{
Sequence = 0,
Steps = gatheringRoot.Steps
};
foreach (QuestStep step in gatheringSequence.Steps)
{
foreach (ITask item in serviceProvider.GetRequiredService<TaskCreator>().CreateTasks(base.Task.Quest, base.Task.Sequence, gatheringSequence, step))
{
if (item is WaitAtEnd.NextStep)
{
yield return new SkipMarker();
}
else
{
yield return item;
}
}
}
}
ushort territoryId = gatheringRoot.Steps.Last().TerritoryId;
yield return new WaitCondition.Task(() => clientState.TerritoryType == territoryId, "Wait(territory: " + territoryData.GetNameAndId(territoryId) + ")");
yield return new WaitNavmesh.Task();
yield return new GatheringTask(gatheringPointId, base.Task.GatheredItem);
yield return new WaitAtEnd.WaitDelay();
}
private unsafe bool HasRequiredItems(GatheredItem itemToGather)
{
InventoryManager* ptr = InventoryManager.Instance();
if (ptr != null)
{
return ptr->GetInventoryItemCount(itemToGather.ItemId, isHq: false, checkEquipped: true, checkArmory: true, (short)itemToGather.Collectability) >= itemToGather.ItemCount;
}
return false;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed record GatheringTask(GatheringPointId gatheringPointId, GatheredItem gatheredItem) : ITask
{
public override string ToString()
{
if (gatheredItem.Collectability != 0)
{
return $"Gather({gatheredItem.ItemCount}x {gatheredItem.ItemId} {SeIconChar.Collectible.ToIconString()} {gatheredItem.Collectability})";
}
return $"Gather({gatheredItem.ItemCount}x {gatheredItem.ItemId})";
}
}
internal sealed class StartGathering(GatheringController gatheringController) : TaskExecutor<GatheringTask>(), IToastAware, ITaskExecutor
{
protected override bool Start()
{
return gatheringController.Start(new GatheringController.GatheringRequest(base.Task.gatheringPointId, base.Task.gatheredItem.ItemId, base.Task.gatheredItem.AlternativeItemId, base.Task.gatheredItem.ItemCount, base.Task.gatheredItem.Collectability));
}
public override ETaskResult Update()
{
if (gatheringController.Update() == GatheringController.EStatus.Complete)
{
return ETaskResult.TaskComplete;
}
return ETaskResult.StillRunning;
}
public bool OnErrorToast(SeString message)
{
bool isHandled = false;
gatheringController.OnErrorToast(ref message, ref isHandled);
return isHandled;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed class SkipMarker : ITask
{
public override string ToString()
{
return "Gather/SkipMarker";
}
}
internal sealed class DoSkip : TaskExecutor<SkipMarker>
{
protected override bool Start()
{
return true;
}
public override ETaskResult Update()
{
return ETaskResult.TaskComplete;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}

View file

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using Questionable.Data;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Shared;
internal static class RedeemRewardItems
{
internal sealed class Factory(QuestData questData) : ITaskFactory
{
public unsafe IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.AcceptQuest)
{
return Array.Empty<ITask>();
}
List<ITask> list = new List<ITask>();
InventoryManager* ptr = InventoryManager.Instance();
if (ptr == null)
{
return list;
}
foreach (ItemReward redeemableItem in questData.RedeemableItems)
{
if (ptr->GetInventoryItemCount(redeemableItem.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0 && !redeemableItem.IsUnlocked())
{
list.Add(new Task(redeemableItem));
}
}
return list;
}
}
internal sealed record Task(ItemReward ItemReward) : ITask
{
public override string ToString()
{
return "TryRedeem(" + ItemReward.Name + ")";
}
}
internal sealed class Executor(GameFunctions gameFunctions, ICondition condition) : TaskExecutor<Task>()
{
private static readonly TimeSpan MinimumCastTime = TimeSpan.FromSeconds(4L);
private DateTime _continueAt;
protected override bool Start()
{
if (condition[ConditionFlag.Mounted])
{
return false;
}
TimeSpan timeSpan = base.Task.ItemReward.CastTime;
if (timeSpan < MinimumCastTime)
{
timeSpan = MinimumCastTime;
}
_continueAt = DateTime.Now.Add(timeSpan).AddSeconds(3.0);
return gameFunctions.UseItem(base.Task.ItemReward.ItemId);
}
public override ETaskResult Update()
{
if (condition[ConditionFlag.Casting])
{
return ETaskResult.StillRunning;
}
if (!(DateTime.Now <= _continueAt))
{
return ETaskResult.TaskComplete;
}
return ETaskResult.StillRunning;
}
public override bool ShouldInterruptOnDamage()
{
return true;
}
}
}

View file

@ -0,0 +1,457 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
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 LLib.GameData;
using Microsoft.Extensions.Logging;
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.Shared;
internal static class SkipCondition
{
internal sealed class Factory(Configuration configuration) : SimpleTaskFactory()
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
SkipStepConditions skipStepConditions = step.SkipConditions?.StepIf;
if (skipStepConditions != null && skipStepConditions.Never)
{
return null;
}
if ((skipStepConditions == null || !skipStepConditions.HasSkipConditions()) && !QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags) && step.RequiredQuestVariables.Count == 0 && !step.TaxiStandId.HasValue && step.PickUpQuestId == null && step.NextQuestId == null && step.RequiredCurrentJob.Count == 0 && step.RequiredQuestAcceptedJob.Count == 0 && (step.InteractionType != EInteractionType.AttuneAetherCurrent || !configuration.Advanced.SkipAetherCurrents))
{
return null;
}
return new SkipTask(step, skipStepConditions ?? new SkipStepConditions(), quest.Id);
}
}
internal sealed record SkipTask(QuestStep Step, SkipStepConditions SkipConditions, ElementId ElementId) : ITask
{
public override string ToString()
{
return "CheckSkip";
}
}
internal sealed class CheckSkip(ILogger<CheckSkip> logger, Configuration configuration, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, QuestFunctions questFunctions, IClientState clientState, ICondition condition, ExtraConditionUtils extraConditionUtils, ClassJobUtils classJobUtils) : TaskExecutor<SkipTask>()
{
protected override bool Start()
{
SkipStepConditions skipConditions = base.Task.SkipConditions;
QuestStep step = base.Task.Step;
ElementId elementId = base.Task.ElementId;
logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", skipConditions));
if (CheckFlyingCondition(step, skipConditions))
{
return true;
}
if (CheckUnlockedMountCondition(skipConditions))
{
return true;
}
if (CheckDivingCondition(skipConditions))
{
return true;
}
if (CheckTerritoryCondition(skipConditions))
{
return true;
}
if (CheckQuestConditions(skipConditions))
{
return true;
}
if (CheckTargetableCondition(step, skipConditions))
{
return true;
}
if (CheckNameplateCondition(step, skipConditions))
{
return true;
}
if (CheckItemCondition(step, skipConditions))
{
return true;
}
if (CheckAetheryteCondition(step, skipConditions))
{
return true;
}
if (CheckAetherCurrentCondition(step))
{
return true;
}
if (CheckQuestWorkConditions(elementId, step))
{
return true;
}
if (CheckJobCondition(elementId, step))
{
return true;
}
if (CheckPositionCondition(skipConditions))
{
return true;
}
if (skipConditions.ExtraCondition.HasValue && skipConditions.ExtraCondition != EExtraSkipCondition.None && extraConditionUtils.MatchesExtraCondition(skipConditions.ExtraCondition.Value))
{
logger.LogInformation("Skipping step, extra condition {} matches", skipConditions.ExtraCondition);
return true;
}
if (CheckPickUpTurnInQuestIds(step))
{
return true;
}
if (CheckTaxiStandUnlocked(step))
{
return true;
}
return false;
}
private bool CheckFlyingCondition(QuestStep step, SkipStepConditions skipConditions)
{
if (skipConditions.Flying == ELockedSkipCondition.Unlocked && gameFunctions.IsFlyingUnlocked(step.TerritoryId))
{
logger.LogInformation("Skipping step, as flying is unlocked");
return true;
}
if (skipConditions.Flying == ELockedSkipCondition.Locked && !gameFunctions.IsFlyingUnlocked(step.TerritoryId))
{
logger.LogInformation("Skipping step, as flying is locked");
return true;
}
return false;
}
private unsafe bool CheckUnlockedMountCondition(SkipStepConditions skipConditions)
{
if (skipConditions.Chocobo == ELockedSkipCondition.Unlocked && PlayerState.Instance()->IsMountUnlocked(1u))
{
logger.LogInformation("Skipping step, as chocobo is unlocked");
return true;
}
return false;
}
private bool CheckTerritoryCondition(SkipStepConditions skipConditions)
{
if (skipConditions.InTerritory.Count > 0 && skipConditions.InTerritory.Contains(clientState.TerritoryType))
{
logger.LogInformation("Skipping step, as in a skip.InTerritory");
return true;
}
if (skipConditions.NotInTerritory.Count > 0 && !skipConditions.NotInTerritory.Contains(clientState.TerritoryType))
{
logger.LogInformation("Skipping step, as not in a skip.NotInTerritory");
return true;
}
return false;
}
private bool CheckDivingCondition(SkipStepConditions skipConditions)
{
if (skipConditions.Diving == true && condition[ConditionFlag.Diving])
{
logger.LogInformation("Skipping step, as you're currently diving underwater");
return true;
}
if (skipConditions.Diving == false && !condition[ConditionFlag.Diving])
{
logger.LogInformation("Skipping step, as you're not currently diving underwater");
return true;
}
return false;
}
private bool CheckQuestConditions(SkipStepConditions skipConditions)
{
if (skipConditions.QuestsCompleted.Count > 0 && skipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete))
{
logger.LogInformation("Skipping step, all prequisite quests are complete");
return true;
}
if (skipConditions.QuestsAccepted.Count > 0 && skipConditions.QuestsAccepted.All(questFunctions.IsQuestAccepted))
{
logger.LogInformation("Skipping step, all prequisite quests are accepted");
return true;
}
return false;
}
private bool CheckTargetableCondition(QuestStep step, SkipStepConditions skipConditions)
{
if (skipConditions.NotTargetable && step != null && step.DataId.HasValue)
{
IGameObject gameObject = gameFunctions.FindObjectByDataId(step.DataId.Value);
if (gameObject == null)
{
if ((step.Position.GetValueOrDefault() - clientState.LocalPlayer.Position).Length() < 100f)
{
logger.LogInformation("Skipping step, object is not nearby (but we are)");
return true;
}
}
else if (!gameObject.IsTargetable)
{
logger.LogInformation("Skipping step, object is not targetable");
return true;
}
}
return false;
}
private unsafe bool CheckNameplateCondition(QuestStep step, SkipStepConditions skipConditions)
{
if (skipConditions.NotNamePlateIconId.Count > 0 && step != null && step.DataId.HasValue)
{
IGameObject gameObject = gameFunctions.FindObjectByDataId(step.DataId.Value);
if (gameObject != null)
{
GameObject* address = (GameObject*)gameObject.Address;
if (!skipConditions.NotNamePlateIconId.Contains(address->NamePlateIconId))
{
logger.LogInformation("Skipping step, object has icon id {IconId}", address->NamePlateIconId);
return true;
}
}
}
return false;
}
private unsafe bool CheckItemCondition(QuestStep step, SkipStepConditions skipConditions)
{
SkipItemConditions item = skipConditions.Item;
if (item != null && item.NotInInventory && step != null && step.ItemId.HasValue)
{
InventoryManager* ptr = InventoryManager.Instance();
if (ptr->GetInventoryItemCount(step.ItemId.Value, isHq: false, checkEquipped: true, checkArmory: true, 0) == 0 && ptr->GetInventoryItemCount(step.ItemId.Value, isHq: true, checkEquipped: true, checkArmory: true, 0) == 0)
{
logger.LogInformation("Skipping step, no item with itemId {ItemId} in inventory", step.ItemId.Value);
return true;
}
}
return false;
}
private bool CheckAetheryteCondition(QuestStep step, SkipStepConditions skipConditions)
{
if (step != null)
{
EAetheryteLocation? aetheryte = step.Aetheryte;
if (aetheryte.HasValue)
{
EAetheryteLocation valueOrDefault = aetheryte.GetValueOrDefault();
if (step.InteractionType == EInteractionType.AttuneAetheryte && aetheryteFunctions.IsAetheryteUnlocked(valueOrDefault))
{
logger.LogInformation("Skipping step, as aetheryte is unlocked");
return true;
}
}
}
if (step != null)
{
EAetheryteLocation? aetheryte = step.Aetheryte;
if (aetheryte.HasValue)
{
EAetheryteLocation valueOrDefault2 = aetheryte.GetValueOrDefault();
if (step.InteractionType == EInteractionType.AttuneAethernetShard && aetheryteFunctions.IsAetheryteUnlocked(valueOrDefault2))
{
logger.LogInformation("Skipping step, as aethernet shard is unlocked");
return true;
}
}
}
if (step != null)
{
EAetheryteLocation? aetheryte = step.Aetheryte;
if (aetheryte.HasValue)
{
EAetheryteLocation valueOrDefault3 = aetheryte.GetValueOrDefault();
if (step.InteractionType == EInteractionType.RegisterFreeOrFavoredAetheryte && aetheryteFunctions.CanRegisterFreeOrFavoriteAetheryte(valueOrDefault3) == AetheryteRegistrationResult.NotPossible)
{
logger.LogInformation("Skipping step, already registered all possible free or favored aetherytes");
return true;
}
}
}
if (skipConditions.AetheryteLocked.HasValue && !aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteLocked.Value))
{
logger.LogInformation("Skipping step, as aetheryte is locked");
return true;
}
if (skipConditions.AetheryteUnlocked.HasValue && aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteUnlocked.Value))
{
logger.LogInformation("Skipping step, as aetheryte is unlocked");
return true;
}
return false;
}
private bool CheckAetherCurrentCondition(QuestStep step)
{
if (step != null && step.DataId.HasValue && step.InteractionType == EInteractionType.AttuneAetherCurrent && gameFunctions.IsAetherCurrentUnlocked(step.DataId.Value))
{
logger.LogInformation("Skipping step, as current is unlocked");
return true;
}
if (step != null && step.InteractionType == EInteractionType.AttuneAetherCurrent && configuration.Advanced.SkipAetherCurrents)
{
logger.LogInformation("Skipping step, as aether currents should be skipped");
return true;
}
return false;
}
private bool CheckQuestWorkConditions(ElementId elementId, QuestStep step)
{
QuestProgressInfo questProgressInfo = questFunctions.GetQuestProgressInfo(elementId);
if (questProgressInfo != null)
{
if (QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags) && QuestWorkUtils.MatchesQuestWork(step.CompletionQuestVariablesFlags, questProgressInfo))
{
logger.LogInformation("Skipping step, as quest variables match (step is complete)");
return true;
}
if (step != null)
{
SkipConditions skipConditions = step.SkipConditions;
if (skipConditions != null)
{
SkipStepConditions stepIf = skipConditions.StepIf;
if (stepIf != null && QuestWorkUtils.MatchesQuestWork(stepIf.CompletionQuestVariablesFlags, questProgressInfo))
{
logger.LogInformation("Skipping step, as quest variables match (step can be skipped)");
return true;
}
}
}
if (step != null)
{
List<List<QuestWorkValue>> requiredQuestVariables = step.RequiredQuestVariables;
if (requiredQuestVariables != null && !QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questProgressInfo, logger))
{
logger.LogInformation("Skipping step, as required variables do not match");
return true;
}
}
if (step != null)
{
List<EExtendedClassJob> requiredQuestAcceptedJob = step.RequiredQuestAcceptedJob;
if (requiredQuestAcceptedJob != null && requiredQuestAcceptedJob.Count > 0)
{
List<EClassJob> list = step.RequiredQuestAcceptedJob.SelectMany((EExtendedClassJob x) => classJobUtils.AsIndividualJobs(x, elementId)).ToList();
EClassJob classJob = questProgressInfo.ClassJob;
logger.LogInformation("Checking quest job {QuestJob} against {ExpectedJobs}", classJob, string.Join(",", list));
if (classJob != EClassJob.Adventurer && !list.Contains(classJob))
{
logger.LogInformation("Skipping step, as quest was accepted on a different job");
return true;
}
}
}
}
return false;
}
private bool CheckJobCondition(ElementId elementId, QuestStep step)
{
if (step != null)
{
List<EExtendedClassJob> requiredCurrentJob = step.RequiredCurrentJob;
if (requiredCurrentJob != null && requiredCurrentJob.Count > 0)
{
List<EClassJob> list = step.RequiredCurrentJob.SelectMany((EExtendedClassJob x) => classJobUtils.AsIndividualJobs(x, elementId)).ToList();
EClassJob rowId = (EClassJob)clientState.LocalPlayer.ClassJob.RowId;
logger.LogInformation("Checking current job {CurrentJob} against {ExpectedJobs}", rowId, string.Join(",", list));
if (!list.Contains(rowId))
{
logger.LogInformation("Skipping step, as step requires a different job");
return true;
}
}
}
return false;
}
private bool CheckPositionCondition(SkipStepConditions skipConditions)
{
NearPositionCondition nearPosition = skipConditions.NearPosition;
if (nearPosition != null && clientState.TerritoryType == nearPosition.TerritoryId && Vector3.Distance(nearPosition.Position, clientState.LocalPlayer.Position) <= nearPosition.MaximumDistance)
{
logger.LogInformation("Skipping step, as we're near the position");
return true;
}
NearPositionCondition notNearPosition = skipConditions.NotNearPosition;
if (notNearPosition != null && clientState.TerritoryType == notNearPosition.TerritoryId && notNearPosition.MaximumDistance <= Vector3.Distance(notNearPosition.Position, clientState.LocalPlayer.Position))
{
logger.LogInformation("Skipping step, as we're not near the position");
return true;
}
return false;
}
private bool CheckPickUpTurnInQuestIds(QuestStep step)
{
if (step.PickUpQuestId != null && questFunctions.IsQuestAcceptedOrComplete(step.PickUpQuestId))
{
logger.LogInformation("Skipping step, as we have already picked up the relevant quest");
return true;
}
if (step.TurnInQuestId != null && questFunctions.IsQuestComplete(step.TurnInQuestId))
{
logger.LogInformation("Skipping step, as we have already completed the relevant quest");
return true;
}
if (step.PickUpQuestId != null && configuration.Advanced.SkipAetherCurrents && QuestData.AetherCurrentQuests.Contains(step.PickUpQuestId))
{
logger.LogInformation("Skipping step, as aether current quests should be skipped");
return true;
}
if (step.PickUpQuestId != null && configuration.Advanced.SkipARealmRebornHardModePrimals && QuestData.HardModePrimals.Contains(step.PickUpQuestId))
{
logger.LogInformation("Skipping step, as hard mode primal quests should be skipped");
return true;
}
return false;
}
private unsafe bool CheckTaxiStandUnlocked(QuestStep step)
{
UIState* ptr = UIState.Instance();
byte? taxiStandId = step.TaxiStandId;
if (taxiStandId.HasValue)
{
byte valueOrDefault = taxiStandId.GetValueOrDefault();
if (ptr->IsChocoboTaxiStandUnlocked(valueOrDefault))
{
logger.LogInformation("Skipping step, as taxi stand {TaxiStandId} is unlocked", valueOrDefault);
return true;
}
}
return false;
}
public override ETaskResult Update()
{
return ETaskResult.SkipRemainingTasksForStep;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}

View file

@ -0,0 +1,47 @@
using Microsoft.Extensions.Logging;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Shared;
internal static class StepDisabled
{
internal sealed class Factory : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (!step.Disabled)
{
return null;
}
return new SkipRemainingTasks();
}
}
internal sealed class SkipRemainingTasks : ITask
{
public override string ToString()
{
return "StepDisabled";
}
}
internal sealed class SkipDisabledStepsExecutor(ILogger<SkipRemainingTasks> logger) : TaskExecutor<SkipRemainingTasks>()
{
protected override bool Start()
{
return true;
}
public override ETaskResult Update()
{
logger.LogInformation("Skipping step, as it is disabled");
return ETaskResult.SkipRemainingTasksForStep;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}

View file

@ -0,0 +1,68 @@
using System.Linq;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using LLib.GameData;
using Questionable.Controller.Steps.Common;
using Questionable.Data;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Shared;
internal static class SwitchClassJob
{
internal sealed class Factory(ClassJobUtils classJobUtils) : SimpleTaskFactory()
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.SwitchClass)
{
return null;
}
return new Task(classJobUtils.AsIndividualJobs(step.TargetClass, quest.Id).Single());
}
}
internal sealed record Task(EClassJob ClassJob) : ITask
{
public override string ToString()
{
return $"SwitchJob({ClassJob})";
}
}
internal sealed class SwitchClassJobExecutor(IClientState clientState) : AbstractDelayedTaskExecutor<Task>()
{
protected unsafe override bool StartInternal()
{
if (clientState.LocalPlayer.ClassJob.RowId == (uint)base.Task.ClassJob)
{
return false;
}
RaptureGearsetModule* ptr = RaptureGearsetModule.Instance();
if (ptr != null)
{
for (int i = 0; i < 100; i++)
{
RaptureGearsetModule.GearsetEntry* gearset = ptr->GetGearset(i);
if (gearset->ClassJob == (byte)base.Task.ClassJob)
{
ptr->EquipGearset(gearset->Id, 0);
return true;
}
}
}
throw new TaskException($"No gearset found for {base.Task.ClassJob}");
}
protected override ETaskResult UpdateInternal()
{
return ETaskResult.TaskComplete;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}

View file

@ -0,0 +1,405 @@
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 Questionable.Controller.Steps.Common;
using Questionable.Controller.Utils;
using Questionable.Data;
using Questionable.External;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Shared;
internal static class WaitAtEnd
{
internal sealed class Factory(IClientState clientState, ICondition condition, TerritoryData territoryData, AutoDutyIpc autoDutyIpc, BossModIpc bossModIpc) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.CompletionQuestVariablesFlags.Count == 6 && QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
{
WaitForCompletionFlags waitForCompletionFlags = new WaitForCompletionFlags((QuestId)quest.Id, step);
WaitDelay waitDelay = new WaitDelay();
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[3]
{
waitForCompletionFlags,
waitDelay,
Next(quest, sequence)
});
}
ITask task;
switch (step.InteractionType)
{
case EInteractionType.Combat:
{
if (step.EnemySpawnType == EEnemySpawnType.FinishCombatIfAny)
{
return new global::_003C_003Ez__ReadOnlySingleElementList<ITask>(Next(quest, sequence));
}
WaitCondition.Task task2 = new WaitCondition.Task(() => !condition[ConditionFlag.InCombat], "Wait(not in combat)");
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[4]
{
new WaitDelay(),
task2,
new WaitDelay(),
Next(quest, sequence)
});
}
case EInteractionType.WaitForManualProgress:
case EInteractionType.Snipe:
case EInteractionType.Instruction:
return new global::_003C_003Ez__ReadOnlySingleElementList<ITask>(new WaitNextStepOrSequence());
case EInteractionType.Duty:
if (autoDutyIpc.IsConfiguredToRunContent(step.DutyOptions))
{
break;
}
goto IL_019d;
case EInteractionType.SinglePlayerDuty:
if (bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyOptions))
{
break;
}
goto IL_019d;
case EInteractionType.WalkTo:
case EInteractionType.Jump:
return new global::_003C_003Ez__ReadOnlySingleElementList<ITask>(Next(quest, sequence));
case EInteractionType.WaitForObjectAtPosition:
ArgumentNullException.ThrowIfNull(step.DataId, "step.DataId");
ArgumentNullException.ThrowIfNull(step.Position, "step.Position");
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[3]
{
new WaitObjectAtPosition(step.DataId.Value, step.Position.Value, step.NpcWaitDistance ?? 0.5f),
new WaitDelay(),
Next(quest, sequence)
});
case EInteractionType.Interact:
if (!step.TargetTerritoryId.HasValue)
{
break;
}
goto IL_0284;
case EInteractionType.UseItem:
if (!step.TargetTerritoryId.HasValue)
{
break;
}
goto IL_0284;
case EInteractionType.AcceptQuest:
{
WaitQuestAccepted waitQuestAccepted = new WaitQuestAccepted(step.PickUpQuestId ?? quest.Id);
WaitDelay waitDelay3 = new WaitDelay();
if (step.PickUpQuestId != null)
{
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[3]
{
waitQuestAccepted,
waitDelay3,
Next(quest, sequence)
});
}
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2] { waitQuestAccepted, waitDelay3 });
}
case EInteractionType.CompleteQuest:
{
WaitQuestCompleted waitQuestCompleted = new WaitQuestCompleted(step.TurnInQuestId ?? quest.Id);
WaitDelay waitDelay2 = new WaitDelay();
if (step.TurnInQuestId != null)
{
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[3]
{
waitQuestCompleted,
waitDelay2,
Next(quest, sequence)
});
}
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2] { waitQuestCompleted, waitDelay2 });
}
IL_019d:
return new global::_003C_003Ez__ReadOnlySingleElementList<ITask>(new EndAutomation());
IL_0284:
if (step.TerritoryId != step.TargetTerritoryId)
{
task = new WaitCondition.Task(() => clientState.TerritoryType == step.TargetTerritoryId, "Wait(tp to territory: " + territoryData.GetNameAndId(step.TargetTerritoryId.Value) + ")");
}
else
{
Vector3 lastPosition = step.Position ?? clientState.LocalPlayer?.Position ?? Vector3.Zero;
task = new WaitCondition.Task(delegate
{
Vector3? vector = clientState.LocalPlayer?.Position;
return vector.HasValue && (lastPosition - vector.Value).Length() > 2f;
}, "Wait(tp away from " + lastPosition.ToString("G", CultureInfo.InvariantCulture) + ")");
}
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[3]
{
task,
new WaitDelay(),
Next(quest, sequence)
});
}
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2]
{
new WaitDelay(),
Next(quest, sequence)
});
}
private static NextStep Next(Quest quest, QuestSequence sequence)
{
return new NextStep(quest.Id, sequence.Sequence);
}
}
internal sealed record WaitDelay(TimeSpan Delay) : ITask
{
public WaitDelay()
: this(TimeSpan.FromSeconds(1L))
{
}
public bool ShouldRedoOnInterrupt()
{
return true;
}
public override string ToString()
{
return $"Wait(seconds: {Delay.TotalSeconds})";
}
}
internal sealed class WaitDelayExecutor : AbstractDelayedTaskExecutor<WaitDelay>
{
protected override bool StartInternal()
{
base.Delay = base.Task.Delay;
return true;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed class WaitNextStepOrSequence : ITask
{
public override string ToString()
{
return "Wait(next step or sequence)";
}
}
internal sealed class WaitNextStepOrSequenceExecutor : TaskExecutor<WaitNextStepOrSequence>
{
protected override bool Start()
{
return true;
}
public override ETaskResult Update()
{
return ETaskResult.StillRunning;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed record WaitForCompletionFlags(QuestId Quest, QuestStep Step) : ITask
{
public override string ToString()
{
return "Wait(QW: " + string.Join(", ", Step.CompletionQuestVariablesFlags.Select((QuestWorkValue x) => x?.ToString() ?? "-")) + ")";
}
}
internal sealed class WaitForCompletionFlagsExecutor(QuestFunctions questFunctions) : TaskExecutor<WaitForCompletionFlags>()
{
protected override bool Start()
{
return true;
}
public override ETaskResult Update()
{
QuestProgressInfo questProgressInfo = questFunctions.GetQuestProgressInfo(base.Task.Quest);
if (questProgressInfo == null || !QuestWorkUtils.MatchesQuestWork(base.Task.Step.CompletionQuestVariablesFlags, questProgressInfo))
{
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed record WaitObjectAtPosition(uint DataId, Vector3 Destination, float Distance) : ITask
{
public override string ToString()
{
return $"WaitObj({DataId} at {Destination.ToString("G", CultureInfo.InvariantCulture)} < {Distance})";
}
}
internal sealed class WaitObjectAtPositionExecutor(GameFunctions gameFunctions) : TaskExecutor<WaitObjectAtPosition>()
{
protected override bool Start()
{
return true;
}
public override ETaskResult Update()
{
if (!gameFunctions.IsObjectAtPosition(base.Task.DataId, base.Task.Destination, base.Task.Distance))
{
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed record WaitQuestAccepted(ElementId ElementId) : ITask
{
public override string ToString()
{
return $"WaitQuestAccepted({ElementId})";
}
}
internal sealed class WaitQuestAcceptedExecutor(QuestFunctions questFunctions) : TaskExecutor<WaitQuestAccepted>()
{
protected override bool Start()
{
return true;
}
public override ETaskResult Update()
{
if (!questFunctions.IsQuestAccepted(base.Task.ElementId))
{
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed record WaitQuestCompleted(ElementId ElementId) : ITask
{
public override string ToString()
{
return $"WaitQuestComplete({ElementId})";
}
}
internal sealed class WaitQuestCompletedExecutor(QuestFunctions questFunctions) : TaskExecutor<WaitQuestCompleted>()
{
protected override bool Start()
{
return true;
}
public override ETaskResult Update()
{
if (!questFunctions.IsQuestComplete(base.Task.ElementId))
{
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed record NextStep(ElementId ElementId, int Sequence) : ILastTask, ITask
{
public override string ToString()
{
return "NextStep";
}
}
internal sealed class NextStepExecutor : TaskExecutor<NextStep>
{
protected override bool Start()
{
return true;
}
public override ETaskResult Update()
{
return ETaskResult.NextStep;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed class EndAutomation : ILastTask, ITask
{
public ElementId ElementId
{
get
{
throw new InvalidOperationException();
}
}
public int Sequence
{
get
{
throw new InvalidOperationException();
}
}
public override string ToString()
{
return "EndAutomation";
}
}
internal sealed class EndAutomationExecutor : TaskExecutor<EndAutomation>
{
protected override bool Start()
{
return true;
}
public override ETaskResult Update()
{
return ETaskResult.End;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}

View file

@ -0,0 +1,43 @@
using System;
using Questionable.Controller.Steps.Common;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Shared;
internal static class WaitAtStart
{
internal sealed class Factory : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (!step.DelaySecondsAtStart.HasValue)
{
return null;
}
return new WaitDelay(TimeSpan.FromSeconds(step.DelaySecondsAtStart.Value));
}
}
internal sealed record WaitDelay(TimeSpan Delay) : ITask
{
public override string ToString()
{
return $"Wait[S](seconds: {Delay.TotalSeconds})";
}
}
internal sealed class WaitDelayExecutor : AbstractDelayedTaskExecutor<WaitDelay>
{
protected override bool StartInternal()
{
base.Delay = base.Task.Delay;
return true;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}