diff --git a/GatheringPaths/3.x - Heavensward/Coerthas Western Highlands/351_Red Rim_MIN.json b/GatheringPaths/3.x - Heavensward/Coerthas Western Highlands/351_Red Rim_MIN.json index cb1d96af..e61ee374 100644 --- a/GatheringPaths/3.x - Heavensward/Coerthas Western Highlands/351_Red Rim_MIN.json +++ b/GatheringPaths/3.x - Heavensward/Coerthas Western Highlands/351_Red Rim_MIN.json @@ -16,12 +16,15 @@ "Z": 405.1829 }, "MinimumAngle": 100, - "MaximumAngle": 250 + "MaximumAngle": 250, + "MinimumDistance": 1.5, + "MaximumDistance": 3 } ] }, { "DataId": 31345, + "Fly": false, "Locations": [ { "Position": { @@ -29,8 +32,10 @@ "Y": 216.5585, "Z": 412.4353 }, - "MinimumAngle": 50, - "MaximumAngle": 165 + "MinimumAngle": 75, + "MaximumAngle": 145, + "MinimumDistance": 1.5, + "MaximumDistance": 3 }, { "Position": { @@ -39,7 +44,9 @@ "Z": 421.5481 }, "MinimumAngle": 0, - "MaximumAngle": 145 + "MaximumAngle": 145, + "MinimumDistance": 1.5, + "MaximumDistance": 3 }, { "Position": { @@ -48,7 +55,9 @@ "Z": 408.2164 }, "MinimumAngle": 155, - "MaximumAngle": 225 + "MaximumAngle": 225, + "MinimumDistance": 1.5, + "MaximumDistance": 3 } ] } @@ -155,4 +164,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/Questionable.Model/Gathering/GatheringNode.cs b/Questionable.Model/Gathering/GatheringNode.cs index 9a3ed58c..d5bbce44 100644 --- a/Questionable.Model/Gathering/GatheringNode.cs +++ b/Questionable.Model/Gathering/GatheringNode.cs @@ -5,7 +5,7 @@ namespace Questionable.Model.Gathering; public sealed class GatheringNode { public uint DataId { get; set; } - public bool Fly { get; set; } + public bool? Fly { get; set; } public List Locations { get; set; } = []; } diff --git a/Questionable.Model/Gathering/GatheringRoot.cs b/Questionable.Model/Gathering/GatheringRoot.cs index b1f98ca8..c76f45c7 100644 --- a/Questionable.Model/Gathering/GatheringRoot.cs +++ b/Questionable.Model/Gathering/GatheringRoot.cs @@ -16,6 +16,6 @@ public sealed class GatheringRoot public EAetheryteLocation? AetheryteShortcut { get; set; } public AethernetShortcut? AethernetShortcut { get; set; } - public bool FlyBetweenNodes { get; set; } = true; + public bool? FlyBetweenNodes { get; set; } public List Groups { get; set; } = []; } diff --git a/Questionable/Controller/GatheringController.cs b/Questionable/Controller/GatheringController.cs index 53878e53..f5a517fa 100644 --- a/Questionable/Controller/GatheringController.cs +++ b/Questionable/Controller/GatheringController.cs @@ -1,13 +1,18 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Numerics; +using System.Text.RegularExpressions; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Event; using FFXIVClientStructs.FFXIV.Client.Game.UI; +using LLib; +using Lumina.Excel.GeneratedSheets; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps; @@ -32,6 +37,7 @@ internal sealed unsafe class GatheringController : MiniTaskController logger, IServiceProvider serviceProvider, - ICondition condition) + ICondition condition, + IDataManager dataManager, + IPluginLog pluginLog) : base(chatGui, logger) { _movementController = movementController; @@ -54,6 +62,9 @@ internal sealed unsafe class GatheringController : MiniTaskController(5574, x => x.Text, pluginLog) + ?? throw new InvalidDataException("No regex found for revisit message"); } public bool Start(GatheringRequest gatheringRequest) @@ -88,13 +99,19 @@ internal sealed unsafe class GatheringController : MiniTaskController() .With(_currentRequest.Root.TerritoryId, MountTask.EMountIf.Always)); + + bool fly = currentNode.Fly.GetValueOrDefault(_currentRequest.Root.FlyBetweenNodes.GetValueOrDefault(true)) && + _gameFunctions.IsFlyingUnlocked(_currentRequest.Root.TerritoryId); if (currentNode.Locations.Count > 1) { Vector3 averagePosition = new Vector3 @@ -144,8 +164,7 @@ internal sealed unsafe class GatheringController : MiniTaskController x.Position.Y).Max() + 5f, Z = currentNode.Locations.Sum(x => x.Position.Z) / currentNode.Locations.Count, }; - bool fly = (currentNode.Fly || _currentRequest.Root.FlyBetweenNodes) - && _gameFunctions.IsFlyingUnlocked(_currentRequest.Root.TerritoryId); + Vector3? pointOnFloor = _navmeshIpc.GetPointOnFloor(averagePosition, true); if (pointOnFloor != null) pointOnFloor = pointOnFloor.Value with { Y = pointOnFloor.Value.Y + (fly ? 3f : 0f) }; @@ -156,17 +175,19 @@ internal sealed unsafe class GatheringController : MiniTaskController() - .With(_currentRequest.Root.TerritoryId, - currentNode.Fly || _currentRequest.Root.FlyBetweenNodes, - currentNode)); + .With(_currentRequest.Root.TerritoryId, fly, currentNode)); _taskQueue.Enqueue(_serviceProvider.GetRequiredService() .With(currentNode.DataId, null, EInteractionType.InternalGather, true)); - _taskQueue.Enqueue(_serviceProvider.GetRequiredService() - .With(_currentRequest.Data, currentNode)); - if (_currentRequest.Data.Collectability > 0) + + foreach (bool revisitRequired in new[] { false, true }) { - _taskQueue.Enqueue(_serviceProvider.GetRequiredService() - .With(_currentRequest.Data, currentNode)); + _taskQueue.Enqueue(_serviceProvider.GetRequiredService() + .With(_currentRequest.Data, currentNode, revisitRequired)); + if (_currentRequest.Data.Collectability > 0) + { + _taskQueue.Enqueue(_serviceProvider.GetRequiredService() + .With(_currentRequest.Data, currentNode, revisitRequired)); + } } } @@ -232,6 +253,21 @@ internal sealed unsafe class GatheringController : MiniTaskController, IDi _taskFactories = taskFactories.ToList().AsReadOnly(); _condition.ConditionChange += OnConditionChange; + _toastGui.Toast += OnNormalToast; _toastGui.ErrorToast += OnErrorToast; } @@ -786,6 +788,11 @@ internal sealed class QuestController : MiniTaskController, IDi conditionChangeAware.OnConditionChange(flag, value); } + private void OnNormalToast(ref SeString message, ref ToastOptions options, ref bool ishandled) + { + _gatheringController.OnNormalToast(message); + } + private void OnErrorToast(ref SeString message, ref bool ishandled) { if (_currentTask is IToastAware toastAware) @@ -795,6 +802,7 @@ internal sealed class QuestController : MiniTaskController, IDi public void Dispose() { _toastGui.ErrorToast -= OnErrorToast; + _toastGui.Toast -= OnNormalToast; _condition.ConditionChange -= OnConditionChange; } diff --git a/Questionable/Controller/Steps/Gathering/DoGather.cs b/Questionable/Controller/Steps/Gathering/DoGather.cs index e947212c..6cd4b3e7 100644 --- a/Questionable/Controller/Steps/Gathering/DoGather.cs +++ b/Questionable/Controller/Steps/Gathering/DoGather.cs @@ -22,20 +22,24 @@ internal sealed class DoGather( IGameGui gameGui, IClientState clientState, ICondition condition, - ILogger logger) : ITask + ILogger logger) : ITask, IRevisitAware { 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) + public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode, + bool revisitRequired) { _currentRequest = currentRequest; _currentNode = currentNode; + _revisitRequired = revisitRequired; return this; } @@ -43,8 +47,17 @@ internal sealed class DoGather( public unsafe ETaskResult Update() { - if (gatheringController.HasNodeDisappeared(_currentNode)) + if (_revisitRequired && !_revisitTriggered) + { + logger.LogInformation("No revisit"); return ETaskResult.TaskComplete; + } + + if (gatheringController.HasNodeDisappeared(_currentNode)) + { + logger.LogInformation("Node disappeared"); + return ETaskResult.TaskComplete; + } if (gameFunctions.GetFreeInventorySlots() == 0) throw new TaskException("Inventory full"); @@ -225,7 +238,12 @@ internal sealed class DoGather( return ActionManager.Instance()->GetActionStatus(ActionType.Action, (uint)action) == 0; } - public override string ToString() => "DoGather"; + public void OnRevisit() + { + _revisitTriggered = true; + } + + 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 63432931..10fb9faa 100644 --- a/Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs +++ b/Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs @@ -17,16 +17,19 @@ internal sealed class DoGatherCollectable( GameFunctions gameFunctions, IClientState clientState, IGameGui gameGui, - ILogger logger) : ITask + ILogger logger) : ITask, IRevisitAware { private GatheringController.GatheringRequest _currentRequest = null!; private GatheringNode _currentNode = null!; + private bool _revisitRequired; + private bool _revisitTriggered; private Queue? _actionQueue; - public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode) + public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode, bool revisitRequired) { _currentRequest = currentRequest; _currentNode = currentNode; + _revisitRequired = revisitRequired; return this; } @@ -34,8 +37,17 @@ internal sealed class DoGatherCollectable( public unsafe ETaskResult Update() { - if (gatheringController.HasNodeDisappeared(_currentNode)) + if (_revisitRequired && !_revisitTriggered) + { + logger.LogInformation("No revisit"); return ETaskResult.TaskComplete; + } + + if (gatheringController.HasNodeDisappeared(_currentNode)) + { + logger.LogInformation("Node disappeared"); + return ETaskResult.TaskComplete; + } if (gatheringController.HasRequestedItems()) { @@ -150,8 +162,13 @@ internal sealed class DoGatherCollectable( return botanistAction; } + public void OnRevisit() + { + _revisitTriggered = true; + } + public override string ToString() => - $"DoGatherCollectable({SeIconChar.Collectible.ToIconString()} {_currentRequest.Collectability})"; + $"DoGatherCollectable({SeIconChar.Collectible.ToIconString()} {_currentRequest.Collectability}){(_revisitRequired ? " if revist" : "")}"; [SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")] private sealed record NodeCondition( diff --git a/Questionable/Controller/Steps/IRevisitAware.cs b/Questionable/Controller/Steps/IRevisitAware.cs new file mode 100644 index 00000000..4faf1c0b --- /dev/null +++ b/Questionable/Controller/Steps/IRevisitAware.cs @@ -0,0 +1,6 @@ +namespace Questionable.Controller.Steps; + +public interface IRevisitAware +{ + void OnRevisit(); +}