"Z": 405.1829
},
"MinimumAngle": 100,
- "MaximumAngle": 250
+ "MaximumAngle": 250,
+ "MinimumDistance": 1.5,
+ "MaximumDistance": 3
}
]
},
{
"DataId": 31345,
+ "Fly": false,
"Locations": [
{
"Position": {
"Y": 216.5585,
"Z": 412.4353
},
- "MinimumAngle": 50,
- "MaximumAngle": 165
+ "MinimumAngle": 75,
+ "MaximumAngle": 145,
+ "MinimumDistance": 1.5,
+ "MaximumDistance": 3
},
{
"Position": {
"Z": 421.5481
},
"MinimumAngle": 0,
- "MaximumAngle": 145
+ "MaximumAngle": 145,
+ "MinimumDistance": 1.5,
+ "MaximumDistance": 3
},
{
"Position": {
"Z": 408.2164
},
"MinimumAngle": 155,
- "MaximumAngle": 225
+ "MaximumAngle": 225,
+ "MinimumDistance": 1.5,
+ "MaximumDistance": 3
}
]
}
]
}
]
-}
+}
\ No newline at end of file
public sealed class GatheringNode
{
public uint DataId { get; set; }
- public bool Fly { get; set; }
+ public bool? Fly { get; set; }
public List<GatheringLocation> Locations { get; set; } = [];
}
public EAetheryteLocation? AetheryteShortcut { get; set; }
public AethernetShortcut? AethernetShortcut { get; set; }
- public bool FlyBetweenNodes { get; set; } = true;
+ public bool? FlyBetweenNodes { get; set; }
public List<GatheringNodeGroup> Groups { get; set; } = [];
}
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;
private readonly IObjectTable _objectTable;
private readonly IServiceProvider _serviceProvider;
private readonly ICondition _condition;
+ private readonly Regex _revisitRegex;
private CurrentRequest? _currentRequest;
IChatGui chatGui,
ILogger<GatheringController> logger,
IServiceProvider serviceProvider,
- ICondition condition)
+ ICondition condition,
+ IDataManager dataManager,
+ IPluginLog pluginLog)
: base(chatGui, logger)
{
_movementController = movementController;
_objectTable = objectTable;
_serviceProvider = serviceProvider;
_condition = condition;
+
+ _revisitRegex = dataManager.GetRegex<LogMessage>(5574, x => x.Text, pluginLog)
+ ?? throw new InvalidDataException("No regex found for revisit message");
}
public bool Start(GatheringRequest gatheringRequest)
public EStatus Update()
{
if (_currentRequest == null)
+ {
+ Stop("No request");
return EStatus.Complete;
+ }
if (_movementController.IsPathfinding || _movementController.IsPathfinding)
return EStatus.Moving;
if (HasRequestedItems() && !_condition[ConditionFlag.Gathering])
+ {
+ Stop("Has all items");
return EStatus.Complete;
+ }
if (_currentTask == null && _taskQueue.Count == 0)
GoToNextNode();
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<MountTask>()
.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
Y = currentNode.Locations.Select(x => 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) };
}
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<MoveToLandingLocation>()
- .With(_currentRequest.Root.TerritoryId,
- currentNode.Fly || _currentRequest.Root.FlyBetweenNodes,
- currentNode));
+ .With(_currentRequest.Root.TerritoryId, fly, currentNode));
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<Interact.DoInteract>()
.With(currentNode.DataId, null, EInteractionType.InternalGather, true));
- _taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGather>()
- .With(_currentRequest.Data, currentNode));
- if (_currentRequest.Data.Collectability > 0)
+
+ foreach (bool revisitRequired in new[] { false, true })
{
- _taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGatherCollectable>()
- .With(_currentRequest.Data, currentNode));
+ _taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGather>()
+ .With(_currentRequest.Data, currentNode, revisitRequired));
+ if (_currentRequest.Data.Collectability > 0)
+ {
+ _taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGatherCollectable>()
+ .With(_currentRequest.Data, currentNode, revisitRequired));
+ }
}
}
return base.GetRemainingTaskNames();
}
+ public void OnNormalToast(SeString message)
+ {
+ if (_revisitRegex.IsMatch(message.TextValue))
+ {
+ if (_currentTask is IRevisitAware currentTaskRevisitAware)
+ currentTaskRevisitAware.OnRevisit();
+
+ foreach (ITask task in _taskQueue)
+ {
+ if (task is IRevisitAware taskRevisitAware)
+ taskRevisitAware.OnRevisit();
+ }
+ }
+ }
+
private sealed class CurrentRequest
{
public required GatheringRequest Data { get; init; }
using System.Linq;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Keys;
+using Dalamud.Game.Gui.Toast;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
_taskFactories = taskFactories.ToList().AsReadOnly();
_condition.ConditionChange += OnConditionChange;
+ _toastGui.Toast += OnNormalToast;
_toastGui.ErrorToast += OnErrorToast;
}
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)
public void Dispose()
{
_toastGui.ErrorToast -= OnErrorToast;
+ _toastGui.Toast -= OnNormalToast;
_condition.ConditionChange -= OnConditionChange;
}
IGameGui gameGui,
IClientState clientState,
ICondition condition,
- ILogger<DoGather> logger) : ITask
+ ILogger<DoGather> 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<EAction>? _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;
}
public unsafe ETaskResult Update()
{
+ 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");
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);
GameFunctions gameFunctions,
IClientState clientState,
IGameGui gameGui,
- ILogger<DoGatherCollectable> logger) : ITask
+ ILogger<DoGatherCollectable> logger) : ITask, IRevisitAware
{
private GatheringController.GatheringRequest _currentRequest = null!;
private GatheringNode _currentNode = null!;
+ private bool _revisitRequired;
+ private bool _revisitTriggered;
private Queue<EAction>? _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;
}
public unsafe ETaskResult Update()
{
+ if (_revisitRequired && !_revisitTriggered)
+ {
+ logger.LogInformation("No revisit");
+ return ETaskResult.TaskComplete;
+ }
+
if (gatheringController.HasNodeDisappeared(_currentNode))
+ {
+ logger.LogInformation("Node disappeared");
return ETaskResult.TaskComplete;
+ }
if (gatheringController.HasRequestedItems())
{
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(
--- /dev/null
+namespace Questionable.Controller.Steps;
+
+public interface IRevisitAware
+{
+ void OnRevisit();
+}