diff --git a/Questionable/Controller/GameUi/InteractionUiController.cs b/Questionable/Controller/GameUi/InteractionUiController.cs index 4c8367e7..a6f11060 100644 --- a/Questionable/Controller/GameUi/InteractionUiController.cs +++ b/Questionable/Controller/GameUi/InteractionUiController.cs @@ -92,6 +92,14 @@ internal sealed class InteractionUiController : IDisposable _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; @@ -778,7 +786,7 @@ internal sealed class InteractionUiController : IDisposable { 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 diff --git a/Questionable/Controller/GatheringController.cs b/Questionable/Controller/GatheringController.cs index e8ceccc3..4dfae667 100644 --- a/Questionable/Controller/GatheringController.cs +++ b/Questionable/Controller/GatheringController.cs @@ -25,24 +25,34 @@ using Questionable.Functions; 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 { 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, @@ -52,16 +62,25 @@ internal sealed unsafe class GatheringController : MiniTaskController(5574, x => x.Text, pluginLog) ?? throw new InvalidDataException("No regex found for revisit message"); @@ -152,8 +171,7 @@ internal sealed unsafe class GatheringController : MiniTaskController() - .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); @@ -170,24 +188,28 @@ internal sealed unsafe class GatheringController : MiniTaskController() - .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() - .With(territoryId, fly, currentNode)); - _taskQueue.Enqueue(_serviceProvider.GetRequiredService() - .With(currentNode.DataId, null, EInteractionType.InternalGather, true)); + _taskQueue.Enqueue(new MoveToLandingLocation(territoryId, fly, currentNode, _moveFactory, _gameFunctions, + _objectTable, _loggerFactory.CreateLogger())); + _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() - .With(_currentRequest.Data, currentNode, revisitRequired)); + _taskQueue.Enqueue(new DoGather(_currentRequest!.Data, currentNode, revisitRequired, this, _gameFunctions, + _gameGui, _clientState, _condition, _loggerFactory.CreateLogger())); if (_currentRequest.Data.Collectability > 0) { - _taskQueue.Enqueue(_serviceProvider.GetRequiredService() - .With(_currentRequest.Data, currentNode, revisitRequired)); + _taskQueue.Enqueue(new DoGatherCollectable(_currentRequest.Data, currentNode, revisitRequired, this, + _gameFunctions, _clientState, _gameGui, _loggerFactory.CreateLogger())); } } } diff --git a/Questionable/Controller/Steps/Common/Mount.cs b/Questionable/Controller/Steps/Common/Mount.cs new file mode 100644 index 00000000..144de5ba --- /dev/null +++ b/Questionable/Controller/Steps/Common/Mount.cs @@ -0,0 +1,183 @@ +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()); + } + + public ITask Unmount() + { + return new UnmountTask(condition, loggerFactory.CreateLogger(), gameFunctions); + } + } + + private sealed class MountTask( + ushort territoryId, + EMountIf mountIf, + Vector3? position, + GameFunctions gameFunctions, + ICondition condition, + TerritoryData territoryData, + IClientState clientState, + ILogger 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 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, + } +} diff --git a/Questionable/Controller/Steps/Common/MountTask.cs b/Questionable/Controller/Steps/Common/MountTask.cs deleted file mode 100644 index 6dfba0b0..00000000 --- a/Questionable/Controller/Steps/Common/MountTask.cs +++ /dev/null @@ -1,114 +0,0 @@ -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 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, - } -} diff --git a/Questionable/Controller/Steps/Common/NextQuest.cs b/Questionable/Controller/Steps/Common/NextQuest.cs index f338c88c..e7fd2b62 100644 --- a/Questionable/Controller/Steps/Common/NextQuest.cs +++ b/Questionable/Controller/Steps/Common/NextQuest.cs @@ -1,6 +1,4 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Questionable.Functions; using Questionable.Model; using Questionable.Model.Questing; @@ -9,7 +7,7 @@ namespace Questionable.Controller.Steps.Common; 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) { @@ -22,37 +20,26 @@ internal static class NextQuest if (step.NextQuestId == quest.Id) return null; - return serviceProvider.GetRequiredService() - .With(step.NextQuestId, quest.Id); + return new SetQuest(step.NextQuestId, quest.Id, questRegistry, questController, questFunctions, loggerFactory.CreateLogger()); } } - internal sealed class SetQuest(QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILogger logger) : ITask + private sealed class SetQuest(ElementId nextQuestId, ElementId currentQuestId, QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILogger 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); } @@ -61,6 +48,6 @@ internal static class NextQuest public ETaskResult Update() => ETaskResult.TaskComplete; - public override string ToString() => $"SetNextQuest({NextQuestId})"; + public override string ToString() => $"SetNextQuest({nextQuestId})"; } } diff --git a/Questionable/Controller/Steps/Common/UnmountTask.cs b/Questionable/Controller/Steps/Common/UnmountTask.cs deleted file mode 100644 index a379dc9d..00000000 --- a/Questionable/Controller/Steps/Common/UnmountTask.cs +++ /dev/null @@ -1,63 +0,0 @@ -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 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"; -} diff --git a/Questionable/Controller/Steps/Gathering/DoGather.cs b/Questionable/Controller/Steps/Gathering/DoGather.cs index 6cd4b3e7..5eb29bd7 100644 --- a/Questionable/Controller/Steps/Gathering/DoGather.cs +++ b/Questionable/Controller/Steps/Gathering/DoGather.cs @@ -17,6 +17,9 @@ using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Gathering; internal sealed class DoGather( + GatheringController.GatheringRequest currentRequest, + GatheringNode currentNode, + bool revisitRequired, GatheringController gatheringController, GameFunctions gameFunctions, IGameGui gameGui, @@ -26,34 +29,22 @@ internal sealed class DoGather( { 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? _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; @@ -78,9 +69,9 @@ internal sealed class DoGather( 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 @@ -103,7 +94,7 @@ internal sealed class DoGather( _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); } } @@ -157,9 +148,9 @@ internal sealed class DoGather( 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) { @@ -195,7 +186,7 @@ internal sealed class DoGather( } } - 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 && @@ -243,7 +234,7 @@ internal sealed class DoGather( _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); diff --git a/Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs b/Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs index 3c6f776d..99779c14 100644 --- a/Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs +++ b/Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs @@ -14,40 +14,31 @@ using Questionable.Model.Questing; 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 logger) : ITask, IRevisitAware { - private GatheringController.GatheringRequest _currentRequest = null!; - private GatheringNode _currentNode = null!; - private bool _revisitRequired; private bool _revisitTriggered; private Queue? _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; @@ -103,7 +94,7 @@ internal sealed class DoGatherCollectable( return ETaskResult.StillRunning; } - if (nodeCondition.CollectabilityToGoal(_currentRequest.Collectability) > 0) + if (nodeCondition.CollectabilityToGoal(currentRequest.Collectability) > 0) { _actionQueue = GetNextActions(nodeCondition); if (_actionQueue != null) @@ -147,7 +138,7 @@ internal sealed class DoGatherCollectable( Queue 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", @@ -203,7 +194,7 @@ internal sealed class DoGatherCollectable( } 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( diff --git a/Questionable/Controller/Steps/Gathering/MoveToLandingLocation.cs b/Questionable/Controller/Steps/Gathering/MoveToLandingLocation.cs index 84b9c888..5694d65e 100644 --- a/Questionable/Controller/Steps/Gathering/MoveToLandingLocation.cs +++ b/Questionable/Controller/Steps/Gathering/MoveToLandingLocation.cs @@ -1,5 +1,4 @@ -using System; -using System.Globalization; +using System.Globalization; using System.Linq; using System.Numerics; using Dalamud.Game.ClientState.Objects.Enums; @@ -14,49 +13,40 @@ using Questionable.Model.Gathering; 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 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() - .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}"; } diff --git a/Questionable/Controller/Steps/Gathering/TurnInDelivery.cs b/Questionable/Controller/Steps/Gathering/TurnInDelivery.cs index 23abcb3c..ca6a93d8 100644 --- a/Questionable/Controller/Steps/Gathering/TurnInDelivery.cs +++ b/Questionable/Controller/Steps/Gathering/TurnInDelivery.cs @@ -13,18 +13,18 @@ namespace Questionable.Controller.Steps.Gathering; 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(); + return new SatisfactionSupplyTurnIn(loggerFactory.CreateLogger()); } } - internal sealed class SatisfactionSupplyTurnIn(ILogger logger) : ITask + private sealed class SatisfactionSupplyTurnIn(ILogger logger) : ITask { private ushort? _remainingAllowances; diff --git a/Questionable/Controller/Steps/Interactions/Action.cs b/Questionable/Controller/Steps/Interactions/Action.cs index 802d2d8c..a91983b3 100644 --- a/Questionable/Controller/Steps/Interactions/Action.cs +++ b/Questionable/Controller/Steps/Interactions/Action.cs @@ -14,7 +14,8 @@ namespace Questionable.Controller.Steps.Interactions; 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 CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { @@ -23,54 +24,45 @@ internal static class Action ArgumentNullException.ThrowIfNull(step.Action); - var task = serviceProvider.GetRequiredService() - .With(step.DataId, step.Action.Value); + var task = new UseOnObject(step.DataId, step.Action.Value, gameFunctions, + loggerFactory.CreateLogger()); if (step.Action.Value.RequiresMount()) return [task]; else - { - var unmount = serviceProvider.GetRequiredService(); - return [unmount, task]; - } + return [mountFactory.Unmount(), task]; } } - internal sealed class UseOnObject(GameFunctions gameFunctions, ILogger logger) : ITask + private sealed class UseOnObject( + uint? dataId, + EAction action, + GameFunctions gameFunctions, + ILogger 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; } @@ -85,18 +77,18 @@ internal static class Action 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); } @@ -106,6 +98,6 @@ internal static class Action return ETaskResult.TaskComplete; } - public override string ToString() => $"Action({Action})"; + public override string ToString() => $"Action({action})"; } } diff --git a/Questionable/Controller/Steps/Interactions/AetherCurrent.cs b/Questionable/Controller/Steps/Interactions/AetherCurrent.cs index d4387288..45507e80 100644 --- a/Questionable/Controller/Steps/Interactions/AetherCurrent.cs +++ b/Questionable/Controller/Steps/Interactions/AetherCurrent.cs @@ -11,7 +11,11 @@ namespace Questionable.Controller.Steps.Interactions; 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) { @@ -23,47 +27,37 @@ internal static class AetherCurrent 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() - .With(step.DataId.Value, step.AetherCurrentId.Value); + return new DoAttune(step.DataId.Value, step.AetherCurrentId.Value, gameFunctions, loggerFactory.CreateLogger()); } } - internal sealed class DoAttune(GameFunctions gameFunctions, ILogger logger) : ITask + private sealed class DoAttune(uint dataId, uint aetherCurrentId, GameFunctions gameFunctions, ILogger 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})"; } } diff --git a/Questionable/Controller/Steps/Interactions/AethernetShard.cs b/Questionable/Controller/Steps/Interactions/AethernetShard.cs index 06079acb..edcf4720 100644 --- a/Questionable/Controller/Steps/Interactions/AethernetShard.cs +++ b/Questionable/Controller/Steps/Interactions/AethernetShard.cs @@ -11,7 +11,10 @@ namespace Questionable.Controller.Steps.Interactions; 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) { @@ -20,42 +23,35 @@ internal static class AethernetShard ArgumentNullException.ThrowIfNull(step.AethernetShard); - return serviceProvider.GetRequiredService() - .With(step.AethernetShard.Value); + return new DoAttune(step.AethernetShard.Value, aetheryteFunctions, gameFunctions, + loggerFactory.CreateLogger()); } } - internal sealed class DoAttune( + private sealed class DoAttune( + EAetheryteLocation aetheryteLocation, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, ILogger 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})"; } } diff --git a/Questionable/Controller/Steps/Interactions/Aetheryte.cs b/Questionable/Controller/Steps/Interactions/Aetheryte.cs index 61033749..5af0a6a3 100644 --- a/Questionable/Controller/Steps/Interactions/Aetheryte.cs +++ b/Questionable/Controller/Steps/Interactions/Aetheryte.cs @@ -10,7 +10,10 @@ namespace Questionable.Controller.Steps.Interactions; 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) { @@ -19,42 +22,35 @@ internal static class Aetheryte ArgumentNullException.ThrowIfNull(step.Aetheryte); - return serviceProvider.GetRequiredService() - .With(step.Aetheryte.Value); + return new DoAttune(step.Aetheryte.Value, aetheryteFunctions, gameFunctions, + loggerFactory.CreateLogger()); } } - internal sealed class DoAttune( + private sealed class DoAttune( + EAetheryteLocation aetheryteLocation, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, ILogger 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})"; } } diff --git a/Questionable/Controller/Steps/Interactions/Combat.cs b/Questionable/Controller/Steps/Interactions/Combat.cs index e3bfc258..8776280a 100644 --- a/Questionable/Controller/Steps/Interactions/Combat.cs +++ b/Questionable/Controller/Steps/Interactions/Combat.cs @@ -13,7 +13,12 @@ namespace Questionable.Controller.Steps.Interactions; 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 CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { @@ -22,12 +27,11 @@ internal static class Combat ArgumentNullException.ThrowIfNull(step.EnemySpawnType); - yield return serviceProvider.GetRequiredService(); + yield return mountFactory.Unmount(); if (step.CombatDelaySecondsAtStart != null) { - yield return serviceProvider.GetRequiredService() - .With(TimeSpan.FromSeconds(step.CombatDelaySecondsAtStart.Value)); + yield return new WaitAtStart.WaitDelay(TimeSpan.FromSeconds(step.CombatDelaySecondsAtStart.Value)); } switch (step.EnemySpawnType) @@ -36,8 +40,7 @@ internal static class Combat { ArgumentNullException.ThrowIfNull(step.DataId); - yield return serviceProvider.GetRequiredService() - .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; } @@ -47,9 +50,8 @@ internal static class Combat ArgumentNullException.ThrowIfNull(step.DataId); ArgumentNullException.ThrowIfNull(step.ItemId); - yield return serviceProvider.GetRequiredService() - .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; } @@ -73,34 +75,32 @@ internal static class Combat ArgumentNullException.ThrowIfNull(step.EnemySpawnType); bool isLastStep = sequence.Steps.Last() == step; - return serviceProvider.GetRequiredService() - .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 _completionQuestVariableFlags = null!; - - public ITask With(ElementId elementId, bool isLastStep, EEnemySpawnType enemySpawnType, IList killEnemyDataIds, - IList completionQuestVariablesFlags, IList complexCombatData) + private HandleCombat CreateTask(ElementId elementId, bool isLastStep, EEnemySpawnType enemySpawnType, + IList killEnemyDataIds, IList completionQuestVariablesFlags, + IList 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 completionQuestVariableFlags, + CombatController combatController, + QuestFunctions questFunctions) : ITask + { + public bool Start() => combatController.Start(combatData); public ETaskResult Update() { @@ -108,13 +108,14 @@ internal static class Combat 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; @@ -122,7 +123,7 @@ internal static class Combat // 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 { @@ -133,9 +134,9 @@ internal static class Combat 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)"; diff --git a/Questionable/Controller/Steps/Interactions/Dive.cs b/Questionable/Controller/Steps/Interactions/Dive.cs index 3b8712f4..0fb2d3a6 100644 --- a/Questionable/Controller/Steps/Interactions/Dive.cs +++ b/Questionable/Controller/Steps/Interactions/Dive.cs @@ -18,18 +18,23 @@ namespace Questionable.Controller.Steps.Interactions; 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(); + return Dive(); + } + + public ITask Dive() + { + return new DoDive(condition, loggerFactory.CreateLogger()); } } - internal sealed class DoDive(ICondition condition, ILogger logger) + private sealed class DoDive(ICondition condition, ILogger logger) : AbstractDelayedTask(TimeSpan.FromSeconds(5)) { private readonly Queue<(uint Type, nint Key)> _keysToPress = []; diff --git a/Questionable/Controller/Steps/Interactions/Duty.cs b/Questionable/Controller/Steps/Interactions/Duty.cs index ed80dd4c..975d107e 100644 --- a/Questionable/Controller/Steps/Interactions/Duty.cs +++ b/Questionable/Controller/Steps/Interactions/Duty.cs @@ -10,7 +10,7 @@ namespace Questionable.Controller.Steps.Interactions; 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) { @@ -18,33 +18,26 @@ internal static class Duty return null; ArgumentNullException.ThrowIfNull(step.ContentFinderConditionId); - - return serviceProvider.GetRequiredService() - .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})"; } } diff --git a/Questionable/Controller/Steps/Interactions/Emote.cs b/Questionable/Controller/Steps/Interactions/Emote.cs index 35cd64b7..940aabb1 100644 --- a/Questionable/Controller/Steps/Interactions/Emote.cs +++ b/Questionable/Controller/Steps/Interactions/Emote.cs @@ -10,7 +10,7 @@ namespace Questionable.Controller.Steps.Interactions; internal static class Emote { - internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory + internal sealed class Factory(ChatFunctions chatFunctions, Mount.Factory mountFactory) : ITaskFactory { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { @@ -24,57 +24,39 @@ internal static class Emote ArgumentNullException.ThrowIfNull(step.Emote); - var unmount = serviceProvider.GetRequiredService(); + var unmount = mountFactory.Unmount(); if (step.DataId != null) { - var task = serviceProvider.GetRequiredService().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().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})"; } } diff --git a/Questionable/Controller/Steps/Interactions/EquipItem.cs b/Questionable/Controller/Steps/Interactions/EquipItem.cs index abd72c7c..4adea079 100644 --- a/Questionable/Controller/Steps/Interactions/EquipItem.cs +++ b/Questionable/Controller/Steps/Interactions/EquipItem.cs @@ -16,7 +16,7 @@ namespace Questionable.Controller.Steps.Interactions; 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) { @@ -24,14 +24,39 @@ internal static class EquipItem return null; ArgumentNullException.ThrowIfNull(step.ItemId); - return serviceProvider.GetRequiredService() - .With(step.ItemId.Value); + return Equip(step.ItemId.Value); + } + + private DoEquip Equip(uint itemId) + { + var item = dataManager.GetExcelSheet()!.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()); + } + + private static List? 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 logger) : ITask, IToastAware + private sealed class DoEquip( + uint itemId, + Item item, + List targetSlots, + IDataManager dataManager, + ILogger logger) : ITask, IToastAware { private const int MaxAttempts = 3; + private static readonly IReadOnlyList SourceInventoryTypes = [ InventoryType.ArmoryMainHand, @@ -55,22 +80,9 @@ internal static class EquipItem InventoryType.Inventory4, ]; - private uint _itemId; - private Item _item = null!; - private List _targetSlots = []; private int _attempts; - private DateTime _continueAt = DateTime.MaxValue; - public ITask With(uint itemId) - { - _itemId = itemId; - _item = dataManager.GetExcelSheet()!.GetRow(itemId) ?? - throw new ArgumentOutOfRangeException(nameof(itemId)); - _targetSlots = GetEquipSlot(_item) ?? throw new InvalidOperationException("Not a piece of equipment"); - return this; - } - public bool Start() { Equip(); @@ -87,10 +99,10 @@ internal static class EquipItem 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; } @@ -113,12 +125,12 @@ internal static class EquipItem 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; } } @@ -129,24 +141,24 @@ internal static class EquipItem 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}", @@ -160,19 +172,7 @@ internal static class EquipItem } } - private static List? 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) { diff --git a/Questionable/Controller/Steps/Interactions/EquipRecommended.cs b/Questionable/Controller/Steps/Interactions/EquipRecommended.cs index e94f819f..5ff7ab35 100644 --- a/Questionable/Controller/Steps/Interactions/EquipRecommended.cs +++ b/Questionable/Controller/Steps/Interactions/EquipRecommended.cs @@ -10,18 +10,23 @@ namespace Questionable.Controller.Steps.Interactions; 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(); + 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) { @@ -30,11 +35,11 @@ internal static class EquipRecommended step.InteractionType != EInteractionType.Combat) return null; - return serviceProvider.GetRequiredService(); + 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; diff --git a/Questionable/Controller/Steps/Interactions/Interact.cs b/Questionable/Controller/Steps/Interactions/Interact.cs index 50aae41a..39caf1ae 100644 --- a/Questionable/Controller/Steps/Interactions/Interact.cs +++ b/Questionable/Controller/Steps/Interactions/Interact.cs @@ -15,7 +15,7 @@ namespace Questionable.Controller.Steps.Interactions; internal static class Interact { - internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory + internal sealed class Factory(GameFunctions gameFunctions, ICondition condition, ILoggerFactory loggerFactory) : ITaskFactory { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { @@ -36,41 +36,46 @@ internal static class Interact // 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(); + yield return new WaitAtEnd.WaitDelay(); - yield return serviceProvider.GetRequiredService() - .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()); + } } - internal sealed class DoInteract(GameFunctions gameFunctions, ICondition condition, ILogger logger) + internal sealed class DoInteract( + uint dataId, + Quest? quest, + EInteractionType interactionType, + bool skipMarkerCheck, + GameFunctions gameFunctions, + ICondition condition, + ILogger 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; } @@ -78,7 +83,7 @@ internal static class Interact 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); @@ -117,10 +122,10 @@ internal static class Interact 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; @@ -133,14 +138,14 @@ internal static class Interact 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) { diff --git a/Questionable/Controller/Steps/Interactions/Jump.cs b/Questionable/Controller/Steps/Interactions/Jump.cs index dd2dc92a..464eaace 100644 --- a/Questionable/Controller/Steps/Interactions/Jump.cs +++ b/Questionable/Controller/Steps/Interactions/Jump.cs @@ -11,7 +11,11 @@ namespace Questionable.Controller.Steps.Interactions; 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) { @@ -21,43 +25,39 @@ internal static class Jump ArgumentNullException.ThrowIfNull(step.JumpDestination); if (step.JumpDestination.Type == EJumpType.SingleJump) - { - return serviceProvider.GetRequiredService() - .With(step.DataId, step.JumpDestination, step.Comment); - } + return SingleJump(step.DataId, step.JumpDestination, step.Comment); else - { - return serviceProvider.GetRequiredService() - .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()); } } - 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 @@ -65,7 +65,7 @@ internal static class Jump ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2); } }, - TimeSpan.FromSeconds(JumpDestination.DelaySeconds ?? 0.5f)); + TimeSpan.FromSeconds(jumpDestination.DelaySeconds ?? 0.5f)); return true; } @@ -81,22 +81,28 @@ internal static class Jump 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 logger) : SingleJump(movementController, clientState, framework) + ILogger 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(); } @@ -105,13 +111,13 @@ internal static class Jump 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); @@ -121,10 +127,10 @@ internal static class Jump 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})"; } } diff --git a/Questionable/Controller/Steps/Interactions/Say.cs b/Questionable/Controller/Steps/Interactions/Say.cs index f203d42f..7b8855a1 100644 --- a/Questionable/Controller/Steps/Interactions/Say.cs +++ b/Questionable/Controller/Steps/Interactions/Say.cs @@ -10,7 +10,10 @@ namespace Questionable.Controller.Steps.Interactions; 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 CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { @@ -21,31 +24,24 @@ internal static class Say 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(); - var task = serviceProvider.GetRequiredService().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})"; } } diff --git a/Questionable/Controller/Steps/Interactions/UseItem.cs b/Questionable/Controller/Steps/Interactions/UseItem.cs index 04e75c78..2481e8f5 100644 --- a/Questionable/Controller/Steps/Interactions/UseItem.cs +++ b/Questionable/Controller/Steps/Interactions/UseItem.cs @@ -26,9 +26,17 @@ internal static class UseItem 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 logger) : ITaskFactory { @@ -48,8 +56,7 @@ internal static class UseItem return CreateVesperBayFallbackTask(); } - var task = serviceProvider.GetRequiredService() - .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(); @@ -59,47 +66,69 @@ internal static class UseItem task, new WaitConditionTask(() => clientState.TerritoryType == 140, $"Wait(territory: {territoryData.GetNameAndId(140)})"), - serviceProvider.GetRequiredService() - .With(140, - nextPosition != null ? MountTask.EMountIf.AwayFromPosition : MountTask.EMountIf.Always, - nextPosition), - serviceProvider.GetRequiredService() - .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(); + var unmount = mountFactory.Unmount(); if (step.GroundTarget == true) { ITask task; if (step.DataId != null) - task = serviceProvider.GetRequiredService() - .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() - .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() - .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() - .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 completionQuestVariablesFlags) + { + return new UseOnGround(questId, dataId, itemId, completionQuestVariablesFlags, gameFunctions, + questFunctions, condition, loggerFactory.CreateLogger()); + } + + public ITask OnPosition(ElementId questId, Vector3 position, uint itemId, + List completionQuestVariablesFlags) + { + return new UseOnPosition(questId, position, itemId, completionQuestVariablesFlags, gameFunctions, + questFunctions, condition, loggerFactory.CreateLogger()); + } + + public ITask OnObject(ElementId questId, uint dataId, uint itemId, + List completionQuestVariablesFlags, bool startingCombat = false) + { + return new UseOnObject(questId, dataId, itemId, completionQuestVariablesFlags, startingCombat, + questFunctions, gameFunctions, condition, loggerFactory.CreateLogger()); + } + + public ITask OnSelf(ElementId questId, uint itemId, List completionQuestVariablesFlags) + { + return new Use(questId, itemId, completionQuestVariablesFlags, gameFunctions, questFunctions, condition, + loggerFactory.CreateLogger()); + } + private IEnumerable CreateVesperBayFallbackTask() { logger.LogWarning("No vesper bay aetheryte tickets in inventory, navigating via ferry in Limsa instead"); @@ -107,28 +136,32 @@ internal static class UseItem uint npcId = 1003540; ushort territoryId = 129; Vector3 destination = new(-360.9217f, 8f, 38.92566f); - yield return serviceProvider.GetRequiredService() - .With(null, null, EAetheryteLocation.Limsa, territoryId); - yield return serviceProvider.GetRequiredService() - .With(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist); - yield return serviceProvider.GetRequiredService(); - yield return serviceProvider.GetRequiredService() - .With(territoryId, destination, dataId: npcId, sprint: false); - yield return serviceProvider.GetRequiredService() - .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 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 CompletionQuestVariablesFlags { get; set; } = new List(); - public bool StartingCombat { get; set; } + public ElementId? QuestId => questId; + public uint ItemId => itemId; + public IList CompletionQuestVariablesFlags => completionQuestVariablesFlags; + public bool StartingCombat => startingCombat; protected abstract bool UseItem(); @@ -149,9 +182,9 @@ internal static class 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; @@ -203,96 +236,66 @@ internal static class UseItem } - internal sealed class UseOnGround( + private sealed class UseOnGround( + ElementId? questId, + uint dataId, + uint itemId, + IList completionQuestVariablesFlags, GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger logger) - : UseItemBase(questFunctions, condition, logger) + : UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger) { - public uint DataId { get; set; } + protected override bool UseItem() => gameFunctions.UseItemOnGround(dataId, ItemId); - public ITask With(ElementId? questId, uint dataId, uint itemId, - IList completionQuestVariablesFlags) - { - QuestId = questId; - DataId = dataId; - ItemId = itemId; - CompletionQuestVariablesFlags = completionQuestVariablesFlags; - return this; - } - - 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 completionQuestVariablesFlags, GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger 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 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 completionQuestVariablesFlags, + bool startingCombat, QuestFunctions questFunctions, GameFunctions gameFunctions, ICondition condition, ILogger logger) - : UseItemBase(questFunctions, condition, logger) + : UseItemBase(questId, itemId, completionQuestVariablesFlags, startingCombat, questFunctions, condition, logger) { - public uint DataId { get; set; } + protected override bool UseItem() => gameFunctions.UseItem(dataId, ItemId); - public ITask With(ElementId? questId, uint dataId, uint itemId, - IList completionQuestVariablesFlags, - bool startingCombat = false) - { - QuestId = questId; - DataId = dataId; - ItemId = itemId; - StartingCombat = startingCombat; - CompletionQuestVariablesFlags = completionQuestVariablesFlags; - return this; - } - - 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 completionQuestVariablesFlags, GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger logger) - : UseItemBase(questFunctions, condition, logger) + : UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger) { - public ITask With(ElementId? questId, uint itemId, IList completionQuestVariablesFlags) - { - QuestId = questId; - ItemId = itemId; - CompletionQuestVariablesFlags = completionQuestVariablesFlags; - return this; - } - protected override bool UseItem() => gameFunctions.UseItem(ItemId); public override string ToString() => $"UseItem({ItemId})"; diff --git a/Questionable/Controller/Steps/Leves/InitiateLeve.cs b/Questionable/Controller/Steps/Leves/InitiateLeve.cs index 545324a4..7b12c95e 100644 --- a/Questionable/Controller/Steps/Leves/InitiateLeve.cs +++ b/Questionable/Controller/Steps/Leves/InitiateLeve.cs @@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Component.GUI; 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; @@ -17,31 +18,23 @@ namespace Questionable.Controller.Steps.Leves; internal static class InitiateLeve { - internal sealed class Factory(IServiceProvider serviceProvider, ICondition condition) : ITaskFactory + internal sealed class Factory(IGameGui gameGui, ICondition condition) : ITaskFactory { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { if (step.InteractionType != EInteractionType.InitiateLeve) yield break; - yield return serviceProvider.GetRequiredService().With(quest.Id); - yield return serviceProvider.GetRequiredService().With(quest.Id); - yield return serviceProvider.GetRequiredService().With(quest.Id); - yield return serviceProvider.GetRequiredService(); + 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() @@ -50,31 +43,23 @@ internal static class InitiateLeve 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; } @@ -83,32 +68,24 @@ internal static class InitiateLeve { 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() @@ -118,7 +95,7 @@ internal static class InitiateLeve 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; @@ -127,7 +104,7 @@ internal static class InitiateLeve return ETaskResult.StillRunning; } - public override string ToString() => $"InitiateLeve({_elementId})"; + public override string ToString() => $"InitiateLeve({elementId})"; } internal sealed unsafe class SelectDifficulty(IGameGui gameGui) : ITask diff --git a/Questionable/Controller/Steps/Shared/AethernetShortcut.cs b/Questionable/Controller/Steps/Shared/AethernetShortcut.cs index cca44d09..9297297e 100644 --- a/Questionable/Controller/Steps/Shared/AethernetShortcut.cs +++ b/Questionable/Controller/Steps/Shared/AethernetShortcut.cs @@ -20,7 +20,16 @@ namespace Questionable.Controller.Steps.Shared; 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 CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) @@ -30,12 +39,22 @@ internal static class AethernetShortcut yield return new WaitConditionTask(() => movementController.IsNavmeshReady, "Wait(navmesh ready)"); - yield return serviceProvider.GetRequiredService() - .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(), aetheryteFunctions, gameFunctions, clientState, + aetheryteData, territoryData, lifestreamIpc, movementController, condition); } } internal sealed class UseAethernetShortcut( + EAetheryteLocation from, + EAetheryteLocation to, + SkipAetheryteCondition skipConditions, ILogger logger, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, @@ -51,68 +70,58 @@ internal static class AethernetShortcut 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 nearbyPoints = @@ -125,14 +134,14 @@ internal static class AethernetShortcut 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(); @@ -151,7 +160,7 @@ internal static class AethernetShortcut else logger.LogWarning( "Aethernet shortcut not unlocked (from: {FromAetheryte}, to: {ToAetheryte}), walking manually", - From, To); + from, to); return false; } @@ -160,26 +169,26 @@ internal static class AethernetShortcut { 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; } } @@ -219,22 +228,22 @@ internal static class AethernetShortcut 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; } @@ -242,6 +251,6 @@ internal static class AethernetShortcut return ETaskResult.TaskComplete; } - public override string ToString() => $"UseAethernet({From} -> {To})"; + public override string ToString() => $"UseAethernet({from} -> {to})"; } } diff --git a/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs b/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs index 52bb26b6..2d72afd1 100644 --- a/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs +++ b/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs @@ -18,23 +18,38 @@ namespace Questionable.Controller.Steps.Shared; 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 CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { if (step.AetheryteShortcut == null) yield break; - yield return serviceProvider.GetRequiredService() - .With(step, quest.Id, step.AetheryteShortcut.Value, - aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]); - yield return serviceProvider.GetRequiredService() - .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(), aetheryteFunctions, questFunctions, clientState, + chatGui, aetheryteData); } } - internal sealed class UseAetheryteShortcut( + /// 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. + private sealed class UseAetheryteShortcut( + QuestStep? step, + ElementId? elementId, + EAetheryteLocation targetAetheryte, + ushort expectedTerritoryId, ILogger logger, AetheryteFunctions aetheryteFunctions, QuestFunctions questFunctions, @@ -45,26 +60,6 @@ internal static class AetheryteShortcut private bool _teleported; private DateTime _continueAt; - public QuestStep? Step { get; set; } - public ElementId? ElementId { get; set; } - public EAetheryteLocation TargetAetheryte { get; set; } - - /// - /// 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. - /// - 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() @@ -78,7 +73,7 @@ internal static class AetheryteShortcut return ETaskResult.StillRunning; } - if (clientState.TerritoryType == ExpectedTerritoryId) + if (clientState.TerritoryType == expectedTerritoryId) return ETaskResult.TaskComplete; return ETaskResult.StillRunning; @@ -87,9 +82,9 @@ internal static class AetheryteShortcut 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)) @@ -112,12 +107,12 @@ internal static class AetheryteShortcut 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"); @@ -126,10 +121,11 @@ internal static class AetheryteShortcut } - - 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; @@ -137,7 +133,7 @@ internal static class AetheryteShortcut } } - if (ExpectedTerritoryId == territoryType) + if (expectedTerritoryId == territoryType) { if (!skipConditions.Never) { @@ -148,17 +144,17 @@ internal static class AetheryteShortcut } 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; @@ -172,7 +168,7 @@ internal static class AetheryteShortcut 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."); @@ -184,12 +180,12 @@ internal static class AetheryteShortcut _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; @@ -201,6 +197,6 @@ internal static class AetheryteShortcut } } - public override string ToString() => $"UseAetheryte({TargetAetheryte})"; + public override string ToString() => $"UseAetheryte({targetAetheryte})"; } } diff --git a/Questionable/Controller/Steps/Shared/Craft.cs b/Questionable/Controller/Steps/Shared/Craft.cs index 92487f67..5d09f37c 100644 --- a/Questionable/Controller/Steps/Shared/Craft.cs +++ b/Questionable/Controller/Steps/Shared/Craft.cs @@ -7,18 +7,22 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent; 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 CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { @@ -29,40 +33,34 @@ internal static class Craft ArgumentNullException.ThrowIfNull(step.ItemCount); return [ - serviceProvider.GetRequiredService(), - serviceProvider.GetRequiredService() - .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()); } - internal sealed class DoCraft( + private sealed class DoCraft( + uint itemId, + int itemCount, IDataManager dataManager, IClientState clientState, ArtisanIpc artisanIpc, ILogger 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()!.GetRow(_itemId); + RecipeLookup? recipeLookup = dataManager.GetExcelSheet()!.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 { @@ -94,12 +92,12 @@ internal static class Craft } 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}"); @@ -130,15 +128,15 @@ internal static class Craft 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)"; } } diff --git a/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs b/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs index 9318ce13..6e821e6a 100644 --- a/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs +++ b/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs @@ -10,7 +10,6 @@ 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.Gathering; using Questionable.Model.Questing; @@ -22,6 +21,7 @@ internal static class GatheringRequiredItems internal sealed class Factory( IServiceProvider serviceProvider, MovementController movementController, + GatheringController gatheringController, GatheringPointRegistry gatheringPointRegistry, IClientState clientState, GatheringData gatheringData, @@ -50,8 +50,7 @@ internal static class GatheringRequiredItems if (classJob != currentClassJob) { - yield return serviceProvider.GetRequiredService() - .With(classJob); + yield return new SwitchClassJob(classJob, clientState); } if (HasRequiredItems(requiredGatheredItems)) @@ -69,7 +68,7 @@ internal static class GatheringRequiredItems foreach (var task in serviceProvider.GetRequiredService() .CreateTasks(quest, gatheringSequence, gatheringStep)) if (task is WaitAtEnd.NextStep) - yield return serviceProvider.GetRequiredService(); + yield return CreateSkipMarkerTask(); else yield return task; } @@ -82,8 +81,7 @@ internal static class GatheringRequiredItems yield return new WaitConditionTask(() => movementController.IsNavmeshReady, "Wait(navmesh ready)"); - yield return serviceProvider.GetRequiredService() - .With(gatheringPointId, requiredGatheredItems); + yield return CreateStartGatheringTask(gatheringPointId, requiredGatheredItems); } } @@ -107,25 +105,28 @@ internal static class GatheringRequiredItems minCollectability: (short)requiredGatheredItems.Collectability) >= requiredGatheredItems.ItemCount; } - } - internal sealed class StartGathering(GatheringController gatheringController) : ITask - { - private GatheringPointId _gatheringPointId = null!; - private GatheredItem _gatheredItem = null!; - - public ITask With(GatheringPointId gatheringPointId, GatheredItem gatheredItem) + private StartGathering CreateStartGatheringTask(GatheringPointId gatheringPointId, GatheredItem gatheredItem) { - _gatheringPointId = gatheringPointId; - _gatheredItem = gatheredItem; - return this; + return new StartGathering(gatheringPointId, gatheredItem, gatheringController); } + private static SkipMarker CreateSkipMarkerTask() + { + 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() @@ -138,11 +139,11 @@ internal static class GatheringRequiredItems 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})"; } } diff --git a/Questionable/Controller/Steps/Shared/Move.cs b/Questionable/Controller/Steps/Shared/MoveTo.cs similarity index 50% rename from Questionable/Controller/Steps/Shared/Move.cs rename to Questionable/Controller/Steps/Shared/MoveTo.cs index 9d666f9a..1ba43fdb 100644 --- a/Questionable/Controller/Steps/Shared/Move.cs +++ b/Questionable/Controller/Steps/Shared/MoveTo.cs @@ -18,87 +18,88 @@ 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 Move +internal static class MoveTo { - internal sealed class Factory(IServiceProvider serviceProvider, AetheryteData aetheryteData) : ITaskFactory + internal sealed class Factory( + MovementController movementController, + GameFunctions gameFunctions, + ICondition condition, + IDataManager dataManager, + IClientState clientState, + AetheryteData aetheryteData, + TerritoryData territoryData, + ILoggerFactory loggerFactory, + Mount.Factory mountFactory, + ILogger logger) : ITaskFactory { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { if (step.Position != null) { - var builder = serviceProvider.GetRequiredService() - .With(quest.Id, step, step.Position.Value); - return builder.Build(); + return CreateMountTasks(quest.Id, step, step.Position.Value); } else if (step is { DataId: not null, StopDistance: not null }) { - var task = serviceProvider.GetRequiredService(); - task.DataId = step.DataId.Value; - task.StopDistance = step.StopDistance.Value; - return [task]; + return [ExpectToBeNearDataId(step.DataId.Value, step.StopDistance.Value)]; } else if (step is { InteractionType: EInteractionType.AttuneAetheryte, Aetheryte: not null }) { - var builder = serviceProvider.GetRequiredService() - .With(quest.Id, step, aetheryteData.Locations[step.Aetheryte.Value]); - return builder.Build(); + return CreateMountTasks(quest.Id, step, aetheryteData.Locations[step.Aetheryte.Value]); } else if (step is { InteractionType: EInteractionType.AttuneAethernetShard, AethernetShard: not null }) { - var builder = serviceProvider.GetRequiredService().With(quest.Id, step, - aetheryteData.Locations[step.AethernetShard.Value]); - return builder.Build(); + return CreateMountTasks(quest.Id, step, aetheryteData.Locations[step.AethernetShard.Value]); } return []; } - } - internal sealed class MoveBuilder( - IServiceProvider serviceProvider, - ILogger 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) + public ITask Move(QuestStep step, Vector3 destination) { - _questId = questId; - _step = step; - _destination = destination; - return this; + return Move(new MoveParams(step, destination)); } - public IEnumerable Build() + public ITask Move(MoveParams moveParams) { - if (_step.InteractionType == EInteractionType.Jump && _step.JumpDestination != null && - (clientState.LocalPlayer!.Position - _step.JumpDestination.Position).Length() <= - (_step.JumpDestination.StopDistance ?? 1f)) + return new MoveInternal(moveParams, movementController, gameFunctions, + loggerFactory.CreateLogger(), condition, dataManager); + } + + public ITask Land() + { + return new LandTask(clientState, condition, loggerFactory.CreateLogger()); + } + + public ITask ExpectToBeNearDataId(uint dataId, float stopDistance) + { + return new WaitForNearDataId(dataId, stopDistance, gameFunctions, clientState); + } + + public IEnumerable 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)})"); + yield return new WaitConditionTask(() => clientState.TerritoryType == step.TerritoryId, + $"Wait(territory: {territoryData.GetNameAndId(step.TerritoryId)})"); - if (!_step.DisableNavmesh) + if (!step.DisableNavmesh) yield return new WaitConditionTask(() => movementController.IsNavmeshReady, "Wait(navmesh ready)"); - float stopDistance = _step.CalculateActualStopDistance(); + float stopDistance = step.CalculateActualStopDistance(); Vector3? position = clientState.LocalPlayer?.Position; - float actualDistance = position == null ? float.MaxValue : Vector3.Distance(position.Value, _destination); + 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 @@ -107,8 +108,8 @@ internal static class Move // 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) + 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; @@ -116,36 +117,33 @@ internal static class Move // 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) + 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() - .With(_step.TerritoryId, MountTask.EMountIf.Always); - else if (_step.Mount == false) - yield return serviceProvider.GetRequiredService(); + 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.DisableNavmesh) { - if (_step.Mount == null) + 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() - .With(_step.TerritoryId, mountIf, _destination); + 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 serviceProvider.GetRequiredService() - .With(_step, _destination); + yield return Move(step, destination); } else logger.LogInformation("Skipping move task, distance: {ActualDistance} < {StopDistance}", @@ -156,124 +154,138 @@ internal static class Move // navmesh won't move close enough if (actualDistance > stopDistance) { - yield return serviceProvider.GetRequiredService() - .With(_step, _destination); + 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 serviceProvider.GetRequiredService(); + if (step.Fly == true && step.Land == true) + yield return Land(); } } - internal sealed class MoveInternal( - MovementController movementController, - GameFunctions gameFunctions, - ILogger logger, - ICondition condition, - IDataManager dataManager) : ITask, IToastAware + private sealed class MoveInternal : ITask, IToastAware { - private string _cannotExecuteAtThisTime = dataManager.GetString(579, x => x.Text)!; + private readonly string _cannotExecuteAtThisTime; + private readonly MovementController _movementController; + private readonly ILogger _logger; + private readonly ICondition _condition; - public Action StartAction { get; set; } = null!; - public Vector3 Destination { get; set; } + private readonly Action _startAction; + private readonly Vector3 _destination; - public ITask With(QuestStep step, Vector3 destination) + public MoveInternal(MoveParams moveParams, + MovementController movementController, + GameFunctions gameFunctions, + ILogger logger, + ICondition condition, + IDataManager dataManager) { - 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); - } + _movementController = movementController; + _logger = logger; + _condition = condition; + _cannotExecuteAtThisTime = dataManager.GetString(579, x => x.Text)!; - 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; + _destination = moveParams.Destination; - if (!gameFunctions.IsFlyingUnlocked(territoryId)) + if (!gameFunctions.IsFlyingUnlocked(moveParams.TerritoryId)) { - fly = false; - land = false; + moveParams = moveParams with { Fly = false, Land = false }; } - if (!disableNavMesh) + if (!moveParams.DisableNavMesh) { - StartAction = () => - movementController.NavigateTo(EMovementType.Quest, dataId, Destination, - fly: fly, - sprint: sprint, - stopDistance: stopDistance, - ignoreDistanceToObject: ignoreDistanceToObject, - land: land); + _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, dataId, [Destination], - fly: fly, - sprint: sprint, - stopDistance: stopDistance, - ignoreDistanceToObject: ignoreDistanceToObject, - land: land); + _startAction = () => + _movementController.NavigateTo(EMovementType.Quest, moveParams.DataId, [_destination], + fly: moveParams.Fly, + sprint: moveParams.Sprint, + stopDistance: moveParams.StopDistance, + ignoreDistanceToObject: moveParams.IgnoreDistanceToObject, + land: moveParams.Land); } - - return this; } public bool Start() { - logger.LogInformation("Moving to {Destination}", Destination.ToString("G", CultureInfo.InvariantCulture)); - StartAction(); + _logger.LogInformation("Moving to {Destination}", _destination.ToString("G", CultureInfo.InvariantCulture)); + _startAction(); return true; } public ETaskResult Update() { - if (movementController.IsPathfinding || movementController.IsPathRunning) + if (_movementController.IsPathfinding || _movementController.IsPathRunning) return ETaskResult.StillRunning; - DateTime movementStartedAt = movementController.MovementStartedAt; + 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 override string ToString() => $"MoveTo({_destination.ToString("G", CultureInfo.InvariantCulture)})"; public bool OnErrorToast(SeString message) { if (GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue) && - condition[ConditionFlag.Diving]) + _condition[ConditionFlag.Diving]) return true; return false; } } - internal sealed class ExpectToBeNearDataId(GameFunctions gameFunctions, IClientState clientState) : ITask + 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 uint DataId { get; set; } - public float StopDistance { get; set; } + 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); + IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId); if (gameObject == null || - (gameObject.Position - clientState.LocalPlayer!.Position).Length() > StopDistance) + (gameObject.Position - clientState.LocalPlayer!.Position).Length() > stopDistance) { throw new TaskException("Object not found or too far away, no position so we can't move"); } @@ -282,7 +294,7 @@ internal static class Move } } - internal sealed class Land(IClientState clientState, ICondition condition, ILogger logger) : ITask + private sealed class LandTask(IClientState clientState, ICondition condition, ILogger logger) : ITask { private bool _landing; private DateTime _continueAt; diff --git a/Questionable/Controller/Steps/Shared/SkipCondition.cs b/Questionable/Controller/Steps/Shared/SkipCondition.cs index 69d44149..1d2420cf 100644 --- a/Questionable/Controller/Steps/Shared/SkipCondition.cs +++ b/Questionable/Controller/Steps/Shared/SkipCondition.cs @@ -20,7 +20,12 @@ namespace Questionable.Controller.Steps.Shared; 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) { @@ -35,90 +40,86 @@ internal static class SkipCondition step.NextQuestId == null) return null; - return serviceProvider.GetRequiredService() - .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(), + aetheryteFunctions, gameFunctions, questFunctions, clientState); } } - internal sealed class CheckSkip( + private sealed class CheckSkip( + QuestStep step, + SkipStepConditions skipConditions, + ElementId elementId, ILogger 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; @@ -131,60 +132,60 @@ internal static class SkipCondition } } - 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)) { @@ -193,7 +194,7 @@ internal static class SkipCondition } } - if (Step is { RequiredQuestVariables: { } requiredQuestVariables }) + if (step is { RequiredQuestVariables: { } requiredQuestVariables }) { if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork, logger)) { @@ -203,16 +204,17 @@ internal static class SkipCondition } } - 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; @@ -223,7 +225,7 @@ internal static class SkipCondition } } - if (SkipConditions.ExtraCondition == EExtraSkipCondition.RisingStonesSolar && + if (skipConditions.ExtraCondition == EExtraSkipCondition.RisingStonesSolar && clientState.TerritoryType == 351) { var position = clientState.LocalPlayer!.Position; @@ -234,13 +236,13 @@ internal static class SkipCondition } } - 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; diff --git a/Questionable/Controller/Steps/Shared/StepDisabled.cs b/Questionable/Controller/Steps/Shared/StepDisabled.cs index 430a8981..86487621 100644 --- a/Questionable/Controller/Steps/Shared/StepDisabled.cs +++ b/Questionable/Controller/Steps/Shared/StepDisabled.cs @@ -1,6 +1,4 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Questionable.Model; using Questionable.Model.Questing; @@ -8,14 +6,14 @@ namespace Questionable.Controller.Steps.Shared; 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(); + return new Task(loggerFactory.CreateLogger()); } } diff --git a/Questionable/Controller/Steps/Shared/SwitchClassJob.cs b/Questionable/Controller/Steps/Shared/SwitchClassJob.cs index c8159b93..4a4fa4c0 100644 --- a/Questionable/Controller/Steps/Shared/SwitchClassJob.cs +++ b/Questionable/Controller/Steps/Shared/SwitchClassJob.cs @@ -6,19 +6,11 @@ using Questionable.Controller.Steps.Common; 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(); @@ -27,7 +19,7 @@ internal sealed class SwitchClassJob(IClientState clientState) : AbstractDelayed 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; @@ -35,10 +27,10 @@ internal sealed class SwitchClassJob(IClientState clientState) : AbstractDelayed } } - 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})"; } diff --git a/Questionable/Controller/Steps/Shared/WaitAtEnd.cs b/Questionable/Controller/Steps/Shared/WaitAtEnd.cs index faa36e00..631cbf11 100644 --- a/Questionable/Controller/Steps/Shared/WaitAtEnd.cs +++ b/Questionable/Controller/Steps/Shared/WaitAtEnd.cs @@ -5,9 +5,6 @@ using System.Linq; 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; @@ -20,19 +17,20 @@ namespace Questionable.Controller.Steps.Shared; 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 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() - .With((QuestId)quest.Id, step); - var delay = serviceProvider.GetRequiredService(); + var task = new WaitForCompletionFlags((QuestId)quest.Id, step, questFunctions); + var delay = new WaitDelay(); return [task, delay, Next(quest, sequence)]; } @@ -43,15 +41,15 @@ internal static class WaitAtEnd new WaitConditionTask(() => !condition[ConditionFlag.InCombat], "Wait(not in combat)"); return [ - serviceProvider.GetRequiredService(), + new WaitDelay(), notInCombat, - serviceProvider.GetRequiredService(), + new WaitDelay(), Next(quest, sequence) ]; case EInteractionType.WaitForManualProgress: case EInteractionType.Instruction: - return [serviceProvider.GetRequiredService()]; + return [new WaitNextStepOrSequence()]; case EInteractionType.Duty: case EInteractionType.SinglePlayerDuty: @@ -68,9 +66,9 @@ internal static class WaitAtEnd return [ - serviceProvider.GetRequiredService() - .With(step.DataId.Value, step.Position.Value, step.NpcWaitDistance ?? 0.05f), - serviceProvider.GetRequiredService(), + new WaitObjectAtPosition(step.DataId.Value, step.Position.Value, step.NpcWaitDistance ?? 0.05f, + gameFunctions), + new WaitDelay(), Next(quest, sequence) ]; @@ -104,15 +102,14 @@ internal static class WaitAtEnd return [ waitInteraction, - serviceProvider.GetRequiredService(), + new WaitDelay(), Next(quest, sequence) ]; case EInteractionType.AcceptQuest: { - var accept = serviceProvider.GetRequiredService() - .With(step.PickUpQuestId ?? quest.Id); - var delay = serviceProvider.GetRequiredService(); + var accept = new WaitQuestAccepted(step.PickUpQuestId ?? quest.Id, questFunctions); + var delay = new WaitDelay(); if (step.PickUpQuestId != null) return [accept, delay, Next(quest, sequence)]; else @@ -121,9 +118,8 @@ internal static class WaitAtEnd case EInteractionType.CompleteQuest: { - var complete = serviceProvider.GetRequiredService() - .With(step.TurnInQuestId ?? quest.Id); - var delay = serviceProvider.GetRequiredService(); + var complete = new WaitQuestCompleted(step.TurnInQuestId ?? quest.Id, questFunctions); + var delay = new WaitDelay(); if (step.TurnInQuestId != null) return [complete, delay, Next(quest, sequence)]; else @@ -132,7 +128,7 @@ internal static class WaitAtEnd case EInteractionType.Interact: default: - return [serviceProvider.GetRequiredService(), Next(quest, sequence)]; + return [new WaitDelay(), Next(quest, sequence)]; } } @@ -142,14 +138,8 @@ internal static class WaitAtEnd } } - 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})"; @@ -164,100 +154,64 @@ internal static class WaitAtEnd 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 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 diff --git a/Questionable/Controller/Steps/Shared/WaitAtStart.cs b/Questionable/Controller/Steps/Shared/WaitAtStart.cs index 1e7944c5..cbe63e7f 100644 --- a/Questionable/Controller/Steps/Shared/WaitAtStart.cs +++ b/Questionable/Controller/Steps/Shared/WaitAtStart.cs @@ -1,5 +1,4 @@ using System; -using Microsoft.Extensions.DependencyInjection; using Questionable.Controller.Steps.Common; using Questionable.Model; using Questionable.Model.Questing; @@ -8,26 +7,19 @@ namespace Questionable.Controller.Steps.Shared; 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() - .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})"; diff --git a/Questionable/Controller/Steps/TaskCreator.cs b/Questionable/Controller/Steps/TaskCreator.cs index c5e8255c..b96ed5be 100644 --- a/Questionable/Controller/Steps/TaskCreator.cs +++ b/Questionable/Controller/Steps/TaskCreator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Model; using Questionable.Model.Questing; @@ -9,18 +10,19 @@ namespace Questionable.Controller.Steps; internal sealed class TaskCreator { - private readonly IReadOnlyList _taskFactories; + private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; - public TaskCreator(IEnumerable taskFactories, ILogger logger) + public TaskCreator(IServiceProvider serviceProvider, ILogger logger) { - _taskFactories = taskFactories.ToList().AsReadOnly(); + _serviceProvider = serviceProvider; _logger = logger; } public IReadOnlyList CreateTasks(Quest quest, QuestSequence sequence, QuestStep step) { - var newTasks = _taskFactories + using var scope = _serviceProvider.CreateScope(); + var newTasks = scope.ServiceProvider.GetRequiredService>() .SelectMany(x => { IList tasks = x.CreateAllTasks(quest, sequence, step).ToList(); diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index 4c7f05b7..db3510cf 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -125,56 +125,42 @@ public sealed class QuestionablePlugin : IDalamudPlugin private static void AddTaskFactories(ServiceCollection serviceCollection) { // individual tasks - serviceCollection.AddTransient(); - serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddSingleton(); // task factories - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddSingleton(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTransient(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection - .AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection.AddTaskWithFactory(); - serviceCollection - .AddTaskWithFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); + serviceCollection.AddTaskFactory(); - serviceCollection - .AddTaskWithFactory(); + serviceCollection.AddTaskFactory(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); diff --git a/Questionable/ServiceCollectionExtensions.cs b/Questionable/ServiceCollectionExtensions.cs index a97401f2..0247cefe 100644 --- a/Questionable/ServiceCollectionExtensions.cs +++ b/Questionable/ServiceCollectionExtensions.cs @@ -6,79 +6,12 @@ namespace Questionable; 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(); - serviceCollection.AddTransient(); - } - - 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(); - serviceCollection.AddTransient(); - serviceCollection.AddTransient(); - } - - 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(); - serviceCollection.AddTransient(); - serviceCollection.AddTransient(); - serviceCollection.AddTransient(); - } - - 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(); - serviceCollection.AddTransient(); - serviceCollection.AddTransient(); - serviceCollection.AddTransient(); - serviceCollection.AddTransient(); + serviceCollection.AddSingleton(); } }