_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "TelepotTown", TeleportTownPostSetup);
+
+ unsafe
+ {
+ if (_gameGui.TryGetAddonByName("RhythmAction", out AtkUnitBase* addon))
+ {
+ addon->Close(true);
+ }
+ }
}
private bool ShouldHandleUiInteractions => _isInitialCheck || _questController.IsRunning;
{
if (ShouldHandleUiInteractions &&
_questController.HasCurrentTaskMatching(out AethernetShortcut.UseAethernetShortcut? aethernetShortcut) &&
- EAetheryteLocationExtensions.IsFirmamentAetheryte(aethernetShortcut.From))
+ aethernetShortcut.From.IsFirmamentAetheryte())
{
// this might be better via atkvalues; but this works for now
uint toIndex = aethernetShortcut.To switch
using Questionable.GatheringPaths;
using Questionable.Model.Gathering;
using Questionable.Model.Questing;
+using Mount = Questionable.Controller.Steps.Common.Mount;
namespace Questionable.Controller;
internal sealed unsafe class GatheringController : MiniTaskController<GatheringController>
{
private readonly MovementController _movementController;
+ private readonly MoveTo.Factory _moveFactory;
+ private readonly Mount.Factory _mountFactory;
+ private readonly Interact.Factory _interactFactory;
private readonly GatheringPointRegistry _gatheringPointRegistry;
private readonly GameFunctions _gameFunctions;
private readonly NavmeshIpc _navmeshIpc;
private readonly IObjectTable _objectTable;
private readonly IServiceProvider _serviceProvider;
private readonly ICondition _condition;
+ private readonly ILoggerFactory _loggerFactory;
+ private readonly IGameGui _gameGui;
+ private readonly IClientState _clientState;
private readonly Regex _revisitRegex;
private CurrentRequest? _currentRequest;
public GatheringController(
MovementController movementController,
+ MoveTo.Factory moveFactory,
+ Mount.Factory mountFactory,
+ Interact.Factory interactFactory,
GatheringPointRegistry gatheringPointRegistry,
GameFunctions gameFunctions,
NavmeshIpc navmeshIpc,
IServiceProvider serviceProvider,
ICondition condition,
IDataManager dataManager,
+ ILoggerFactory loggerFactory,
+ IGameGui gameGui,
+ IClientState clientState,
IPluginLog pluginLog)
: base(chatGui, logger)
{
_movementController = movementController;
+ _moveFactory = moveFactory;
+ _mountFactory = mountFactory;
+ _interactFactory = interactFactory;
_gatheringPointRegistry = gatheringPointRegistry;
_gameFunctions = gameFunctions;
_navmeshIpc = navmeshIpc;
_objectTable = objectTable;
_serviceProvider = serviceProvider;
_condition = condition;
+ _loggerFactory = loggerFactory;
+ _gameGui = gameGui;
+ _clientState = clientState;
_revisitRegex = dataManager.GetRegex<LogMessage>(5574, x => x.Text, pluginLog)
?? throw new InvalidDataException("No regex found for revisit message");
return;
ushort territoryId = _currentRequest.Root.Steps.Last().TerritoryId;
- _taskQueue.Enqueue(_serviceProvider.GetRequiredService<MountTask>()
- .With(territoryId, MountTask.EMountIf.Always));
+ _taskQueue.Enqueue(_mountFactory.Mount(territoryId, Mount.EMountIf.Always));
bool fly = currentNode.Fly.GetValueOrDefault(_currentRequest.Root.FlyBetweenNodes.GetValueOrDefault(true)) &&
_gameFunctions.IsFlyingUnlocked(territoryId);
if (pointOnFloor != null)
pointOnFloor = pointOnFloor.Value with { Y = pointOnFloor.Value.Y + (fly ? 3f : 0f) };
- _taskQueue.Enqueue(_serviceProvider.GetRequiredService<Move.MoveInternal>()
- .With(territoryId, pointOnFloor ?? averagePosition, 50f, fly: fly,
- ignoreDistanceToObject: true));
+ _taskQueue.Enqueue(_moveFactory.Move(new MoveTo.MoveParams(territoryId, pointOnFloor ?? averagePosition,
+ 50f,
+ Fly: fly, IgnoreDistanceToObject: true)));
}
- _taskQueue.Enqueue(_serviceProvider.GetRequiredService<MoveToLandingLocation>()
- .With(territoryId, fly, currentNode));
- _taskQueue.Enqueue(_serviceProvider.GetRequiredService<Interact.DoInteract>()
- .With(currentNode.DataId, null, EInteractionType.InternalGather, true));
+ _taskQueue.Enqueue(new MoveToLandingLocation(territoryId, fly, currentNode, _moveFactory, _gameFunctions,
+ _objectTable, _loggerFactory.CreateLogger<MoveToLandingLocation>()));
+ _taskQueue.Enqueue(_interactFactory.Interact(currentNode.DataId, null, EInteractionType.InternalGather, true));
+ QueueGatherNode(currentNode);
+ }
+
+ private void QueueGatherNode(GatheringNode currentNode)
+ {
foreach (bool revisitRequired in new[] { false, true })
{
- _taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGather>()
- .With(_currentRequest.Data, currentNode, revisitRequired));
+ _taskQueue.Enqueue(new DoGather(_currentRequest!.Data, currentNode, revisitRequired, this, _gameFunctions,
+ _gameGui, _clientState, _condition, _loggerFactory.CreateLogger<DoGather>()));
if (_currentRequest.Data.Collectability > 0)
{
- _taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGatherCollectable>()
- .With(_currentRequest.Data, currentNode, revisitRequired));
+ _taskQueue.Enqueue(new DoGatherCollectable(_currentRequest.Data, currentNode, revisitRequired, this,
+ _gameFunctions, _clientState, _gameGui, _loggerFactory.CreateLogger<DoGatherCollectable>()));
}
}
}
--- /dev/null
+using System;
+using Dalamud.Game.ClientState.Conditions;
+using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using FFXIVClientStructs.FFXIV.Common.Math;
+using Microsoft.Extensions.Logging;
+using Questionable.Data;
+using Questionable.Functions;
+
+namespace Questionable.Controller.Steps.Common;
+
+internal static class Mount
+{
+ internal sealed class Factory(
+ GameFunctions gameFunctions,
+ ICondition condition,
+ TerritoryData territoryData,
+ IClientState clientState,
+ ILoggerFactory loggerFactory)
+ {
+ public ITask Mount(ushort territoryId, EMountIf mountIf, Vector3? position = null)
+ {
+ if (mountIf == EMountIf.AwayFromPosition)
+ ArgumentNullException.ThrowIfNull(position);
+
+ return new MountTask(territoryId, mountIf, position, gameFunctions, condition, territoryData, clientState,
+ loggerFactory.CreateLogger<MountTask>());
+ }
+
+ public ITask Unmount()
+ {
+ return new UnmountTask(condition, loggerFactory.CreateLogger<UnmountTask>(), gameFunctions);
+ }
+ }
+
+ private sealed class MountTask(
+ ushort territoryId,
+ EMountIf mountIf,
+ Vector3? position,
+ GameFunctions gameFunctions,
+ ICondition condition,
+ TerritoryData territoryData,
+ IClientState clientState,
+ ILogger<MountTask> logger) : ITask
+ {
+ private bool _mountTriggered;
+ private DateTime _retryAt = DateTime.MinValue;
+
+ public bool Start()
+ {
+ if (condition[ConditionFlag.Mounted])
+ return false;
+
+ if (!territoryData.CanUseMount(territoryId))
+ {
+ logger.LogInformation("Can't use mount in current territory {Id}", territoryId);
+ return false;
+ }
+
+ if (gameFunctions.HasStatusPreventingMount())
+ {
+ logger.LogInformation("Can't mount due to status preventing sprint or mount");
+ return false;
+ }
+
+ if (mountIf == EMountIf.AwayFromPosition)
+ {
+ Vector3 playerPosition = clientState.LocalPlayer?.Position ?? Vector3.Zero;
+ float distance = System.Numerics.Vector3.Distance(playerPosition, position.GetValueOrDefault());
+ if (territoryId == clientState.TerritoryType && distance < 30f && !Conditions.IsDiving)
+ {
+ logger.LogInformation("Not using mount, as we're close to the target");
+ return false;
+ }
+
+ logger.LogInformation(
+ "Want to use mount if away from destination ({Distance} yalms), trying (in territory {Id})...",
+ distance, territoryId);
+ }
+ else
+ logger.LogInformation("Want to use mount, trying (in territory {Id})...", territoryId);
+
+ if (!condition[ConditionFlag.InCombat])
+ {
+ _retryAt = DateTime.Now.AddSeconds(0.5);
+ return true;
+ }
+
+ return false;
+ }
+
+ public ETaskResult Update()
+ {
+ if (_mountTriggered && !condition[ConditionFlag.Mounted] && DateTime.Now > _retryAt)
+ {
+ logger.LogInformation("Not mounted, retrying...");
+ _mountTriggered = false;
+ _retryAt = DateTime.MaxValue;
+ }
+
+ if (!_mountTriggered)
+ {
+ if (gameFunctions.HasStatusPreventingMount())
+ {
+ logger.LogInformation("Can't mount due to status preventing sprint or mount");
+ return ETaskResult.TaskComplete;
+ }
+
+ _mountTriggered = gameFunctions.Mount();
+ _retryAt = DateTime.Now.AddSeconds(5);
+ return ETaskResult.StillRunning;
+ }
+
+ return condition[ConditionFlag.Mounted]
+ ? ETaskResult.TaskComplete
+ : ETaskResult.StillRunning;
+ }
+
+ public override string ToString() => "Mount";
+ }
+
+ private sealed class UnmountTask(ICondition condition, ILogger<UnmountTask> logger, GameFunctions gameFunctions)
+ : ITask
+ {
+ private bool _unmountTriggered;
+ private DateTime _continueAt = DateTime.MinValue;
+
+ public bool Start()
+ {
+ if (!condition[ConditionFlag.Mounted])
+ return false;
+
+ logger.LogInformation("Step explicitly wants no mount, trying to unmount...");
+ if (condition[ConditionFlag.InFlight])
+ {
+ gameFunctions.Unmount();
+ _continueAt = DateTime.Now.AddSeconds(1);
+ return true;
+ }
+
+ _unmountTriggered = gameFunctions.Unmount();
+ _continueAt = DateTime.Now.AddSeconds(1);
+ return true;
+ }
+
+ public ETaskResult Update()
+ {
+ if (_continueAt >= DateTime.Now)
+ return ETaskResult.StillRunning;
+
+ if (!_unmountTriggered)
+ {
+ // if still flying, we still need to land
+ if (condition[ConditionFlag.InFlight])
+ gameFunctions.Unmount();
+ else
+ _unmountTriggered = gameFunctions.Unmount();
+
+ _continueAt = DateTime.Now.AddSeconds(1);
+ return ETaskResult.StillRunning;
+ }
+
+ if (condition[ConditionFlag.Mounted] && condition[ConditionFlag.InCombat])
+ {
+ _unmountTriggered = gameFunctions.Unmount();
+ _continueAt = DateTime.Now.AddSeconds(1);
+ return ETaskResult.StillRunning;
+ }
+
+ return condition[ConditionFlag.Mounted]
+ ? ETaskResult.StillRunning
+ : ETaskResult.TaskComplete;
+ }
+
+ public override string ToString() => "Unmount";
+ }
+
+ public enum EMountIf
+ {
+ Always,
+ AwayFromPosition,
+ }
+}
+++ /dev/null
-using System;
-using System.Numerics;
-using Dalamud.Game.ClientState.Conditions;
-using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Client.Game;
-using Microsoft.Extensions.Logging;
-using Questionable.Data;
-using Questionable.Functions;
-
-namespace Questionable.Controller.Steps.Common;
-
-internal sealed class MountTask(
- GameFunctions gameFunctions,
- ICondition condition,
- TerritoryData territoryData,
- IClientState clientState,
- ILogger<MountTask> logger) : ITask
-{
- private ushort _territoryId;
- private EMountIf _mountIf;
- private Vector3? _position;
-
- private bool _mountTriggered;
- private DateTime _retryAt = DateTime.MinValue;
-
- public ITask With(ushort territoryId, EMountIf mountIf, Vector3? position = null)
- {
- _territoryId = territoryId;
- _mountIf = mountIf;
- _position = position;
-
- if (_mountIf == EMountIf.AwayFromPosition)
- ArgumentNullException.ThrowIfNull(position);
- return this;
- }
-
- public bool Start()
- {
- if (condition[ConditionFlag.Mounted])
- return false;
-
- if (!territoryData.CanUseMount(_territoryId))
- {
- logger.LogInformation("Can't use mount in current territory {Id}", _territoryId);
- return false;
- }
-
- if (gameFunctions.HasStatusPreventingMount())
- {
- logger.LogInformation("Can't mount due to status preventing sprint or mount");
- return false;
- }
-
- if (_mountIf == EMountIf.AwayFromPosition)
- {
- Vector3 playerPosition = clientState.LocalPlayer?.Position ?? Vector3.Zero;
- float distance = (playerPosition - _position.GetValueOrDefault()).Length();
- if (_territoryId == clientState.TerritoryType && distance < 30f && !Conditions.IsDiving)
- {
- logger.LogInformation("Not using mount, as we're close to the target");
- return false;
- }
-
- logger.LogInformation(
- "Want to use mount if away from destination ({Distance} yalms), trying (in territory {Id})...",
- distance, _territoryId);
- }
- else
- logger.LogInformation("Want to use mount, trying (in territory {Id})...", _territoryId);
-
- if (!condition[ConditionFlag.InCombat])
- {
- _retryAt = DateTime.Now.AddSeconds(0.5);
- return true;
- }
-
- return false;
- }
-
- public ETaskResult Update()
- {
- if (_mountTriggered && !condition[ConditionFlag.Mounted] && DateTime.Now > _retryAt)
- {
- logger.LogInformation("Not mounted, retrying...");
- _mountTriggered = false;
- _retryAt = DateTime.MaxValue;
- }
-
- if (!_mountTriggered)
- {
- if (gameFunctions.HasStatusPreventingMount())
- {
- logger.LogInformation("Can't mount due to status preventing sprint or mount");
- return ETaskResult.TaskComplete;
- }
-
- _mountTriggered = gameFunctions.Mount();
- _retryAt = DateTime.Now.AddSeconds(5);
- return ETaskResult.StillRunning;
- }
-
- return condition[ConditionFlag.Mounted]
- ? ETaskResult.TaskComplete
- : ETaskResult.StillRunning;
- }
-
- public override string ToString() => "Mount";
-
- public enum EMountIf
- {
- Always,
- AwayFromPosition,
- }
-}
-using System;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
internal static class NextQuest
{
- internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+ internal sealed class Factory(QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILoggerFactory loggerFactory) : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.NextQuestId == quest.Id)
return null;
- return serviceProvider.GetRequiredService<SetQuest>()
- .With(step.NextQuestId, quest.Id);
+ return new SetQuest(step.NextQuestId, quest.Id, questRegistry, questController, questFunctions, loggerFactory.CreateLogger<SetQuest>());
}
}
- internal sealed class SetQuest(QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILogger<SetQuest> logger) : ITask
+ private sealed class SetQuest(ElementId nextQuestId, ElementId currentQuestId, QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILogger<SetQuest> logger) : ITask
{
- public ElementId NextQuestId { get; set; } = null!;
- public ElementId CurrentQuestId { get; set; } = null!;
-
- public ITask With(ElementId nextQuestId, ElementId currentQuestId)
- {
- NextQuestId = nextQuestId;
- CurrentQuestId = currentQuestId;
- return this;
- }
-
public bool Start()
{
- if (questFunctions.IsQuestLocked(NextQuestId, CurrentQuestId))
+ if (questFunctions.IsQuestLocked(nextQuestId, currentQuestId))
{
- logger.LogInformation("Can't set next quest to {QuestId}, quest is locked", NextQuestId);
+ logger.LogInformation("Can't set next quest to {QuestId}, quest is locked", nextQuestId);
}
- else if (questRegistry.TryGetQuest(NextQuestId, out Quest? quest))
+ else if (questRegistry.TryGetQuest(nextQuestId, out Quest? quest))
{
- logger.LogInformation("Setting next quest to {QuestId}: '{QuestName}'", NextQuestId, quest.Info.Name);
+ logger.LogInformation("Setting next quest to {QuestId}: '{QuestName}'", nextQuestId, quest.Info.Name);
questController.SetNextQuest(quest);
}
else
{
- logger.LogInformation("Next quest with id {QuestId} not found", NextQuestId);
+ logger.LogInformation("Next quest with id {QuestId} not found", nextQuestId);
questController.SetNextQuest(null);
}
public ETaskResult Update() => ETaskResult.TaskComplete;
- public override string ToString() => $"SetNextQuest({NextQuestId})";
+ public override string ToString() => $"SetNextQuest({nextQuestId})";
}
}
+++ /dev/null
-using System;
-using Dalamud.Game.ClientState.Conditions;
-using Dalamud.Plugin.Services;
-using Microsoft.Extensions.Logging;
-using Questionable.Functions;
-
-namespace Questionable.Controller.Steps.Common;
-
-internal sealed class UnmountTask(ICondition condition, ILogger<UnmountTask> logger, GameFunctions gameFunctions)
- : ITask
-{
- private bool _unmountTriggered;
- private DateTime _continueAt = DateTime.MinValue;
-
- public bool Start()
- {
- if (!condition[ConditionFlag.Mounted])
- return false;
-
- logger.LogInformation("Step explicitly wants no mount, trying to unmount...");
- if (condition[ConditionFlag.InFlight])
- {
- gameFunctions.Unmount();
- _continueAt = DateTime.Now.AddSeconds(1);
- return true;
- }
-
- _unmountTriggered = gameFunctions.Unmount();
- _continueAt = DateTime.Now.AddSeconds(1);
- return true;
- }
-
- public ETaskResult Update()
- {
- if (_continueAt >= DateTime.Now)
- return ETaskResult.StillRunning;
-
- if (!_unmountTriggered)
- {
- // if still flying, we still need to land
- if (condition[ConditionFlag.InFlight])
- gameFunctions.Unmount();
- else
- _unmountTriggered = gameFunctions.Unmount();
-
- _continueAt = DateTime.Now.AddSeconds(1);
- return ETaskResult.StillRunning;
- }
-
- if (condition[ConditionFlag.Mounted] && condition[ConditionFlag.InCombat])
- {
- _unmountTriggered = gameFunctions.Unmount();
- _continueAt = DateTime.Now.AddSeconds(1);
- return ETaskResult.StillRunning;
- }
-
- return condition[ConditionFlag.Mounted]
- ? ETaskResult.StillRunning
- : ETaskResult.TaskComplete;
- }
-
- public override string ToString() => "Unmount";
-}
namespace Questionable.Controller.Steps.Gathering;
internal sealed class DoGather(
+ GatheringController.GatheringRequest currentRequest,
+ GatheringNode currentNode,
+ bool revisitRequired,
GatheringController gatheringController,
GameFunctions gameFunctions,
IGameGui gameGui,
{
private const uint StatusGatheringRateUp = 218;
- private GatheringController.GatheringRequest _currentRequest = null!;
- private GatheringNode _currentNode = null!;
- private bool _revisitRequired;
private bool _revisitTriggered;
private bool _wasGathering;
private SlotInfo? _slotToGather;
private Queue<EAction>? _actionQueue;
- public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode,
- bool revisitRequired)
- {
- _currentRequest = currentRequest;
- _currentNode = currentNode;
- _revisitRequired = revisitRequired;
- return this;
- }
-
public bool Start() => true;
public unsafe ETaskResult Update()
{
- if (_revisitRequired && !_revisitTriggered)
+ if (revisitRequired && !_revisitTriggered)
{
logger.LogInformation("No revisit");
return ETaskResult.TaskComplete;
}
- if (gatheringController.HasNodeDisappeared(_currentNode))
+ if (gatheringController.HasNodeDisappeared(currentNode))
{
logger.LogInformation("Node disappeared");
return ETaskResult.TaskComplete;
else
{
var slots = ReadSlots(addonGathering);
- if (_currentRequest.Collectability > 0)
+ if (currentRequest.Collectability > 0)
{
- var slot = slots.Single(x => x.ItemId == _currentRequest.ItemId);
+ var slot = slots.Single(x => x.ItemId == currentRequest.ItemId);
addonGathering->FireCallbackInt(slot.Index);
}
else
_actionQueue = GetNextActions(nodeCondition, slots);
if (_actionQueue.Count == 0)
{
- var slot = _slotToGather ?? slots.Single(x => x.ItemId == _currentRequest.ItemId);
+ var slot = _slotToGather ?? slots.Single(x => x.ItemId == currentRequest.ItemId);
addonGathering->FireCallbackInt(slot.Index);
}
}
if (!gameFunctions.HasStatus(StatusGatheringRateUp))
{
// do we have an alternative item? only happens for 'evaluation' leve quests
- if (_currentRequest.AlternativeItemId != 0)
+ if (currentRequest.AlternativeItemId != 0)
{
- var alternativeSlot = slots.Single(x => x.ItemId == _currentRequest.AlternativeItemId);
+ var alternativeSlot = slots.Single(x => x.ItemId == currentRequest.AlternativeItemId);
if (alternativeSlot.GatheringChance == 100)
{
}
}
- var slot = slots.Single(x => x.ItemId == _currentRequest.ItemId);
+ var slot = slots.Single(x => x.ItemId == currentRequest.ItemId);
if (slot.GatheringChance > 0 && slot.GatheringChance < 100)
{
if (slot.GatheringChance >= 95 &&
_revisitTriggered = true;
}
- public override string ToString() => $"DoGather{(_revisitRequired ? " if revist" : "")}";
+ public override string ToString() => $"DoGather{(revisitRequired ? " if revist" : "")}";
private sealed record SlotInfo(int Index, uint ItemId, int GatheringChance, int BoonChance, int Quantity);
namespace Questionable.Controller.Steps.Gathering;
internal sealed class DoGatherCollectable(
+ GatheringController.GatheringRequest currentRequest,
+ GatheringNode currentNode,
+ bool revisitRequired,
GatheringController gatheringController,
GameFunctions gameFunctions,
IClientState clientState,
IGameGui gameGui,
ILogger<DoGatherCollectable> logger) : ITask, IRevisitAware
{
- private GatheringController.GatheringRequest _currentRequest = null!;
- private GatheringNode _currentNode = null!;
- private bool _revisitRequired;
private bool _revisitTriggered;
private Queue<EAction>? _actionQueue;
private bool? _expectedScrutiny;
- public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode,
- bool revisitRequired)
- {
- _currentRequest = currentRequest;
- _currentNode = currentNode;
- _revisitRequired = revisitRequired;
- return this;
- }
-
public bool Start() => true;
public unsafe ETaskResult Update()
{
- if (_revisitRequired && !_revisitTriggered)
+ if (revisitRequired && !_revisitTriggered)
{
logger.LogInformation("No revisit");
return ETaskResult.TaskComplete;
}
- if (gatheringController.HasNodeDisappeared(_currentNode))
+ if (gatheringController.HasNodeDisappeared(currentNode))
{
logger.LogInformation("Node disappeared");
return ETaskResult.TaskComplete;
return ETaskResult.StillRunning;
}
- if (nodeCondition.CollectabilityToGoal(_currentRequest.Collectability) > 0)
+ if (nodeCondition.CollectabilityToGoal(currentRequest.Collectability) > 0)
{
_actionQueue = GetNextActions(nodeCondition);
if (_actionQueue != null)
Queue<EAction> actions = new();
- uint neededCollectability = nodeCondition.CollectabilityToGoal(_currentRequest.Collectability);
+ uint neededCollectability = nodeCondition.CollectabilityToGoal(currentRequest.Collectability);
if (neededCollectability <= nodeCondition.CollectabilityFromMeticulous)
{
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ meticulous",
}
public override string ToString() =>
- $"DoGatherCollectable({SeIconChar.Collectible.ToIconString()}/{_expectedScrutiny} {_currentRequest.Collectability}){(_revisitRequired ? " if revist" : "")}";
+ $"DoGatherCollectable({SeIconChar.Collectible.ToIconString()}/{_expectedScrutiny} {currentRequest.Collectability}){(revisitRequired ? " if revist" : "")}";
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]
private sealed record NodeCondition(
-using System;
-using System.Globalization;
+using System.Globalization;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Enums;
namespace Questionable.Controller.Steps.Gathering;
internal sealed class MoveToLandingLocation(
- IServiceProvider serviceProvider,
+ ushort territoryId,
+ bool flyBetweenNodes,
+ GatheringNode gatheringNode,
+ MoveTo.Factory moveFactory,
GameFunctions gameFunctions,
IObjectTable objectTable,
ILogger<MoveToLandingLocation> logger) : ITask
{
- private ushort _territoryId;
- private bool _flyBetweenNodes;
- private GatheringNode _gatheringNode = null!;
private ITask _moveTask = null!;
- public ITask With(ushort territoryId, bool flyBetweenNodes, GatheringNode gatheringNode)
- {
- _territoryId = territoryId;
- _flyBetweenNodes = flyBetweenNodes;
- _gatheringNode = gatheringNode;
- return this;
- }
-
public bool Start()
{
- var location = _gatheringNode.Locations.First();
- if (_gatheringNode.Locations.Count > 1)
+ var location = gatheringNode.Locations.First();
+ if (gatheringNode.Locations.Count > 1)
{
var gameObject = objectTable.SingleOrDefault(x =>
- x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == _gatheringNode.DataId && x.IsTargetable);
+ x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == gatheringNode.DataId && x.IsTargetable);
if (gameObject == null)
return false;
- location = _gatheringNode.Locations.Single(x => Vector3.Distance(x.Position, gameObject.Position) < 0.1f);
+ location = gatheringNode.Locations.Single(x => Vector3.Distance(x.Position, gameObject.Position) < 0.1f);
}
var (target, degrees, range) = GatheringMath.CalculateLandingLocation(location);
logger.LogInformation("Preliminary landing location: {Location}, with degrees = {Degrees}, range = {Range}",
target.ToString("G", CultureInfo.InvariantCulture), degrees, range);
- bool fly = _flyBetweenNodes && gameFunctions.IsFlyingUnlocked(_territoryId);
- _moveTask = serviceProvider.GetRequiredService<Move.MoveInternal>()
- .With(_territoryId, target, 0.25f, dataId: _gatheringNode.DataId, fly: fly,
- ignoreDistanceToObject: true);
+ bool fly = flyBetweenNodes && gameFunctions.IsFlyingUnlocked(territoryId);
+ _moveTask = moveFactory.Move(new MoveTo.MoveParams(territoryId, target, 0.25f, DataId: gatheringNode.DataId,
+ Fly: fly, IgnoreDistanceToObject: true));
return _moveTask.Start();
}
public ETaskResult Update() => _moveTask.Update();
- public override string ToString() => $"Land/{_moveTask}/{_flyBetweenNodes}";
+ public override string ToString() => $"Land/{_moveTask}/{flyBetweenNodes}";
}
internal static class TurnInDelivery
{
- internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+ internal sealed class Factory(ILoggerFactory loggerFactory) : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (quest.Id is not SatisfactionSupplyNpcId || sequence.Sequence != 1)
return null;
- return serviceProvider.GetRequiredService<SatisfactionSupplyTurnIn>();
+ return new SatisfactionSupplyTurnIn(loggerFactory.CreateLogger<SatisfactionSupplyTurnIn>());
}
}
- internal sealed class SatisfactionSupplyTurnIn(ILogger<SatisfactionSupplyTurnIn> logger) : ITask
+ private sealed class SatisfactionSupplyTurnIn(ILogger<SatisfactionSupplyTurnIn> logger) : ITask
{
private ushort? _remainingAllowances;
internal static class Action
{
- internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+ internal sealed class Factory(GameFunctions gameFunctions, Mount.Factory mountFactory, ILoggerFactory loggerFactory)
+ : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
ArgumentNullException.ThrowIfNull(step.Action);
- var task = serviceProvider.GetRequiredService<UseOnObject>()
- .With(step.DataId, step.Action.Value);
+ var task = new UseOnObject(step.DataId, step.Action.Value, gameFunctions,
+ loggerFactory.CreateLogger<UseOnObject>());
if (step.Action.Value.RequiresMount())
return [task];
else
- {
- var unmount = serviceProvider.GetRequiredService<UnmountTask>();
- return [unmount, task];
- }
+ return [mountFactory.Unmount(), task];
}
}
- internal sealed class UseOnObject(GameFunctions gameFunctions, ILogger<UseOnObject> logger) : ITask
+ private sealed class UseOnObject(
+ uint? dataId,
+ EAction action,
+ GameFunctions gameFunctions,
+ ILogger<UseOnObject> logger) : ITask
{
private bool _usedAction;
private DateTime _continueAt = DateTime.MinValue;
- public uint? DataId { get; set; }
- public EAction Action { get; set; }
-
- public ITask With(uint? dataId, EAction action)
- {
- DataId = dataId;
- Action = action;
- return this;
- }
-
public bool Start()
{
- if (DataId != null)
+ if (dataId != null)
{
- IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId.Value);
+ IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId.Value);
if (gameObject == null)
{
- logger.LogWarning("No game object with dataId {DataId}", DataId);
+ logger.LogWarning("No game object with dataId {DataId}", dataId);
return false;
}
if (gameObject.IsTargetable)
{
- _usedAction = gameFunctions.UseAction(gameObject, Action);
+ _usedAction = gameFunctions.UseAction(gameObject, action);
_continueAt = DateTime.Now.AddSeconds(0.5);
return true;
}
}
else
{
- _usedAction = gameFunctions.UseAction(Action);
+ _usedAction = gameFunctions.UseAction(action);
_continueAt = DateTime.Now.AddSeconds(0.5);
return true;
}
if (!_usedAction)
{
- if (DataId != null)
+ if (dataId != null)
{
- IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId.Value);
+ IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId.Value);
if (gameObject == null || !gameObject.IsTargetable)
return ETaskResult.StillRunning;
- _usedAction = gameFunctions.UseAction(gameObject, Action);
+ _usedAction = gameFunctions.UseAction(gameObject, action);
_continueAt = DateTime.Now.AddSeconds(0.5);
}
else
{
- _usedAction = gameFunctions.UseAction(Action);
+ _usedAction = gameFunctions.UseAction(action);
_continueAt = DateTime.Now.AddSeconds(0.5);
}
return ETaskResult.TaskComplete;
}
- public override string ToString() => $"Action({Action})";
+ public override string ToString() => $"Action({action})";
}
}
internal static class AetherCurrent
{
- internal sealed class Factory(IServiceProvider serviceProvider, AetherCurrentData aetherCurrentData, IChatGui chatGui) : SimpleTaskFactory
+ internal sealed class Factory(
+ GameFunctions gameFunctions,
+ AetherCurrentData aetherCurrentData,
+ IChatGui chatGui,
+ ILoggerFactory loggerFactory) : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (!aetherCurrentData.IsValidAetherCurrent(step.TerritoryId, step.AetherCurrentId.Value))
{
- chatGui.PrintError($"[Questionable] Aether current with id {step.AetherCurrentId} is referencing an invalid aether current, will skip attunement");
+ chatGui.PrintError(
+ $"[Questionable] Aether current with id {step.AetherCurrentId} is referencing an invalid aether current, will skip attunement");
return null;
}
- return serviceProvider.GetRequiredService<DoAttune>()
- .With(step.DataId.Value, step.AetherCurrentId.Value);
+ return new DoAttune(step.DataId.Value, step.AetherCurrentId.Value, gameFunctions, loggerFactory.CreateLogger<DoAttune>());
}
}
- internal sealed class DoAttune(GameFunctions gameFunctions, ILogger<DoAttune> logger) : ITask
+ private sealed class DoAttune(uint dataId, uint aetherCurrentId, GameFunctions gameFunctions, ILogger<DoAttune> logger) : ITask
{
- public uint DataId { get; set; }
- public uint AetherCurrentId { get; set; }
-
- public ITask With(uint dataId, uint aetherCurrentId)
- {
- DataId = dataId;
- AetherCurrentId = aetherCurrentId;
- return this;
- }
-
public bool Start()
{
- if (!gameFunctions.IsAetherCurrentUnlocked(AetherCurrentId))
+ if (!gameFunctions.IsAetherCurrentUnlocked(aetherCurrentId))
{
- logger.LogInformation("Attuning to aether current {AetherCurrentId} / {DataId}", AetherCurrentId,
- DataId);
- gameFunctions.InteractWith(DataId);
+ logger.LogInformation("Attuning to aether current {AetherCurrentId} / {DataId}", aetherCurrentId,
+ dataId);
+ gameFunctions.InteractWith(dataId);
return true;
}
- logger.LogInformation("Already attuned to aether current {AetherCurrentId} / {DataId}", AetherCurrentId,
- DataId);
+ logger.LogInformation("Already attuned to aether current {AetherCurrentId} / {DataId}", aetherCurrentId,
+ dataId);
return false;
}
public ETaskResult Update() =>
- gameFunctions.IsAetherCurrentUnlocked(AetherCurrentId)
+ gameFunctions.IsAetherCurrentUnlocked(aetherCurrentId)
? ETaskResult.TaskComplete
: ETaskResult.StillRunning;
- public override string ToString() => $"AttuneAetherCurrent({AetherCurrentId})";
+ public override string ToString() => $"AttuneAetherCurrent({aetherCurrentId})";
}
}
internal static class AethernetShard
{
- internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+ internal sealed class Factory(
+ AetheryteFunctions aetheryteFunctions,
+ GameFunctions gameFunctions,
+ ILoggerFactory loggerFactory) : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
ArgumentNullException.ThrowIfNull(step.AethernetShard);
- return serviceProvider.GetRequiredService<DoAttune>()
- .With(step.AethernetShard.Value);
+ return new DoAttune(step.AethernetShard.Value, aetheryteFunctions, gameFunctions,
+ loggerFactory.CreateLogger<DoAttune>());
}
}
- internal sealed class DoAttune(
+ private sealed class DoAttune(
+ EAetheryteLocation aetheryteLocation,
AetheryteFunctions aetheryteFunctions,
GameFunctions gameFunctions,
ILogger<DoAttune> logger) : ITask
{
- public EAetheryteLocation AetheryteLocation { get; set; }
-
- public ITask With(EAetheryteLocation aetheryteLocation)
- {
- AetheryteLocation = aetheryteLocation;
- return this;
- }
-
public bool Start()
{
- if (!aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation))
+ if (!aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation))
{
- logger.LogInformation("Attuning to aethernet shard {AethernetShard}", AetheryteLocation);
- gameFunctions.InteractWith((uint)AetheryteLocation, ObjectKind.Aetheryte);
+ logger.LogInformation("Attuning to aethernet shard {AethernetShard}", aetheryteLocation);
+ gameFunctions.InteractWith((uint)aetheryteLocation, ObjectKind.Aetheryte);
return true;
}
- logger.LogInformation("Already attuned to aethernet shard {AethernetShard}", AetheryteLocation);
+ logger.LogInformation("Already attuned to aethernet shard {AethernetShard}", aetheryteLocation);
return false;
}
public ETaskResult Update() =>
- aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation)
+ aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation)
? ETaskResult.TaskComplete
: ETaskResult.StillRunning;
- public override string ToString() => $"AttuneAethernetShard({AetheryteLocation})";
+ public override string ToString() => $"AttuneAethernetShard({aetheryteLocation})";
}
}
internal static class Aetheryte
{
- internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+ internal sealed class Factory(
+ AetheryteFunctions aetheryteFunctions,
+ GameFunctions gameFunctions,
+ ILoggerFactory loggerFactory) : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
ArgumentNullException.ThrowIfNull(step.Aetheryte);
- return serviceProvider.GetRequiredService<DoAttune>()
- .With(step.Aetheryte.Value);
+ return new DoAttune(step.Aetheryte.Value, aetheryteFunctions, gameFunctions,
+ loggerFactory.CreateLogger<DoAttune>());
}
}
- internal sealed class DoAttune(
+ private sealed class DoAttune(
+ EAetheryteLocation aetheryteLocation,
AetheryteFunctions aetheryteFunctions,
GameFunctions gameFunctions,
ILogger<DoAttune> logger) : ITask
{
- public EAetheryteLocation AetheryteLocation { get; set; }
-
- public ITask With(EAetheryteLocation aetheryteLocation)
- {
- AetheryteLocation = aetheryteLocation;
- return this;
- }
-
public bool Start()
{
- if (!aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation))
+ if (!aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation))
{
- logger.LogInformation("Attuning to aetheryte {Aetheryte}", AetheryteLocation);
- gameFunctions.InteractWith((uint)AetheryteLocation);
+ logger.LogInformation("Attuning to aetheryte {Aetheryte}", aetheryteLocation);
+ gameFunctions.InteractWith((uint)aetheryteLocation);
return true;
}
- logger.LogInformation("Already attuned to aetheryte {Aetheryte}", AetheryteLocation);
+ logger.LogInformation("Already attuned to aetheryte {Aetheryte}", aetheryteLocation);
return false;
}
public ETaskResult Update() =>
- aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation)
+ aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation)
? ETaskResult.TaskComplete
: ETaskResult.StillRunning;
- public override string ToString() => $"AttuneAetheryte({AetheryteLocation})";
+ public override string ToString() => $"AttuneAetheryte({aetheryteLocation})";
}
}
internal static class Combat
{
- internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+ internal sealed class Factory(
+ CombatController combatController,
+ Interact.Factory interactFactory,
+ Mount.Factory mountFactory,
+ UseItem.Factory useItemFactory,
+ QuestFunctions questFunctions) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
- yield return serviceProvider.GetRequiredService<UnmountTask>();
+ yield return mountFactory.Unmount();
if (step.CombatDelaySecondsAtStart != null)
{
- yield return serviceProvider.GetRequiredService<WaitAtStart.WaitDelay>()
- .With(TimeSpan.FromSeconds(step.CombatDelaySecondsAtStart.Value));
+ yield return new WaitAtStart.WaitDelay(TimeSpan.FromSeconds(step.CombatDelaySecondsAtStart.Value));
}
switch (step.EnemySpawnType)
{
ArgumentNullException.ThrowIfNull(step.DataId);
- yield return serviceProvider.GetRequiredService<Interact.DoInteract>()
- .With(step.DataId.Value, quest, EInteractionType.None, true);
+ yield return interactFactory.Interact(step.DataId.Value, quest, EInteractionType.None, true);
yield return CreateTask(quest, sequence, step);
break;
}
ArgumentNullException.ThrowIfNull(step.DataId);
ArgumentNullException.ThrowIfNull(step.ItemId);
- yield return serviceProvider.GetRequiredService<UseItem.UseOnObject>()
- .With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags,
- true);
+ yield return useItemFactory.OnObject(quest.Id, step.DataId.Value, step.ItemId.Value,
+ step.CompletionQuestVariablesFlags, true);
yield return CreateTask(quest, sequence, step);
break;
}
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
bool isLastStep = sequence.Steps.Last() == step;
- return serviceProvider.GetRequiredService<HandleCombat>()
- .With(quest.Id, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds,
- step.CompletionQuestVariablesFlags, step.ComplexCombatData);
+ return CreateTask(quest.Id, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds,
+ step.CompletionQuestVariablesFlags, step.ComplexCombatData);
}
- }
-
- internal sealed class HandleCombat(CombatController combatController, QuestFunctions questFunctions) : ITask
- {
- private bool _isLastStep;
- private CombatController.CombatData _combatData = null!;
- private IList<QuestWorkValue?> _completionQuestVariableFlags = null!;
- public ITask With(ElementId elementId, bool isLastStep, EEnemySpawnType enemySpawnType, IList<uint> killEnemyDataIds,
- IList<QuestWorkValue?> completionQuestVariablesFlags, IList<ComplexCombatData> complexCombatData)
+ private HandleCombat CreateTask(ElementId elementId, bool isLastStep, EEnemySpawnType enemySpawnType,
+ IList<uint> killEnemyDataIds, IList<QuestWorkValue?> completionQuestVariablesFlags,
+ IList<ComplexCombatData> complexCombatData)
{
- _isLastStep = isLastStep;
- _combatData = new CombatController.CombatData
+ return new HandleCombat(isLastStep, new CombatController.CombatData
{
ElementId = elementId,
SpawnType = enemySpawnType,
KillEnemyDataIds = killEnemyDataIds.ToList(),
ComplexCombatDatas = complexCombatData.ToList(),
- };
- _completionQuestVariableFlags = completionQuestVariablesFlags;
- return this;
+ }, completionQuestVariablesFlags, combatController, questFunctions);
}
+ }
- public bool Start() => combatController.Start(_combatData);
+ private sealed class HandleCombat(
+ bool isLastStep,
+ CombatController.CombatData combatData,
+ IList<QuestWorkValue?> completionQuestVariableFlags,
+ CombatController combatController,
+ QuestFunctions questFunctions) : ITask
+ {
+ public bool Start() => combatController.Start(combatData);
public ETaskResult Update()
{
return ETaskResult.StillRunning;
// if our quest step has any completion flags, we need to check if they are set
- if (QuestWorkUtils.HasCompletionFlags(_completionQuestVariableFlags) && _combatData.ElementId is QuestId questId)
+ if (QuestWorkUtils.HasCompletionFlags(completionQuestVariableFlags) &&
+ combatData.ElementId is QuestId questId)
{
var questWork = questFunctions.GetQuestProgressInfo(questId);
if (questWork == null)
return ETaskResult.StillRunning;
- if (QuestWorkUtils.MatchesQuestWork(_completionQuestVariableFlags, questWork))
+ if (QuestWorkUtils.MatchesQuestWork(completionQuestVariableFlags, questWork))
return ETaskResult.TaskComplete;
else
return ETaskResult.StillRunning;
// the last step, by definition, can only be progressed by the game recognizing we're in a new sequence,
// so this is an indefinite wait
- if (_isLastStep)
+ if (isLastStep)
return ETaskResult.StillRunning;
else
{
public override string ToString()
{
- if (QuestWorkUtils.HasCompletionFlags(_completionQuestVariableFlags))
+ if (QuestWorkUtils.HasCompletionFlags(completionQuestVariableFlags))
return "HandleCombat(wait: QW flags)";
- else if (_isLastStep)
+ else if (isLastStep)
return "HandleCombat(wait: next sequence)";
else
return "HandleCombat(wait: not in combat)";
internal static class Dive
{
- internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+ internal sealed class Factory(ICondition condition, ILoggerFactory loggerFactory) : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.Dive)
return null;
- return serviceProvider.GetRequiredService<DoDive>();
+ return Dive();
+ }
+
+ public ITask Dive()
+ {
+ return new DoDive(condition, loggerFactory.CreateLogger<DoDive>());
}
}
- internal sealed class DoDive(ICondition condition, ILogger<DoDive> logger)
+ private sealed class DoDive(ICondition condition, ILogger<DoDive> logger)
: AbstractDelayedTask(TimeSpan.FromSeconds(5))
{
private readonly Queue<(uint Type, nint Key)> _keysToPress = [];
internal static class Duty
{
- internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+ internal sealed class Factory(GameFunctions gameFunctions, ICondition condition) : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
return null;
ArgumentNullException.ThrowIfNull(step.ContentFinderConditionId);
-
- return serviceProvider.GetRequiredService<OpenDutyFinder>()
- .With(step.ContentFinderConditionId.Value);
+ return new OpenDutyFinder(step.ContentFinderConditionId.Value, gameFunctions, condition);
}
}
- internal sealed class OpenDutyFinder(GameFunctions gameFunctions, ICondition condition) : ITask
+ private sealed class OpenDutyFinder(
+ uint contentFinderConditionId,
+ GameFunctions gameFunctions,
+ ICondition condition) : ITask
{
- public uint ContentFinderConditionId { get; set; }
-
- public ITask With(uint contentFinderConditionId)
- {
- ContentFinderConditionId = contentFinderConditionId;
- return this;
- }
-
public bool Start()
{
if (condition[ConditionFlag.InDutyQueue])
return false;
- gameFunctions.OpenDutyFinder(ContentFinderConditionId);
+ gameFunctions.OpenDutyFinder(contentFinderConditionId);
return true;
}
public ETaskResult Update() => ETaskResult.TaskComplete;
- public override string ToString() => $"OpenDutyFinder({ContentFinderConditionId})";
+ public override string ToString() => $"OpenDutyFinder({contentFinderConditionId})";
}
}
internal static class Emote
{
- internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+ internal sealed class Factory(ChatFunctions chatFunctions, Mount.Factory mountFactory) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
ArgumentNullException.ThrowIfNull(step.Emote);
- var unmount = serviceProvider.GetRequiredService<UnmountTask>();
+ var unmount = mountFactory.Unmount();
if (step.DataId != null)
{
- var task = serviceProvider.GetRequiredService<UseOnObject>().With(step.Emote.Value, step.DataId.Value);
+ var task = new UseOnObject(step.Emote.Value, step.DataId.Value, chatFunctions);
return [unmount, task];
}
else
{
- var task = serviceProvider.GetRequiredService<Use>().With(step.Emote.Value);
+ var task = new UseOnSelf(step.Emote.Value, chatFunctions);
return [unmount, task];
}
}
}
- internal sealed class UseOnObject(ChatFunctions chatFunctions) : AbstractDelayedTask
+ private sealed class UseOnObject(EEmote emote, uint dataId, ChatFunctions chatFunctions) : AbstractDelayedTask
{
- public EEmote Emote { get; set; }
- public uint DataId { get; set; }
-
- public ITask With(EEmote emote, uint dataId)
- {
- Emote = emote;
- DataId = dataId;
- return this;
- }
-
protected override bool StartInternal()
{
- chatFunctions.UseEmote(DataId, Emote);
+ chatFunctions.UseEmote(dataId, emote);
return true;
}
- public override string ToString() => $"Emote({Emote} on {DataId})";
+ public override string ToString() => $"Emote({emote} on {dataId})";
}
- internal sealed class Use(ChatFunctions chatFunctions) : AbstractDelayedTask
+ private sealed class UseOnSelf(EEmote emote, ChatFunctions chatFunctions) : AbstractDelayedTask
{
- public EEmote Emote { get; set; }
-
- public ITask With(EEmote emote)
- {
- Emote = emote;
- return this;
- }
-
protected override bool StartInternal()
{
- chatFunctions.UseEmote(Emote);
+ chatFunctions.UseEmote(emote);
return true;
}
- public override string ToString() => $"Emote({Emote})";
+ public override string ToString() => $"Emote({emote})";
}
}
internal static class EquipItem
{
- internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+ internal sealed class Factory(IDataManager dataManager, ILoggerFactory loggerFactory) : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
return null;
ArgumentNullException.ThrowIfNull(step.ItemId);
- return serviceProvider.GetRequiredService<DoEquip>()
- .With(step.ItemId.Value);
+ return Equip(step.ItemId.Value);
+ }
+
+ private DoEquip Equip(uint itemId)
+ {
+ var item = dataManager.GetExcelSheet<Item>()!.GetRow(itemId) ??
+ throw new ArgumentOutOfRangeException(nameof(itemId));
+ var targetSlots = GetEquipSlot(item) ?? throw new InvalidOperationException("Not a piece of equipment");
+ return new DoEquip(itemId, item, targetSlots, dataManager, loggerFactory.CreateLogger<DoEquip>());
+ }
+
+ private static List<ushort>? GetEquipSlot(Item item)
+ {
+ return item.EquipSlotCategory.Row switch
+ {
+ >= 1 and <= 11 => [(ushort)(item.EquipSlotCategory.Row - 1)],
+ 12 => [11, 12], // rings
+ 13 => [0],
+ 17 => [13], // soul crystal
+ _ => null
+ };
}
}
- internal sealed class DoEquip(IDataManager dataManager, ILogger<DoEquip> logger) : ITask, IToastAware
+ private sealed class DoEquip(
+ uint itemId,
+ Item item,
+ List<ushort> targetSlots,
+ IDataManager dataManager,
+ ILogger<DoEquip> logger) : ITask, IToastAware
{
private const int MaxAttempts = 3;
+
private static readonly IReadOnlyList<InventoryType> SourceInventoryTypes =
[
InventoryType.ArmoryMainHand,
InventoryType.Inventory4,
];
- private uint _itemId;
- private Item _item = null!;
- private List<ushort> _targetSlots = [];
private int _attempts;
-
private DateTime _continueAt = DateTime.MaxValue;
- public ITask With(uint itemId)
- {
- _itemId = itemId;
- _item = dataManager.GetExcelSheet<Item>()!.GetRow(itemId) ??
- throw new ArgumentOutOfRangeException(nameof(itemId));
- _targetSlots = GetEquipSlot(_item) ?? throw new InvalidOperationException("Not a piece of equipment");
- return this;
- }
-
public bool Start()
{
Equip();
if (inventoryManager == null)
return ETaskResult.StillRunning;
- foreach (ushort x in _targetSlots)
+ foreach (ushort x in targetSlots)
{
var itemSlot = inventoryManager->GetInventorySlot(InventoryType.EquippedItems, x);
- if (itemSlot != null && itemSlot->ItemId == _itemId)
+ if (itemSlot != null && itemSlot->ItemId == itemId)
return ETaskResult.TaskComplete;
}
if (equippedContainer == null)
return;
- foreach (ushort slot in _targetSlots)
+ foreach (ushort slot in targetSlots)
{
var itemSlot = equippedContainer->GetInventorySlot(slot);
- if (itemSlot != null && itemSlot->ItemId == _itemId)
+ if (itemSlot != null && itemSlot->ItemId == itemId)
{
- logger.LogInformation("Already equipped {Item}, skipping step", _item.Name?.ToString());
+ logger.LogInformation("Already equipped {Item}, skipping step", item.Name?.ToString());
return;
}
}
if (sourceContainer == null)
continue;
- if (inventoryManager->GetItemCountInContainer(_itemId, sourceInventoryType, true) == 0 &&
- inventoryManager->GetItemCountInContainer(_itemId, sourceInventoryType) == 0)
+ if (inventoryManager->GetItemCountInContainer(itemId, sourceInventoryType, true) == 0 &&
+ inventoryManager->GetItemCountInContainer(itemId, sourceInventoryType) == 0)
continue;
for (ushort sourceSlot = 0; sourceSlot < sourceContainer->Size; sourceSlot++)
{
var sourceItem = sourceContainer->GetInventorySlot(sourceSlot);
- if (sourceItem == null || sourceItem->ItemId != _itemId)
+ if (sourceItem == null || sourceItem->ItemId != itemId)
continue;
// Move the item to the first available slot
- ushort targetSlot = _targetSlots
+ ushort targetSlot = targetSlots
.Where(x =>
{
var itemSlot = inventoryManager->GetInventorySlot(InventoryType.EquippedItems, x);
return itemSlot == null || itemSlot->ItemId == 0;
})
- .Concat(_targetSlots).First();
+ .Concat(targetSlots).First();
logger.LogInformation(
"Equipping item from {SourceInventory}, {SourceSlot} to {TargetInventory}, {TargetSlot}",
}
}
- private static List<ushort>? GetEquipSlot(Item item)
- {
- return item.EquipSlotCategory.Row switch
- {
- >= 1 and <= 11 => [(ushort)(item.EquipSlotCategory.Row - 1)],
- 12 => [11, 12], // rings
- 13 => [0],
- 17 => [13], // soul crystal
- _ => null
- };
- }
-
- public override string ToString() => $"Equip({_item.Name})";
+ public override string ToString() => $"Equip({item.Name})";
public bool OnErrorToast(SeString message)
{
internal static class EquipRecommended
{
- internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+ internal sealed class Factory(IClientState clientState, IChatGui chatGui) : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.EquipRecommended)
return null;
- return serviceProvider.GetRequiredService<DoEquipRecommended>();
+ return DoEquip();
+ }
+
+ public ITask DoEquip()
+ {
+ return new DoEquipRecommended(clientState, chatGui);
}
}
- internal sealed class BeforeDutyOrInstance(IServiceProvider serviceProvider) : SimpleTaskFactory
+ internal sealed class BeforeDutyOrInstance(IClientState clientState, IChatGui chatGui) : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
step.InteractionType != EInteractionType.Combat)
return null;
- return serviceProvider.GetRequiredService<DoEquipRecommended>();
+ return new DoEquipRecommended(clientState, chatGui);
}
}
- internal sealed unsafe class DoEquipRecommended(IClientState clientState, IChatGui chatGui) : ITask
+ private sealed unsafe class DoEquipRecommended(IClientState clientState, IChatGui chatGui) : ITask
{
private bool _equipped;
internal static class Interact
{
- internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+ internal sealed class Factory(GameFunctions gameFunctions, ICondition condition, ILoggerFactory loggerFactory) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
// if we're fast enough, it is possible to get the smalltalk prompt
if (sequence.Sequence == 0 && sequence.Steps.IndexOf(step) == 0)
- yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>();
+ yield return new WaitAtEnd.WaitDelay();
- yield return serviceProvider.GetRequiredService<DoInteract>()
- .With(step.DataId.Value, quest, step.InteractionType,
+ yield return Interact(step.DataId.Value, quest, step.InteractionType,
step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId);
}
+
+ internal ITask Interact(uint dataId, Quest? quest, EInteractionType interactionType, bool skipMarkerCheck = false)
+ {
+ return new DoInteract(dataId, quest, interactionType, skipMarkerCheck, gameFunctions, condition,
+ loggerFactory.CreateLogger<DoInteract>());
+ }
}
- internal sealed class DoInteract(GameFunctions gameFunctions, ICondition condition, ILogger<DoInteract> logger)
+ internal sealed class DoInteract(
+ uint dataId,
+ Quest? quest,
+ EInteractionType interactionType,
+ bool skipMarkerCheck,
+ GameFunctions gameFunctions,
+ ICondition condition,
+ ILogger<DoInteract> logger)
: ITask, IConditionChangeAware
{
private bool _needsUnmount;
private EInteractionState _interactionState = EInteractionState.None;
private DateTime _continueAt = DateTime.MinValue;
- private uint DataId { get; set; }
- public Quest? Quest { get; private set; }
- public EInteractionType InteractionType { get; set; }
- private bool SkipMarkerCheck { get; set; }
-
- public DoInteract With(uint dataId, Quest? quest, EInteractionType interactionType, bool skipMarkerCheck)
+ public Quest? Quest => quest;
+ public EInteractionType InteractionType
{
- DataId = dataId;
- Quest = quest;
- InteractionType = interactionType;
- SkipMarkerCheck = skipMarkerCheck;
- return this;
+ get => interactionType;
+ set => interactionType = value;
}
public bool Start()
{
- IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId);
+ IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId);
if (gameObject == null)
{
- logger.LogWarning("No game object with dataId {DataId}", DataId);
+ logger.LogWarning("No game object with dataId {DataId}", dataId);
return false;
}
if (!gameObject.IsTargetable && condition[ConditionFlag.Mounted] &&
gameObject.ObjectKind != ObjectKind.GatheringPoint)
{
- logger.LogInformation("Preparing interaction for {DataId} by unmounting", DataId);
+ logger.LogInformation("Preparing interaction for {DataId} by unmounting", dataId);
_needsUnmount = true;
gameFunctions.Unmount();
_continueAt = DateTime.Now.AddSeconds(1);
if (_interactionState == EInteractionState.InteractionConfirmed)
return ETaskResult.TaskComplete;
- if (InteractionType == EInteractionType.InternalGather && condition[ConditionFlag.Gathering])
+ if (interactionType == EInteractionType.InternalGather && condition[ConditionFlag.Gathering])
return ETaskResult.TaskComplete;
- IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId);
+ IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId);
if (gameObject == null || !gameObject.IsTargetable || !HasAnyMarker(gameObject))
return ETaskResult.StillRunning;
private unsafe bool HasAnyMarker(IGameObject gameObject)
{
- if (SkipMarkerCheck || gameObject.ObjectKind != ObjectKind.EventNpc)
+ if (skipMarkerCheck || gameObject.ObjectKind != ObjectKind.EventNpc)
return true;
var gameObjectStruct = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address;
return gameObjectStruct->NamePlateIconId != 0;
}
- public override string ToString() => $"Interact({DataId})";
+ public override string ToString() => $"Interact({dataId})";
public void OnConditionChange(ConditionFlag flag, bool value)
{
internal static class Jump
{
- internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+ internal sealed class Factory(
+ MovementController movementController,
+ IClientState clientState,
+ IFramework framework,
+ ILoggerFactory loggerFactory) : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
ArgumentNullException.ThrowIfNull(step.JumpDestination);
if (step.JumpDestination.Type == EJumpType.SingleJump)
- {
- return serviceProvider.GetRequiredService<SingleJump>()
- .With(step.DataId, step.JumpDestination, step.Comment);
- }
+ return SingleJump(step.DataId, step.JumpDestination, step.Comment);
else
- {
- return serviceProvider.GetRequiredService<RepeatedJumps>()
- .With(step.DataId, step.JumpDestination, step.Comment);
- }
+ return RepeatedJumps(step.DataId, step.JumpDestination, step.Comment);
+ }
+
+ public ITask SingleJump(uint? dataId, JumpDestination jumpDestination, string? comment)
+ {
+ return new DoSingleJump(dataId, jumpDestination, comment, movementController, clientState, framework);
+ }
+
+ public ITask RepeatedJumps(uint? dataId, JumpDestination jumpDestination, string? comment)
+ {
+ return new DoRepeatedJumps(dataId, jumpDestination, comment, movementController, clientState, framework,
+ loggerFactory.CreateLogger<DoRepeatedJumps>());
}
}
- internal class SingleJump(
+ private class DoSingleJump(
+ uint? dataId,
+ JumpDestination jumpDestination,
+ string? comment,
MovementController movementController,
IClientState clientState,
IFramework framework) : ITask
{
- public uint? DataId { get; set; }
- public JumpDestination JumpDestination { get; set; } = null!;
- public string? Comment { get; set; }
-
- public ITask With(uint? dataId, JumpDestination jumpDestination, string? comment)
- {
- DataId = dataId;
- JumpDestination = jumpDestination;
- Comment = comment ?? string.Empty;
- return this;
- }
-
public virtual bool Start()
{
- float stopDistance = JumpDestination.CalculateStopDistance();
- if ((clientState.LocalPlayer!.Position - JumpDestination.Position).Length() <= stopDistance)
+ float stopDistance = jumpDestination.CalculateStopDistance();
+ if ((clientState.LocalPlayer!.Position - jumpDestination.Position).Length() <= stopDistance)
return false;
- movementController.NavigateTo(EMovementType.Quest, DataId, [JumpDestination.Position], false, false,
- JumpDestination.StopDistance ?? stopDistance);
+ movementController.NavigateTo(EMovementType.Quest, dataId, [jumpDestination.Position], false, false,
+ jumpDestination.StopDistance ?? stopDistance);
framework.RunOnTick(() =>
{
unsafe
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2);
}
},
- TimeSpan.FromSeconds(JumpDestination.DelaySeconds ?? 0.5f));
+ TimeSpan.FromSeconds(jumpDestination.DelaySeconds ?? 0.5f));
return true;
}
return ETaskResult.TaskComplete;
}
- public override string ToString() => $"Jump({Comment})";
+ public override string ToString() => $"Jump({comment})";
}
- internal sealed class RepeatedJumps(
+ private sealed class DoRepeatedJumps(
+ uint? dataId,
+ JumpDestination jumpDestination,
+ string? comment,
MovementController movementController,
IClientState clientState,
IFramework framework,
- ILogger<RepeatedJumps> logger) : SingleJump(movementController, clientState, framework)
+ ILogger<DoRepeatedJumps> logger)
+ : DoSingleJump(dataId, jumpDestination, comment, movementController, clientState, framework)
{
+ private readonly JumpDestination _jumpDestination = jumpDestination;
+ private readonly string? _comment = comment;
private readonly IClientState _clientState = clientState;
private DateTime _continueAt = DateTime.MinValue;
private int _attempts;
public override bool Start()
{
- _continueAt = DateTime.Now + TimeSpan.FromSeconds(2 * (JumpDestination.DelaySeconds ?? 0.5f));
+ _continueAt = DateTime.Now + TimeSpan.FromSeconds(2 * (_jumpDestination.DelaySeconds ?? 0.5f));
return base.Start();
}
if (DateTime.Now < _continueAt)
return ETaskResult.StillRunning;
- float stopDistance = JumpDestination.CalculateStopDistance();
- if ((_clientState.LocalPlayer!.Position - JumpDestination.Position).Length() <= stopDistance ||
- _clientState.LocalPlayer.Position.Y >= JumpDestination.Position.Y - 0.5f)
+ float stopDistance = _jumpDestination.CalculateStopDistance();
+ if ((_clientState.LocalPlayer!.Position - _jumpDestination.Position).Length() <= stopDistance ||
+ _clientState.LocalPlayer.Position.Y >= _jumpDestination.Position.Y - 0.5f)
return ETaskResult.TaskComplete;
logger.LogTrace("Y-Heights for jumps: player={A}, target={B}", _clientState.LocalPlayer.Position.Y,
- JumpDestination.Position.Y - 0.5f);
+ _jumpDestination.Position.Y - 0.5f);
unsafe
{
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2);
if (_attempts >= 50)
throw new TaskException("Tried to jump too many times, didn't reach the target");
- _continueAt = DateTime.Now + TimeSpan.FromSeconds(JumpDestination.DelaySeconds ?? 0.5f);
+ _continueAt = DateTime.Now + TimeSpan.FromSeconds(_jumpDestination.DelaySeconds ?? 0.5f);
return ETaskResult.StillRunning;
}
- public override string ToString() => $"RepeatedJump({Comment})";
+ public override string ToString() => $"RepeatedJump({_comment})";
}
}
internal static class Say
{
- internal sealed class Factory(IServiceProvider serviceProvider, ExcelFunctions excelFunctions) : ITaskFactory
+ internal sealed class Factory(
+ ChatFunctions chatFunctions,
+ Mount.Factory mountFactory,
+ ExcelFunctions excelFunctions) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
ArgumentNullException.ThrowIfNull(step.ChatMessage);
string? excelString =
- excelFunctions.GetDialogueText(quest, step.ChatMessage.ExcelSheet, step.ChatMessage.Key, false).GetString();
+ excelFunctions.GetDialogueText(quest, step.ChatMessage.ExcelSheet, step.ChatMessage.Key, false)
+ .GetString();
ArgumentNullException.ThrowIfNull(excelString);
- var unmount = serviceProvider.GetRequiredService<UnmountTask>();
- var task = serviceProvider.GetRequiredService<UseChat>().With(excelString);
+ var unmount = mountFactory.Unmount();
+ var task = new UseChat(excelString, chatFunctions);
return [unmount, task];
}
}
- internal sealed class UseChat(ChatFunctions chatFunctions) : AbstractDelayedTask
+ private sealed class UseChat(string chatMessage, ChatFunctions chatFunctions) : AbstractDelayedTask
{
- public string ChatMessage { get; set; } = null!;
-
- public ITask With(string chatMessage)
- {
- ChatMessage = chatMessage;
- return this;
- }
-
protected override bool StartInternal()
{
- chatFunctions.ExecuteCommand($"/say {ChatMessage}");
+ chatFunctions.ExecuteCommand($"/say {chatMessage}");
return true;
}
- public override string ToString() => $"Say({ChatMessage})";
+ public override string ToString() => $"Say({chatMessage})";
}
}
public const int VesperBayAetheryteTicket = 30362;
internal sealed class Factory(
- IServiceProvider serviceProvider,
+ Mount.Factory mountFactory,
+ MoveTo.Factory moveFactory,
+ Interact.Factory interactFactory,
+ AetheryteShortcut.Factory aetheryteShortcutFactory,
+ AethernetShortcut.Factory aethernetShortcutFactory,
+ GameFunctions gameFunctions,
+ QuestFunctions questFunctions,
+ ICondition condition,
IClientState clientState,
TerritoryData territoryData,
+ ILoggerFactory loggerFactory,
ILogger<Factory> logger)
: ITaskFactory
{
return CreateVesperBayFallbackTask();
}
- var task = serviceProvider.GetRequiredService<Use>()
- .With(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
+ var task = OnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
int currentStepIndex = sequence.Steps.IndexOf(step);
QuestStep? nextStep = sequence.Steps.Skip(currentStepIndex + 1).FirstOrDefault();
task,
new WaitConditionTask(() => clientState.TerritoryType == 140,
$"Wait(territory: {territoryData.GetNameAndId(140)})"),
- serviceProvider.GetRequiredService<MountTask>()
- .With(140,
- nextPosition != null ? MountTask.EMountIf.AwayFromPosition : MountTask.EMountIf.Always,
- nextPosition),
- serviceProvider.GetRequiredService<Move.MoveInternal>()
- .With(140, new(-408.92343f, 23.167036f, -351.16223f), 0.25f, dataId: null, disableNavMesh: true,
- sprint: false, fly: false)
+ mountFactory.Mount(140,
+ nextPosition != null ? Mount.EMountIf.AwayFromPosition : Mount.EMountIf.Always,
+ nextPosition),
+ moveFactory.Move(new MoveTo.MoveParams(140, new(-408.92343f, 23.167036f, -351.16223f), 0.25f,
+ DataId: null, DisableNavMesh: true, Sprint: false, Fly: false))
];
}
- var unmount = serviceProvider.GetRequiredService<UnmountTask>();
+ var unmount = mountFactory.Unmount();
if (step.GroundTarget == true)
{
ITask task;
if (step.DataId != null)
- task = serviceProvider.GetRequiredService<UseOnGround>()
- .With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
+ task = OnGroundTarget(quest.Id, step.DataId.Value, step.ItemId.Value,
+ step.CompletionQuestVariablesFlags);
else
{
ArgumentNullException.ThrowIfNull(step.Position);
- task = serviceProvider.GetRequiredService<UseOnPosition>()
- .With(quest.Id, step.Position.Value, step.ItemId.Value,
- step.CompletionQuestVariablesFlags);
+ task = OnPosition(quest.Id, step.Position.Value, step.ItemId.Value,
+ step.CompletionQuestVariablesFlags);
}
return [unmount, task];
}
else if (step.DataId != null)
{
- var task = serviceProvider.GetRequiredService<UseOnObject>()
- .With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
+ var task = OnObject(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
return [unmount, task];
}
else
{
- var task = serviceProvider.GetRequiredService<Use>()
- .With(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
+ var task = OnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
return [unmount, task];
}
}
+ public ITask OnGroundTarget(ElementId questId, uint dataId, uint itemId,
+ List<QuestWorkValue?> completionQuestVariablesFlags)
+ {
+ return new UseOnGround(questId, dataId, itemId, completionQuestVariablesFlags, gameFunctions,
+ questFunctions, condition, loggerFactory.CreateLogger<UseOnGround>());
+ }
+
+ public ITask OnPosition(ElementId questId, Vector3 position, uint itemId,
+ List<QuestWorkValue?> completionQuestVariablesFlags)
+ {
+ return new UseOnPosition(questId, position, itemId, completionQuestVariablesFlags, gameFunctions,
+ questFunctions, condition, loggerFactory.CreateLogger<UseOnPosition>());
+ }
+
+ public ITask OnObject(ElementId questId, uint dataId, uint itemId,
+ List<QuestWorkValue?> completionQuestVariablesFlags, bool startingCombat = false)
+ {
+ return new UseOnObject(questId, dataId, itemId, completionQuestVariablesFlags, startingCombat,
+ questFunctions, gameFunctions, condition, loggerFactory.CreateLogger<UseOnObject>());
+ }
+
+ public ITask OnSelf(ElementId questId, uint itemId, List<QuestWorkValue?> completionQuestVariablesFlags)
+ {
+ return new Use(questId, itemId, completionQuestVariablesFlags, gameFunctions, questFunctions, condition,
+ loggerFactory.CreateLogger<Use>());
+ }
+
private IEnumerable<ITask> CreateVesperBayFallbackTask()
{
logger.LogWarning("No vesper bay aetheryte tickets in inventory, navigating via ferry in Limsa instead");
uint npcId = 1003540;
ushort territoryId = 129;
Vector3 destination = new(-360.9217f, 8f, 38.92566f);
- yield return serviceProvider.GetRequiredService<AetheryteShortcut.UseAetheryteShortcut>()
- .With(null, null, EAetheryteLocation.Limsa, territoryId);
- yield return serviceProvider.GetRequiredService<AethernetShortcut.UseAethernetShortcut>()
- .With(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist);
- yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>();
- yield return serviceProvider.GetRequiredService<Move.MoveInternal>()
- .With(territoryId, destination, dataId: npcId, sprint: false);
- yield return serviceProvider.GetRequiredService<Interact.DoInteract>()
- .With(npcId, null, EInteractionType.None, true);
+ yield return aetheryteShortcutFactory.Use(null, null, EAetheryteLocation.Limsa, territoryId);
+ yield return aethernetShortcutFactory.Use(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist);
+ yield return new WaitAtEnd.WaitDelay();
+ yield return
+ moveFactory.Move(new MoveTo.MoveParams(territoryId, destination, DataId: npcId, Sprint: false));
+ yield return interactFactory.Interact(npcId, null, EInteractionType.None, true);
}
}
- internal abstract class UseItemBase(QuestFunctions questFunctions, ICondition condition, ILogger logger) : ITask
+ private abstract class UseItemBase(
+ ElementId? questId,
+ uint itemId,
+ IList<QuestWorkValue?> completionQuestVariablesFlags,
+ bool startingCombat,
+ QuestFunctions questFunctions,
+ ICondition condition,
+ ILogger logger) : ITask
{
private bool _usedItem;
private DateTime _continueAt;
private int _itemCount;
- public ElementId? QuestId { get; set; }
- public uint ItemId { get; set; }
- public IList<QuestWorkValue?> CompletionQuestVariablesFlags { get; set; } = new List<QuestWorkValue?>();
- public bool StartingCombat { get; set; }
+ public ElementId? QuestId => questId;
+ public uint ItemId => itemId;
+ public IList<QuestWorkValue?> CompletionQuestVariablesFlags => completionQuestVariablesFlags;
+ public bool StartingCombat => startingCombat;
protected abstract bool UseItem();
public unsafe ETaskResult Update()
{
- if (QuestId is QuestId questId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
+ if (QuestId is QuestId realQuestId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
{
- QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(questId);
+ QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(realQuestId);
if (questWork != null &&
QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork))
return ETaskResult.TaskComplete;
}
- internal sealed class UseOnGround(
+ private sealed class UseOnGround(
+ ElementId? questId,
+ uint dataId,
+ uint itemId,
+ IList<QuestWorkValue?> completionQuestVariablesFlags,
GameFunctions gameFunctions,
QuestFunctions questFunctions,
ICondition condition,
ILogger<UseOnGround> logger)
- : UseItemBase(questFunctions, condition, logger)
+ : UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger)
{
- public uint DataId { get; set; }
-
- public ITask With(ElementId? questId, uint dataId, uint itemId,
- IList<QuestWorkValue?> completionQuestVariablesFlags)
- {
- QuestId = questId;
- DataId = dataId;
- ItemId = itemId;
- CompletionQuestVariablesFlags = completionQuestVariablesFlags;
- return this;
- }
+ protected override bool UseItem() => gameFunctions.UseItemOnGround(dataId, ItemId);
- protected override bool UseItem() => gameFunctions.UseItemOnGround(DataId, ItemId);
-
- public override string ToString() => $"UseItem({ItemId} on ground at {DataId})";
+ public override string ToString() => $"UseItem({ItemId} on ground at {dataId})";
}
- internal sealed class UseOnPosition(
+ private sealed class UseOnPosition(
+ ElementId? questId,
+ Vector3 position,
+ uint itemId,
+ IList<QuestWorkValue?> completionQuestVariablesFlags,
GameFunctions gameFunctions,
QuestFunctions questFunctions,
ICondition condition,
ILogger<UseOnPosition> logger)
- : UseItemBase(questFunctions, condition, logger)
+ : UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger)
{
- public Vector3 Position { get; set; }
-
- public ITask With(ElementId? questId, Vector3 position, uint itemId,
- IList<QuestWorkValue?> completionQuestVariablesFlags)
- {
- QuestId = questId;
- Position = position;
- ItemId = itemId;
- CompletionQuestVariablesFlags = completionQuestVariablesFlags;
- return this;
- }
-
- protected override bool UseItem() => gameFunctions.UseItemOnPosition(Position, ItemId);
+ protected override bool UseItem() => gameFunctions.UseItemOnPosition(position, ItemId);
public override string ToString() =>
- $"UseItem({ItemId} on ground at {Position.ToString("G", CultureInfo.InvariantCulture)})";
+ $"UseItem({ItemId} on ground at {position.ToString("G", CultureInfo.InvariantCulture)})";
}
- internal sealed class UseOnObject(
+ private sealed class UseOnObject(
+ ElementId? questId,
+ uint dataId,
+ uint itemId,
+ IList<QuestWorkValue?> completionQuestVariablesFlags,
+ bool startingCombat,
QuestFunctions questFunctions,
GameFunctions gameFunctions,
ICondition condition,
ILogger<UseOnObject> logger)
- : UseItemBase(questFunctions, condition, logger)
+ : UseItemBase(questId, itemId, completionQuestVariablesFlags, startingCombat, questFunctions, condition, logger)
{
- public uint DataId { get; set; }
-
- public ITask With(ElementId? questId, uint dataId, uint itemId,
- IList<QuestWorkValue?> completionQuestVariablesFlags,
- bool startingCombat = false)
- {
- QuestId = questId;
- DataId = dataId;
- ItemId = itemId;
- StartingCombat = startingCombat;
- CompletionQuestVariablesFlags = completionQuestVariablesFlags;
- return this;
- }
+ protected override bool UseItem() => gameFunctions.UseItem(dataId, ItemId);
- protected override bool UseItem() => gameFunctions.UseItem(DataId, ItemId);
-
- public override string ToString() => $"UseItem({ItemId} on {DataId})";
+ public override string ToString() => $"UseItem({ItemId} on {dataId})";
}
- internal sealed class Use(
+ private sealed class Use(
+ ElementId? questId,
+ uint itemId,
+ IList<QuestWorkValue?> completionQuestVariablesFlags,
GameFunctions gameFunctions,
QuestFunctions questFunctions,
ICondition condition,
ILogger<Use> logger)
- : UseItemBase(questFunctions, condition, logger)
+ : UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger)
{
- public ITask With(ElementId? questId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
- {
- QuestId = questId;
- ItemId = itemId;
- CompletionQuestVariablesFlags = completionQuestVariablesFlags;
- return this;
- }
-
protected override bool UseItem() => gameFunctions.UseItem(ItemId);
public override string ToString() => $"UseItem({ItemId})";
using LLib.GameUI;
using Microsoft.Extensions.DependencyInjection;
using Questionable.Controller.Steps.Common;
+using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
internal static class InitiateLeve
{
- internal sealed class Factory(IServiceProvider serviceProvider, ICondition condition) : ITaskFactory
+ internal sealed class Factory(IGameGui gameGui, ICondition condition) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.InitiateLeve)
yield break;
- yield return serviceProvider.GetRequiredService<SkipInitiateIfActive>().With(quest.Id);
- yield return serviceProvider.GetRequiredService<OpenJournal>().With(quest.Id);
- yield return serviceProvider.GetRequiredService<Initiate>().With(quest.Id);
- yield return serviceProvider.GetRequiredService<SelectDifficulty>();
+ yield return new SkipInitiateIfActive(quest.Id);
+ yield return new OpenJournal(quest.Id);
+ yield return new Initiate(quest.Id, gameGui);
+ yield return new SelectDifficulty(gameGui);
yield return new WaitConditionTask(() => condition[ConditionFlag.BoundByDuty], "Wait(BoundByDuty)");
}
}
- internal sealed unsafe class SkipInitiateIfActive : ITask
+ internal sealed unsafe class SkipInitiateIfActive(ElementId elementId) : ITask
{
- private ElementId _elementId = null!;
-
- public ITask With(ElementId elementId)
- {
- _elementId = elementId;
- return this;
- }
-
public bool Start() => true;
public ETaskResult Update()
if (director != null &&
director->EventHandlerInfo != null &&
director->EventHandlerInfo->EventId.ContentId == EventHandlerType.GatheringLeveDirector &&
- director->ContentId == _elementId.Value)
+ director->ContentId == elementId.Value)
return ETaskResult.SkipRemainingTasksForStep;
return ETaskResult.TaskComplete;
}
- public override string ToString() => $"CheckIfAlreadyActive({_elementId})";
+ public override string ToString() => $"CheckIfAlreadyActive({elementId})";
}
- internal sealed unsafe class OpenJournal : ITask
+ internal sealed unsafe class OpenJournal(ElementId elementId) : ITask
{
- private ElementId _elementId = null!;
- private uint _questType;
+ private readonly uint _questType = elementId is LeveId ? 2u : 1u;
private DateTime _openedAt = DateTime.MinValue;
- public ITask With(ElementId elementId)
- {
- _elementId = elementId;
- _questType = _elementId is LeveId ? 2u : 1u;
- return this;
- }
-
public bool Start()
{
- AgentQuestJournal.Instance()->OpenForQuest(_elementId.Value, _questType);
+ AgentQuestJournal.Instance()->OpenForQuest(elementId.Value, _questType);
_openedAt = DateTime.Now;
return true;
}
{
AgentQuestJournal* agentQuestJournal = AgentQuestJournal.Instance();
if (agentQuestJournal->IsAgentActive() &&
- agentQuestJournal->SelectedQuestId == _elementId.Value &&
+ agentQuestJournal->SelectedQuestId == elementId.Value &&
agentQuestJournal->SelectedQuestType == _questType)
return ETaskResult.TaskComplete;
if (DateTime.Now > _openedAt.AddSeconds(3))
{
- AgentQuestJournal.Instance()->OpenForQuest(_elementId.Value, _questType);
+ AgentQuestJournal.Instance()->OpenForQuest(elementId.Value, _questType);
_openedAt = DateTime.Now;
}
return ETaskResult.StillRunning;
}
- public override string ToString() => $"OpenJournal({_elementId})";
+ public override string ToString() => $"OpenJournal({elementId})";
}
- internal sealed unsafe class Initiate(IGameGui gameGui) : ITask
+ internal sealed unsafe class Initiate(ElementId elementId, IGameGui gameGui) : ITask
{
- private ElementId _elementId = null!;
-
- public ITask With(ElementId elementId)
- {
- _elementId = elementId;
- return this;
- }
-
public bool Start() => true;
public ETaskResult Update()
var pickQuest = stackalloc AtkValue[]
{
new() { Type = ValueType.Int, Int = 4 },
- new() { Type = ValueType.UInt, Int = _elementId.Value }
+ new() { Type = ValueType.UInt, Int = elementId.Value }
};
addonJournalDetail->FireCallback(2, pickQuest);
return ETaskResult.TaskComplete;
return ETaskResult.StillRunning;
}
- public override string ToString() => $"InitiateLeve({_elementId})";
+ public override string ToString() => $"InitiateLeve({elementId})";
}
internal sealed unsafe class SelectDifficulty(IGameGui gameGui) : ITask
internal static class AethernetShortcut
{
- internal sealed class Factory(IServiceProvider serviceProvider, MovementController movementController)
+ internal sealed class Factory(
+ MovementController movementController,
+ AetheryteFunctions aetheryteFunctions,
+ GameFunctions gameFunctions,
+ IClientState clientState,
+ AetheryteData aetheryteData,
+ TerritoryData territoryData,
+ LifestreamIpc lifestreamIpc,
+ ICondition condition,
+ ILoggerFactory loggerFactory)
: ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
"Wait(navmesh ready)");
- yield return serviceProvider.GetRequiredService<UseAethernetShortcut>()
- .With(step.AethernetShortcut.From, step.AethernetShortcut.To, step.SkipConditions?.AethernetShortcutIf);
+ yield return Use(step.AethernetShortcut.From, step.AethernetShortcut.To,
+ step.SkipConditions?.AethernetShortcutIf);
+ }
+
+ public ITask Use(EAetheryteLocation from, EAetheryteLocation to, SkipAetheryteCondition? skipConditions = null)
+ {
+ return new UseAethernetShortcut(from, to, skipConditions ?? new(),
+ loggerFactory.CreateLogger<UseAethernetShortcut>(), aetheryteFunctions, gameFunctions, clientState,
+ aetheryteData, territoryData, lifestreamIpc, movementController, condition);
}
}
internal sealed class UseAethernetShortcut(
+ EAetheryteLocation from,
+ EAetheryteLocation to,
+ SkipAetheryteCondition skipConditions,
ILogger<UseAethernetShortcut> logger,
AetheryteFunctions aetheryteFunctions,
GameFunctions gameFunctions,
private bool _triedMounting;
private DateTime _continueAt = DateTime.MinValue;
- public EAetheryteLocation From { get; set; }
- public EAetheryteLocation To { get; set; }
- public SkipAetheryteCondition SkipConditions { get; set; } = null!;
-
- public ITask With(EAetheryteLocation from, EAetheryteLocation to,
- SkipAetheryteCondition? skipConditions = null)
- {
- From = from;
- To = to;
- SkipConditions = skipConditions ?? new();
- return this;
- }
+ public EAetheryteLocation From => from;
+ public EAetheryteLocation To => to;
public bool Start()
{
- if (!SkipConditions.Never)
+ if (!skipConditions.Never)
{
- if (SkipConditions.InSameTerritory && clientState.TerritoryType == aetheryteData.TerritoryIds[To])
+ if (skipConditions.InSameTerritory && clientState.TerritoryType == aetheryteData.TerritoryIds[to])
{
logger.LogInformation("Skipping aethernet shortcut because the target is in the same territory");
return false;
}
- if (SkipConditions.InTerritory.Contains(clientState.TerritoryType))
+ if (skipConditions.InTerritory.Contains(clientState.TerritoryType))
{
logger.LogInformation(
"Skipping aethernet shortcut because the target is in the specified territory");
return false;
}
- if (SkipConditions.AetheryteLocked != null &&
- !aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteLocked.Value))
+ if (skipConditions.AetheryteLocked != null &&
+ !aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteLocked.Value))
{
logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is locked");
return false;
}
- if (SkipConditions.AetheryteUnlocked != null &&
- aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteUnlocked.Value))
+ if (skipConditions.AetheryteUnlocked != null &&
+ aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteUnlocked.Value))
{
logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is unlocked");
return false;
}
}
- if (aetheryteFunctions.IsAetheryteUnlocked(From) &&
- aetheryteFunctions.IsAetheryteUnlocked(To))
+ if (aetheryteFunctions.IsAetheryteUnlocked(from) &&
+ aetheryteFunctions.IsAetheryteUnlocked(to))
{
ushort territoryType = clientState.TerritoryType;
Vector3 playerPosition = clientState.LocalPlayer!.Position;
// closer to the source
- if (aetheryteData.CalculateDistance(playerPosition, territoryType, From) <
- aetheryteData.CalculateDistance(playerPosition, territoryType, To))
+ if (aetheryteData.CalculateDistance(playerPosition, territoryType, from) <
+ aetheryteData.CalculateDistance(playerPosition, territoryType, to))
{
- if (aetheryteData.CalculateDistance(playerPosition, territoryType, From) <
- (From.IsFirmamentAetheryte() ? 11f : 4f))
+ if (aetheryteData.CalculateDistance(playerPosition, territoryType, from) <
+ (from.IsFirmamentAetheryte() ? 11f : 4f))
{
DoTeleport();
return true;
}
- else if (From == EAetheryteLocation.SolutionNine)
+ else if (from == EAetheryteLocation.SolutionNine)
{
logger.LogInformation("Moving to S9 aetheryte");
List<Vector3> nearbyPoints =
Vector3 closestPoint = nearbyPoints.MinBy(x => (playerPosition - x).Length());
_moving = true;
- movementController.NavigateTo(EMovementType.Quest, (uint)From, closestPoint, false, true,
+ movementController.NavigateTo(EMovementType.Quest, (uint)from, closestPoint, false, true,
0.25f);
return true;
}
else
{
if (territoryData.CanUseMount(territoryType) &&
- aetheryteData.CalculateDistance(playerPosition, territoryType, From) > 30 &&
+ aetheryteData.CalculateDistance(playerPosition, territoryType, from) > 30 &&
!gameFunctions.HasStatusPreventingMount())
{
_triedMounting = gameFunctions.Mount();
else
logger.LogWarning(
"Aethernet shortcut not unlocked (from: {FromAetheryte}, to: {ToAetheryte}), walking manually",
- From, To);
+ from, to);
return false;
}
{
logger.LogInformation("Moving to aethernet shortcut");
_moving = true;
- movementController.NavigateTo(EMovementType.Quest, (uint)From, aetheryteData.Locations[From],
+ movementController.NavigateTo(EMovementType.Quest, (uint)from, aetheryteData.Locations[from],
false, true,
- From.IsFirmamentAetheryte()
+ from.IsFirmamentAetheryte()
? 4.4f
- : AetheryteConverter.IsLargeAetheryte(From)
+ : AetheryteConverter.IsLargeAetheryte(from)
? 10.9f
: 6.9f);
}
private void DoTeleport()
{
- if (From.IsFirmamentAetheryte())
+ if (from.IsFirmamentAetheryte())
{
logger.LogInformation("Using manual teleport interaction");
- _teleported = gameFunctions.InteractWith((uint)From, ObjectKind.EventObj);
+ _teleported = gameFunctions.InteractWith((uint)from, ObjectKind.EventObj);
}
else
{
- logger.LogInformation("Using lifestream to teleport to {Destination}", To);
- lifestreamIpc.Teleport(To);
+ logger.LogInformation("Using lifestream to teleport to {Destination}", to);
+ lifestreamIpc.Teleport(to);
_teleported = true;
}
}
return ETaskResult.StillRunning;
}
- if (aetheryteData.IsAirshipLanding(To))
+ if (aetheryteData.IsAirshipLanding(to))
{
if (aetheryteData.CalculateAirshipLandingDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
- clientState.TerritoryType, To) > 5)
+ clientState.TerritoryType, to) > 5)
return ETaskResult.StillRunning;
}
- else if (aetheryteData.IsCityAetheryte(To))
+ else if (aetheryteData.IsCityAetheryte(to))
{
if (aetheryteData.CalculateDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
- clientState.TerritoryType, To) > 20)
+ clientState.TerritoryType, to) > 20)
return ETaskResult.StillRunning;
}
else
{
// some overworld location (e.g. 'Tesselation (Lakeland)' would end up here
- if (clientState.TerritoryType != aetheryteData.TerritoryIds[To])
+ if (clientState.TerritoryType != aetheryteData.TerritoryIds[to])
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}
- public override string ToString() => $"UseAethernet({From} -> {To})";
+ public override string ToString() => $"UseAethernet({from} -> {to})";
}
}
internal static class AetheryteShortcut
{
internal sealed class Factory(
- IServiceProvider serviceProvider,
- AetheryteData aetheryteData) : ITaskFactory
+ AetheryteData aetheryteData,
+ AetheryteFunctions aetheryteFunctions,
+ QuestFunctions questFunctions,
+ IClientState clientState,
+ IChatGui chatGui,
+ ILoggerFactory loggerFactory) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.AetheryteShortcut == null)
yield break;
- yield return serviceProvider.GetRequiredService<UseAetheryteShortcut>()
- .With(step, quest.Id, step.AetheryteShortcut.Value,
- aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]);
- yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>()
- .With(TimeSpan.FromSeconds(0.5));
+ yield return Use(step, quest.Id, step.AetheryteShortcut.Value,
+ aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]);
+ yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(0.5));
+ }
+
+ public ITask Use(QuestStep? step, ElementId? elementId, EAetheryteLocation targetAetheryte,
+ ushort expectedTerritoryId)
+ {
+ return new UseAetheryteShortcut(step, elementId, targetAetheryte, expectedTerritoryId,
+ loggerFactory.CreateLogger<UseAetheryteShortcut>(), aetheryteFunctions, questFunctions, clientState,
+ chatGui, aetheryteData);
}
}
- internal sealed class UseAetheryteShortcut(
+ /// <param name="expectedTerritoryId">If using an aethernet shortcut after, the aetheryte's territory-id and the step's territory-id can differ, we always use the aetheryte's territory-id.</param>
+ private sealed class UseAetheryteShortcut(
+ QuestStep? step,
+ ElementId? elementId,
+ EAetheryteLocation targetAetheryte,
+ ushort expectedTerritoryId,
ILogger<UseAetheryteShortcut> logger,
AetheryteFunctions aetheryteFunctions,
QuestFunctions questFunctions,
private bool _teleported;
private DateTime _continueAt;
- public QuestStep? Step { get; set; }
- public ElementId? ElementId { get; set; }
- public EAetheryteLocation TargetAetheryte { get; set; }
-
- /// <summary>
- /// If using an aethernet shortcut after, the aetheryte's territory-id and the step's territory-id can differ,
- /// we always use the aetheryte's territory-id.
- /// </summary>
- public ushort ExpectedTerritoryId { get; set; }
-
- public ITask With(QuestStep? step, ElementId? elementId, EAetheryteLocation targetAetheryte,
- ushort expectedTerritoryId)
- {
- Step = step;
- ElementId = elementId;
- TargetAetheryte = targetAetheryte;
- ExpectedTerritoryId = expectedTerritoryId;
- return this;
- }
-
public bool Start() => !ShouldSkipTeleport();
public ETaskResult Update()
return ETaskResult.StillRunning;
}
- if (clientState.TerritoryType == ExpectedTerritoryId)
+ if (clientState.TerritoryType == expectedTerritoryId)
return ETaskResult.TaskComplete;
return ETaskResult.StillRunning;
private bool ShouldSkipTeleport()
{
ushort territoryType = clientState.TerritoryType;
- if (Step != null)
+ if (step != null)
{
- var skipConditions = Step.SkipConditions?.AetheryteShortcutIf ?? new();
+ var skipConditions = step.SkipConditions?.AetheryteShortcutIf ?? new();
if (skipConditions is { Never: false })
{
if (skipConditions.InTerritory.Contains(territoryType))
return true;
}
- if (ElementId != null)
+ if (elementId != null)
{
- QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(ElementId);
+ QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(elementId);
if (skipConditions.RequiredQuestVariablesNotMet &&
questWork != null &&
- !QuestWorkUtils.MatchesRequiredQuestWorkConfig(Step.RequiredQuestVariables, questWork,
+ !QuestWorkUtils.MatchesRequiredQuestWorkConfig(step.RequiredQuestVariables, questWork,
logger))
{
logger.LogInformation("Skipping aetheryte teleport, as required variables do not match");
}
-
- if (skipConditions.NearPosition is { } nearPosition && clientState.TerritoryType == Step.TerritoryId)
+ if (skipConditions.NearPosition is { } nearPosition &&
+ clientState.TerritoryType == step.TerritoryId)
{
- if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <= nearPosition.MaximumDistance)
+ if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <=
+ nearPosition.MaximumDistance)
{
logger.LogInformation("Skipping aetheryte shortcut, as we're near the position");
return true;
}
}
- if (ExpectedTerritoryId == territoryType)
+ if (expectedTerritoryId == territoryType)
{
if (!skipConditions.Never)
{
}
Vector3 pos = clientState.LocalPlayer!.Position;
- if (Step.Position != null &&
- (pos - Step.Position.Value).Length() < Step.CalculateActualStopDistance())
+ if (step.Position != null &&
+ (pos - step.Position.Value).Length() < step.CalculateActualStopDistance())
{
logger.LogInformation("Skipping aetheryte teleport, we're near the target");
return true;
}
- if (aetheryteData.CalculateDistance(pos, territoryType, TargetAetheryte) < 20 ||
- (Step.AethernetShortcut != null &&
- (aetheryteData.CalculateDistance(pos, territoryType, Step.AethernetShortcut.From) < 20 ||
- aetheryteData.CalculateDistance(pos, territoryType, Step.AethernetShortcut.To) < 20)))
+ if (aetheryteData.CalculateDistance(pos, territoryType, targetAetheryte) < 20 ||
+ (step.AethernetShortcut != null &&
+ (aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.From) < 20 ||
+ aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.To) < 20)))
{
logger.LogInformation("Skipping aetheryte teleport");
return true;
private bool DoTeleport()
{
- if (!aetheryteFunctions.CanTeleport(TargetAetheryte))
+ if (!aetheryteFunctions.CanTeleport(targetAetheryte))
{
if (!aetheryteFunctions.IsTeleportUnlocked())
throw new TaskException("Teleport is not unlocked, attune to any aetheryte first.");
_continueAt = DateTime.Now.AddSeconds(8);
- if (!aetheryteFunctions.IsAetheryteUnlocked(TargetAetheryte))
+ if (!aetheryteFunctions.IsAetheryteUnlocked(targetAetheryte))
{
- chatGui.PrintError($"[Questionable] Aetheryte {TargetAetheryte} is not unlocked.");
+ chatGui.PrintError($"[Questionable] Aetheryte {targetAetheryte} is not unlocked.");
throw new TaskException("Aetheryte is not unlocked");
}
- else if (aetheryteFunctions.TeleportAetheryte(TargetAetheryte))
+ else if (aetheryteFunctions.TeleportAetheryte(targetAetheryte))
{
logger.LogInformation("Travelling via aetheryte...");
return true;
}
}
- public override string ToString() => $"UseAetheryte({TargetAetheryte})";
+ public override string ToString() => $"UseAetheryte({targetAetheryte})";
}
}
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameData;
using Lumina.Excel.GeneratedSheets;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Questionable.Controller.Steps.Common;
using Questionable.External;
using Questionable.Model.Questing;
+using Mount = Questionable.Controller.Steps.Common.Mount;
using Quest = Questionable.Model.Quest;
namespace Questionable.Controller.Steps.Shared;
internal static class Craft
{
- internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+ internal sealed class Factory(
+ IDataManager dataManager,
+ IClientState clientState,
+ ArtisanIpc artisanIpc,
+ Mount.Factory mountFactory,
+ ILoggerFactory loggerFactory) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
ArgumentNullException.ThrowIfNull(step.ItemCount);
return
[
- serviceProvider.GetRequiredService<UnmountTask>(),
- serviceProvider.GetRequiredService<DoCraft>()
- .With(step.ItemId.Value, step.ItemCount.Value)
+ mountFactory.Unmount(),
+ Craft(step.ItemId.Value, step.ItemCount.Value)
];
}
+
+ public ITask Craft(uint itemId, int itemCount) =>
+ new DoCraft(itemId, itemCount, dataManager, clientState, artisanIpc, loggerFactory.CreateLogger<DoCraft>());
}
- internal sealed class DoCraft(
+ private sealed class DoCraft(
+ uint itemId,
+ int itemCount,
IDataManager dataManager,
IClientState clientState,
ArtisanIpc artisanIpc,
ILogger<DoCraft> logger) : ITask
{
- private uint _itemId;
- private int _itemCount;
-
- public ITask With(uint itemId, int itemCount)
- {
- _itemId = itemId;
- _itemCount = itemCount;
- return this;
- }
-
public bool Start()
{
if (HasRequestedItems())
{
- logger.LogInformation("Already own {ItemCount}x {ItemId}", _itemCount, _itemId);
+ logger.LogInformation("Already own {ItemCount}x {ItemId}", itemCount, itemId);
return false;
}
- RecipeLookup? recipeLookup = dataManager.GetExcelSheet<RecipeLookup>()!.GetRow(_itemId);
+ RecipeLookup? recipeLookup = dataManager.GetExcelSheet<RecipeLookup>()!.GetRow(itemId);
if (recipeLookup == null)
- throw new TaskException($"Item {_itemId} is not craftable");
+ throw new TaskException($"Item {itemId} is not craftable");
uint recipeId = (EClassJob)clientState.LocalPlayer!.ClassJob.Id switch
{
}
if (recipeId == 0)
- throw new TaskException($"Unable to determine recipe for item {_itemId}");
+ throw new TaskException($"Unable to determine recipe for item {itemId}");
- int remainingItemCount = _itemCount - GetOwnedItemCount();
+ int remainingItemCount = itemCount - GetOwnedItemCount();
logger.LogInformation(
"Starting craft for item {ItemId} with recipe {RecipeId} for {RemainingItemCount} items",
- _itemId, recipeId, remainingItemCount);
+ itemId, recipeId, remainingItemCount);
if (!artisanIpc.CraftItem((ushort)recipeId, remainingItemCount))
throw new TaskException($"Failed to start Artisan craft for recipe {recipeId}");
return ETaskResult.StillRunning;
}
- private bool HasRequestedItems() => GetOwnedItemCount() >= _itemCount;
+ private bool HasRequestedItems() => GetOwnedItemCount() >= itemCount;
private unsafe int GetOwnedItemCount()
{
InventoryManager* inventoryManager = InventoryManager.Instance();
- return inventoryManager->GetInventoryItemCount(_itemId, isHq: false, checkEquipped: false)
- + inventoryManager->GetInventoryItemCount(_itemId, isHq: true, checkEquipped: false);
+ return inventoryManager->GetInventoryItemCount(itemId, isHq: false, checkEquipped: false)
+ + inventoryManager->GetInventoryItemCount(itemId, isHq: true, checkEquipped: false);
}
- public override string ToString() => $"Craft {_itemCount}x {_itemId} (with Artisan)";
+ public override string ToString() => $"Craft {itemCount}x {itemId} (with Artisan)";
}
}
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
using Questionable.Data;
-using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Gathering;
using Questionable.Model.Questing;
internal sealed class Factory(
IServiceProvider serviceProvider,
MovementController movementController,
+ GatheringController gatheringController,
GatheringPointRegistry gatheringPointRegistry,
IClientState clientState,
GatheringData gatheringData,
if (classJob != currentClassJob)
{
- yield return serviceProvider.GetRequiredService<SwitchClassJob>()
- .With(classJob);
+ yield return new SwitchClassJob(classJob, clientState);
}
if (HasRequiredItems(requiredGatheredItems))
foreach (var task in serviceProvider.GetRequiredService<TaskCreator>()
.CreateTasks(quest, gatheringSequence, gatheringStep))
if (task is WaitAtEnd.NextStep)
- yield return serviceProvider.GetRequiredService<SkipMarker>();
+ yield return CreateSkipMarkerTask();
else
yield return task;
}
yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
"Wait(navmesh ready)");
- yield return serviceProvider.GetRequiredService<StartGathering>()
- .With(gatheringPointId, requiredGatheredItems);
+ yield return CreateStartGatheringTask(gatheringPointId, requiredGatheredItems);
}
}
minCollectability: (short)requiredGatheredItems.Collectability) >=
requiredGatheredItems.ItemCount;
}
- }
- internal sealed class StartGathering(GatheringController gatheringController) : ITask
- {
- private GatheringPointId _gatheringPointId = null!;
- private GatheredItem _gatheredItem = null!;
+ private StartGathering CreateStartGatheringTask(GatheringPointId gatheringPointId, GatheredItem gatheredItem)
+ {
+ return new StartGathering(gatheringPointId, gatheredItem, gatheringController);
+ }
- public ITask With(GatheringPointId gatheringPointId, GatheredItem gatheredItem)
+ private static SkipMarker CreateSkipMarkerTask()
{
- _gatheringPointId = gatheringPointId;
- _gatheredItem = gatheredItem;
- return this;
+ return new SkipMarker();
}
+ }
+ private sealed class StartGathering(
+ GatheringPointId gatheringPointId,
+ GatheredItem gatheredItem,
+ GatheringController gatheringController) : ITask
+ {
public bool Start()
{
- return gatheringController.Start(new GatheringController.GatheringRequest(_gatheringPointId,
- _gatheredItem.ItemId, _gatheredItem.AlternativeItemId, _gatheredItem.ItemCount,
- _gatheredItem.Collectability));
+ return gatheringController.Start(new GatheringController.GatheringRequest(gatheringPointId,
+ gatheredItem.ItemId, gatheredItem.AlternativeItemId, gatheredItem.ItemCount,
+ gatheredItem.Collectability));
}
public ETaskResult Update()
public override string ToString()
{
- if (_gatheredItem.Collectability == 0)
- return $"Gather({_gatheredItem.ItemCount}x {_gatheredItem.ItemId})";
+ if (gatheredItem.Collectability == 0)
+ return $"Gather({gatheredItem.ItemCount}x {gatheredItem.ItemId})";
else
return
- $"Gather({_gatheredItem.ItemCount}x {_gatheredItem.ItemId} {SeIconChar.Collectible.ToIconString()} {_gatheredItem.Collectability})";
+ $"Gather({gatheredItem.ItemCount}x {gatheredItem.ItemId} {SeIconChar.Collectible.ToIconString()} {gatheredItem.Collectability})";
}
}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Numerics;
-using Dalamud.Game.ClientState.Conditions;
-using Dalamud.Game.ClientState.Objects.Types;
-using Dalamud.Game.Text.SeStringHandling;
-using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Client.Game;
-using FFXIVClientStructs.FFXIV.Client.Game.Character;
-using LLib;
-using Lumina.Excel.GeneratedSheets;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Questionable.Controller.Steps.Common;
-using Questionable.Data;
-using Questionable.Functions;
-using Questionable.Model;
-using Questionable.Model.Questing;
-using Action = System.Action;
-using Quest = Questionable.Model.Quest;
-
-namespace Questionable.Controller.Steps.Shared;
-
-internal static class Move
-{
- internal sealed class Factory(IServiceProvider serviceProvider, AetheryteData aetheryteData) : ITaskFactory
- {
- public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
- {
- if (step.Position != null)
- {
- var builder = serviceProvider.GetRequiredService<MoveBuilder>()
- .With(quest.Id, step, step.Position.Value);
- return builder.Build();
- }
- else if (step is { DataId: not null, StopDistance: not null })
- {
- var task = serviceProvider.GetRequiredService<ExpectToBeNearDataId>();
- task.DataId = step.DataId.Value;
- task.StopDistance = step.StopDistance.Value;
- return [task];
- }
- else if (step is { InteractionType: EInteractionType.AttuneAetheryte, Aetheryte: not null })
- {
- var builder = serviceProvider.GetRequiredService<MoveBuilder>()
- .With(quest.Id, step, aetheryteData.Locations[step.Aetheryte.Value]);
- return builder.Build();
- }
- else if (step is { InteractionType: EInteractionType.AttuneAethernetShard, AethernetShard: not null })
- {
- var builder = serviceProvider.GetRequiredService<MoveBuilder>().With(quest.Id, step,
- aetheryteData.Locations[step.AethernetShard.Value]);
- return builder.Build();
- }
-
- return [];
- }
- }
-
- internal sealed class MoveBuilder(
- IServiceProvider serviceProvider,
- ILogger<MoveBuilder> logger,
- GameFunctions gameFunctions,
- IClientState clientState,
- MovementController movementController,
- TerritoryData territoryData,
- AetheryteData aetheryteData)
- {
- private ElementId _questId = null!;
- private QuestStep _step = null!;
- private Vector3 _destination;
-
- public MoveBuilder With(ElementId questId, QuestStep step, Vector3 destination)
- {
- _questId = questId;
- _step = step;
- _destination = destination;
- return this;
- }
-
- public IEnumerable<ITask> Build()
- {
- if (_step.InteractionType == EInteractionType.Jump && _step.JumpDestination != null &&
- (clientState.LocalPlayer!.Position - _step.JumpDestination.Position).Length() <=
- (_step.JumpDestination.StopDistance ?? 1f))
- {
- logger.LogInformation("We're at the jump destination, skipping movement");
- yield break;
- }
-
- yield return new WaitConditionTask(() => clientState.TerritoryType == _step.TerritoryId,
- $"Wait(territory: {territoryData.GetNameAndId(_step.TerritoryId)})");
-
- if (!_step.DisableNavmesh)
- yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
- "Wait(navmesh ready)");
-
- float stopDistance = _step.CalculateActualStopDistance();
- Vector3? position = clientState.LocalPlayer?.Position;
- float actualDistance = position == null ? float.MaxValue : Vector3.Distance(position.Value, _destination);
-
- // if we teleport to a different zone, assume we always need to move; this is primarily relevant for cases
- // where you're e.g. in Lakeland, and the step navigates via Crystarium → Tesselation back into the same
- // zone.
- //
- // Side effects of this check being broken include:
- // - mounting when near the target npc (if you spawn close enough for the next step)
- // - trying to fly when near the target npc (if close enough where no movement is required)
- if (_step.AetheryteShortcut != null &&
- aetheryteData.TerritoryIds[_step.AetheryteShortcut.Value] != _step.TerritoryId)
- {
- logger.LogDebug("Aetheryte: Changing distance to max, previous distance: {Distance}", actualDistance);
- actualDistance = float.MaxValue;
- }
-
- // In particular, MoveBuilder is used so early that it'll have the position when you're starting gathering,
- // not when you're finished.
- if (_questId is SatisfactionSupplyNpcId)
- {
- logger.LogDebug("SatisfactionSupply: Changing distance to max, previous distance: {Distance}",
- actualDistance);
- actualDistance = float.MaxValue;
- }
-
- if (_step.Mount == true)
- yield return serviceProvider.GetRequiredService<MountTask>()
- .With(_step.TerritoryId, MountTask.EMountIf.Always);
- else if (_step.Mount == false)
- yield return serviceProvider.GetRequiredService<UnmountTask>();
-
- if (!_step.DisableNavmesh)
- {
- if (_step.Mount == null)
- {
- MountTask.EMountIf mountIf =
- actualDistance > stopDistance && _step.Fly == true &&
- gameFunctions.IsFlyingUnlocked(_step.TerritoryId)
- ? MountTask.EMountIf.Always
- : MountTask.EMountIf.AwayFromPosition;
- yield return serviceProvider.GetRequiredService<MountTask>()
- .With(_step.TerritoryId, mountIf, _destination);
- }
-
- if (actualDistance > stopDistance)
- {
- yield return serviceProvider.GetRequiredService<MoveInternal>()
- .With(_step, _destination);
- }
- else
- logger.LogInformation("Skipping move task, distance: {ActualDistance} < {StopDistance}",
- actualDistance, stopDistance);
- }
- else
- {
- // navmesh won't move close enough
- if (actualDistance > stopDistance)
- {
- yield return serviceProvider.GetRequiredService<MoveInternal>()
- .With(_step, _destination);
- }
- else
- logger.LogInformation("Skipping move task, distance: {ActualDistance} < {StopDistance}",
- actualDistance, stopDistance);
- }
-
- if (_step.Fly == true && _step.Land == true)
- yield return serviceProvider.GetRequiredService<Land>();
- }
- }
-
- internal sealed class MoveInternal(
- MovementController movementController,
- GameFunctions gameFunctions,
- ILogger<MoveInternal> logger,
- ICondition condition,
- IDataManager dataManager) : ITask, IToastAware
- {
- private string _cannotExecuteAtThisTime = dataManager.GetString<LogMessage>(579, x => x.Text)!;
-
- public Action StartAction { get; set; } = null!;
- public Vector3 Destination { get; set; }
-
- public ITask With(QuestStep step, Vector3 destination)
- {
- return With(
- territoryId: step.TerritoryId,
- destination: destination,
- stopDistance: step.CalculateActualStopDistance(),
- dataId: step.DataId,
- disableNavMesh: step.DisableNavmesh,
- sprint: step.Sprint != false,
- fly: step.Fly == true,
- land: step.Land == true,
- ignoreDistanceToObject: step.IgnoreDistanceToObject == true);
- }
-
- public ITask With(ushort territoryId, Vector3 destination, float? stopDistance = null, uint? dataId = null,
- bool disableNavMesh = false, bool sprint = true, bool fly = false, bool land = false,
- bool ignoreDistanceToObject = false)
- {
- Destination = destination;
-
- if (!gameFunctions.IsFlyingUnlocked(territoryId))
- {
- fly = false;
- land = false;
- }
-
- if (!disableNavMesh)
- {
- StartAction = () =>
- movementController.NavigateTo(EMovementType.Quest, dataId, Destination,
- fly: fly,
- sprint: sprint,
- stopDistance: stopDistance,
- ignoreDistanceToObject: ignoreDistanceToObject,
- land: land);
- }
- else
- {
- StartAction = () =>
- movementController.NavigateTo(EMovementType.Quest, dataId, [Destination],
- fly: fly,
- sprint: sprint,
- stopDistance: stopDistance,
- ignoreDistanceToObject: ignoreDistanceToObject,
- land: land);
- }
-
- return this;
- }
-
- public bool Start()
- {
- logger.LogInformation("Moving to {Destination}", Destination.ToString("G", CultureInfo.InvariantCulture));
- StartAction();
- return true;
- }
-
- public ETaskResult Update()
- {
- if (movementController.IsPathfinding || movementController.IsPathRunning)
- return ETaskResult.StillRunning;
-
- DateTime movementStartedAt = movementController.MovementStartedAt;
- if (movementStartedAt == DateTime.MaxValue || movementStartedAt.AddSeconds(2) >= DateTime.Now)
- return ETaskResult.StillRunning;
-
- return ETaskResult.TaskComplete;
- }
-
- public override string ToString() => $"MoveTo({Destination.ToString("G", CultureInfo.InvariantCulture)})";
-
- public bool OnErrorToast(SeString message)
- {
- if (GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue) &&
- condition[ConditionFlag.Diving])
- return true;
-
- return false;
- }
- }
-
- internal sealed class ExpectToBeNearDataId(GameFunctions gameFunctions, IClientState clientState) : ITask
- {
- public uint DataId { get; set; }
- public float StopDistance { get; set; }
-
- public bool Start() => true;
-
- public ETaskResult Update()
- {
- IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId);
- if (gameObject == null ||
- (gameObject.Position - clientState.LocalPlayer!.Position).Length() > StopDistance)
- {
- throw new TaskException("Object not found or too far away, no position so we can't move");
- }
-
- return ETaskResult.TaskComplete;
- }
- }
-
- internal sealed class Land(IClientState clientState, ICondition condition, ILogger<Land> logger) : ITask
- {
- private bool _landing;
- private DateTime _continueAt;
-
- public bool Start()
- {
- if (!condition[ConditionFlag.InFlight])
- {
- logger.LogInformation("Not flying, not attempting to land");
- return false;
- }
-
- _landing = AttemptLanding();
- _continueAt = DateTime.Now.AddSeconds(0.25);
- return true;
- }
-
- public ETaskResult Update()
- {
- if (DateTime.Now < _continueAt)
- return ETaskResult.StillRunning;
-
- if (condition[ConditionFlag.InFlight])
- {
- if (!_landing)
- {
- _landing = AttemptLanding();
- _continueAt = DateTime.Now.AddSeconds(0.25);
- }
-
- return ETaskResult.StillRunning;
- }
-
- return ETaskResult.TaskComplete;
- }
-
- private unsafe bool AttemptLanding()
- {
- var character = (Character*)(clientState.LocalPlayer?.Address ?? 0);
- if (character != null)
- {
- if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23) == 0)
- {
- logger.LogInformation("Attempting to land");
- return ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23);
- }
- }
-
- return false;
- }
- }
-}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Numerics;
+using Dalamud.Game.ClientState.Conditions;
+using Dalamud.Game.ClientState.Objects.Types;
+using Dalamud.Game.Text.SeStringHandling;
+using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using FFXIVClientStructs.FFXIV.Client.Game.Character;
+using LLib;
+using Lumina.Excel.GeneratedSheets;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Questionable.Controller.Steps.Common;
+using Questionable.Data;
+using Questionable.Functions;
+using Questionable.Model;
+using Questionable.Model.Questing;
+using Action = System.Action;
+using Mount = Questionable.Controller.Steps.Common.Mount;
+using Quest = Questionable.Model.Quest;
+
+namespace Questionable.Controller.Steps.Shared;
+
+internal static class MoveTo
+{
+ internal sealed class Factory(
+ MovementController movementController,
+ GameFunctions gameFunctions,
+ ICondition condition,
+ IDataManager dataManager,
+ IClientState clientState,
+ AetheryteData aetheryteData,
+ TerritoryData territoryData,
+ ILoggerFactory loggerFactory,
+ Mount.Factory mountFactory,
+ ILogger<Factory> logger) : ITaskFactory
+ {
+ public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
+ {
+ if (step.Position != null)
+ {
+ return CreateMountTasks(quest.Id, step, step.Position.Value);
+ }
+ else if (step is { DataId: not null, StopDistance: not null })
+ {
+ return [ExpectToBeNearDataId(step.DataId.Value, step.StopDistance.Value)];
+ }
+ else if (step is { InteractionType: EInteractionType.AttuneAetheryte, Aetheryte: not null })
+ {
+ return CreateMountTasks(quest.Id, step, aetheryteData.Locations[step.Aetheryte.Value]);
+ }
+ else if (step is { InteractionType: EInteractionType.AttuneAethernetShard, AethernetShard: not null })
+ {
+ return CreateMountTasks(quest.Id, step, aetheryteData.Locations[step.AethernetShard.Value]);
+ }
+
+ return [];
+ }
+
+ public ITask Move(QuestStep step, Vector3 destination)
+ {
+ return Move(new MoveParams(step, destination));
+ }
+
+ public ITask Move(MoveParams moveParams)
+ {
+ return new MoveInternal(moveParams, movementController, gameFunctions,
+ loggerFactory.CreateLogger<MoveInternal>(), condition, dataManager);
+ }
+
+ public ITask Land()
+ {
+ return new LandTask(clientState, condition, loggerFactory.CreateLogger<LandTask>());
+ }
+
+ public ITask ExpectToBeNearDataId(uint dataId, float stopDistance)
+ {
+ return new WaitForNearDataId(dataId, stopDistance, gameFunctions, clientState);
+ }
+
+ public IEnumerable<ITask> CreateMountTasks(ElementId questId, QuestStep step, Vector3 destination)
+ {
+ if (step.InteractionType == EInteractionType.Jump && step.JumpDestination != null &&
+ (clientState.LocalPlayer!.Position - step.JumpDestination.Position).Length() <=
+ (step.JumpDestination.StopDistance ?? 1f))
+ {
+ logger.LogInformation("We're at the jump destination, skipping movement");
+ yield break;
+ }
+
+ yield return new WaitConditionTask(() => clientState.TerritoryType == step.TerritoryId,
+ $"Wait(territory: {territoryData.GetNameAndId(step.TerritoryId)})");
+
+ if (!step.DisableNavmesh)
+ yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
+ "Wait(navmesh ready)");
+
+ float stopDistance = step.CalculateActualStopDistance();
+ Vector3? position = clientState.LocalPlayer?.Position;
+ float actualDistance = position == null ? float.MaxValue : Vector3.Distance(position.Value, destination);
+
+ // if we teleport to a different zone, assume we always need to move; this is primarily relevant for cases
+ // where you're e.g. in Lakeland, and the step navigates via Crystarium → Tesselation back into the same
+ // zone.
+ //
+ // Side effects of this check being broken include:
+ // - mounting when near the target npc (if you spawn close enough for the next step)
+ // - trying to fly when near the target npc (if close enough where no movement is required)
+ if (step.AetheryteShortcut != null &&
+ aetheryteData.TerritoryIds[step.AetheryteShortcut.Value] != step.TerritoryId)
+ {
+ logger.LogDebug("Aetheryte: Changing distance to max, previous distance: {Distance}", actualDistance);
+ actualDistance = float.MaxValue;
+ }
+
+ // In particular, MoveBuilder is used so early that it'll have the position when you're starting gathering,
+ // not when you're finished.
+ if (questId is SatisfactionSupplyNpcId)
+ {
+ logger.LogDebug("SatisfactionSupply: Changing distance to max, previous distance: {Distance}",
+ actualDistance);
+ actualDistance = float.MaxValue;
+ }
+
+ if (step.Mount == true)
+ yield return mountFactory.Mount(step.TerritoryId, Mount.EMountIf.Always);
+ else if (step.Mount == false)
+ yield return mountFactory.Unmount();
+
+ if (!step.DisableNavmesh)
+ {
+ if (step.Mount == null)
+ {
+ Mount.EMountIf mountIf =
+ actualDistance > stopDistance && step.Fly == true &&
+ gameFunctions.IsFlyingUnlocked(step.TerritoryId)
+ ? Mount.EMountIf.Always
+ : Mount.EMountIf.AwayFromPosition;
+ yield return mountFactory.Mount(step.TerritoryId, mountIf, destination);
+ }
+
+ if (actualDistance > stopDistance)
+ {
+ yield return Move(step, destination);
+ }
+ else
+ logger.LogInformation("Skipping move task, distance: {ActualDistance} < {StopDistance}",
+ actualDistance, stopDistance);
+ }
+ else
+ {
+ // navmesh won't move close enough
+ if (actualDistance > stopDistance)
+ {
+ yield return Move(step, destination);
+ }
+ else
+ logger.LogInformation("Skipping move task, distance: {ActualDistance} < {StopDistance}",
+ actualDistance, stopDistance);
+ }
+
+ if (step.Fly == true && step.Land == true)
+ yield return Land();
+ }
+ }
+
+ private sealed class MoveInternal : ITask, IToastAware
+ {
+ private readonly string _cannotExecuteAtThisTime;
+ private readonly MovementController _movementController;
+ private readonly ILogger<MoveInternal> _logger;
+ private readonly ICondition _condition;
+
+ private readonly Action _startAction;
+ private readonly Vector3 _destination;
+
+ public MoveInternal(MoveParams moveParams,
+ MovementController movementController,
+ GameFunctions gameFunctions,
+ ILogger<MoveInternal> logger,
+ ICondition condition,
+ IDataManager dataManager)
+ {
+ _movementController = movementController;
+ _logger = logger;
+ _condition = condition;
+ _cannotExecuteAtThisTime = dataManager.GetString<LogMessage>(579, x => x.Text)!;
+
+ _destination = moveParams.Destination;
+
+ if (!gameFunctions.IsFlyingUnlocked(moveParams.TerritoryId))
+ {
+ moveParams = moveParams with { Fly = false, Land = false };
+ }
+
+ if (!moveParams.DisableNavMesh)
+ {
+ _startAction = () =>
+ _movementController.NavigateTo(EMovementType.Quest, moveParams.DataId, _destination,
+ fly: moveParams.Fly,
+ sprint: moveParams.Sprint,
+ stopDistance: moveParams.StopDistance,
+ ignoreDistanceToObject: moveParams.IgnoreDistanceToObject,
+ land: moveParams.Land);
+ }
+ else
+ {
+ _startAction = () =>
+ _movementController.NavigateTo(EMovementType.Quest, moveParams.DataId, [_destination],
+ fly: moveParams.Fly,
+ sprint: moveParams.Sprint,
+ stopDistance: moveParams.StopDistance,
+ ignoreDistanceToObject: moveParams.IgnoreDistanceToObject,
+ land: moveParams.Land);
+ }
+ }
+
+ public bool Start()
+ {
+ _logger.LogInformation("Moving to {Destination}", _destination.ToString("G", CultureInfo.InvariantCulture));
+ _startAction();
+ return true;
+ }
+
+ public ETaskResult Update()
+ {
+ if (_movementController.IsPathfinding || _movementController.IsPathRunning)
+ return ETaskResult.StillRunning;
+
+ DateTime movementStartedAt = _movementController.MovementStartedAt;
+ if (movementStartedAt == DateTime.MaxValue || movementStartedAt.AddSeconds(2) >= DateTime.Now)
+ return ETaskResult.StillRunning;
+
+ return ETaskResult.TaskComplete;
+ }
+
+ public override string ToString() => $"MoveTo({_destination.ToString("G", CultureInfo.InvariantCulture)})";
+
+ public bool OnErrorToast(SeString message)
+ {
+ if (GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue) &&
+ _condition[ConditionFlag.Diving])
+ return true;
+
+ return false;
+ }
+ }
+
+ internal sealed record MoveParams(
+ ushort TerritoryId,
+ Vector3 Destination,
+ float? StopDistance = null,
+ uint? DataId = null,
+ bool DisableNavMesh = false,
+ bool Sprint = true,
+ bool Fly = false,
+ bool Land = false,
+ bool IgnoreDistanceToObject = false)
+ {
+ public MoveParams(QuestStep step, Vector3 destination)
+ : this(step.TerritoryId,
+ destination,
+ step.CalculateActualStopDistance(),
+ step.DataId,
+ step.DisableNavmesh,
+ step.Sprint != false,
+ step.Fly == true,
+ step.Land == true,
+ step.IgnoreDistanceToObject == true)
+ {
+ }
+ }
+
+ private sealed class WaitForNearDataId(
+ uint dataId,
+ float stopDistance,
+ GameFunctions gameFunctions,
+ IClientState clientState) : ITask
+ {
+ public bool Start() => true;
+
+ public ETaskResult Update()
+ {
+ IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId);
+ if (gameObject == null ||
+ (gameObject.Position - clientState.LocalPlayer!.Position).Length() > stopDistance)
+ {
+ throw new TaskException("Object not found or too far away, no position so we can't move");
+ }
+
+ return ETaskResult.TaskComplete;
+ }
+ }
+
+ private sealed class LandTask(IClientState clientState, ICondition condition, ILogger<LandTask> logger) : ITask
+ {
+ private bool _landing;
+ private DateTime _continueAt;
+
+ public bool Start()
+ {
+ if (!condition[ConditionFlag.InFlight])
+ {
+ logger.LogInformation("Not flying, not attempting to land");
+ return false;
+ }
+
+ _landing = AttemptLanding();
+ _continueAt = DateTime.Now.AddSeconds(0.25);
+ return true;
+ }
+
+ public ETaskResult Update()
+ {
+ if (DateTime.Now < _continueAt)
+ return ETaskResult.StillRunning;
+
+ if (condition[ConditionFlag.InFlight])
+ {
+ if (!_landing)
+ {
+ _landing = AttemptLanding();
+ _continueAt = DateTime.Now.AddSeconds(0.25);
+ }
+
+ return ETaskResult.StillRunning;
+ }
+
+ return ETaskResult.TaskComplete;
+ }
+
+ private unsafe bool AttemptLanding()
+ {
+ var character = (Character*)(clientState.LocalPlayer?.Address ?? 0);
+ if (character != null)
+ {
+ if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23) == 0)
+ {
+ logger.LogInformation("Attempting to land");
+ return ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23);
+ }
+ }
+
+ return false;
+ }
+ }
+}
internal static class SkipCondition
{
- internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+ internal sealed class Factory(
+ ILoggerFactory loggerFactory,
+ AetheryteFunctions aetheryteFunctions,
+ GameFunctions gameFunctions,
+ QuestFunctions questFunctions,
+ IClientState clientState) : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
step.NextQuestId == null)
return null;
- return serviceProvider.GetRequiredService<CheckSkip>()
- .With(step, skipConditions ?? new(), quest.Id);
+ return Check(step, skipConditions, quest.Id);
+ }
+
+ private CheckSkip Check(QuestStep step, SkipStepConditions? skipConditions, ElementId questId)
+ {
+ return new CheckSkip(step, skipConditions ?? new(), questId, loggerFactory.CreateLogger<CheckSkip>(),
+ aetheryteFunctions, gameFunctions, questFunctions, clientState);
}
}
- internal sealed class CheckSkip(
+ private sealed class CheckSkip(
+ QuestStep step,
+ SkipStepConditions skipConditions,
+ ElementId elementId,
ILogger<CheckSkip> logger,
AetheryteFunctions aetheryteFunctions,
GameFunctions gameFunctions,
QuestFunctions questFunctions,
IClientState clientState) : ITask
{
- public QuestStep Step { get; set; } = null!;
- public SkipStepConditions SkipConditions { get; set; } = null!;
- public ElementId ElementId { get; set; } = null!;
-
- public ITask With(QuestStep step, SkipStepConditions skipConditions, ElementId elementId)
- {
- Step = step;
- SkipConditions = skipConditions;
- ElementId = elementId;
- return this;
- }
-
public unsafe bool Start()
{
- logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", SkipConditions));
+ logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", skipConditions));
- if (SkipConditions.Flying == ELockedSkipCondition.Unlocked &&
- gameFunctions.IsFlyingUnlocked(Step.TerritoryId))
+ 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))
+ if (skipConditions.Flying == ELockedSkipCondition.Locked &&
+ !gameFunctions.IsFlyingUnlocked(step.TerritoryId))
{
logger.LogInformation("Skipping step, as flying is locked");
return true;
}
- if (SkipConditions.Chocobo == ELockedSkipCondition.Unlocked &&
+ if (skipConditions.Chocobo == ELockedSkipCondition.Unlocked &&
PlayerState.Instance()->IsMountUnlocked(1))
{
logger.LogInformation("Skipping step, as chocobo is unlocked");
return true;
}
- if (SkipConditions.InTerritory.Count > 0 &&
- SkipConditions.InTerritory.Contains(clientState.TerritoryType))
+ 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))
+ if (skipConditions.NotInTerritory.Count > 0 &&
+ !skipConditions.NotInTerritory.Contains(clientState.TerritoryType))
{
logger.LogInformation("Skipping step, as not in a skip.NotInTerritory");
return true;
}
- if (SkipConditions.QuestsCompleted.Count > 0 &&
- SkipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete))
+ 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))
+ if (skipConditions.QuestsAccepted.Count > 0 &&
+ skipConditions.QuestsAccepted.All(questFunctions.IsQuestAccepted))
{
logger.LogInformation("Skipping step, all prequisite quests are accepted");
return true;
}
- if (SkipConditions.NotTargetable &&
- Step is { DataId: not null })
+ if (skipConditions.NotTargetable &&
+ step is { DataId: not null })
{
- IGameObject? gameObject = gameFunctions.FindObjectByDataId(Step.DataId.Value);
+ IGameObject? gameObject = gameFunctions.FindObjectByDataId(step.DataId.Value);
if (gameObject == null)
{
- if ((Step.Position.GetValueOrDefault() - clientState.LocalPlayer!.Position).Length() < 100)
+ if ((step.Position.GetValueOrDefault() - clientState.LocalPlayer!.Position).Length() < 100)
{
logger.LogInformation("Skipping step, object is not nearby (but we are)");
return true;
}
}
- if (SkipConditions.Item is { NotInInventory: true } && Step is { ItemId: not null })
+ if (skipConditions.Item is { NotInInventory: true } && step is { ItemId: not null })
{
InventoryManager* inventoryManager = InventoryManager.Instance();
- if (inventoryManager->GetInventoryItemCount(Step.ItemId.Value) == 0)
+ if (inventoryManager->GetInventoryItemCount(step.ItemId.Value) == 0)
{
logger.LogInformation("Skipping step, no item with itemId {ItemId} in inventory",
- Step.ItemId.Value);
+ step.ItemId.Value);
return true;
}
}
- if (Step is
+ if (step is
{
DataId: not null,
InteractionType: EInteractionType.AttuneAetheryte or EInteractionType.AttuneAethernetShard
} &&
- aetheryteFunctions.IsAetheryteUnlocked((EAetheryteLocation)Step.DataId.Value))
+ aetheryteFunctions.IsAetheryteUnlocked((EAetheryteLocation)step.DataId.Value))
{
logger.LogInformation("Skipping step, as aetheryte/aethernet shard is unlocked");
return true;
}
- if (SkipConditions.AetheryteLocked != null &&
- !aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteLocked.Value))
+ if (skipConditions.AetheryteLocked != null &&
+ !aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteLocked.Value))
{
logger.LogInformation("Skipping step, as aetheryte is locked");
return true;
}
- if (SkipConditions.AetheryteUnlocked != null &&
- aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteUnlocked.Value))
+ if (skipConditions.AetheryteUnlocked != null &&
+ aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteUnlocked.Value))
{
logger.LogInformation("Skipping step, as aetheryte is unlocked");
return true;
}
- if (Step is { DataId: not null, InteractionType: EInteractionType.AttuneAetherCurrent } &&
- gameFunctions.IsAetherCurrentUnlocked(Step.DataId.Value))
+ if (step is { DataId: not null, InteractionType: EInteractionType.AttuneAetherCurrent } &&
+ gameFunctions.IsAetherCurrentUnlocked(step.DataId.Value))
{
logger.LogInformation("Skipping step, as current is unlocked");
return true;
}
- QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(ElementId);
+ QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(elementId);
if (questWork != null)
{
- if (QuestWorkUtils.HasCompletionFlags(Step.CompletionQuestVariablesFlags) &&
- QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork))
+ if (QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags) &&
+ QuestWorkUtils.MatchesQuestWork(step.CompletionQuestVariablesFlags, questWork))
{
logger.LogInformation("Skipping step, as quest variables match (step is complete)");
return true;
}
- if (Step is { SkipConditions.StepIf: { } conditions })
+ if (step is { SkipConditions.StepIf: { } conditions })
{
if (QuestWorkUtils.MatchesQuestWork(conditions.CompletionQuestVariablesFlags, questWork))
{
}
}
- if (Step is { RequiredQuestVariables: { } requiredQuestVariables })
+ if (step is { RequiredQuestVariables: { } requiredQuestVariables })
{
if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork, logger))
{
}
}
- if (SkipConditions.NearPosition is { } nearPosition && clientState.TerritoryType == Step.TerritoryId)
+ if (skipConditions.NearPosition is { } nearPosition && clientState.TerritoryType == step.TerritoryId)
{
- if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <= nearPosition.MaximumDistance)
+ if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <=
+ nearPosition.MaximumDistance)
{
logger.LogInformation("Skipping step, as we're near the position");
return true;
}
}
- if (SkipConditions.ExtraCondition == EExtraSkipCondition.WakingSandsMainArea &&
+ if (skipConditions.ExtraCondition == EExtraSkipCondition.WakingSandsMainArea &&
clientState.TerritoryType == 212)
{
var position = clientState.LocalPlayer!.Position;
}
}
- if (SkipConditions.ExtraCondition == EExtraSkipCondition.RisingStonesSolar &&
+ if (skipConditions.ExtraCondition == EExtraSkipCondition.RisingStonesSolar &&
clientState.TerritoryType == 351)
{
var position = clientState.LocalPlayer!.Position;
}
}
- if (Step.PickUpQuestId != null && questFunctions.IsQuestAcceptedOrComplete(Step.PickUpQuestId))
+ 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))
+ if (step.TurnInQuestId != null && questFunctions.IsQuestComplete(step.TurnInQuestId))
{
logger.LogInformation("Skipping step, as we have already completed the relevant quest");
return true;
-using System;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
using Questionable.Model;
using Questionable.Model.Questing;
internal static class StepDisabled
{
- internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+ internal sealed class Factory(ILoggerFactory loggerFactory) : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (!step.Disabled)
return null;
- return serviceProvider.GetRequiredService<Task>();
+ return new Task(loggerFactory.CreateLogger<Task>());
}
}
namespace Questionable.Controller.Steps.Shared;
-internal sealed class SwitchClassJob(IClientState clientState) : AbstractDelayedTask
+internal sealed class SwitchClassJob(EClassJob classJob, IClientState clientState) : AbstractDelayedTask
{
- private EClassJob _classJob;
-
- public ITask With(EClassJob classJob)
- {
- _classJob = classJob;
- return this;
- }
-
protected override unsafe bool StartInternal()
{
- if (clientState.LocalPlayer!.ClassJob.Id == (uint)_classJob)
+ if (clientState.LocalPlayer!.ClassJob.Id == (uint)classJob)
return false;
var gearsetModule = RaptureGearsetModule.Instance();
for (int i = 0; i < 100; ++i)
{
var gearset = gearsetModule->GetGearset(i);
- if (gearset->ClassJob == (byte)_classJob)
+ if (gearset->ClassJob == (byte)classJob)
{
gearsetModule->EquipGearset(gearset->Id, gearset->BannerIndex);
return true;
}
}
- throw new TaskException($"No gearset found for {_classJob}");
+ throw new TaskException($"No gearset found for {classJob}");
}
protected override ETaskResult UpdateInternal() => ETaskResult.TaskComplete;
- public override string ToString() => $"SwitchJob({_classJob})";
+ public override string ToString() => $"SwitchJob({classJob})";
}
using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
-using FFXIVClientStructs.FFXIV.Client.Game;
-using Microsoft.Extensions.DependencyInjection;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Utils;
using Questionable.Data;
internal static class WaitAtEnd
{
internal sealed class Factory(
- IServiceProvider serviceProvider,
IClientState clientState,
ICondition condition,
- TerritoryData territoryData)
+ TerritoryData territoryData,
+ QuestFunctions questFunctions,
+ GameFunctions gameFunctions)
: ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
- if (step.CompletionQuestVariablesFlags.Count == 6 && QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
+ if (step.CompletionQuestVariablesFlags.Count == 6 &&
+ QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
{
- var task = serviceProvider.GetRequiredService<WaitForCompletionFlags>()
- .With((QuestId)quest.Id, step);
- var delay = serviceProvider.GetRequiredService<WaitDelay>();
+ var task = new WaitForCompletionFlags((QuestId)quest.Id, step, questFunctions);
+ var delay = new WaitDelay();
return [task, delay, Next(quest, sequence)];
}
new WaitConditionTask(() => !condition[ConditionFlag.InCombat], "Wait(not in combat)");
return
[
- serviceProvider.GetRequiredService<WaitDelay>(),
+ new WaitDelay(),
notInCombat,
- serviceProvider.GetRequiredService<WaitDelay>(),
+ new WaitDelay(),
Next(quest, sequence)
];
case EInteractionType.WaitForManualProgress:
case EInteractionType.Instruction:
- return [serviceProvider.GetRequiredService<WaitNextStepOrSequence>()];
+ return [new WaitNextStepOrSequence()];
case EInteractionType.Duty:
case EInteractionType.SinglePlayerDuty:
return
[
- serviceProvider.GetRequiredService<WaitObjectAtPosition>()
- .With(step.DataId.Value, step.Position.Value, step.NpcWaitDistance ?? 0.05f),
- serviceProvider.GetRequiredService<WaitDelay>(),
+ new WaitObjectAtPosition(step.DataId.Value, step.Position.Value, step.NpcWaitDistance ?? 0.05f,
+ gameFunctions),
+ new WaitDelay(),
Next(quest, sequence)
];
return
[
waitInteraction,
- serviceProvider.GetRequiredService<WaitDelay>(),
+ new WaitDelay(),
Next(quest, sequence)
];
case EInteractionType.AcceptQuest:
{
- var accept = serviceProvider.GetRequiredService<WaitQuestAccepted>()
- .With(step.PickUpQuestId ?? quest.Id);
- var delay = serviceProvider.GetRequiredService<WaitDelay>();
+ var accept = new WaitQuestAccepted(step.PickUpQuestId ?? quest.Id, questFunctions);
+ var delay = new WaitDelay();
if (step.PickUpQuestId != null)
return [accept, delay, Next(quest, sequence)];
else
case EInteractionType.CompleteQuest:
{
- var complete = serviceProvider.GetRequiredService<WaitQuestCompleted>()
- .With(step.TurnInQuestId ?? quest.Id);
- var delay = serviceProvider.GetRequiredService<WaitDelay>();
+ var complete = new WaitQuestCompleted(step.TurnInQuestId ?? quest.Id, questFunctions);
+ var delay = new WaitDelay();
if (step.TurnInQuestId != null)
return [complete, delay, Next(quest, sequence)];
else
case EInteractionType.Interact:
default:
- return [serviceProvider.GetRequiredService<WaitDelay>(), Next(quest, sequence)];
+ return [new WaitDelay(), Next(quest, sequence)];
}
}
}
}
- internal sealed class WaitDelay() : AbstractDelayedTask(TimeSpan.FromSeconds(1))
+ internal sealed class WaitDelay(TimeSpan? delay = null) : AbstractDelayedTask(delay ?? TimeSpan.FromSeconds(1))
{
- public ITask With(TimeSpan delay)
- {
- Delay = delay;
- return this;
- }
-
protected override bool StartInternal() => true;
public override string ToString() => $"Wait(seconds: {Delay.TotalSeconds})";
public override string ToString() => "Wait(next step or sequence)";
}
- internal sealed class WaitForCompletionFlags(QuestFunctions questFunctions) : ITask
+ internal sealed class WaitForCompletionFlags(QuestId quest, QuestStep step, QuestFunctions questFunctions) : ITask
{
- public QuestId Quest { get; set; } = null!;
- public QuestStep Step { get; set; } = null!;
- public IList<QuestWorkValue?> Flags { get; set; } = null!;
-
- public ITask With(QuestId quest, QuestStep step)
- {
- Quest = quest;
- Step = step;
- Flags = step.CompletionQuestVariablesFlags;
- return this;
- }
-
public bool Start() => true;
public ETaskResult Update()
{
- QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(Quest);
+ QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(quest);
return questWork != null &&
- QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork)
+ QuestWorkUtils.MatchesQuestWork(step.CompletionQuestVariablesFlags, questWork)
? ETaskResult.TaskComplete
: ETaskResult.StillRunning;
}
public override string ToString() =>
- $"Wait(QW: {string.Join(", ", Flags.Select(x => x?.ToString() ?? "-"))})";
+ $"Wait(QW: {string.Join(", ", step.CompletionQuestVariablesFlags.Select(x => x?.ToString() ?? "-"))})";
}
- internal sealed class WaitObjectAtPosition(GameFunctions gameFunctions) : ITask
+ private sealed class WaitObjectAtPosition(
+ uint dataId,
+ Vector3 destination,
+ float distance,
+ GameFunctions gameFunctions) : ITask
{
- public uint DataId { get; set; }
- public Vector3 Destination { get; set; }
- public float Distance { get; set; }
-
- public ITask With(uint dataId, Vector3 destination, float distance)
- {
- DataId = dataId;
- Destination = destination;
- Distance = distance;
- return this;
- }
-
public bool Start() => true;
public ETaskResult Update() =>
- gameFunctions.IsObjectAtPosition(DataId, Destination, Distance)
+ gameFunctions.IsObjectAtPosition(dataId, destination, distance)
? ETaskResult.TaskComplete
: ETaskResult.StillRunning;
public override string ToString() =>
- $"WaitObj({DataId} at {Destination.ToString("G", CultureInfo.InvariantCulture)} < {Distance})";
+ $"WaitObj({dataId} at {destination.ToString("G", CultureInfo.InvariantCulture)} < {distance})";
}
- internal sealed class WaitQuestAccepted(QuestFunctions questFunctions) : ITask
+ internal sealed class WaitQuestAccepted(ElementId elementId, QuestFunctions questFunctions) : ITask
{
- public ElementId ElementId { get; set; } = null!;
-
- public ITask With(ElementId elementId)
- {
- ElementId = elementId;
- return this;
- }
-
public bool Start() => true;
public ETaskResult Update()
{
- return questFunctions.IsQuestAccepted(ElementId)
+ return questFunctions.IsQuestAccepted(elementId)
? ETaskResult.TaskComplete
: ETaskResult.StillRunning;
}
- public override string ToString() => $"WaitQuestAccepted({ElementId})";
+ public override string ToString() => $"WaitQuestAccepted({elementId})";
}
- internal sealed class WaitQuestCompleted(QuestFunctions questFunctions) : ITask
+ internal sealed class WaitQuestCompleted(ElementId elementId, QuestFunctions questFunctions) : ITask
{
- public ElementId ElementId { get; set; } = null!;
-
- public ITask With(ElementId elementId)
- {
- ElementId = elementId;
- return this;
- }
-
public bool Start() => true;
public ETaskResult Update()
{
- return questFunctions.IsQuestComplete(ElementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
+ return questFunctions.IsQuestComplete(elementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
}
- public override string ToString() => $"WaitQuestComplete({ElementId})";
+ public override string ToString() => $"WaitQuestComplete({elementId})";
}
internal sealed class NextStep(ElementId elementId, int sequence) : ILastTask
using System;
-using Microsoft.Extensions.DependencyInjection;
using Questionable.Controller.Steps.Common;
using Questionable.Model;
using Questionable.Model.Questing;
internal static class WaitAtStart
{
- internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+ internal sealed class Factory : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.DelaySecondsAtStart == null)
return null;
- return serviceProvider.GetRequiredService<WaitDelay>()
- .With(TimeSpan.FromSeconds(step.DelaySecondsAtStart.Value));
+ return new WaitDelay(TimeSpan.FromSeconds(step.DelaySecondsAtStart.Value));
}
}
- internal sealed class WaitDelay : AbstractDelayedTask
+ internal sealed class WaitDelay(TimeSpan delay) : AbstractDelayedTask(delay)
{
- public ITask With(TimeSpan delay)
- {
- Delay = delay;
- return this;
- }
-
protected override bool StartInternal() => true;
public override string ToString() => $"Wait[S](seconds: {Delay.TotalSeconds})";
using System;
using System.Collections.Generic;
using System.Linq;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Model;
using Questionable.Model.Questing;
internal sealed class TaskCreator
{
- private readonly IReadOnlyList<ITaskFactory> _taskFactories;
+ private readonly IServiceProvider _serviceProvider;
private readonly ILogger<TaskCreator> _logger;
- public TaskCreator(IEnumerable<ITaskFactory> taskFactories, ILogger<TaskCreator> logger)
+ public TaskCreator(IServiceProvider serviceProvider, ILogger<TaskCreator> logger)
{
- _taskFactories = taskFactories.ToList().AsReadOnly();
+ _serviceProvider = serviceProvider;
_logger = logger;
}
public IReadOnlyList<ITask> CreateTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
- var newTasks = _taskFactories
+ using var scope = _serviceProvider.CreateScope();
+ var newTasks = scope.ServiceProvider.GetRequiredService<IEnumerable<ITaskFactory>>()
.SelectMany(x =>
{
IList<ITask> tasks = x.CreateAllTasks(quest, sequence, step).ToList();
private static void AddTaskFactories(ServiceCollection serviceCollection)
{
// individual tasks
- serviceCollection.AddTransient<MountTask>();
- serviceCollection.AddTransient<UnmountTask>();
serviceCollection.AddTransient<MoveToLandingLocation>();
serviceCollection.AddTransient<DoGather>();
serviceCollection.AddTransient<DoGatherCollectable>();
serviceCollection.AddTransient<SwitchClassJob>();
+ serviceCollection.AddSingleton<Mount.Factory>();
// task factories
- serviceCollection.AddTaskWithFactory<StepDisabled.Factory, StepDisabled.Task>();
- serviceCollection.AddSingleton<ITaskFactory, EquipRecommended.BeforeDutyOrInstance>();
- serviceCollection.AddTaskWithFactory<GatheringRequiredItems.Factory, GatheringRequiredItems.StartGathering, GatheringRequiredItems.SkipMarker>();
- serviceCollection.AddTaskWithFactory<AetheryteShortcut.Factory, AetheryteShortcut.UseAetheryteShortcut>();
- serviceCollection.AddTaskWithFactory<SkipCondition.Factory, SkipCondition.CheckSkip>();
- serviceCollection.AddTaskWithFactory<AethernetShortcut.Factory, AethernetShortcut.UseAethernetShortcut>();
- serviceCollection.AddTaskWithFactory<WaitAtStart.Factory, WaitAtStart.WaitDelay>();
- serviceCollection.AddTaskWithFactory<Move.Factory, Move.MoveInternal, Move.ExpectToBeNearDataId, Move.Land>();
- serviceCollection.AddTransient<Move.MoveBuilder>();
-
- serviceCollection.AddTaskWithFactory<NextQuest.Factory, NextQuest.SetQuest>();
- serviceCollection.AddTaskWithFactory<AetherCurrent.Factory, AetherCurrent.DoAttune>();
- serviceCollection.AddTaskWithFactory<AethernetShard.Factory, AethernetShard.DoAttune>();
- serviceCollection.AddTaskWithFactory<Aetheryte.Factory, Aetheryte.DoAttune>();
- serviceCollection.AddTaskWithFactory<Combat.Factory, Combat.HandleCombat>();
- serviceCollection.AddTaskWithFactory<Duty.Factory, Duty.OpenDutyFinder>();
- serviceCollection.AddTaskWithFactory<Emote.Factory, Emote.UseOnObject, Emote.Use>();
- serviceCollection.AddTaskWithFactory<Action.Factory, Action.UseOnObject>();
- serviceCollection.AddTaskWithFactory<Interact.Factory, Interact.DoInteract>();
- serviceCollection.AddTaskWithFactory<Jump.Factory, Jump.SingleJump, Jump.RepeatedJumps>();
- serviceCollection.AddTaskWithFactory<Dive.Factory, Dive.DoDive>();
- serviceCollection.AddTaskWithFactory<Say.Factory, Say.UseChat>();
- serviceCollection
- .AddTaskWithFactory<UseItem.Factory, UseItem.UseOnGround, UseItem.UseOnObject, UseItem.Use,
- UseItem.UseOnPosition>();
- serviceCollection.AddTaskWithFactory<EquipItem.Factory, EquipItem.DoEquip>();
- serviceCollection.AddTaskWithFactory<EquipRecommended.Factory, EquipRecommended.DoEquipRecommended>();
- serviceCollection.AddTaskWithFactory<Craft.Factory, Craft.DoCraft>();
- serviceCollection.AddTaskWithFactory<TurnInDelivery.Factory, TurnInDelivery.SatisfactionSupplyTurnIn>();
- serviceCollection
- .AddTaskWithFactory<InitiateLeve.Factory,
- InitiateLeve.SkipInitiateIfActive,
- InitiateLeve.OpenJournal,
- InitiateLeve.Initiate,
- InitiateLeve.SelectDifficulty>();
-
- serviceCollection
- .AddTaskWithFactory<WaitAtEnd.Factory,
- WaitAtEnd.WaitDelay,
- WaitAtEnd.WaitNextStepOrSequence,
- WaitAtEnd.WaitForCompletionFlags,
- WaitAtEnd.WaitObjectAtPosition>();
+ serviceCollection.AddTaskFactory<StepDisabled.Factory>();
+ serviceCollection.AddTaskFactory<EquipRecommended.BeforeDutyOrInstance>();
+ serviceCollection.AddTaskFactory<GatheringRequiredItems.Factory>();
+ serviceCollection.AddTaskFactory<AetheryteShortcut.Factory>();
+ serviceCollection.AddTaskFactory<SkipCondition.Factory>();
+ serviceCollection.AddTaskFactory<AethernetShortcut.Factory>();
+ serviceCollection.AddTaskFactory<WaitAtStart.Factory>();
+ serviceCollection.AddTaskFactory<MoveTo.Factory>();
+
+ serviceCollection.AddTaskFactory<NextQuest.Factory>();
+ serviceCollection.AddTaskFactory<AetherCurrent.Factory>();
+ serviceCollection.AddTaskFactory<AethernetShard.Factory>();
+ serviceCollection.AddTaskFactory<Aetheryte.Factory>();
+ serviceCollection.AddTaskFactory<Combat.Factory>();
+ serviceCollection.AddTaskFactory<Duty.Factory>();
+ serviceCollection.AddTaskFactory<Emote.Factory>();
+ serviceCollection.AddTaskFactory<Action.Factory>();
+ serviceCollection.AddTaskFactory<Interact.Factory>();
+ serviceCollection.AddTaskFactory<Jump.Factory>();
+ serviceCollection.AddTaskFactory<Dive.Factory>();
+ serviceCollection.AddTaskFactory<Say.Factory>();
+ serviceCollection.AddTaskFactory<UseItem.Factory>();
+ serviceCollection.AddTaskFactory<EquipItem.Factory>();
+ serviceCollection.AddTaskFactory<EquipRecommended.Factory>();
+ serviceCollection.AddTaskFactory<Craft.Factory>();
+ serviceCollection.AddTaskFactory<TurnInDelivery.Factory>();
+ serviceCollection.AddTaskFactory<InitiateLeve.Factory>();
+
+ serviceCollection.AddTaskFactory<WaitAtEnd.Factory>();
serviceCollection.AddTransient<WaitAtEnd.WaitQuestAccepted>();
serviceCollection.AddTransient<WaitAtEnd.WaitQuestCompleted>();
internal static class ServiceCollectionExtensions
{
- public static void AddTaskWithFactory<
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TFactory,
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TTask>(
+ public static void AddTaskFactory<
+ [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] TFactory>(
this IServiceCollection serviceCollection)
where TFactory : class, ITaskFactory
- where TTask : class, ITask
{
serviceCollection.AddSingleton<ITaskFactory, TFactory>();
- serviceCollection.AddTransient<TTask>();
- }
-
- public static void AddTaskWithFactory<
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TFactory,
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TTask1,
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TTask2>(
- this IServiceCollection serviceCollection)
- where TFactory : class, ITaskFactory
- where TTask1 : class, ITask
- where TTask2 : class, ITask
- {
- serviceCollection.AddSingleton<ITaskFactory, TFactory>();
- serviceCollection.AddTransient<TTask1>();
- serviceCollection.AddTransient<TTask2>();
- }
-
- public static void AddTaskWithFactory<
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TFactory,
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TTask1,
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TTask2,
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TTask3>(
- this IServiceCollection serviceCollection)
- where TFactory : class, ITaskFactory
- where TTask1 : class, ITask
- where TTask2 : class, ITask
- where TTask3 : class, ITask
- {
- serviceCollection.AddSingleton<ITaskFactory, TFactory>();
- serviceCollection.AddTransient<TTask1>();
- serviceCollection.AddTransient<TTask2>();
- serviceCollection.AddTransient<TTask3>();
- }
-
- public static void AddTaskWithFactory<
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TFactory,
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TTask1,
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TTask2,
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TTask3,
- [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- TTask4>(
- this IServiceCollection serviceCollection)
- where TFactory : class, ITaskFactory
- where TTask1 : class, ITask
- where TTask2 : class, ITask
- where TTask3 : class, ITask
- where TTask4 : class, ITask
- {
- serviceCollection.AddSingleton<ITaskFactory, TFactory>();
- serviceCollection.AddTransient<TTask1>();
- serviceCollection.AddTransient<TTask2>();
- serviceCollection.AddTransient<TTask3>();
- serviceCollection.AddTransient<TTask4>();
+ serviceCollection.AddSingleton<TFactory>();
}
}