Make task logic stateless to support rewind
This commit is contained in:
parent
721f9617a3
commit
7e9070950e
@ -121,7 +121,7 @@ public sealed class RendererPlugin : IDalamudPlugin
|
|||||||
if (!directory.Exists)
|
if (!directory.Exists)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_pluginLog.Information($"Loading locations from {directory}");
|
//_pluginLog.Information($"Loading locations from {directory}");
|
||||||
foreach (FileInfo fileInfo in directory.GetFiles("*.json"))
|
foreach (FileInfo fileInfo in directory.GetFiles("*.json"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -285,7 +285,7 @@ internal sealed class InteractionUiController : IDisposable
|
|||||||
List<DialogueChoiceInfo> dialogueChoices = [];
|
List<DialogueChoiceInfo> dialogueChoices = [];
|
||||||
|
|
||||||
// levequest choices have some vague sort of priority
|
// levequest choices have some vague sort of priority
|
||||||
if (_questController.HasCurrentTaskMatching<Interact.DoInteract>(out var interact) &&
|
if (_questController.HasCurrentTaskExecutorMatching<Interact.DoInteract>(out var interact) &&
|
||||||
interact.Quest != null &&
|
interact.Quest != null &&
|
||||||
interact.InteractionType is EInteractionType.AcceptLeve or EInteractionType.CompleteLeve)
|
interact.InteractionType is EInteractionType.AcceptLeve or EInteractionType.CompleteLeve)
|
||||||
{
|
{
|
||||||
@ -799,7 +799,7 @@ internal sealed class InteractionUiController : IDisposable
|
|||||||
private void TeleportTownPostSetup(AddonEvent type, AddonArgs args)
|
private void TeleportTownPostSetup(AddonEvent type, AddonArgs args)
|
||||||
{
|
{
|
||||||
if (ShouldHandleUiInteractions &&
|
if (ShouldHandleUiInteractions &&
|
||||||
_questController.HasCurrentTaskMatching(out AethernetShortcut.UseAethernetShortcut? aethernetShortcut) &&
|
_questController.HasCurrentTaskMatching(out AethernetShortcut.Task? aethernetShortcut) &&
|
||||||
aethernetShortcut.From.IsFirmamentAetheryte())
|
aethernetShortcut.From.IsFirmamentAetheryte())
|
||||||
{
|
{
|
||||||
// this might be better via atkvalues; but this works for now
|
// this might be better via atkvalues; but this works for now
|
||||||
|
@ -13,16 +13,13 @@ using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
|||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using LLib;
|
using LLib;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps;
|
using Questionable.Controller.Steps;
|
||||||
using Questionable.Controller.Steps.Common;
|
|
||||||
using Questionable.Controller.Steps.Gathering;
|
using Questionable.Controller.Steps.Gathering;
|
||||||
using Questionable.Controller.Steps.Interactions;
|
using Questionable.Controller.Steps.Interactions;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
using Questionable.External;
|
using Questionable.External;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
using Questionable.GatheringPaths;
|
|
||||||
using Questionable.Model.Gathering;
|
using Questionable.Model.Gathering;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
using Mount = Questionable.Controller.Steps.Common.Mount;
|
using Mount = Questionable.Controller.Steps.Common.Mount;
|
||||||
@ -32,17 +29,11 @@ namespace Questionable.Controller;
|
|||||||
internal sealed unsafe class GatheringController : MiniTaskController<GatheringController>
|
internal sealed unsafe class GatheringController : MiniTaskController<GatheringController>
|
||||||
{
|
{
|
||||||
private readonly MovementController _movementController;
|
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 GatheringPointRegistry _gatheringPointRegistry;
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly GameFunctions _gameFunctions;
|
||||||
private readonly NavmeshIpc _navmeshIpc;
|
private readonly NavmeshIpc _navmeshIpc;
|
||||||
private readonly IObjectTable _objectTable;
|
private readonly IObjectTable _objectTable;
|
||||||
private readonly ICondition _condition;
|
private readonly ICondition _condition;
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
|
||||||
private readonly IGameGui _gameGui;
|
|
||||||
private readonly IClientState _clientState;
|
|
||||||
private readonly ILogger<GatheringController> _logger;
|
private readonly ILogger<GatheringController> _logger;
|
||||||
private readonly Regex _revisitRegex;
|
private readonly Regex _revisitRegex;
|
||||||
|
|
||||||
@ -50,10 +41,6 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
|
|
||||||
public GatheringController(
|
public GatheringController(
|
||||||
MovementController movementController,
|
MovementController movementController,
|
||||||
MoveTo.Factory moveFactory,
|
|
||||||
Mount.Factory mountFactory,
|
|
||||||
Combat.Factory combatFactory,
|
|
||||||
Interact.Factory interactFactory,
|
|
||||||
GatheringPointRegistry gatheringPointRegistry,
|
GatheringPointRegistry gatheringPointRegistry,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
NavmeshIpc navmeshIpc,
|
NavmeshIpc navmeshIpc,
|
||||||
@ -61,25 +48,17 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
IChatGui chatGui,
|
IChatGui chatGui,
|
||||||
ILogger<GatheringController> logger,
|
ILogger<GatheringController> logger,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
IDataManager dataManager,
|
IDataManager dataManager,
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
IGameGui gameGui,
|
|
||||||
IClientState clientState,
|
|
||||||
IPluginLog pluginLog)
|
IPluginLog pluginLog)
|
||||||
: base(chatGui, mountFactory, combatFactory, condition, logger)
|
: base(chatGui, condition, serviceProvider, logger)
|
||||||
{
|
{
|
||||||
_movementController = movementController;
|
_movementController = movementController;
|
||||||
_moveFactory = moveFactory;
|
|
||||||
_mountFactory = mountFactory;
|
|
||||||
_interactFactory = interactFactory;
|
|
||||||
_gatheringPointRegistry = gatheringPointRegistry;
|
_gatheringPointRegistry = gatheringPointRegistry;
|
||||||
_gameFunctions = gameFunctions;
|
_gameFunctions = gameFunctions;
|
||||||
_navmeshIpc = navmeshIpc;
|
_navmeshIpc = navmeshIpc;
|
||||||
_objectTable = objectTable;
|
_objectTable = objectTable;
|
||||||
_condition = condition;
|
_condition = condition;
|
||||||
_loggerFactory = loggerFactory;
|
|
||||||
_gameGui = gameGui;
|
|
||||||
_clientState = clientState;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
_revisitRegex = dataManager.GetRegex<LogMessage>(5574, x => x.Text, pluginLog)
|
_revisitRegex = dataManager.GetRegex<LogMessage>(5574, x => x.Text, pluginLog)
|
||||||
@ -170,7 +149,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
ushort territoryId = _currentRequest.Root.Steps.Last().TerritoryId;
|
ushort territoryId = _currentRequest.Root.Steps.Last().TerritoryId;
|
||||||
_taskQueue.Enqueue(_mountFactory.Mount(territoryId, Mount.EMountIf.Always));
|
_taskQueue.Enqueue(new Mount.MountTask(territoryId, Mount.EMountIf.Always));
|
||||||
|
|
||||||
bool fly = currentNode.Fly.GetValueOrDefault(_currentRequest.Root.FlyBetweenNodes.GetValueOrDefault(true)) &&
|
bool fly = currentNode.Fly.GetValueOrDefault(_currentRequest.Root.FlyBetweenNodes.GetValueOrDefault(true)) &&
|
||||||
_gameFunctions.IsFlyingUnlocked(territoryId);
|
_gameFunctions.IsFlyingUnlocked(territoryId);
|
||||||
@ -187,14 +166,13 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
if (pointOnFloor != null)
|
if (pointOnFloor != null)
|
||||||
pointOnFloor = pointOnFloor.Value with { Y = pointOnFloor.Value.Y + (fly ? 3f : 0f) };
|
pointOnFloor = pointOnFloor.Value with { Y = pointOnFloor.Value.Y + (fly ? 3f : 0f) };
|
||||||
|
|
||||||
_taskQueue.Enqueue(_moveFactory.Move(new MoveTo.MoveParams(territoryId, pointOnFloor ?? averagePosition,
|
_taskQueue.Enqueue(new MoveTo.MoveTask(territoryId, pointOnFloor ?? averagePosition,
|
||||||
null, 50f, Fly: fly, IgnoreDistanceToObject: true)));
|
null, 50f, Fly: fly, IgnoreDistanceToObject: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
_taskQueue.Enqueue(new MoveToLandingLocation(territoryId, fly, currentNode, _moveFactory, _gameFunctions,
|
_taskQueue.Enqueue(new MoveToLandingLocation.Task(territoryId, fly, currentNode));
|
||||||
_objectTable, _loggerFactory.CreateLogger<MoveToLandingLocation>()));
|
_taskQueue.Enqueue(new Mount.UnmountTask());
|
||||||
_taskQueue.Enqueue(_mountFactory.Unmount());
|
_taskQueue.Enqueue(new Interact.Task(currentNode.DataId, null, EInteractionType.Gather, true));
|
||||||
_taskQueue.Enqueue(_interactFactory.Interact(currentNode.DataId, null, EInteractionType.Gather, true));
|
|
||||||
|
|
||||||
QueueGatherNode(currentNode);
|
QueueGatherNode(currentNode);
|
||||||
}
|
}
|
||||||
@ -203,12 +181,10 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
{
|
{
|
||||||
foreach (bool revisitRequired in new[] { false, true })
|
foreach (bool revisitRequired in new[] { false, true })
|
||||||
{
|
{
|
||||||
_taskQueue.Enqueue(new DoGather(_currentRequest!.Data, currentNode, revisitRequired, this, _gameFunctions,
|
_taskQueue.Enqueue(new DoGather.Task(_currentRequest!.Data, currentNode, revisitRequired));
|
||||||
_gameGui, _clientState, _condition, _loggerFactory.CreateLogger<DoGather>()));
|
|
||||||
if (_currentRequest.Data.Collectability > 0)
|
if (_currentRequest.Data.Collectability > 0)
|
||||||
{
|
{
|
||||||
_taskQueue.Enqueue(new DoGatherCollectable(_currentRequest.Data, currentNode, revisitRequired, this,
|
_taskQueue.Enqueue(new DoGatherCollectable.Task(_currentRequest.Data, currentNode, revisitRequired));
|
||||||
_gameFunctions, _clientState, _gameGui, _loggerFactory.CreateLogger<DoGatherCollectable>()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -269,7 +245,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
|
|
||||||
public override IList<string> GetRemainingTaskNames()
|
public override IList<string> GetRemainingTaskNames()
|
||||||
{
|
{
|
||||||
if (_taskQueue.CurrentTask is {} currentTask)
|
if (_taskQueue.CurrentTaskExecutor?.CurrentTask is {} currentTask)
|
||||||
return [currentTask.ToString() ?? "?", .. base.GetRemainingTaskNames()];
|
return [currentTask.ToString() ?? "?", .. base.GetRemainingTaskNames()];
|
||||||
else
|
else
|
||||||
return base.GetRemainingTaskNames();
|
return base.GetRemainingTaskNames();
|
||||||
@ -279,7 +255,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
{
|
{
|
||||||
if (_revisitRegex.IsMatch(message.TextValue))
|
if (_revisitRegex.IsMatch(message.TextValue))
|
||||||
{
|
{
|
||||||
if (_taskQueue.CurrentTask is IRevisitAware currentTaskRevisitAware)
|
if (_taskQueue.CurrentTaskExecutor?.CurrentTask is IRevisitAware currentTaskRevisitAware)
|
||||||
currentTaskRevisitAware.OnRevisit();
|
currentTaskRevisitAware.OnRevisit();
|
||||||
|
|
||||||
foreach (ITask task in _taskQueue.RemainingTasks)
|
foreach (ITask task in _taskQueue.RemainingTasks)
|
||||||
|
@ -94,7 +94,7 @@ internal sealed class GatheringPointRegistry : IDisposable
|
|||||||
|
|
||||||
private void LoadGatheringPointFromStream(string fileName, Stream stream)
|
private void LoadGatheringPointFromStream(string fileName, Stream stream)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("Loading gathering point from '{FileName}'", fileName);
|
//_logger.LogTrace("Loading gathering point from '{FileName}'", fileName);
|
||||||
GatheringPointId? gatheringPointId = ExtractGatheringPointIdFromName(fileName);
|
GatheringPointId? gatheringPointId = ExtractGatheringPointIdFromName(fileName);
|
||||||
if (gatheringPointId == null)
|
if (gatheringPointId == null)
|
||||||
return;
|
return;
|
||||||
@ -110,7 +110,7 @@ internal sealed class GatheringPointRegistry : IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Log(logLevel, "Loading gathering points from {DirectoryName}", directory);
|
//_logger.Log(logLevel, "Loading gathering points from {DirectoryName}", directory);
|
||||||
foreach (FileInfo fileInfo in directory.GetFiles("*.json"))
|
foreach (FileInfo fileInfo in directory.GetFiles("*.json"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps;
|
using Questionable.Controller.Steps;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
@ -17,33 +18,33 @@ internal abstract class MiniTaskController<T>
|
|||||||
protected readonly TaskQueue _taskQueue = new();
|
protected readonly TaskQueue _taskQueue = new();
|
||||||
|
|
||||||
private readonly IChatGui _chatGui;
|
private readonly IChatGui _chatGui;
|
||||||
private readonly Mount.Factory _mountFactory;
|
|
||||||
private readonly Combat.Factory _combatFactory;
|
|
||||||
private readonly ICondition _condition;
|
private readonly ICondition _condition;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly ILogger<T> _logger;
|
private readonly ILogger<T> _logger;
|
||||||
|
|
||||||
protected MiniTaskController(IChatGui chatGui, Mount.Factory mountFactory, Combat.Factory combatFactory,
|
protected MiniTaskController(IChatGui chatGui, ICondition condition, IServiceProvider serviceProvider,
|
||||||
ICondition condition, ILogger<T> logger)
|
ILogger<T> logger)
|
||||||
{
|
{
|
||||||
_chatGui = chatGui;
|
_chatGui = chatGui;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mountFactory = mountFactory;
|
_serviceProvider = serviceProvider;
|
||||||
_combatFactory = combatFactory;
|
|
||||||
_condition = condition;
|
_condition = condition;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void UpdateCurrentTask()
|
protected virtual void UpdateCurrentTask()
|
||||||
{
|
{
|
||||||
if (_taskQueue.CurrentTask == null)
|
if (_taskQueue.CurrentTaskExecutor == null)
|
||||||
{
|
{
|
||||||
if (_taskQueue.TryDequeue(out ITask? upcomingTask))
|
if (_taskQueue.TryDequeue(out ITask? upcomingTask))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Starting task {TaskName}", upcomingTask.ToString());
|
_logger.LogInformation("Starting task {TaskName}", upcomingTask.ToString());
|
||||||
if (upcomingTask.Start())
|
ITaskExecutor taskExecutor =
|
||||||
|
_serviceProvider.GetRequiredKeyedService<ITaskExecutor>(upcomingTask.GetType());
|
||||||
|
if (taskExecutor.Start(upcomingTask))
|
||||||
{
|
{
|
||||||
_taskQueue.CurrentTask = upcomingTask;
|
_taskQueue.CurrentTaskExecutor = taskExecutor;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -68,19 +69,20 @@ internal abstract class MiniTaskController<T>
|
|||||||
ETaskResult result;
|
ETaskResult result;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_taskQueue.CurrentTask.WasInterrupted())
|
if (_taskQueue.CurrentTaskExecutor.WasInterrupted())
|
||||||
{
|
{
|
||||||
InterruptQueueWithCombat();
|
InterruptQueueWithCombat();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = _taskQueue.CurrentTask.Update();
|
result = _taskQueue.CurrentTaskExecutor.Update();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError(e, "Failed to update task {TaskName}", _taskQueue.CurrentTask.ToString());
|
_logger.LogError(e, "Failed to update task {TaskName}",
|
||||||
|
_taskQueue.CurrentTaskExecutor.CurrentTask.ToString());
|
||||||
_chatGui.PrintError(
|
_chatGui.PrintError(
|
||||||
$"[Questionable] Failed to update task '{_taskQueue.CurrentTask}', please check /xllog for details.");
|
$"[Questionable] Failed to update task '{_taskQueue.CurrentTaskExecutor.CurrentTask}', please check /xllog for details.");
|
||||||
Stop("Task failed to update");
|
Stop("Task failed to update");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -92,14 +94,16 @@ internal abstract class MiniTaskController<T>
|
|||||||
|
|
||||||
case ETaskResult.SkipRemainingTasksForStep:
|
case ETaskResult.SkipRemainingTasksForStep:
|
||||||
_logger.LogInformation("{Task} → {Result}, skipping remaining tasks for step",
|
_logger.LogInformation("{Task} → {Result}, skipping remaining tasks for step",
|
||||||
_taskQueue.CurrentTask, result);
|
_taskQueue.CurrentTaskExecutor.CurrentTask, result);
|
||||||
_taskQueue.CurrentTask = null;
|
_taskQueue.CurrentTaskExecutor = null;
|
||||||
|
|
||||||
while (_taskQueue.TryDequeue(out ITask? nextTask))
|
while (_taskQueue.TryDequeue(out ITask? nextTask))
|
||||||
{
|
{
|
||||||
if (nextTask is ILastTask or Gather.SkipMarker)
|
if (nextTask is ILastTask or Gather.SkipMarker)
|
||||||
{
|
{
|
||||||
_taskQueue.CurrentTask = nextTask;
|
ITaskExecutor taskExecutor =
|
||||||
|
_serviceProvider.GetRequiredKeyedService<ITaskExecutor>(nextTask.GetType());
|
||||||
|
_taskQueue.CurrentTaskExecutor = taskExecutor;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,27 +112,27 @@ internal abstract class MiniTaskController<T>
|
|||||||
|
|
||||||
case ETaskResult.TaskComplete:
|
case ETaskResult.TaskComplete:
|
||||||
_logger.LogInformation("{Task} → {Result}, remaining tasks: {RemainingTaskCount}",
|
_logger.LogInformation("{Task} → {Result}, remaining tasks: {RemainingTaskCount}",
|
||||||
_taskQueue.CurrentTask, result, _taskQueue.RemainingTasks.Count());
|
_taskQueue.CurrentTaskExecutor.CurrentTask, result, _taskQueue.RemainingTasks.Count());
|
||||||
|
|
||||||
OnTaskComplete(_taskQueue.CurrentTask);
|
OnTaskComplete(_taskQueue.CurrentTaskExecutor.CurrentTask);
|
||||||
|
|
||||||
_taskQueue.CurrentTask = null;
|
_taskQueue.CurrentTaskExecutor = null;
|
||||||
|
|
||||||
// handled in next update
|
// handled in next update
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case ETaskResult.NextStep:
|
case ETaskResult.NextStep:
|
||||||
_logger.LogInformation("{Task} → {Result}", _taskQueue.CurrentTask, result);
|
_logger.LogInformation("{Task} → {Result}", _taskQueue.CurrentTaskExecutor.CurrentTask, result);
|
||||||
|
|
||||||
var lastTask = (ILastTask)_taskQueue.CurrentTask;
|
var lastTask = (ILastTask)_taskQueue.CurrentTaskExecutor.CurrentTask;
|
||||||
_taskQueue.CurrentTask = null;
|
_taskQueue.CurrentTaskExecutor = null;
|
||||||
|
|
||||||
OnNextStep(lastTask);
|
OnNextStep(lastTask);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case ETaskResult.End:
|
case ETaskResult.End:
|
||||||
_logger.LogInformation("{Task} → {Result}", _taskQueue.CurrentTask, result);
|
_logger.LogInformation("{Task} → {Result}", _taskQueue.CurrentTaskExecutor.CurrentTask, result);
|
||||||
_taskQueue.CurrentTask = null;
|
_taskQueue.CurrentTaskExecutor = null;
|
||||||
Stop("Task end");
|
Stop("Task end");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -154,9 +158,9 @@ internal abstract class MiniTaskController<T>
|
|||||||
{
|
{
|
||||||
List<ITask> tasks = [];
|
List<ITask> tasks = [];
|
||||||
if (_condition[ConditionFlag.Mounted])
|
if (_condition[ConditionFlag.Mounted])
|
||||||
tasks.Add(_mountFactory.Unmount());
|
tasks.Add(new Mount.UnmountTask());
|
||||||
|
|
||||||
tasks.Add(_combatFactory.CreateTask(null, false, EEnemySpawnType.QuestInterruption, [], [], []));
|
tasks.Add(Combat.Factory.CreateTask(null, false, EEnemySpawnType.QuestInterruption, [], [], []));
|
||||||
tasks.Add(new WaitAtEnd.WaitDelay());
|
tasks.Add(new WaitAtEnd.WaitDelay());
|
||||||
_taskQueue.InterruptWith(tasks);
|
_taskQueue.InterruptWith(tasks);
|
||||||
}
|
}
|
||||||
|
@ -82,10 +82,9 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
Configuration configuration,
|
Configuration configuration,
|
||||||
YesAlreadyIpc yesAlreadyIpc,
|
YesAlreadyIpc yesAlreadyIpc,
|
||||||
TaskCreator taskCreator,
|
TaskCreator taskCreator,
|
||||||
Mount.Factory mountFactory,
|
IServiceProvider serviceProvider,
|
||||||
Combat.Factory combatFactory,
|
|
||||||
IDataManager dataManager)
|
IDataManager dataManager)
|
||||||
: base(chatGui, mountFactory, combatFactory, condition, logger)
|
: base(chatGui, condition, serviceProvider, logger)
|
||||||
{
|
{
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
_gameFunctions = gameFunctions;
|
_gameFunctions = gameFunctions;
|
||||||
@ -219,7 +218,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (AutomationType == EAutomationType.Automatic &&
|
if (AutomationType == EAutomationType.Automatic &&
|
||||||
(_taskQueue.AllTasksComplete || _taskQueue.CurrentTask is WaitAtEnd.WaitQuestAccepted)
|
(_taskQueue.AllTasksComplete || _taskQueue.CurrentTaskExecutor?.CurrentTask is WaitAtEnd.WaitQuestAccepted)
|
||||||
&& CurrentQuest is { Sequence: 0, Step: 0 } or { Sequence: 0, Step: 255 }
|
&& CurrentQuest is { Sequence: 0, Step: 0 } or { Sequence: 0, Step: 255 }
|
||||||
&& DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15))
|
&& DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15))
|
||||||
{
|
{
|
||||||
@ -638,15 +637,30 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
|
|
||||||
public string ToStatString()
|
public string ToStatString()
|
||||||
{
|
{
|
||||||
return _taskQueue.CurrentTask is { } currentTask
|
return _taskQueue.CurrentTaskExecutor?.CurrentTask is { } currentTask
|
||||||
? $"{currentTask} (+{_taskQueue.RemainingTasks.Count()})"
|
? $"{currentTask} (+{_taskQueue.RemainingTasks.Count()})"
|
||||||
: $"- (+{_taskQueue.RemainingTasks.Count()})";
|
: $"- (+{_taskQueue.RemainingTasks.Count()})";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasCurrentTaskExecutorMatching<T>([NotNullWhen(true)] out T? task)
|
||||||
|
where T : class, ITaskExecutor
|
||||||
|
{
|
||||||
|
if (_taskQueue.CurrentTaskExecutor is T t)
|
||||||
|
{
|
||||||
|
task = t;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
task = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool HasCurrentTaskMatching<T>([NotNullWhen(true)] out T? task)
|
public bool HasCurrentTaskMatching<T>([NotNullWhen(true)] out T? task)
|
||||||
where T : class, ITask
|
where T : class, ITask
|
||||||
{
|
{
|
||||||
if (_taskQueue.CurrentTask is T t)
|
if (_taskQueue.CurrentTaskExecutor?.CurrentTask is T t)
|
||||||
{
|
{
|
||||||
task = t;
|
task = t;
|
||||||
return true;
|
return true;
|
||||||
@ -699,11 +713,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
{
|
{
|
||||||
lock (_progressLock)
|
lock (_progressLock)
|
||||||
{
|
{
|
||||||
if (_taskQueue.CurrentTask is ISkippableTask)
|
if (_taskQueue.CurrentTaskExecutor?.CurrentTask is ISkippableTask)
|
||||||
_taskQueue.CurrentTask = null;
|
_taskQueue.CurrentTaskExecutor = null;
|
||||||
else if (_taskQueue.CurrentTask != null)
|
else if (_taskQueue.CurrentTaskExecutor != null)
|
||||||
{
|
{
|
||||||
_taskQueue.CurrentTask = null;
|
_taskQueue.CurrentTaskExecutor = null;
|
||||||
while (_taskQueue.TryPeek(out ITask? task))
|
while (_taskQueue.TryPeek(out ITask? task))
|
||||||
{
|
{
|
||||||
_taskQueue.TryDequeue(out _);
|
_taskQueue.TryDequeue(out _);
|
||||||
@ -727,7 +741,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
|
|
||||||
public void SkipSimulatedTask()
|
public void SkipSimulatedTask()
|
||||||
{
|
{
|
||||||
_taskQueue.CurrentTask = null;
|
_taskQueue.CurrentTaskExecutor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsInterruptible()
|
public bool IsInterruptible()
|
||||||
@ -786,7 +800,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
|
|
||||||
private void OnConditionChange(ConditionFlag flag, bool value)
|
private void OnConditionChange(ConditionFlag flag, bool value)
|
||||||
{
|
{
|
||||||
if (_taskQueue.CurrentTask is IConditionChangeAware conditionChangeAware)
|
if (_taskQueue.CurrentTaskExecutor is IConditionChangeAware conditionChangeAware)
|
||||||
conditionChangeAware.OnConditionChange(flag, value);
|
conditionChangeAware.OnConditionChange(flag, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -798,7 +812,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
private void OnErrorToast(ref SeString message, ref bool isHandled)
|
private void OnErrorToast(ref SeString message, ref bool isHandled)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("XXX {A} → {B} XXX", _actionCanceledText, message.TextValue);
|
_logger.LogWarning("XXX {A} → {B} XXX", _actionCanceledText, message.TextValue);
|
||||||
if (_taskQueue.CurrentTask is IToastAware toastAware)
|
if (_taskQueue.CurrentTaskExecutor is IToastAware toastAware)
|
||||||
{
|
{
|
||||||
if (toastAware.OnErrorToast(message))
|
if (toastAware.OnErrorToast(message))
|
||||||
{
|
{
|
||||||
|
@ -142,7 +142,8 @@ internal sealed class QuestRegistry
|
|||||||
|
|
||||||
private void LoadQuestFromStream(string fileName, Stream stream, Quest.ESource source)
|
private void LoadQuestFromStream(string fileName, Stream stream, Quest.ESource source)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("Loading quest from '{FileName}'", fileName);
|
if (source == Quest.ESource.UserDirectory)
|
||||||
|
_logger.LogTrace("Loading quest from '{FileName}'", fileName);
|
||||||
ElementId? questId = ExtractQuestIdFromName(fileName);
|
ElementId? questId = ExtractQuestIdFromName(fileName);
|
||||||
if (questId == null)
|
if (questId == null)
|
||||||
return;
|
return;
|
||||||
@ -173,7 +174,8 @@ internal sealed class QuestRegistry
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Log(logLevel, "Loading quests from {DirectoryName}", directory);
|
if (source == Quest.ESource.UserDirectory)
|
||||||
|
_logger.Log(logLevel, "Loading quests from {DirectoryName}", directory);
|
||||||
foreach (FileInfo fileInfo in directory.GetFiles("*.json"))
|
foreach (FileInfo fileInfo in directory.GetFiles("*.json"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -2,33 +2,33 @@
|
|||||||
|
|
||||||
namespace Questionable.Controller.Steps.Common;
|
namespace Questionable.Controller.Steps.Common;
|
||||||
|
|
||||||
internal abstract class AbstractDelayedTask : ITask
|
internal abstract class AbstractDelayedTaskExecutor<T> : TaskExecutor<T>
|
||||||
|
where T : class, ITask
|
||||||
{
|
{
|
||||||
private DateTime _continueAt;
|
private DateTime _continueAt;
|
||||||
|
|
||||||
protected AbstractDelayedTask(TimeSpan delay)
|
protected AbstractDelayedTaskExecutor()
|
||||||
|
: this(TimeSpan.FromSeconds(5))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AbstractDelayedTaskExecutor(TimeSpan delay)
|
||||||
{
|
{
|
||||||
Delay = delay;
|
Delay = delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TimeSpan Delay { get; set; }
|
protected TimeSpan Delay { get; set; }
|
||||||
|
|
||||||
protected AbstractDelayedTask()
|
protected sealed override bool Start()
|
||||||
: this(TimeSpan.FromSeconds(5))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual InteractionProgressContext? ProgressContext() => null;
|
|
||||||
|
|
||||||
public bool Start()
|
|
||||||
{
|
{
|
||||||
|
bool started = StartInternal();
|
||||||
_continueAt = DateTime.Now.Add(Delay);
|
_continueAt = DateTime.Now.Add(Delay);
|
||||||
return StartInternal();
|
return started;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract bool StartInternal();
|
protected abstract bool StartInternal();
|
||||||
|
|
||||||
public virtual ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (_continueAt >= DateTime.Now)
|
if (_continueAt >= DateTime.Now)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
@ -11,54 +11,38 @@ namespace Questionable.Controller.Steps.Common;
|
|||||||
|
|
||||||
internal static class Mount
|
internal static class Mount
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed record MountTask(
|
||||||
GameFunctions gameFunctions,
|
ushort TerritoryId,
|
||||||
ICondition condition,
|
EMountIf MountIf,
|
||||||
TerritoryData territoryData,
|
Vector3? Position = null) : ITask
|
||||||
IClientState clientState,
|
|
||||||
ILoggerFactory loggerFactory)
|
|
||||||
{
|
{
|
||||||
public ITask Mount(ushort territoryId, EMountIf mountIf, Vector3? position = null)
|
public Vector3? Position { get; } = MountIf == EMountIf.AwayFromPosition
|
||||||
{
|
? Position ?? throw new ArgumentNullException(nameof(Position))
|
||||||
if (mountIf == EMountIf.AwayFromPosition)
|
: null;
|
||||||
ArgumentNullException.ThrowIfNull(position);
|
|
||||||
|
|
||||||
return new MountTask(territoryId, mountIf, position, gameFunctions, condition, territoryData, clientState,
|
|
||||||
loggerFactory.CreateLogger<MountTask>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITask Unmount()
|
|
||||||
{
|
|
||||||
return new UnmountTask(condition, loggerFactory.CreateLogger<UnmountTask>(), gameFunctions, clientState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class MountTask(
|
|
||||||
ushort territoryId,
|
|
||||||
EMountIf mountIf,
|
|
||||||
Vector3? position,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
ICondition condition,
|
|
||||||
TerritoryData territoryData,
|
|
||||||
IClientState clientState,
|
|
||||||
ILogger<MountTask> logger) : ITask
|
|
||||||
{
|
|
||||||
private bool _mountTriggered;
|
|
||||||
private InteractionProgressContext? _progressContext;
|
|
||||||
private DateTime _retryAt = DateTime.MinValue;
|
|
||||||
|
|
||||||
public InteractionProgressContext? ProgressContext() => _progressContext;
|
|
||||||
|
|
||||||
public bool ShouldRedoOnInterrupt() => true;
|
public bool ShouldRedoOnInterrupt() => true;
|
||||||
|
|
||||||
public bool Start()
|
public override string ToString() => "Mount";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class MountExecutor(
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
ICondition condition,
|
||||||
|
TerritoryData territoryData,
|
||||||
|
IClientState clientState,
|
||||||
|
ILogger<MountTask> logger) : TaskExecutor<MountTask>
|
||||||
|
{
|
||||||
|
private bool _mountTriggered;
|
||||||
|
private DateTime _retryAt = DateTime.MinValue;
|
||||||
|
|
||||||
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
if (condition[ConditionFlag.Mounted])
|
if (condition[ConditionFlag.Mounted])
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!territoryData.CanUseMount(territoryId))
|
if (!territoryData.CanUseMount(Task.TerritoryId))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Can't use mount in current territory {Id}", territoryId);
|
logger.LogInformation("Can't use mount in current territory {Id}", Task.TerritoryId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,11 +52,11 @@ internal static class Mount
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mountIf == EMountIf.AwayFromPosition)
|
if (Task.MountIf == EMountIf.AwayFromPosition)
|
||||||
{
|
{
|
||||||
Vector3 playerPosition = clientState.LocalPlayer?.Position ?? Vector3.Zero;
|
Vector3 playerPosition = clientState.LocalPlayer?.Position ?? Vector3.Zero;
|
||||||
float distance = System.Numerics.Vector3.Distance(playerPosition, position.GetValueOrDefault());
|
float distance = System.Numerics.Vector3.Distance(playerPosition, Task.Position.GetValueOrDefault());
|
||||||
if (territoryId == clientState.TerritoryType && distance < 30f && !Conditions.IsDiving)
|
if (Task.TerritoryId == clientState.TerritoryType && distance < 30f && !Conditions.IsDiving)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Not using mount, as we're close to the target");
|
logger.LogInformation("Not using mount, as we're close to the target");
|
||||||
return false;
|
return false;
|
||||||
@ -80,10 +64,10 @@ internal static class Mount
|
|||||||
|
|
||||||
logger.LogInformation(
|
logger.LogInformation(
|
||||||
"Want to use mount if away from destination ({Distance} yalms), trying (in territory {Id})...",
|
"Want to use mount if away from destination ({Distance} yalms), trying (in territory {Id})...",
|
||||||
distance, territoryId);
|
distance, Task.TerritoryId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
logger.LogInformation("Want to use mount, trying (in territory {Id})...", territoryId);
|
logger.LogInformation("Want to use mount, trying (in territory {Id})...", Task.TerritoryId);
|
||||||
|
|
||||||
if (!condition[ConditionFlag.InCombat])
|
if (!condition[ConditionFlag.InCombat])
|
||||||
{
|
{
|
||||||
@ -94,7 +78,7 @@ internal static class Mount
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (_mountTriggered && !condition[ConditionFlag.Mounted] && DateTime.Now > _retryAt)
|
if (_mountTriggered && !condition[ConditionFlag.Mounted] && DateTime.Now > _retryAt)
|
||||||
{
|
{
|
||||||
@ -111,7 +95,8 @@ internal static class Mount
|
|||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
_progressContext = InteractionProgressContext.FromActionUse(() => _mountTriggered = gameFunctions.Mount());
|
ProgressContext =
|
||||||
|
InteractionProgressContext.FromActionUse(() => _mountTriggered = gameFunctions.Mount());
|
||||||
|
|
||||||
_retryAt = DateTime.Now.AddSeconds(5);
|
_retryAt = DateTime.Now.AddSeconds(5);
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
@ -121,23 +106,26 @@ internal static class Mount
|
|||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => "Mount";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class UnmountTask(
|
internal sealed record UnmountTask : ITask
|
||||||
|
{
|
||||||
|
public bool ShouldRedoOnInterrupt() => true;
|
||||||
|
|
||||||
|
public override string ToString() => "Unmount";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class UnmountExecutor(
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger<UnmountTask> logger,
|
ILogger<UnmountTask> logger,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
IClientState clientState)
|
IClientState clientState)
|
||||||
: ITask
|
: TaskExecutor<UnmountTask>
|
||||||
{
|
{
|
||||||
private bool _unmountTriggered;
|
private bool _unmountTriggered;
|
||||||
private DateTime _continueAt = DateTime.MinValue;
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
|
|
||||||
public bool ShouldRedoOnInterrupt() => true;
|
protected override bool Start()
|
||||||
|
|
||||||
public bool Start()
|
|
||||||
{
|
{
|
||||||
if (!condition[ConditionFlag.Mounted])
|
if (!condition[ConditionFlag.Mounted])
|
||||||
return false;
|
return false;
|
||||||
@ -155,7 +143,7 @@ internal static class Mount
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (_continueAt >= DateTime.Now)
|
if (_continueAt >= DateTime.Now)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
@ -188,8 +176,6 @@ internal static class Mount
|
|||||||
}
|
}
|
||||||
|
|
||||||
private unsafe bool IsUnmounting() => **(byte**)(clientState.LocalPlayer!.Address + 1432) == 1;
|
private unsafe bool IsUnmounting() => **(byte**)(clientState.LocalPlayer!.Address + 1432) == 1;
|
||||||
|
|
||||||
public override string ToString() => "Unmount";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum EMountIf
|
public enum EMountIf
|
||||||
|
@ -7,7 +7,7 @@ namespace Questionable.Controller.Steps.Common;
|
|||||||
|
|
||||||
internal static class NextQuest
|
internal static class NextQuest
|
||||||
{
|
{
|
||||||
internal sealed class Factory(QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILoggerFactory loggerFactory) : SimpleTaskFactory
|
internal sealed class Factory(QuestFunctions questFunctions) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -24,34 +24,41 @@ internal static class NextQuest
|
|||||||
if (questFunctions.GetPriorityQuests().Contains(step.NextQuestId))
|
if (questFunctions.GetPriorityQuests().Contains(step.NextQuestId))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new SetQuest(step.NextQuestId, quest.Id, questRegistry, questController, questFunctions, loggerFactory.CreateLogger<SetQuest>());
|
return new SetQuestTask(step.NextQuestId, quest.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class SetQuest(ElementId nextQuestId, ElementId currentQuestId, QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILogger<SetQuest> logger) : ITask
|
internal sealed record SetQuestTask(ElementId NextQuestId, ElementId CurrentQuestId) : ITask
|
||||||
{
|
{
|
||||||
public bool Start()
|
public override string ToString() => $"SetNextQuest({NextQuestId})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class Executor(
|
||||||
|
QuestRegistry questRegistry,
|
||||||
|
QuestController questController,
|
||||||
|
QuestFunctions questFunctions,
|
||||||
|
ILogger<Executor> logger) : TaskExecutor<SetQuestTask>
|
||||||
|
{
|
||||||
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
if (questFunctions.IsQuestLocked(nextQuestId, currentQuestId))
|
if (questFunctions.IsQuestLocked(Task.NextQuestId, Task.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", Task.NextQuestId);
|
||||||
}
|
}
|
||||||
else if (questRegistry.TryGetQuest(nextQuestId, out Quest? quest))
|
else if (questRegistry.TryGetQuest(Task.NextQuestId, out Quest? quest))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Setting next quest to {QuestId}: '{QuestName}'", nextQuestId, quest.Info.Name);
|
logger.LogInformation("Setting next quest to {QuestId}: '{QuestName}'", Task.NextQuestId, quest.Info.Name);
|
||||||
questController.SetNextQuest(quest);
|
questController.SetNextQuest(quest);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogInformation("Next quest with id {QuestId} not found", nextQuestId);
|
logger.LogInformation("Next quest with id {QuestId} not found", Task.NextQuestId);
|
||||||
questController.SetNextQuest(null);
|
questController.SetNextQuest(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() => ETaskResult.TaskComplete;
|
public override ETaskResult Update() => ETaskResult.TaskComplete;
|
||||||
|
|
||||||
public override string ToString() => $"SetNextQuest({nextQuestId})";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,22 +2,28 @@
|
|||||||
|
|
||||||
namespace Questionable.Controller.Steps.Common;
|
namespace Questionable.Controller.Steps.Common;
|
||||||
|
|
||||||
internal sealed class WaitConditionTask(Func<bool> predicate, string description) : ITask
|
internal static class WaitCondition
|
||||||
{
|
{
|
||||||
private DateTime _continueAt = DateTime.MaxValue;
|
internal sealed record Task(Func<bool> Predicate, string Description) : ITask
|
||||||
|
|
||||||
public bool Start() => !predicate();
|
|
||||||
|
|
||||||
public ETaskResult Update()
|
|
||||||
{
|
{
|
||||||
if (_continueAt == DateTime.MaxValue)
|
public override string ToString() => Description;
|
||||||
{
|
|
||||||
if (predicate())
|
|
||||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
return DateTime.Now >= _continueAt ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => description;
|
internal sealed class Executor : TaskExecutor<Task>
|
||||||
|
{
|
||||||
|
private DateTime _continueAt = DateTime.MaxValue;
|
||||||
|
|
||||||
|
protected override bool Start() => !Task.Predicate();
|
||||||
|
|
||||||
|
public override ETaskResult Update()
|
||||||
|
{
|
||||||
|
if (_continueAt == DateTime.MaxValue)
|
||||||
|
{
|
||||||
|
if (Task.Predicate())
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTime.Now >= _continueAt ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,227 +15,231 @@ using Questionable.Model.Questing;
|
|||||||
|
|
||||||
namespace Questionable.Controller.Steps.Gathering;
|
namespace Questionable.Controller.Steps.Gathering;
|
||||||
|
|
||||||
internal sealed class DoGather(
|
internal static class DoGather
|
||||||
GatheringController.GatheringRequest currentRequest,
|
|
||||||
GatheringNode currentNode,
|
|
||||||
bool revisitRequired,
|
|
||||||
GatheringController gatheringController,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
IGameGui gameGui,
|
|
||||||
IClientState clientState,
|
|
||||||
ICondition condition,
|
|
||||||
ILogger<DoGather> logger) : ITask, IRevisitAware
|
|
||||||
{
|
{
|
||||||
private const uint StatusGatheringRateUp = 218;
|
internal sealed record Task(
|
||||||
|
GatheringController.GatheringRequest Request,
|
||||||
private bool _revisitTriggered;
|
GatheringNode Node,
|
||||||
private bool _wasGathering;
|
bool RevisitRequired) : ITask, IRevisitAware
|
||||||
private SlotInfo? _slotToGather;
|
|
||||||
private Queue<EAction>? _actionQueue;
|
|
||||||
|
|
||||||
public bool Start() => true;
|
|
||||||
|
|
||||||
public unsafe ETaskResult Update()
|
|
||||||
{
|
{
|
||||||
if (revisitRequired && !_revisitTriggered)
|
public bool RevisitTriggered { get; private set; }
|
||||||
|
|
||||||
|
public void OnRevisit() => RevisitTriggered = true;
|
||||||
|
|
||||||
|
public override string ToString() => $"DoGather{(RevisitRequired ? " if revist" : "")}";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class Executor(
|
||||||
|
GatheringController gatheringController,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
IGameGui gameGui,
|
||||||
|
IClientState clientState,
|
||||||
|
ICondition condition,
|
||||||
|
ILogger<Executor> logger) : TaskExecutor<Task>
|
||||||
|
{
|
||||||
|
private const uint StatusGatheringRateUp = 218;
|
||||||
|
|
||||||
|
private bool _wasGathering;
|
||||||
|
private SlotInfo? _slotToGather;
|
||||||
|
private Queue<EAction>? _actionQueue;
|
||||||
|
|
||||||
|
protected override bool Start() => true;
|
||||||
|
|
||||||
|
public override unsafe ETaskResult Update()
|
||||||
{
|
{
|
||||||
logger.LogInformation("No revisit");
|
if (Task is { RevisitRequired: true, RevisitTriggered: false })
|
||||||
return ETaskResult.TaskComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gatheringController.HasNodeDisappeared(currentNode))
|
|
||||||
{
|
|
||||||
logger.LogInformation("Node disappeared");
|
|
||||||
return ETaskResult.TaskComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameFunctions.GetFreeInventorySlots() == 0)
|
|
||||||
throw new TaskException("Inventory full");
|
|
||||||
|
|
||||||
if (condition[ConditionFlag.Gathering])
|
|
||||||
{
|
|
||||||
if (gameGui.TryGetAddonByName("GatheringMasterpiece", out AtkUnitBase* _))
|
|
||||||
return ETaskResult.TaskComplete;
|
|
||||||
|
|
||||||
_wasGathering = true;
|
|
||||||
|
|
||||||
if (gameGui.TryGetAddonByName("Gathering", out AddonGathering* addonGathering))
|
|
||||||
{
|
{
|
||||||
if (gatheringController.HasRequestedItems())
|
logger.LogInformation("No revisit");
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gatheringController.HasNodeDisappeared(Task.Node))
|
||||||
|
{
|
||||||
|
logger.LogInformation("Node disappeared");
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameFunctions.GetFreeInventorySlots() == 0)
|
||||||
|
throw new TaskException("Inventory full");
|
||||||
|
|
||||||
|
if (condition[ConditionFlag.Gathering])
|
||||||
|
{
|
||||||
|
if (gameGui.TryGetAddonByName("GatheringMasterpiece", out AtkUnitBase* _))
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
|
_wasGathering = true;
|
||||||
|
|
||||||
|
if (gameGui.TryGetAddonByName("Gathering", out AddonGathering* addonGathering))
|
||||||
{
|
{
|
||||||
addonGathering->FireCallbackInt(-1);
|
if (gatheringController.HasRequestedItems())
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var slots = ReadSlots(addonGathering);
|
|
||||||
if (currentRequest.Collectability > 0)
|
|
||||||
{
|
{
|
||||||
var slot = slots.Single(x => x.ItemId == currentRequest.ItemId);
|
addonGathering->FireCallbackInt(-1);
|
||||||
addonGathering->FireCallbackInt(slot.Index);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NodeCondition nodeCondition = new NodeCondition(
|
var slots = ReadSlots(addonGathering);
|
||||||
addonGathering->AtkValues[110].UInt,
|
if (Task.Request.Collectability > 0)
|
||||||
addonGathering->AtkValues[111].UInt);
|
|
||||||
|
|
||||||
if (_actionQueue != null && _actionQueue.TryPeek(out EAction nextAction))
|
|
||||||
{
|
{
|
||||||
if (gameFunctions.UseAction(nextAction))
|
var slot = slots.Single(x => x.ItemId == Task.Request.ItemId);
|
||||||
|
addonGathering->FireCallbackInt(slot.Index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NodeCondition nodeCondition = new NodeCondition(
|
||||||
|
addonGathering->AtkValues[110].UInt,
|
||||||
|
addonGathering->AtkValues[111].UInt);
|
||||||
|
|
||||||
|
if (_actionQueue != null && _actionQueue.TryPeek(out EAction nextAction))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Used action {Action} on node", nextAction);
|
if (gameFunctions.UseAction(nextAction))
|
||||||
_actionQueue.Dequeue();
|
{
|
||||||
|
logger.LogInformation("Used action {Action} on node", nextAction);
|
||||||
|
_actionQueue.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ETaskResult.StillRunning;
|
_actionQueue = GetNextActions(nodeCondition, slots);
|
||||||
}
|
if (_actionQueue.Count == 0)
|
||||||
|
{
|
||||||
_actionQueue = GetNextActions(nodeCondition, slots);
|
var slot = _slotToGather ?? slots.Single(x => x.ItemId == Task.Request.ItemId);
|
||||||
if (_actionQueue.Count == 0)
|
addonGathering->FireCallbackInt(slot.Index);
|
||||||
{
|
}
|
||||||
var slot = _slotToGather ?? slots.Single(x => x.ItemId == currentRequest.ItemId);
|
|
||||||
addonGathering->FireCallbackInt(slot.Index);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return _wasGathering && !condition[ConditionFlag.Gathering]
|
||||||
|
? ETaskResult.TaskComplete
|
||||||
|
: ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _wasGathering && !condition[ConditionFlag.Gathering]
|
private unsafe List<SlotInfo> ReadSlots(AddonGathering* addonGathering)
|
||||||
? ETaskResult.TaskComplete
|
|
||||||
: ETaskResult.StillRunning;
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe List<SlotInfo> ReadSlots(AddonGathering* addonGathering)
|
|
||||||
{
|
|
||||||
var atkValues = addonGathering->AtkValues;
|
|
||||||
List<SlotInfo> slots = new List<SlotInfo>();
|
|
||||||
for (int i = 0; i < 8; ++i)
|
|
||||||
{
|
{
|
||||||
// +8 = new item?
|
var atkValues = addonGathering->AtkValues;
|
||||||
uint itemId = atkValues[i * 11 + 7].UInt;
|
List<SlotInfo> slots = new List<SlotInfo>();
|
||||||
if (itemId == 0)
|
for (int i = 0; i < 8; ++i)
|
||||||
continue;
|
|
||||||
|
|
||||||
AtkComponentCheckBox* atkCheckbox = addonGathering->GatheredItemComponentCheckbox[i].Value;
|
|
||||||
|
|
||||||
AtkTextNode* atkGatheringChance = atkCheckbox->UldManager.SearchNodeById(10)->GetAsAtkTextNode();
|
|
||||||
if (!int.TryParse(atkGatheringChance->NodeText.ToString(), out int gatheringChance))
|
|
||||||
gatheringChance = 0;
|
|
||||||
|
|
||||||
AtkTextNode* atkBoonChance = atkCheckbox->UldManager.SearchNodeById(16)->GetAsAtkTextNode();
|
|
||||||
if (!int.TryParse(atkBoonChance->NodeText.ToString(), out int boonChance))
|
|
||||||
boonChance = 0;
|
|
||||||
|
|
||||||
AtkComponentNode* atkImage = atkCheckbox->UldManager.SearchNodeById(31)->GetAsAtkComponentNode();
|
|
||||||
AtkTextNode* atkQuantity = atkImage->Component->UldManager.SearchNodeById(7)->GetAsAtkTextNode();
|
|
||||||
if (!atkQuantity->IsVisible() || !int.TryParse(atkQuantity->NodeText.ToString(), out int quantity))
|
|
||||||
quantity = 1;
|
|
||||||
|
|
||||||
var slot = new SlotInfo(i, itemId, gatheringChance, boonChance, quantity);
|
|
||||||
slots.Add(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
return slots;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "UnusedParameter.Local")]
|
|
||||||
private Queue<EAction> GetNextActions(NodeCondition nodeCondition, List<SlotInfo> slots)
|
|
||||||
{
|
|
||||||
//uint gp = clientState.LocalPlayer!.CurrentGp;
|
|
||||||
Queue<EAction> actions = new();
|
|
||||||
|
|
||||||
if (!gameFunctions.HasStatus(StatusGatheringRateUp))
|
|
||||||
{
|
|
||||||
// do we have an alternative item? only happens for 'evaluation' leve quests
|
|
||||||
if (currentRequest.AlternativeItemId != 0)
|
|
||||||
{
|
{
|
||||||
var alternativeSlot = slots.Single(x => x.ItemId == currentRequest.AlternativeItemId);
|
// +8 = new item?
|
||||||
|
uint itemId = atkValues[i * 11 + 7].UInt;
|
||||||
|
if (itemId == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (alternativeSlot.GatheringChance == 100)
|
AtkComponentCheckBox* atkCheckbox = addonGathering->GatheredItemComponentCheckbox[i].Value;
|
||||||
{
|
|
||||||
_slotToGather = alternativeSlot;
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alternativeSlot.GatheringChance > 0)
|
AtkTextNode* atkGatheringChance = atkCheckbox->UldManager.SearchNodeById(10)->GetAsAtkTextNode();
|
||||||
|
if (!int.TryParse(atkGatheringChance->NodeText.ToString(), out int gatheringChance))
|
||||||
|
gatheringChance = 0;
|
||||||
|
|
||||||
|
AtkTextNode* atkBoonChance = atkCheckbox->UldManager.SearchNodeById(16)->GetAsAtkTextNode();
|
||||||
|
if (!int.TryParse(atkBoonChance->NodeText.ToString(), out int boonChance))
|
||||||
|
boonChance = 0;
|
||||||
|
|
||||||
|
AtkComponentNode* atkImage = atkCheckbox->UldManager.SearchNodeById(31)->GetAsAtkComponentNode();
|
||||||
|
AtkTextNode* atkQuantity = atkImage->Component->UldManager.SearchNodeById(7)->GetAsAtkTextNode();
|
||||||
|
if (!atkQuantity->IsVisible() || !int.TryParse(atkQuantity->NodeText.ToString(), out int quantity))
|
||||||
|
quantity = 1;
|
||||||
|
|
||||||
|
var slot = new SlotInfo(i, itemId, gatheringChance, boonChance, quantity);
|
||||||
|
slots.Add(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "UnusedParameter.Local")]
|
||||||
|
private Queue<EAction> GetNextActions(NodeCondition nodeCondition, List<SlotInfo> slots)
|
||||||
|
{
|
||||||
|
//uint gp = clientState.LocalPlayer!.CurrentGp;
|
||||||
|
Queue<EAction> actions = new();
|
||||||
|
|
||||||
|
if (!gameFunctions.HasStatus(StatusGatheringRateUp))
|
||||||
|
{
|
||||||
|
// do we have an alternative item? only happens for 'evaluation' leve quests
|
||||||
|
if (Task.Request.AlternativeItemId != 0)
|
||||||
{
|
{
|
||||||
if (alternativeSlot.GatheringChance >= 95 &&
|
var alternativeSlot = slots.Single(x => x.ItemId == Task.Request.AlternativeItemId);
|
||||||
CanUseAction(EAction.SharpVision1, EAction.FieldMastery1))
|
|
||||||
|
if (alternativeSlot.GatheringChance == 100)
|
||||||
{
|
{
|
||||||
_slotToGather = alternativeSlot;
|
_slotToGather = alternativeSlot;
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternativeSlot.GatheringChance > 0)
|
||||||
|
{
|
||||||
|
if (alternativeSlot.GatheringChance >= 95 &&
|
||||||
|
CanUseAction(EAction.SharpVision1, EAction.FieldMastery1))
|
||||||
|
{
|
||||||
|
_slotToGather = alternativeSlot;
|
||||||
|
actions.Enqueue(PickAction(EAction.SharpVision1, EAction.FieldMastery1));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternativeSlot.GatheringChance >= 85 &&
|
||||||
|
CanUseAction(EAction.SharpVision2, EAction.FieldMastery2))
|
||||||
|
{
|
||||||
|
_slotToGather = alternativeSlot;
|
||||||
|
actions.Enqueue(PickAction(EAction.SharpVision2, EAction.FieldMastery2));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternativeSlot.GatheringChance >= 50 &&
|
||||||
|
CanUseAction(EAction.SharpVision3, EAction.FieldMastery3))
|
||||||
|
{
|
||||||
|
_slotToGather = alternativeSlot;
|
||||||
|
actions.Enqueue(PickAction(EAction.SharpVision3, EAction.FieldMastery3));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var slot = slots.Single(x => x.ItemId == Task.Request.ItemId);
|
||||||
|
if (slot.GatheringChance > 0 && slot.GatheringChance < 100)
|
||||||
|
{
|
||||||
|
if (slot.GatheringChance >= 95 &&
|
||||||
|
CanUseAction(EAction.SharpVision1, EAction.FieldMastery1))
|
||||||
|
{
|
||||||
actions.Enqueue(PickAction(EAction.SharpVision1, EAction.FieldMastery1));
|
actions.Enqueue(PickAction(EAction.SharpVision1, EAction.FieldMastery1));
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alternativeSlot.GatheringChance >= 85 &&
|
if (slot.GatheringChance >= 85 &&
|
||||||
CanUseAction(EAction.SharpVision2, EAction.FieldMastery2))
|
CanUseAction(EAction.SharpVision2, EAction.FieldMastery2))
|
||||||
{
|
{
|
||||||
_slotToGather = alternativeSlot;
|
|
||||||
actions.Enqueue(PickAction(EAction.SharpVision2, EAction.FieldMastery2));
|
actions.Enqueue(PickAction(EAction.SharpVision2, EAction.FieldMastery2));
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alternativeSlot.GatheringChance >= 50 &&
|
if (slot.GatheringChance >= 50 &&
|
||||||
CanUseAction(EAction.SharpVision3, EAction.FieldMastery3))
|
CanUseAction(EAction.SharpVision3, EAction.FieldMastery3))
|
||||||
{
|
{
|
||||||
_slotToGather = alternativeSlot;
|
|
||||||
actions.Enqueue(PickAction(EAction.SharpVision3, EAction.FieldMastery3));
|
actions.Enqueue(PickAction(EAction.SharpVision3, EAction.FieldMastery3));
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var slot = slots.Single(x => x.ItemId == currentRequest.ItemId);
|
return actions;
|
||||||
if (slot.GatheringChance > 0 && slot.GatheringChance < 100)
|
|
||||||
{
|
|
||||||
if (slot.GatheringChance >= 95 &&
|
|
||||||
CanUseAction(EAction.SharpVision1, EAction.FieldMastery1))
|
|
||||||
{
|
|
||||||
actions.Enqueue(PickAction(EAction.SharpVision1, EAction.FieldMastery1));
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slot.GatheringChance >= 85 &&
|
|
||||||
CanUseAction(EAction.SharpVision2, EAction.FieldMastery2))
|
|
||||||
{
|
|
||||||
actions.Enqueue(PickAction(EAction.SharpVision2, EAction.FieldMastery2));
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slot.GatheringChance >= 50 &&
|
|
||||||
CanUseAction(EAction.SharpVision3, EAction.FieldMastery3))
|
|
||||||
{
|
|
||||||
actions.Enqueue(PickAction(EAction.SharpVision3, EAction.FieldMastery3));
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return actions;
|
private EAction PickAction(EAction minerAction, EAction botanistAction)
|
||||||
}
|
{
|
||||||
|
if ((EClassJob?)clientState.LocalPlayer?.ClassJob.Id == EClassJob.Miner)
|
||||||
|
return minerAction;
|
||||||
|
else
|
||||||
|
return botanistAction;
|
||||||
|
}
|
||||||
|
|
||||||
private EAction PickAction(EAction minerAction, EAction botanistAction)
|
private unsafe bool CanUseAction(EAction minerAction, EAction botanistAction)
|
||||||
{
|
{
|
||||||
if ((EClassJob?)clientState.LocalPlayer?.ClassJob.Id == EClassJob.Miner)
|
EAction action = PickAction(minerAction, botanistAction);
|
||||||
return minerAction;
|
return ActionManager.Instance()->GetActionStatus(ActionType.Action, (uint)action) == 0;
|
||||||
else
|
}
|
||||||
return botanistAction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe bool CanUseAction(EAction minerAction, EAction botanistAction)
|
|
||||||
{
|
|
||||||
EAction action = PickAction(minerAction, botanistAction);
|
|
||||||
return ActionManager.Instance()->GetActionStatus(ActionType.Action, (uint)action) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnRevisit()
|
|
||||||
{
|
|
||||||
_revisitTriggered = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() => $"DoGather{(revisitRequired ? " if revist" : "")}";
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]
|
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]
|
||||||
private sealed record SlotInfo(int Index, uint ItemId, int GatheringChance, int BoonChance, int Quantity);
|
private sealed record SlotInfo(int Index, uint ItemId, int GatheringChance, int BoonChance, int Quantity);
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
@ -13,189 +12,194 @@ using Questionable.Model.Questing;
|
|||||||
|
|
||||||
namespace Questionable.Controller.Steps.Gathering;
|
namespace Questionable.Controller.Steps.Gathering;
|
||||||
|
|
||||||
internal sealed class DoGatherCollectable(
|
internal static class DoGatherCollectable
|
||||||
GatheringController.GatheringRequest currentRequest,
|
|
||||||
GatheringNode currentNode,
|
|
||||||
bool revisitRequired,
|
|
||||||
GatheringController gatheringController,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
IClientState clientState,
|
|
||||||
IGameGui gameGui,
|
|
||||||
ILogger<DoGatherCollectable> logger) : ITask, IRevisitAware
|
|
||||||
{
|
{
|
||||||
private bool _revisitTriggered;
|
internal sealed record Task(
|
||||||
private Queue<EAction>? _actionQueue;
|
GatheringController.GatheringRequest Request,
|
||||||
|
GatheringNode Node,
|
||||||
private bool? _expectedScrutiny;
|
bool RevisitRequired) : ITask, IRevisitAware
|
||||||
|
|
||||||
public bool Start() => true;
|
|
||||||
|
|
||||||
public unsafe ETaskResult Update()
|
|
||||||
{
|
{
|
||||||
if (revisitRequired && !_revisitTriggered)
|
public bool RevisitTriggered { get; private set; }
|
||||||
|
|
||||||
|
public void OnRevisit() => RevisitTriggered = true;
|
||||||
|
|
||||||
|
public override string ToString() =>
|
||||||
|
$"DoGatherCollectable({SeIconChar.Collectible.ToIconString()}/{Request.Collectability}){(RevisitRequired ? " if revist" : "")}";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class Executor(
|
||||||
|
GatheringController gatheringController,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
IClientState clientState,
|
||||||
|
IGameGui gameGui,
|
||||||
|
ILogger<Executor> logger) : TaskExecutor<Task>
|
||||||
|
{
|
||||||
|
private Queue<EAction>? _actionQueue;
|
||||||
|
|
||||||
|
private bool? _expectedScrutiny;
|
||||||
|
|
||||||
|
protected override bool Start() => true;
|
||||||
|
|
||||||
|
public override unsafe ETaskResult Update()
|
||||||
{
|
{
|
||||||
logger.LogInformation("No revisit");
|
if (Task.RevisitRequired && !Task.RevisitTriggered)
|
||||||
return ETaskResult.TaskComplete;
|
{
|
||||||
|
logger.LogInformation("No revisit");
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gatheringController.HasNodeDisappeared(Task.Node))
|
||||||
|
{
|
||||||
|
logger.LogInformation("Node disappeared");
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gatheringController.HasRequestedItems())
|
||||||
|
{
|
||||||
|
if (gameGui.TryGetAddonByName("GatheringMasterpiece", out AtkUnitBase* atkUnitBase))
|
||||||
|
{
|
||||||
|
atkUnitBase->FireCallbackInt(1);
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameGui.TryGetAddonByName("Gathering", out atkUnitBase))
|
||||||
|
{
|
||||||
|
atkUnitBase->FireCallbackInt(-1);
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameFunctions.GetFreeInventorySlots() == 0)
|
||||||
|
throw new TaskException("Inventory full");
|
||||||
|
|
||||||
|
NodeCondition? nodeCondition = GetNodeCondition();
|
||||||
|
if (nodeCondition == null)
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
|
if (_expectedScrutiny != null)
|
||||||
|
{
|
||||||
|
if (nodeCondition.ScrutinyActive != _expectedScrutiny)
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
|
// continue on next frame
|
||||||
|
_expectedScrutiny = null;
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_actionQueue != null && _actionQueue.TryPeek(out EAction nextAction))
|
||||||
|
{
|
||||||
|
if (gameFunctions.UseAction(nextAction))
|
||||||
|
{
|
||||||
|
_expectedScrutiny = nextAction switch
|
||||||
|
{
|
||||||
|
EAction.ScrutinyMiner or EAction.ScrutinyBotanist => true,
|
||||||
|
EAction.ScourMiner or EAction.ScourBotanist or EAction.MeticulousMiner
|
||||||
|
or EAction.MeticulousBotanist => false,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
logger.LogInformation("Used action {Action} on node", nextAction);
|
||||||
|
_actionQueue.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeCondition.CollectabilityToGoal(Task.Request.Collectability) > 0)
|
||||||
|
{
|
||||||
|
_actionQueue = GetNextActions(nodeCondition);
|
||||||
|
if (_actionQueue != null)
|
||||||
|
{
|
||||||
|
foreach (var action in _actionQueue)
|
||||||
|
logger.LogInformation("Next Actions {Action}", action);
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_actionQueue = new Queue<EAction>();
|
||||||
|
_actionQueue.Enqueue(PickAction(EAction.CollectMiner, EAction.CollectBotanist));
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gatheringController.HasNodeDisappeared(currentNode))
|
private unsafe NodeCondition? GetNodeCondition()
|
||||||
{
|
|
||||||
logger.LogInformation("Node disappeared");
|
|
||||||
return ETaskResult.TaskComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gatheringController.HasRequestedItems())
|
|
||||||
{
|
{
|
||||||
if (gameGui.TryGetAddonByName("GatheringMasterpiece", out AtkUnitBase* atkUnitBase))
|
if (gameGui.TryGetAddonByName("GatheringMasterpiece", out AtkUnitBase* atkUnitBase))
|
||||||
{
|
{
|
||||||
atkUnitBase->FireCallbackInt(1);
|
var atkValues = atkUnitBase->AtkValues;
|
||||||
return ETaskResult.StillRunning;
|
return new NodeCondition(
|
||||||
|
CurrentCollectability: atkValues[13].UInt,
|
||||||
|
MaxCollectability: atkValues[14].UInt,
|
||||||
|
CurrentIntegrity: atkValues[62].UInt,
|
||||||
|
MaxIntegrity: atkValues[63].UInt,
|
||||||
|
ScrutinyActive: atkValues[54].Bool,
|
||||||
|
CollectabilityFromScour: atkValues[48].UInt,
|
||||||
|
CollectabilityFromMeticulous: atkValues[51].UInt
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameGui.TryGetAddonByName("Gathering", out atkUnitBase))
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Queue<EAction> GetNextActions(NodeCondition nodeCondition)
|
||||||
|
{
|
||||||
|
uint gp = clientState.LocalPlayer!.CurrentGp;
|
||||||
|
logger.LogTrace(
|
||||||
|
"Getting next actions (with {GP} GP, {MeticulousCollectability}~ meticulous, {ScourCollectability}~ scour)",
|
||||||
|
gp, nodeCondition.CollectabilityFromMeticulous, nodeCondition.CollectabilityFromScour);
|
||||||
|
|
||||||
|
Queue<EAction> actions = new();
|
||||||
|
|
||||||
|
uint neededCollectability = nodeCondition.CollectabilityToGoal(Task.Request.Collectability);
|
||||||
|
if (neededCollectability <= nodeCondition.CollectabilityFromMeticulous)
|
||||||
{
|
{
|
||||||
atkUnitBase->FireCallbackInt(-1);
|
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ meticulous",
|
||||||
return ETaskResult.TaskComplete;
|
neededCollectability, nodeCondition.CollectabilityFromMeticulous);
|
||||||
|
actions.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
|
||||||
|
return actions;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (gameFunctions.GetFreeInventorySlots() == 0)
|
if (neededCollectability <= nodeCondition.CollectabilityFromScour)
|
||||||
throw new TaskException("Inventory full");
|
|
||||||
|
|
||||||
NodeCondition? nodeCondition = GetNodeCondition();
|
|
||||||
if (nodeCondition == null)
|
|
||||||
return ETaskResult.TaskComplete;
|
|
||||||
|
|
||||||
if (_expectedScrutiny != null)
|
|
||||||
{
|
|
||||||
if (nodeCondition.ScrutinyActive != _expectedScrutiny)
|
|
||||||
return ETaskResult.StillRunning;
|
|
||||||
|
|
||||||
// continue on next frame
|
|
||||||
_expectedScrutiny = null;
|
|
||||||
return ETaskResult.StillRunning;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_actionQueue != null && _actionQueue.TryPeek(out EAction nextAction))
|
|
||||||
{
|
|
||||||
if (gameFunctions.UseAction(nextAction))
|
|
||||||
{
|
{
|
||||||
_expectedScrutiny = nextAction switch
|
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ scour",
|
||||||
{
|
neededCollectability, nodeCondition.CollectabilityFromScour);
|
||||||
EAction.ScrutinyMiner or EAction.ScrutinyBotanist => true,
|
actions.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
|
||||||
EAction.ScourMiner or EAction.ScourBotanist or EAction.MeticulousMiner
|
return actions;
|
||||||
or EAction.MeticulousBotanist => false,
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
logger.LogInformation("Used action {Action} on node", nextAction);
|
|
||||||
_actionQueue.Dequeue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ETaskResult.StillRunning;
|
// neither action directly solves our problem
|
||||||
}
|
if (!nodeCondition.ScrutinyActive && gp >= 200)
|
||||||
|
|
||||||
if (nodeCondition.CollectabilityToGoal(currentRequest.Collectability) > 0)
|
|
||||||
{
|
|
||||||
_actionQueue = GetNextActions(nodeCondition);
|
|
||||||
if (_actionQueue != null)
|
|
||||||
{
|
{
|
||||||
foreach (var action in _actionQueue)
|
logger.LogTrace("Still missing {NeededCollectability} collectability, scrutiny inactive",
|
||||||
logger.LogInformation("Next Actions {Action}", action);
|
neededCollectability);
|
||||||
return ETaskResult.StillRunning;
|
actions.Enqueue(PickAction(EAction.ScrutinyMiner, EAction.ScrutinyBotanist));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeCondition.ScrutinyActive)
|
||||||
|
{
|
||||||
|
logger.LogTrace(
|
||||||
|
"Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ meticulous",
|
||||||
|
neededCollectability, nodeCondition.CollectabilityFromMeticulous);
|
||||||
|
actions.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ scour",
|
||||||
|
neededCollectability, nodeCondition.CollectabilityFromScour);
|
||||||
|
actions.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
|
||||||
|
return actions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_actionQueue = new Queue<EAction>();
|
private EAction PickAction(EAction minerAction, EAction botanistAction)
|
||||||
_actionQueue.Enqueue(PickAction(EAction.CollectMiner, EAction.CollectBotanist));
|
|
||||||
return ETaskResult.StillRunning;
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe NodeCondition? GetNodeCondition()
|
|
||||||
{
|
|
||||||
if (gameGui.TryGetAddonByName("GatheringMasterpiece", out AtkUnitBase* atkUnitBase))
|
|
||||||
{
|
{
|
||||||
var atkValues = atkUnitBase->AtkValues;
|
if ((EClassJob?)clientState.LocalPlayer?.ClassJob.Id == EClassJob.Miner)
|
||||||
return new NodeCondition(
|
return minerAction;
|
||||||
CurrentCollectability: atkValues[13].UInt,
|
else
|
||||||
MaxCollectability: atkValues[14].UInt,
|
return botanistAction;
|
||||||
CurrentIntegrity: atkValues[62].UInt,
|
|
||||||
MaxIntegrity: atkValues[63].UInt,
|
|
||||||
ScrutinyActive: atkValues[54].Bool,
|
|
||||||
CollectabilityFromScour: atkValues[48].UInt,
|
|
||||||
CollectabilityFromMeticulous: atkValues[51].UInt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Queue<EAction> GetNextActions(NodeCondition nodeCondition)
|
|
||||||
{
|
|
||||||
uint gp = clientState.LocalPlayer!.CurrentGp;
|
|
||||||
logger.LogTrace(
|
|
||||||
"Getting next actions (with {GP} GP, {MeticulousCollectability}~ meticulous, {ScourCollectability}~ scour)",
|
|
||||||
gp, nodeCondition.CollectabilityFromMeticulous, nodeCondition.CollectabilityFromScour);
|
|
||||||
|
|
||||||
Queue<EAction> actions = new();
|
|
||||||
|
|
||||||
uint neededCollectability = nodeCondition.CollectabilityToGoal(currentRequest.Collectability);
|
|
||||||
if (neededCollectability <= nodeCondition.CollectabilityFromMeticulous)
|
|
||||||
{
|
|
||||||
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ meticulous",
|
|
||||||
neededCollectability, nodeCondition.CollectabilityFromMeticulous);
|
|
||||||
actions.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (neededCollectability <= nodeCondition.CollectabilityFromScour)
|
|
||||||
{
|
|
||||||
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ scour",
|
|
||||||
neededCollectability, nodeCondition.CollectabilityFromScour);
|
|
||||||
actions.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
// neither action directly solves our problem
|
|
||||||
if (!nodeCondition.ScrutinyActive && gp >= 200)
|
|
||||||
{
|
|
||||||
logger.LogTrace("Still missing {NeededCollectability} collectability, scrutiny inactive",
|
|
||||||
neededCollectability);
|
|
||||||
actions.Enqueue(PickAction(EAction.ScrutinyMiner, EAction.ScrutinyBotanist));
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodeCondition.ScrutinyActive)
|
|
||||||
{
|
|
||||||
logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ meticulous",
|
|
||||||
neededCollectability, nodeCondition.CollectabilityFromMeticulous);
|
|
||||||
actions.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ scour",
|
|
||||||
neededCollectability, nodeCondition.CollectabilityFromScour);
|
|
||||||
actions.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
|
|
||||||
return actions;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private EAction PickAction(EAction minerAction, EAction botanistAction)
|
|
||||||
{
|
|
||||||
if ((EClassJob?)clientState.LocalPlayer?.ClassJob.Id == EClassJob.Miner)
|
|
||||||
return minerAction;
|
|
||||||
else
|
|
||||||
return botanistAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnRevisit()
|
|
||||||
{
|
|
||||||
_revisitTriggered = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() =>
|
|
||||||
$"DoGatherCollectable({SeIconChar.Collectible.ToIconString()}/{_expectedScrutiny} {currentRequest.Collectability}){(revisitRequired ? " if revist" : "")}";
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]
|
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]
|
||||||
private sealed record NodeCondition(
|
private sealed record NodeCondition(
|
||||||
uint CurrentCollectability,
|
uint CurrentCollectability,
|
||||||
|
@ -3,7 +3,6 @@ using System.Linq;
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
@ -12,41 +11,49 @@ using Questionable.Model.Gathering;
|
|||||||
|
|
||||||
namespace Questionable.Controller.Steps.Gathering;
|
namespace Questionable.Controller.Steps.Gathering;
|
||||||
|
|
||||||
internal sealed class MoveToLandingLocation(
|
internal static class MoveToLandingLocation
|
||||||
ushort territoryId,
|
|
||||||
bool flyBetweenNodes,
|
|
||||||
GatheringNode gatheringNode,
|
|
||||||
MoveTo.Factory moveFactory,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
IObjectTable objectTable,
|
|
||||||
ILogger<MoveToLandingLocation> logger) : ITask
|
|
||||||
{
|
{
|
||||||
private ITask _moveTask = null!;
|
internal sealed record Task(
|
||||||
|
ushort TerritoryId,
|
||||||
public bool Start()
|
bool FlyBetweenNodes,
|
||||||
|
GatheringNode GatheringNode) : ITask
|
||||||
{
|
{
|
||||||
var location = gatheringNode.Locations.First();
|
public override string ToString() => $"Land/{FlyBetweenNodes}";
|
||||||
if (gatheringNode.Locations.Count > 1)
|
|
||||||
{
|
|
||||||
var gameObject = objectTable.SingleOrDefault(x =>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = moveFactory.Move(new MoveTo.MoveParams(territoryId, target, null, 0.25f,
|
|
||||||
DataId: gatheringNode.DataId, Fly: fly, IgnoreDistanceToObject: true));
|
|
||||||
return _moveTask.Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() => _moveTask.Update();
|
internal sealed class Executor(
|
||||||
|
MoveTo.MoveExecutor moveExecutor,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
IObjectTable objectTable,
|
||||||
|
ILogger<Executor> logger) : TaskExecutor<Task>
|
||||||
|
{
|
||||||
|
private ITask _moveTask = null!;
|
||||||
|
|
||||||
public override string ToString() => $"Land/{_moveTask}/{flyBetweenNodes}";
|
protected override bool Start()
|
||||||
|
{
|
||||||
|
var location = Task.GatheringNode.Locations.First();
|
||||||
|
if (Task.GatheringNode.Locations.Count > 1)
|
||||||
|
{
|
||||||
|
var gameObject = objectTable.SingleOrDefault(x =>
|
||||||
|
x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == Task.GatheringNode.DataId &&
|
||||||
|
x.IsTargetable);
|
||||||
|
if (gameObject == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
location = Task.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 = Task.FlyBetweenNodes && gameFunctions.IsFlyingUnlocked(Task.TerritoryId);
|
||||||
|
_moveTask = new MoveTo.MoveTask(Task.TerritoryId, target, null, 0.25f,
|
||||||
|
DataId: Task.GatheringNode.DataId, Fly: fly, IgnoreDistanceToObject: true);
|
||||||
|
return moveExecutor.Start(_moveTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ETaskResult Update() => moveExecutor.Update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
using System;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using LLib.GameUI;
|
using LLib.GameUI;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
@ -13,24 +11,29 @@ namespace Questionable.Controller.Steps.Gathering;
|
|||||||
|
|
||||||
internal static class TurnInDelivery
|
internal static class TurnInDelivery
|
||||||
{
|
{
|
||||||
internal sealed class Factory(ILoggerFactory loggerFactory) : SimpleTaskFactory
|
internal sealed class Factory : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
if (quest.Id is not SatisfactionSupplyNpcId || sequence.Sequence != 1)
|
if (quest.Id is not SatisfactionSupplyNpcId || sequence.Sequence != 1)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new SatisfactionSupplyTurnIn(loggerFactory.CreateLogger<SatisfactionSupplyTurnIn>());
|
return new Task();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class SatisfactionSupplyTurnIn(ILogger<SatisfactionSupplyTurnIn> logger) : ITask
|
internal sealed record Task : ITask
|
||||||
|
{
|
||||||
|
public override string ToString() => "WeeklyDeliveryTurnIn";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class SatisfactionSupplyTurnIn(ILogger<SatisfactionSupplyTurnIn> logger) : TaskExecutor<Task>
|
||||||
{
|
{
|
||||||
private ushort? _remainingAllowances;
|
private ushort? _remainingAllowances;
|
||||||
|
|
||||||
public bool Start() => true;
|
protected override bool Start() => true;
|
||||||
|
|
||||||
public unsafe ETaskResult Update()
|
public override unsafe ETaskResult Update()
|
||||||
{
|
{
|
||||||
AgentSatisfactionSupply* agentSatisfactionSupply = AgentSatisfactionSupply.Instance();
|
AgentSatisfactionSupply* agentSatisfactionSupply = AgentSatisfactionSupply.Instance();
|
||||||
if (agentSatisfactionSupply == null || !agentSatisfactionSupply->IsAgentActive())
|
if (agentSatisfactionSupply == null || !agentSatisfactionSupply->IsAgentActive())
|
||||||
@ -77,7 +80,5 @@ internal static class TurnInDelivery
|
|||||||
addon->FireCallback(2, pickGatheringItem);
|
addon->FireCallback(2, pickGatheringItem);
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => "WeeklyDeliveryTurnIn";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Questionable.Controller.Steps;
|
namespace Questionable.Controller.Steps;
|
||||||
|
|
||||||
public interface IConditionChangeAware
|
internal interface IConditionChangeAware : ITaskExecutor
|
||||||
{
|
{
|
||||||
void OnConditionChange(ConditionFlag flag, bool value);
|
void OnConditionChange(ConditionFlag flag, bool value);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
namespace Questionable.Controller.Steps;
|
namespace Questionable.Controller.Steps;
|
||||||
|
|
||||||
public interface IRevisitAware
|
internal interface IRevisitAware : ITask
|
||||||
{
|
{
|
||||||
void OnRevisit();
|
void OnRevisit();
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,6 @@
|
|||||||
using System.Threading;
|
namespace Questionable.Controller.Steps;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Questionable.Controller.Steps;
|
|
||||||
|
|
||||||
internal interface ITask
|
internal interface ITask
|
||||||
{
|
{
|
||||||
InteractionProgressContext? ProgressContext() => null;
|
|
||||||
|
|
||||||
bool WasInterrupted()
|
|
||||||
{
|
|
||||||
var progressContext = ProgressContext();
|
|
||||||
if (progressContext != null)
|
|
||||||
{
|
|
||||||
progressContext.Update();
|
|
||||||
return progressContext.WasInterrupted();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShouldRedoOnInterrupt() => false;
|
bool ShouldRedoOnInterrupt() => false;
|
||||||
|
|
||||||
bool Start();
|
|
||||||
|
|
||||||
ETaskResult Update();
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Questionable.Controller.Steps;
|
namespace Questionable.Controller.Steps;
|
||||||
|
|
||||||
public interface IToastAware
|
internal interface IToastAware : ITaskExecutor
|
||||||
{
|
{
|
||||||
bool OnErrorToast(SeString message);
|
bool OnErrorToast(SeString message);
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Action
|
internal static class Action
|
||||||
{
|
{
|
||||||
internal sealed class Factory(GameFunctions gameFunctions, Mount.Factory mountFactory, ILoggerFactory loggerFactory)
|
internal sealed class Factory : ITaskFactory
|
||||||
: ITaskFactory
|
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -25,39 +24,43 @@ internal static class Action
|
|||||||
if (step.Action.Value.RequiresMount())
|
if (step.Action.Value.RequiresMount())
|
||||||
return [task];
|
return [task];
|
||||||
else
|
else
|
||||||
return [mountFactory.Unmount(), task];
|
return [new Mount.UnmountTask(), task];
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITask OnObject(uint? dataId, EAction action)
|
public static ITask OnObject(uint? dataId, EAction action)
|
||||||
{
|
{
|
||||||
return new UseOnObject(dataId, action, gameFunctions,
|
return new UseOnObject(dataId, action);
|
||||||
loggerFactory.CreateLogger<UseOnObject>());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class UseOnObject(
|
internal sealed record UseOnObject(
|
||||||
uint? dataId,
|
uint? DataId,
|
||||||
EAction action,
|
EAction Action) : ITask
|
||||||
|
{
|
||||||
|
public override string ToString() => $"Action({Action})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class UseOnObjectExecutor(
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
ILogger<UseOnObject> logger) : ITask
|
ILogger<UseOnObject> logger) : TaskExecutor<UseOnObject>
|
||||||
{
|
{
|
||||||
private bool _usedAction;
|
private bool _usedAction;
|
||||||
private DateTime _continueAt = DateTime.MinValue;
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
|
|
||||||
public bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
if (dataId != null)
|
if (Task.DataId != null)
|
||||||
{
|
{
|
||||||
IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId.Value);
|
IGameObject? gameObject = gameFunctions.FindObjectByDataId(Task.DataId.Value);
|
||||||
if (gameObject == null)
|
if (gameObject == null)
|
||||||
{
|
{
|
||||||
logger.LogWarning("No game object with dataId {DataId}", dataId);
|
logger.LogWarning("No game object with dataId {DataId}", Task.DataId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameObject.IsTargetable)
|
if (gameObject.IsTargetable)
|
||||||
{
|
{
|
||||||
if (action == EAction.Diagnosis)
|
if (Task.Action == EAction.Diagnosis)
|
||||||
{
|
{
|
||||||
uint eukrasiaAura = 2606;
|
uint eukrasiaAura = 2606;
|
||||||
// If SGE have Eukrasia status, we need to remove it.
|
// If SGE have Eukrasia status, we need to remove it.
|
||||||
@ -72,14 +75,14 @@ internal static class Action
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_usedAction = gameFunctions.UseAction(gameObject, action);
|
_usedAction = gameFunctions.UseAction(gameObject, Task.Action);
|
||||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_usedAction = gameFunctions.UseAction(action);
|
_usedAction = gameFunctions.UseAction(Task.Action);
|
||||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -87,25 +90,25 @@ internal static class Action
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (DateTime.Now <= _continueAt)
|
if (DateTime.Now <= _continueAt)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
if (!_usedAction)
|
if (!_usedAction)
|
||||||
{
|
{
|
||||||
if (dataId != null)
|
if (Task.DataId != null)
|
||||||
{
|
{
|
||||||
IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId.Value);
|
IGameObject? gameObject = gameFunctions.FindObjectByDataId(Task.DataId.Value);
|
||||||
if (gameObject == null || !gameObject.IsTargetable)
|
if (gameObject == null || !gameObject.IsTargetable)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
_usedAction = gameFunctions.UseAction(gameObject, action);
|
_usedAction = gameFunctions.UseAction(gameObject, Task.Action);
|
||||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_usedAction = gameFunctions.UseAction(action);
|
_usedAction = gameFunctions.UseAction(Task.Action);
|
||||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +117,5 @@ internal static class Action
|
|||||||
|
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Action({action})";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,8 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
internal static class AetherCurrent
|
internal static class AetherCurrent
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory(
|
||||||
GameFunctions gameFunctions,
|
|
||||||
AetherCurrentData aetherCurrentData,
|
AetherCurrentData aetherCurrentData,
|
||||||
IChatGui chatGui,
|
IChatGui chatGui) : SimpleTaskFactory
|
||||||
ILoggerFactory loggerFactory) : SimpleTaskFactory
|
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -32,42 +30,39 @@ internal static class AetherCurrent
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DoAttune(step.DataId.Value, step.AetherCurrentId.Value, gameFunctions,
|
return new Attune(step.DataId.Value, step.AetherCurrentId.Value);
|
||||||
loggerFactory.CreateLogger<DoAttune>());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class DoAttune(
|
internal sealed record Attune(uint DataId, uint AetherCurrentId) : ITask
|
||||||
uint dataId,
|
|
||||||
uint aetherCurrentId,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
ILogger<DoAttune> logger) : ITask
|
|
||||||
{
|
{
|
||||||
private InteractionProgressContext? _progressContext;
|
public override string ToString() => $"AttuneAetherCurrent({AetherCurrentId})";
|
||||||
|
}
|
||||||
|
|
||||||
public InteractionProgressContext? ProgressContext() => _progressContext;
|
internal sealed class DoAttune(
|
||||||
|
GameFunctions gameFunctions,
|
||||||
public bool Start()
|
ILogger<DoAttune> logger) : TaskExecutor<Attune>
|
||||||
|
{
|
||||||
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
if (!gameFunctions.IsAetherCurrentUnlocked(aetherCurrentId))
|
if (!gameFunctions.IsAetherCurrentUnlocked(Task.AetherCurrentId))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Attuning to aether current {AetherCurrentId} / {DataId}", aetherCurrentId,
|
logger.LogInformation("Attuning to aether current {AetherCurrentId} / {DataId}", Task.AetherCurrentId,
|
||||||
dataId);
|
Task.DataId);
|
||||||
_progressContext =
|
ProgressContext =
|
||||||
InteractionProgressContext.FromActionUseOrDefault(() => gameFunctions.InteractWith(dataId));
|
InteractionProgressContext.FromActionUseOrDefault(() => gameFunctions.InteractWith(Task.DataId));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation("Already attuned to aether current {AetherCurrentId} / {DataId}", aetherCurrentId,
|
logger.LogInformation("Already attuned to aether current {AetherCurrentId} / {DataId}",
|
||||||
dataId);
|
Task.AetherCurrentId,
|
||||||
|
Task.DataId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() =>
|
public override ETaskResult Update() =>
|
||||||
gameFunctions.IsAetherCurrentUnlocked(aetherCurrentId)
|
gameFunctions.IsAetherCurrentUnlocked(Task.AetherCurrentId)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
|
|
||||||
public override string ToString() => $"AttuneAetherCurrent({aetherCurrentId})";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class AethernetShard
|
internal static class AethernetShard
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory : SimpleTaskFactory
|
||||||
AetheryteFunctions aetheryteFunctions,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
ILoggerFactory loggerFactory) : SimpleTaskFactory
|
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -23,40 +20,37 @@ internal static class AethernetShard
|
|||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(step.AethernetShard);
|
ArgumentNullException.ThrowIfNull(step.AethernetShard);
|
||||||
|
|
||||||
return new DoAttune(step.AethernetShard.Value, aetheryteFunctions, gameFunctions,
|
return new Attune(step.AethernetShard.Value);
|
||||||
loggerFactory.CreateLogger<DoAttune>());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class DoAttune(
|
internal sealed record Attune(EAetheryteLocation AetheryteLocation) : ITask
|
||||||
EAetheryteLocation aetheryteLocation,
|
{
|
||||||
|
public override string ToString() => $"AttuneAethernetShard({AetheryteLocation})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DoAttune(
|
||||||
AetheryteFunctions aetheryteFunctions,
|
AetheryteFunctions aetheryteFunctions,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
ILogger<DoAttune> logger) : ITask
|
ILogger<DoAttune> logger) : TaskExecutor<Attune>
|
||||||
{
|
{
|
||||||
private InteractionProgressContext? _progressContext;
|
protected override bool Start()
|
||||||
|
|
||||||
public InteractionProgressContext? ProgressContext() => _progressContext;
|
|
||||||
|
|
||||||
public bool Start()
|
|
||||||
{
|
{
|
||||||
if (!aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation))
|
if (!aetheryteFunctions.IsAetheryteUnlocked(Task.AetheryteLocation))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Attuning to aethernet shard {AethernetShard}", aetheryteLocation);
|
logger.LogInformation("Attuning to aethernet shard {AethernetShard}", Task.AetheryteLocation);
|
||||||
_progressContext = InteractionProgressContext.FromActionUseOrDefault(() =>
|
ProgressContext = InteractionProgressContext.FromActionUseOrDefault(() =>
|
||||||
gameFunctions.InteractWith((uint)aetheryteLocation, ObjectKind.Aetheryte));
|
gameFunctions.InteractWith((uint)Task.AetheryteLocation, ObjectKind.Aetheryte));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation("Already attuned to aethernet shard {AethernetShard}", aetheryteLocation);
|
logger.LogInformation("Already attuned to aethernet shard {AethernetShard}", Task.AetheryteLocation);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() =>
|
public override ETaskResult Update() =>
|
||||||
aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation)
|
aetheryteFunctions.IsAetheryteUnlocked(Task.AetheryteLocation)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
|
|
||||||
public override string ToString() => $"AttuneAethernetShard({aetheryteLocation})";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
@ -10,10 +9,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Aetheryte
|
internal static class Aetheryte
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory : SimpleTaskFactory
|
||||||
AetheryteFunctions aetheryteFunctions,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
ILoggerFactory loggerFactory) : SimpleTaskFactory
|
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -22,41 +18,38 @@ internal static class Aetheryte
|
|||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(step.Aetheryte);
|
ArgumentNullException.ThrowIfNull(step.Aetheryte);
|
||||||
|
|
||||||
return new DoAttune(step.Aetheryte.Value, aetheryteFunctions, gameFunctions,
|
return new Attune(step.Aetheryte.Value);
|
||||||
loggerFactory.CreateLogger<DoAttune>());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class DoAttune(
|
internal sealed record Attune(EAetheryteLocation AetheryteLocation) : ITask
|
||||||
EAetheryteLocation aetheryteLocation,
|
{
|
||||||
|
public override string ToString() => $"AttuneAetheryte({AetheryteLocation})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DoAttune(
|
||||||
AetheryteFunctions aetheryteFunctions,
|
AetheryteFunctions aetheryteFunctions,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
ILogger<DoAttune> logger) : ITask
|
ILogger<DoAttune> logger) : TaskExecutor<Attune>
|
||||||
{
|
{
|
||||||
private InteractionProgressContext? _progressContext;
|
protected override bool Start()
|
||||||
|
|
||||||
public InteractionProgressContext? ProgressContext() => _progressContext;
|
|
||||||
|
|
||||||
public bool Start()
|
|
||||||
{
|
{
|
||||||
if (!aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation))
|
if (!aetheryteFunctions.IsAetheryteUnlocked(Task.AetheryteLocation))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Attuning to aetheryte {Aetheryte}", aetheryteLocation);
|
logger.LogInformation("Attuning to aetheryte {Aetheryte}", Task.AetheryteLocation);
|
||||||
_progressContext =
|
ProgressContext =
|
||||||
InteractionProgressContext.FromActionUseOrDefault(() =>
|
InteractionProgressContext.FromActionUseOrDefault(() =>
|
||||||
gameFunctions.InteractWith((uint)aetheryteLocation));
|
gameFunctions.InteractWith((uint)Task.AetheryteLocation));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation("Already attuned to aetheryte {Aetheryte}", aetheryteLocation);
|
logger.LogInformation("Already attuned to aetheryte {Aetheryte}", Task.AetheryteLocation);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() =>
|
public override ETaskResult Update() =>
|
||||||
aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation)
|
aetheryteFunctions.IsAetheryteUnlocked(Task.AetheryteLocation)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
|
|
||||||
public override string ToString() => $"AttuneAetheryte({aetheryteLocation})";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,14 +13,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Combat
|
internal static class Combat
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory(GameFunctions gameFunctions) : ITaskFactory
|
||||||
CombatController combatController,
|
|
||||||
Interact.Factory interactFactory,
|
|
||||||
Mount.Factory mountFactory,
|
|
||||||
UseItem.Factory useItemFactory,
|
|
||||||
Action.Factory actionFactory,
|
|
||||||
QuestFunctions questFunctions,
|
|
||||||
GameFunctions gameFunctions) : ITaskFactory
|
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -30,7 +23,7 @@ internal static class Combat
|
|||||||
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
|
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
|
||||||
|
|
||||||
if (gameFunctions.GetMountId() != Mount128Module.MountId)
|
if (gameFunctions.GetMountId() != Mount128Module.MountId)
|
||||||
yield return mountFactory.Unmount();
|
yield return new Mount.UnmountTask();
|
||||||
|
|
||||||
if (step.CombatDelaySecondsAtStart != null)
|
if (step.CombatDelaySecondsAtStart != null)
|
||||||
{
|
{
|
||||||
@ -43,7 +36,7 @@ internal static class Combat
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(step.DataId);
|
ArgumentNullException.ThrowIfNull(step.DataId);
|
||||||
|
|
||||||
yield return interactFactory.Interact(step.DataId.Value, quest, EInteractionType.None, true);
|
yield return new Interact.Task(step.DataId.Value, quest, EInteractionType.None, true);
|
||||||
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1));
|
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1));
|
||||||
yield return CreateTask(quest, sequence, step);
|
yield return CreateTask(quest, sequence, step);
|
||||||
break;
|
break;
|
||||||
@ -54,7 +47,7 @@ internal static class Combat
|
|||||||
ArgumentNullException.ThrowIfNull(step.DataId);
|
ArgumentNullException.ThrowIfNull(step.DataId);
|
||||||
ArgumentNullException.ThrowIfNull(step.ItemId);
|
ArgumentNullException.ThrowIfNull(step.ItemId);
|
||||||
|
|
||||||
yield return useItemFactory.OnObject(quest.Id, step.DataId.Value, step.ItemId.Value,
|
yield return new UseItem.UseOnObject(quest.Id, step.DataId.Value, step.ItemId.Value,
|
||||||
step.CompletionQuestVariablesFlags, true);
|
step.CompletionQuestVariablesFlags, true);
|
||||||
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1));
|
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1));
|
||||||
yield return CreateTask(quest, sequence, step);
|
yield return CreateTask(quest, sequence, step);
|
||||||
@ -67,8 +60,8 @@ internal static class Combat
|
|||||||
ArgumentNullException.ThrowIfNull(step.Action);
|
ArgumentNullException.ThrowIfNull(step.Action);
|
||||||
|
|
||||||
if (!step.Action.Value.RequiresMount())
|
if (!step.Action.Value.RequiresMount())
|
||||||
yield return mountFactory.Unmount();
|
yield return new Mount.UnmountTask();
|
||||||
yield return actionFactory.OnObject(step.DataId.Value, step.Action.Value);
|
yield return new Action.UseOnObject(step.DataId.Value, step.Action.Value);
|
||||||
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1));
|
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1));
|
||||||
yield return CreateTask(quest, sequence, step);
|
yield return CreateTask(quest, sequence, step);
|
||||||
break;
|
break;
|
||||||
@ -92,7 +85,7 @@ internal static class Combat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
private static Task CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
|
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
|
||||||
|
|
||||||
@ -101,46 +94,60 @@ internal static class Combat
|
|||||||
step.CompletionQuestVariablesFlags, step.ComplexCombatData);
|
step.CompletionQuestVariablesFlags, step.ComplexCombatData);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal HandleCombat CreateTask(ElementId? elementId, bool isLastStep, EEnemySpawnType enemySpawnType,
|
internal static Task CreateTask(ElementId? elementId, bool isLastStep, EEnemySpawnType enemySpawnType,
|
||||||
IList<uint> killEnemyDataIds, IList<QuestWorkValue?> completionQuestVariablesFlags,
|
IList<uint> killEnemyDataIds, IList<QuestWorkValue?> completionQuestVariablesFlags,
|
||||||
IList<ComplexCombatData> complexCombatData)
|
IList<ComplexCombatData> complexCombatData)
|
||||||
{
|
{
|
||||||
return new HandleCombat(isLastStep, new CombatController.CombatData
|
return new Task(new CombatController.CombatData
|
||||||
{
|
{
|
||||||
ElementId = elementId,
|
ElementId = elementId,
|
||||||
SpawnType = enemySpawnType,
|
SpawnType = enemySpawnType,
|
||||||
KillEnemyDataIds = killEnemyDataIds.ToList(),
|
KillEnemyDataIds = killEnemyDataIds.ToList(),
|
||||||
ComplexCombatDatas = complexCombatData.ToList(),
|
ComplexCombatDatas = complexCombatData.ToList(),
|
||||||
}, completionQuestVariablesFlags, combatController, questFunctions);
|
}, completionQuestVariablesFlags, isLastStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed record Task(
|
||||||
|
CombatController.CombatData CombatData,
|
||||||
|
IList<QuestWorkValue?> CompletionQuestVariableFlags,
|
||||||
|
bool IsLastStep) : ITask
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (QuestWorkUtils.HasCompletionFlags(CompletionQuestVariableFlags))
|
||||||
|
return $"HandleCombat(wait: QW flags)";
|
||||||
|
else if (IsLastStep)
|
||||||
|
return $"HandleCombat(wait: next sequence)";
|
||||||
|
else
|
||||||
|
return $"HandleCombat(wait: not in combat)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class HandleCombat(
|
internal sealed class HandleCombat(
|
||||||
bool isLastStep,
|
|
||||||
CombatController.CombatData combatData,
|
|
||||||
IList<QuestWorkValue?> completionQuestVariableFlags,
|
|
||||||
CombatController combatController,
|
CombatController combatController,
|
||||||
QuestFunctions questFunctions) : ITask
|
QuestFunctions questFunctions) : TaskExecutor<Task>
|
||||||
{
|
{
|
||||||
private CombatController.EStatus _status = CombatController.EStatus.NotStarted;
|
private CombatController.EStatus _status = CombatController.EStatus.NotStarted;
|
||||||
|
|
||||||
public bool Start() => combatController.Start(combatData);
|
protected override bool Start() => combatController.Start(Task.CombatData);
|
||||||
|
|
||||||
public ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
_status = combatController.Update();
|
_status = combatController.Update();
|
||||||
if (_status != CombatController.EStatus.Complete)
|
if (_status != CombatController.EStatus.Complete)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
// if our quest step has any completion flags, we need to check if they are set
|
// if our quest step has any completion flags, we need to check if they are set
|
||||||
if (QuestWorkUtils.HasCompletionFlags(completionQuestVariableFlags) &&
|
if (QuestWorkUtils.HasCompletionFlags(Task.CompletionQuestVariableFlags) &&
|
||||||
combatData.ElementId is QuestId questId)
|
Task.CombatData.ElementId is QuestId questId)
|
||||||
{
|
{
|
||||||
var questWork = questFunctions.GetQuestProgressInfo(questId);
|
var questWork = questFunctions.GetQuestProgressInfo(questId);
|
||||||
if (questWork == null)
|
if (questWork == null)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
if (QuestWorkUtils.MatchesQuestWork(completionQuestVariableFlags, questWork))
|
if (QuestWorkUtils.MatchesQuestWork(Task.CompletionQuestVariableFlags, questWork))
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
else
|
else
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
@ -148,7 +155,7 @@ internal static class Combat
|
|||||||
|
|
||||||
// the last step, by definition, can only be progressed by the game recognizing we're in a new sequence,
|
// 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
|
// so this is an indefinite wait
|
||||||
if (isLastStep)
|
if (Task.IsLastStep)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -156,15 +163,5 @@ internal static class Combat
|
|||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
if (QuestWorkUtils.HasCompletionFlags(completionQuestVariableFlags))
|
|
||||||
return $"HandleCombat(wait: QW flags, s: {_status})";
|
|
||||||
else if (isLastStep)
|
|
||||||
return $"HandleCombat(wait: next sequence, s: {_status})";
|
|
||||||
else
|
|
||||||
return $"HandleCombat(wait: not in combat, s: {_status})";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,24 +18,25 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Dive
|
internal static class Dive
|
||||||
{
|
{
|
||||||
internal sealed class Factory(ICondition condition, ILoggerFactory loggerFactory) : SimpleTaskFactory
|
internal sealed class Factory : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
if (step.InteractionType != EInteractionType.Dive)
|
if (step.InteractionType != EInteractionType.Dive)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return Dive();
|
return new Task();
|
||||||
}
|
|
||||||
|
|
||||||
public ITask Dive()
|
|
||||||
{
|
|
||||||
return new DoDive(condition, loggerFactory.CreateLogger<DoDive>());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class DoDive(ICondition condition, ILogger<DoDive> logger)
|
internal sealed class Task : ITask
|
||||||
: AbstractDelayedTask(TimeSpan.FromSeconds(5))
|
{
|
||||||
|
|
||||||
|
public override string ToString() => "Dive";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DoDive(ICondition condition, ILogger<DoDive> logger)
|
||||||
|
: AbstractDelayedTaskExecutor<Task>(TimeSpan.FromSeconds(5))
|
||||||
{
|
{
|
||||||
private readonly Queue<(uint Type, nint Key)> _keysToPress = [];
|
private readonly Queue<(uint Type, nint Key)> _keysToPress = [];
|
||||||
private int _attempts;
|
private int _attempts;
|
||||||
@ -114,8 +115,6 @@ internal static class Dive
|
|||||||
foreach (var key in realKeys)
|
foreach (var key in realKeys)
|
||||||
_keysToPress.Enqueue((NativeMethods.WM_KEYUP, key));
|
_keysToPress.Enqueue((NativeMethods.WM_KEYUP, key));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => "Dive";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<nint>? GetKeysToPress(SeVirtualKey key, ModifierFlag modifier)
|
private static List<nint>? GetKeysToPress(SeVirtualKey key, ModifierFlag modifier)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
@ -10,7 +9,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Duty
|
internal static class Duty
|
||||||
{
|
{
|
||||||
internal sealed class Factory(GameFunctions gameFunctions, ICondition condition) : SimpleTaskFactory
|
internal sealed class Factory : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -18,26 +17,28 @@ internal static class Duty
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(step.ContentFinderConditionId);
|
ArgumentNullException.ThrowIfNull(step.ContentFinderConditionId);
|
||||||
return new OpenDutyFinder(step.ContentFinderConditionId.Value, gameFunctions, condition);
|
return new Task(step.ContentFinderConditionId.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class OpenDutyFinder(
|
internal sealed record Task(uint ContentFinderConditionId) : ITask
|
||||||
uint contentFinderConditionId,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
ICondition condition) : ITask
|
|
||||||
{
|
{
|
||||||
public bool Start()
|
public override string ToString() => $"OpenDutyFinder({ContentFinderConditionId})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class Executor(
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
ICondition condition) : TaskExecutor<Task>
|
||||||
|
{
|
||||||
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
if (condition[ConditionFlag.InDutyQueue])
|
if (condition[ConditionFlag.InDutyQueue])
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
gameFunctions.OpenDutyFinder(contentFinderConditionId);
|
gameFunctions.OpenDutyFinder(Task.ContentFinderConditionId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() => ETaskResult.TaskComplete;
|
public override ETaskResult Update() => ETaskResult.TaskComplete;
|
||||||
|
|
||||||
public override string ToString() => $"OpenDutyFinder({contentFinderConditionId})";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
@ -10,7 +9,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Emote
|
internal static class Emote
|
||||||
{
|
{
|
||||||
internal sealed class Factory(ChatFunctions chatFunctions, Mount.Factory mountFactory) : ITaskFactory
|
internal sealed class Factory : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -25,39 +24,46 @@ internal static class Emote
|
|||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(step.Emote);
|
ArgumentNullException.ThrowIfNull(step.Emote);
|
||||||
|
|
||||||
var unmount = mountFactory.Unmount();
|
var unmount = new Mount.UnmountTask();
|
||||||
if (step.DataId != null)
|
if (step.DataId != null)
|
||||||
{
|
{
|
||||||
var task = new UseOnObject(step.Emote.Value, step.DataId.Value, chatFunctions);
|
var task = new UseOnObject(step.Emote.Value, step.DataId.Value);
|
||||||
return [unmount, task];
|
return [unmount, task];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var task = new UseOnSelf(step.Emote.Value, chatFunctions);
|
var task = new UseOnSelf(step.Emote.Value);
|
||||||
return [unmount, task];
|
return [unmount, task];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class UseOnObject(EEmote emote, uint dataId, ChatFunctions chatFunctions) : AbstractDelayedTask
|
internal sealed record UseOnObject(EEmote Emote, uint DataId) : ITask
|
||||||
{
|
{
|
||||||
protected override bool StartInternal()
|
public override string ToString() => $"Emote({Emote} on {DataId})";
|
||||||
{
|
|
||||||
chatFunctions.UseEmote(dataId, emote);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() => $"Emote({emote} on {dataId})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class UseOnSelf(EEmote emote, ChatFunctions chatFunctions) : AbstractDelayedTask
|
internal sealed class UseOnObjectExecutor(ChatFunctions chatFunctions)
|
||||||
|
: AbstractDelayedTaskExecutor<UseOnObject>
|
||||||
{
|
{
|
||||||
protected override bool StartInternal()
|
protected override bool StartInternal()
|
||||||
{
|
{
|
||||||
chatFunctions.UseEmote(emote);
|
chatFunctions.UseEmote(Task.DataId, Task.Emote);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Emote({emote})";
|
internal sealed record UseOnSelf(EEmote Emote) : ITask
|
||||||
|
{
|
||||||
|
public override string ToString() => $"Emote({Emote})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class UseOnSelfExecutor(ChatFunctions chatFunctions) : AbstractDelayedTaskExecutor<UseOnSelf>
|
||||||
|
{
|
||||||
|
protected override bool StartInternal()
|
||||||
|
{
|
||||||
|
chatFunctions.UseEmote(Task.Emote);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class EquipItem
|
internal static class EquipItem
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IDataManager dataManager, ILoggerFactory loggerFactory) : SimpleTaskFactory
|
internal sealed class Factory : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -24,36 +24,18 @@ internal static class EquipItem
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(step.ItemId);
|
ArgumentNullException.ThrowIfNull(step.ItemId);
|
||||||
return Equip(step.ItemId.Value);
|
return new Task(step.ItemId.Value);
|
||||||
}
|
|
||||||
|
|
||||||
private DoEquip Equip(uint itemId)
|
|
||||||
{
|
|
||||||
var item = dataManager.GetExcelSheet<Item>()!.GetRow(itemId) ??
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(itemId));
|
|
||||||
var targetSlots = GetEquipSlot(item) ?? throw new InvalidOperationException("Not a piece of equipment");
|
|
||||||
return new DoEquip(itemId, item, targetSlots, dataManager, loggerFactory.CreateLogger<DoEquip>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<ushort>? GetEquipSlot(Item item)
|
|
||||||
{
|
|
||||||
return item.EquipSlotCategory.Row switch
|
|
||||||
{
|
|
||||||
>= 1 and <= 11 => [(ushort)(item.EquipSlotCategory.Row - 1)],
|
|
||||||
12 => [11, 12], // rings
|
|
||||||
13 => [0],
|
|
||||||
17 => [13], // soul crystal
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class DoEquip(
|
internal sealed record Task(uint ItemId) : ITask
|
||||||
uint itemId,
|
{
|
||||||
Item item,
|
public override string ToString() => $"Equip({ItemId})";
|
||||||
List<ushort> targetSlots,
|
}
|
||||||
|
|
||||||
|
internal sealed class Executor(
|
||||||
IDataManager dataManager,
|
IDataManager dataManager,
|
||||||
ILogger<DoEquip> logger) : ITask, IToastAware
|
ILogger<Executor> logger) : TaskExecutor<Task>, IToastAware
|
||||||
{
|
{
|
||||||
private const int MaxAttempts = 3;
|
private const int MaxAttempts = 3;
|
||||||
|
|
||||||
@ -81,16 +63,22 @@ internal static class EquipItem
|
|||||||
];
|
];
|
||||||
|
|
||||||
private int _attempts;
|
private int _attempts;
|
||||||
|
private Item _item = null!;
|
||||||
|
private List<ushort> _targetSlots = null!;
|
||||||
private DateTime _continueAt = DateTime.MaxValue;
|
private DateTime _continueAt = DateTime.MaxValue;
|
||||||
|
|
||||||
public bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
|
_item = dataManager.GetExcelSheet<Item>()!.GetRow(Task.ItemId) ??
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(Task.ItemId));
|
||||||
|
_targetSlots = GetEquipSlot(_item) ?? throw new InvalidOperationException("Not a piece of equipment");
|
||||||
|
|
||||||
Equip();
|
Equip();
|
||||||
_continueAt = DateTime.Now.AddSeconds(1);
|
_continueAt = DateTime.Now.AddSeconds(1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe ETaskResult Update()
|
public override unsafe ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (DateTime.Now < _continueAt)
|
if (DateTime.Now < _continueAt)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
@ -99,10 +87,10 @@ internal static class EquipItem
|
|||||||
if (inventoryManager == null)
|
if (inventoryManager == null)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
foreach (ushort x in targetSlots)
|
foreach (ushort x in _targetSlots)
|
||||||
{
|
{
|
||||||
var itemSlot = inventoryManager->GetInventorySlot(InventoryType.EquippedItems, x);
|
var itemSlot = inventoryManager->GetInventorySlot(InventoryType.EquippedItems, x);
|
||||||
if (itemSlot != null && itemSlot->ItemId == itemId)
|
if (itemSlot != null && itemSlot->ItemId == Task.ItemId)
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,12 +113,12 @@ internal static class EquipItem
|
|||||||
if (equippedContainer == null)
|
if (equippedContainer == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (ushort slot in targetSlots)
|
foreach (ushort slot in _targetSlots)
|
||||||
{
|
{
|
||||||
var itemSlot = equippedContainer->GetInventorySlot(slot);
|
var itemSlot = equippedContainer->GetInventorySlot(slot);
|
||||||
if (itemSlot != null && itemSlot->ItemId == itemId)
|
if (itemSlot != null && itemSlot->ItemId == Task.ItemId)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Already equipped {Item}, skipping step", item.Name?.ToString());
|
logger.LogInformation("Already equipped {Item}, skipping step", _item.Name?.ToString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,24 +129,24 @@ internal static class EquipItem
|
|||||||
if (sourceContainer == null)
|
if (sourceContainer == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (inventoryManager->GetItemCountInContainer(itemId, sourceInventoryType, true) == 0 &&
|
if (inventoryManager->GetItemCountInContainer(Task.ItemId, sourceInventoryType, true) == 0 &&
|
||||||
inventoryManager->GetItemCountInContainer(itemId, sourceInventoryType) == 0)
|
inventoryManager->GetItemCountInContainer(Task.ItemId, sourceInventoryType) == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (ushort sourceSlot = 0; sourceSlot < sourceContainer->Size; sourceSlot++)
|
for (ushort sourceSlot = 0; sourceSlot < sourceContainer->Size; sourceSlot++)
|
||||||
{
|
{
|
||||||
var sourceItem = sourceContainer->GetInventorySlot(sourceSlot);
|
var sourceItem = sourceContainer->GetInventorySlot(sourceSlot);
|
||||||
if (sourceItem == null || sourceItem->ItemId != itemId)
|
if (sourceItem == null || sourceItem->ItemId != Task.ItemId)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Move the item to the first available slot
|
// Move the item to the first available slot
|
||||||
ushort targetSlot = targetSlots
|
ushort targetSlot = _targetSlots
|
||||||
.Where(x =>
|
.Where(x =>
|
||||||
{
|
{
|
||||||
var itemSlot = inventoryManager->GetInventorySlot(InventoryType.EquippedItems, x);
|
var itemSlot = inventoryManager->GetInventorySlot(InventoryType.EquippedItems, x);
|
||||||
return itemSlot == null || itemSlot->ItemId == 0;
|
return itemSlot == null || itemSlot->ItemId == 0;
|
||||||
})
|
})
|
||||||
.Concat(targetSlots).First();
|
.Concat(_targetSlots).First();
|
||||||
|
|
||||||
logger.LogInformation(
|
logger.LogInformation(
|
||||||
"Equipping item from {SourceInventory}, {SourceSlot} to {TargetInventory}, {TargetSlot}",
|
"Equipping item from {SourceInventory}, {SourceSlot} to {TargetInventory}, {TargetSlot}",
|
||||||
@ -172,7 +160,17 @@ internal static class EquipItem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Equip({item.Name})";
|
private static List<ushort>? GetEquipSlot(Item item)
|
||||||
|
{
|
||||||
|
return item.EquipSlotCategory.Row switch
|
||||||
|
{
|
||||||
|
>= 1 and <= 11 => [(ushort)(item.EquipSlotCategory.Row - 1)],
|
||||||
|
12 => [11, 12], // rings
|
||||||
|
13 => [0],
|
||||||
|
17 => [13], // soul crystal
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public bool OnErrorToast(SeString message)
|
public bool OnErrorToast(SeString message)
|
||||||
{
|
{
|
||||||
|
@ -10,23 +10,18 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class EquipRecommended
|
internal static class EquipRecommended
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IClientState clientState, IChatGui chatGui) : SimpleTaskFactory
|
internal sealed class Factory : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
if (step.InteractionType != EInteractionType.EquipRecommended)
|
if (step.InteractionType != EInteractionType.EquipRecommended)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return DoEquip();
|
return new EquipTask();
|
||||||
}
|
|
||||||
|
|
||||||
public ITask DoEquip()
|
|
||||||
{
|
|
||||||
return new DoEquipRecommended(clientState, chatGui);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class BeforeDutyOrInstance(IClientState clientState, IChatGui chatGui) : SimpleTaskFactory
|
internal sealed class BeforeDutyOrInstance : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -35,21 +30,26 @@ internal static class EquipRecommended
|
|||||||
step.InteractionType != EInteractionType.Combat)
|
step.InteractionType != EInteractionType.Combat)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new DoEquipRecommended(clientState, chatGui);
|
return new EquipTask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed unsafe class DoEquipRecommended(IClientState clientState, IChatGui chatGui) : ITask
|
internal sealed class EquipTask : ITask
|
||||||
|
{
|
||||||
|
public override string ToString() => "EquipRecommended";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed unsafe class DoEquipRecommended(IClientState clientState, IChatGui chatGui) : TaskExecutor<EquipTask>
|
||||||
{
|
{
|
||||||
private bool _equipped;
|
private bool _equipped;
|
||||||
|
|
||||||
public bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
RecommendEquipModule.Instance()->SetupForClassJob((byte)clientState.LocalPlayer!.ClassJob.Id);
|
RecommendEquipModule.Instance()->SetupForClassJob((byte)clientState.LocalPlayer!.ClassJob.Id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
var recommendedEquipModule = RecommendEquipModule.Instance();
|
var recommendedEquipModule = RecommendEquipModule.Instance();
|
||||||
if (recommendedEquipModule->IsUpdating)
|
if (recommendedEquipModule->IsUpdating)
|
||||||
@ -94,7 +94,5 @@ internal static class EquipRecommended
|
|||||||
|
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => "EquipRecommended";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using Dalamud.Game.ClientState.Objects.Enums;
|
|||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
@ -16,12 +15,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Interact
|
internal static class Interact
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory(Configuration configuration) : ITaskFactory
|
||||||
GameFunctions gameFunctions,
|
|
||||||
Configuration configuration,
|
|
||||||
ICondition condition,
|
|
||||||
ILoggerFactory loggerFactory)
|
|
||||||
: ITaskFactory
|
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -55,58 +49,50 @@ internal static class Interact
|
|||||||
if (sequence.Sequence == 0 && sequence.Steps.IndexOf(step) == 0)
|
if (sequence.Sequence == 0 && sequence.Steps.IndexOf(step) == 0)
|
||||||
yield return new WaitAtEnd.WaitDelay();
|
yield return new WaitAtEnd.WaitDelay();
|
||||||
|
|
||||||
yield return Interact(step.DataId.Value, quest, step.InteractionType,
|
yield return new Task(step.DataId.Value, quest, step.InteractionType,
|
||||||
step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId ||
|
step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId ||
|
||||||
step.SkipConditions is { StepIf.Never: true }, step.PickUpItemId, step.SkipConditions?.StepIf);
|
step.SkipConditions is { StepIf.Never: true }, step.PickUpItemId, step.SkipConditions?.StepIf);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal ITask Interact(uint dataId, Quest? quest, EInteractionType interactionType,
|
internal sealed record Task(
|
||||||
bool skipMarkerCheck = false, uint? pickUpItemId = null, SkipStepConditions? skipConditions = null)
|
uint DataId,
|
||||||
{
|
Quest? Quest,
|
||||||
return new DoInteract(dataId, quest, interactionType, skipMarkerCheck, pickUpItemId, skipConditions,
|
EInteractionType InteractionType,
|
||||||
gameFunctions, condition, loggerFactory.CreateLogger<DoInteract>());
|
bool SkipMarkerCheck = false,
|
||||||
}
|
uint? PickUpItemId = null,
|
||||||
|
SkipStepConditions? SkipConditions = null) : ITask
|
||||||
|
{
|
||||||
|
public override string ToString() => $"Interact({DataId})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DoInteract(
|
internal sealed class DoInteract(
|
||||||
uint dataId,
|
|
||||||
Quest? quest,
|
|
||||||
EInteractionType interactionType,
|
|
||||||
bool skipMarkerCheck,
|
|
||||||
uint? pickUpItemId,
|
|
||||||
SkipStepConditions? skipConditions,
|
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger<DoInteract> logger)
|
ILogger<DoInteract> logger)
|
||||||
: ITask
|
: TaskExecutor<Task>
|
||||||
{
|
{
|
||||||
private bool _needsUnmount;
|
private bool _needsUnmount;
|
||||||
private InteractionProgressContext? _progressContext;
|
|
||||||
private DateTime _continueAt = DateTime.MinValue;
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
|
|
||||||
public Quest? Quest => quest;
|
public Quest? Quest => Task.Quest;
|
||||||
|
public EInteractionType InteractionType { get; set; }
|
||||||
|
|
||||||
public EInteractionType InteractionType
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
get => interactionType;
|
InteractionType = Task.InteractionType;
|
||||||
set => interactionType = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InteractionProgressContext? ProgressContext() => _progressContext;
|
IGameObject? gameObject = gameFunctions.FindObjectByDataId(Task.DataId);
|
||||||
|
|
||||||
public bool Start()
|
|
||||||
{
|
|
||||||
IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId);
|
|
||||||
if (gameObject == null)
|
if (gameObject == null)
|
||||||
{
|
{
|
||||||
logger.LogWarning("No game object with dataId {DataId}", dataId);
|
logger.LogWarning("No game object with dataId {DataId}", Task.DataId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gameObject.IsTargetable && skipConditions is { Never: false, NotTargetable: true })
|
if (!gameObject.IsTargetable && Task.SkipConditions is { Never: false, NotTargetable: true })
|
||||||
{
|
{
|
||||||
logger.LogInformation("Not interacting with {DataId} because it is not targetable (but skippable)",
|
logger.LogInformation("Not interacting with {DataId} because it is not targetable (but skippable)",
|
||||||
dataId);
|
Task.DataId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +100,7 @@ internal static class Interact
|
|||||||
if (!gameObject.IsTargetable && condition[ConditionFlag.Mounted] &&
|
if (!gameObject.IsTargetable && condition[ConditionFlag.Mounted] &&
|
||||||
gameObject.ObjectKind != ObjectKind.GatheringPoint)
|
gameObject.ObjectKind != ObjectKind.GatheringPoint)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Preparing interaction for {DataId} by unmounting", dataId);
|
logger.LogInformation("Preparing interaction for {DataId} by unmounting", Task.DataId);
|
||||||
_needsUnmount = true;
|
_needsUnmount = true;
|
||||||
gameFunctions.Unmount();
|
gameFunctions.Unmount();
|
||||||
_continueAt = DateTime.Now.AddSeconds(1);
|
_continueAt = DateTime.Now.AddSeconds(1);
|
||||||
@ -123,7 +109,7 @@ internal static class Interact
|
|||||||
|
|
||||||
if (gameObject.IsTargetable && HasAnyMarker(gameObject))
|
if (gameObject.IsTargetable && HasAnyMarker(gameObject))
|
||||||
{
|
{
|
||||||
_progressContext =
|
ProgressContext =
|
||||||
InteractionProgressContext.FromActionUseOrDefault(() => gameFunctions.InteractWith(gameObject));
|
InteractionProgressContext.FromActionUseOrDefault(() => gameFunctions.InteractWith(gameObject));
|
||||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||||
return true;
|
return true;
|
||||||
@ -132,7 +118,7 @@ internal static class Interact
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (DateTime.Now <= _continueAt)
|
if (DateTime.Now <= _continueAt)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
@ -149,29 +135,29 @@ internal static class Interact
|
|||||||
_needsUnmount = false;
|
_needsUnmount = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pickUpItemId != null)
|
if (Task.PickUpItemId != null)
|
||||||
{
|
{
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||||
if (inventoryManager->GetInventoryItemCount(pickUpItemId.Value) > 0)
|
if (inventoryManager->GetInventoryItemCount(Task.PickUpItemId.Value) > 0)
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_progressContext != null && _progressContext.WasSuccessful())
|
if (ProgressContext != null && ProgressContext.WasSuccessful())
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
if (interactionType == EInteractionType.Gather && condition[ConditionFlag.Gathering])
|
if (InteractionType == EInteractionType.Gather && condition[ConditionFlag.Gathering])
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId);
|
IGameObject? gameObject = gameFunctions.FindObjectByDataId(Task.DataId);
|
||||||
if (gameObject == null || !gameObject.IsTargetable || !HasAnyMarker(gameObject))
|
if (gameObject == null || !gameObject.IsTargetable || !HasAnyMarker(gameObject))
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
_progressContext =
|
ProgressContext =
|
||||||
InteractionProgressContext.FromActionUseOrDefault(() => gameFunctions.InteractWith(gameObject));
|
InteractionProgressContext.FromActionUseOrDefault(() => gameFunctions.InteractWith(gameObject));
|
||||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
@ -179,13 +165,11 @@ internal static class Interact
|
|||||||
|
|
||||||
private unsafe bool HasAnyMarker(IGameObject gameObject)
|
private unsafe bool HasAnyMarker(IGameObject gameObject)
|
||||||
{
|
{
|
||||||
if (skipMarkerCheck || gameObject.ObjectKind != ObjectKind.EventNpc)
|
if (Task.SkipMarkerCheck || gameObject.ObjectKind != ObjectKind.EventNpc)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var gameObjectStruct = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address;
|
var gameObjectStruct = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address;
|
||||||
return gameObjectStruct->NamePlateIconId != 0;
|
return gameObjectStruct->NamePlateIconId != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Interact({dataId})";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,12 +12,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Jump
|
internal static class Jump
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory : SimpleTaskFactory
|
||||||
MovementController movementController,
|
|
||||||
IClientState clientState,
|
|
||||||
IFramework framework,
|
|
||||||
ICondition condition,
|
|
||||||
ILoggerFactory loggerFactory) : SimpleTaskFactory
|
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -27,39 +22,42 @@ internal static class Jump
|
|||||||
ArgumentNullException.ThrowIfNull(step.JumpDestination);
|
ArgumentNullException.ThrowIfNull(step.JumpDestination);
|
||||||
|
|
||||||
if (step.JumpDestination.Type == EJumpType.SingleJump)
|
if (step.JumpDestination.Type == EJumpType.SingleJump)
|
||||||
return SingleJump(step.DataId, step.JumpDestination, step.Comment);
|
return new SingleJumpTask(step.DataId, step.JumpDestination, step.Comment);
|
||||||
else
|
else
|
||||||
return RepeatedJumps(step.DataId, step.JumpDestination, step.Comment);
|
return new RepeatedJumpTask(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,
|
|
||||||
condition, loggerFactory.CreateLogger<DoRepeatedJumps>());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DoSingleJump(
|
internal interface IJumpTask : ITask
|
||||||
uint? dataId,
|
{
|
||||||
JumpDestination jumpDestination,
|
uint? DataId { get; }
|
||||||
string? comment,
|
JumpDestination JumpDestination { get; }
|
||||||
|
string? Comment { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed record SingleJumpTask(
|
||||||
|
uint? DataId,
|
||||||
|
JumpDestination JumpDestination,
|
||||||
|
string? Comment) : IJumpTask
|
||||||
|
{
|
||||||
|
public override string ToString() => $"Jump({Comment})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal abstract class JumpBase<T>(
|
||||||
MovementController movementController,
|
MovementController movementController,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
IFramework framework) : ITask
|
IFramework framework) : TaskExecutor<T>
|
||||||
|
where T : class, IJumpTask
|
||||||
{
|
{
|
||||||
public virtual bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
float stopDistance = jumpDestination.CalculateStopDistance();
|
float stopDistance = Task.JumpDestination.CalculateStopDistance();
|
||||||
if ((clientState.LocalPlayer!.Position - jumpDestination.Position).Length() <= stopDistance)
|
if ((clientState.LocalPlayer!.Position - Task.JumpDestination.Position).Length() <= stopDistance)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
movementController.NavigateTo(EMovementType.Quest, dataId, [jumpDestination.Position], false, false,
|
movementController.NavigateTo(EMovementType.Quest, Task.DataId, [Task.JumpDestination.Position], false,
|
||||||
jumpDestination.StopDistance ?? stopDistance);
|
false,
|
||||||
|
Task.JumpDestination.StopDistance ?? stopDistance);
|
||||||
framework.RunOnTick(() =>
|
framework.RunOnTick(() =>
|
||||||
{
|
{
|
||||||
unsafe
|
unsafe
|
||||||
@ -67,11 +65,11 @@ internal static class Jump
|
|||||||
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2);
|
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TimeSpan.FromSeconds(jumpDestination.DelaySeconds ?? 0.5f));
|
TimeSpan.FromSeconds(Task.JumpDestination.DelaySeconds ?? 0.5f));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (movementController.IsPathfinding || movementController.IsPathRunning)
|
if (movementController.IsPathfinding || movementController.IsPathRunning)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
@ -82,30 +80,36 @@ internal static class Jump
|
|||||||
|
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Jump({comment})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class DoRepeatedJumps(
|
internal sealed class DoSingleJump(
|
||||||
uint? dataId,
|
MovementController movementController,
|
||||||
JumpDestination jumpDestination,
|
IClientState clientState,
|
||||||
string? comment,
|
IFramework framework) : JumpBase<SingleJumpTask>(movementController, clientState, framework);
|
||||||
|
|
||||||
|
internal sealed record RepeatedJumpTask(
|
||||||
|
uint? DataId,
|
||||||
|
JumpDestination JumpDestination,
|
||||||
|
string? Comment) : IJumpTask
|
||||||
|
{
|
||||||
|
public override string ToString() => $"RepeatedJump({Comment})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DoRepeatedJumps(
|
||||||
MovementController movementController,
|
MovementController movementController,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
IFramework framework,
|
IFramework framework,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger<DoRepeatedJumps> logger)
|
ILogger<DoRepeatedJumps> logger)
|
||||||
: DoSingleJump(dataId, jumpDestination, comment, movementController, clientState, framework)
|
: JumpBase<RepeatedJumpTask>(movementController, clientState, framework)
|
||||||
{
|
{
|
||||||
private readonly JumpDestination _jumpDestination = jumpDestination;
|
|
||||||
private readonly string? _comment = comment;
|
|
||||||
private readonly IClientState _clientState = clientState;
|
private readonly IClientState _clientState = clientState;
|
||||||
private DateTime _continueAt = DateTime.MinValue;
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
private int _attempts;
|
private int _attempts;
|
||||||
|
|
||||||
public override bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
_continueAt = DateTime.Now + TimeSpan.FromSeconds(2 * (_jumpDestination.DelaySeconds ?? 0.5f));
|
_continueAt = DateTime.Now + TimeSpan.FromSeconds(2 * (Task.JumpDestination.DelaySeconds ?? 0.5f));
|
||||||
return base.Start();
|
return base.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,13 +118,13 @@ internal static class Jump
|
|||||||
if (DateTime.Now < _continueAt || condition[ConditionFlag.Jumping])
|
if (DateTime.Now < _continueAt || condition[ConditionFlag.Jumping])
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
float stopDistance = _jumpDestination.CalculateStopDistance();
|
float stopDistance = Task.JumpDestination.CalculateStopDistance();
|
||||||
if ((_clientState.LocalPlayer!.Position - _jumpDestination.Position).Length() <= stopDistance ||
|
if ((_clientState.LocalPlayer!.Position - Task.JumpDestination.Position).Length() <= stopDistance ||
|
||||||
_clientState.LocalPlayer.Position.Y >= _jumpDestination.Position.Y - 0.5f)
|
_clientState.LocalPlayer.Position.Y >= Task.JumpDestination.Position.Y - 0.5f)
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
logger.LogTrace("Y-Heights for jumps: player={A}, target={B}", _clientState.LocalPlayer.Position.Y,
|
logger.LogTrace("Y-Heights for jumps: player={A}, target={B}", _clientState.LocalPlayer.Position.Y,
|
||||||
_jumpDestination.Position.Y - 0.5f);
|
Task.JumpDestination.Position.Y - 0.5f);
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2))
|
if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2))
|
||||||
@ -130,10 +134,8 @@ internal static class Jump
|
|||||||
if (_attempts >= 50)
|
if (_attempts >= 50)
|
||||||
throw new TaskException("Tried to jump too many times, didn't reach the target");
|
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(Task.JumpDestination.DelaySeconds ?? 0.5f);
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"RepeatedJump({_comment})";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Say
|
internal static class Say
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory(ExcelFunctions excelFunctions) : ITaskFactory
|
||||||
ChatFunctions chatFunctions,
|
|
||||||
Mount.Factory mountFactory,
|
|
||||||
ExcelFunctions excelFunctions) : ITaskFactory
|
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -33,20 +30,23 @@ internal static class Say
|
|||||||
.GetString();
|
.GetString();
|
||||||
ArgumentNullException.ThrowIfNull(excelString);
|
ArgumentNullException.ThrowIfNull(excelString);
|
||||||
|
|
||||||
var unmount = mountFactory.Unmount();
|
var unmount = new Mount.UnmountTask();
|
||||||
var task = new UseChat(excelString, chatFunctions);
|
var task = new Task(excelString);
|
||||||
return [unmount, task];
|
return [unmount, task];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class UseChat(string chatMessage, ChatFunctions chatFunctions) : AbstractDelayedTask
|
internal sealed record Task(string ChatMessage) : ITask
|
||||||
|
{
|
||||||
|
public override string ToString() => $"Say({ChatMessage})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class UseChat(ChatFunctions chatFunctions) : AbstractDelayedTaskExecutor<Task>
|
||||||
{
|
{
|
||||||
protected override bool StartInternal()
|
protected override bool StartInternal()
|
||||||
{
|
{
|
||||||
chatFunctions.ExecuteCommand($"/say {chatMessage}");
|
chatFunctions.ExecuteCommand($"/say {Task.ChatMessage}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Say({chatMessage})";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,7 @@ using System.Linq;
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
@ -24,17 +22,8 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
internal static class UseItem
|
internal static class UseItem
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory(
|
||||||
Mount.Factory mountFactory,
|
|
||||||
MoveTo.Factory moveFactory,
|
|
||||||
Interact.Factory interactFactory,
|
|
||||||
AetheryteShortcut.Factory aetheryteShortcutFactory,
|
|
||||||
AethernetShortcut.Factory aethernetShortcutFactory,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
QuestFunctions questFunctions,
|
|
||||||
ICondition condition,
|
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
TerritoryData territoryData,
|
TerritoryData territoryData,
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
ILogger<Factory> logger)
|
ILogger<Factory> logger)
|
||||||
: ITaskFactory
|
: ITaskFactory
|
||||||
{
|
{
|
||||||
@ -59,7 +48,7 @@ internal static class UseItem
|
|||||||
return CreateVesperBayFallbackTask();
|
return CreateVesperBayFallbackTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
var task = OnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
var task = new UseOnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||||
|
|
||||||
int currentStepIndex = sequence.Steps.IndexOf(step);
|
int currentStepIndex = sequence.Steps.IndexOf(step);
|
||||||
QuestStep? nextStep = sequence.Steps.Skip(currentStepIndex + 1).FirstOrDefault();
|
QuestStep? nextStep = sequence.Steps.Skip(currentStepIndex + 1).FirstOrDefault();
|
||||||
@ -67,27 +56,27 @@ internal static class UseItem
|
|||||||
return
|
return
|
||||||
[
|
[
|
||||||
task,
|
task,
|
||||||
new WaitConditionTask(() => clientState.TerritoryType == 140,
|
new WaitCondition.Task(() => clientState.TerritoryType == 140,
|
||||||
$"Wait(territory: {territoryData.GetNameAndId(140)})"),
|
$"Wait(territory: {territoryData.GetNameAndId(140)})"),
|
||||||
mountFactory.Mount(140,
|
new Mount.MountTask(140,
|
||||||
nextPosition != null ? Mount.EMountIf.AwayFromPosition : Mount.EMountIf.Always,
|
nextPosition != null ? Mount.EMountIf.AwayFromPosition : Mount.EMountIf.Always,
|
||||||
nextPosition),
|
nextPosition),
|
||||||
moveFactory.Move(new MoveTo.MoveParams(140, new(-408.92343f, 23.167036f, -351.16223f), null, 0.25f,
|
new MoveTo.MoveTask(140, new(-408.92343f, 23.167036f, -351.16223f), null, 0.25f,
|
||||||
DataId: null, DisableNavMesh: true, Sprint: false, Fly: false))
|
DataId: null, DisableNavmesh: true, Sprint: false, Fly: false)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
var unmount = mountFactory.Unmount();
|
var unmount = new Mount.UnmountTask();
|
||||||
if (step.GroundTarget == true)
|
if (step.GroundTarget == true)
|
||||||
{
|
{
|
||||||
ITask task;
|
ITask task;
|
||||||
if (step.DataId != null)
|
if (step.DataId != null)
|
||||||
task = OnGroundTarget(quest.Id, step.DataId.Value, step.ItemId.Value,
|
task = new UseOnGround(quest.Id, step.DataId.Value, step.ItemId.Value,
|
||||||
step.CompletionQuestVariablesFlags);
|
step.CompletionQuestVariablesFlags);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(step.Position);
|
ArgumentNullException.ThrowIfNull(step.Position);
|
||||||
task = OnPosition(quest.Id, step.Position.Value, step.ItemId.Value,
|
task = new UseOnPosition(quest.Id, step.Position.Value, step.ItemId.Value,
|
||||||
step.CompletionQuestVariablesFlags);
|
step.CompletionQuestVariablesFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,43 +84,17 @@ internal static class UseItem
|
|||||||
}
|
}
|
||||||
else if (step.DataId != null)
|
else if (step.DataId != null)
|
||||||
{
|
{
|
||||||
var task = OnObject(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
var task = new UseOnObject(quest.Id, step.DataId.Value, step.ItemId.Value,
|
||||||
|
step.CompletionQuestVariablesFlags);
|
||||||
return [unmount, task];
|
return [unmount, task];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var task = OnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
var task = new UseOnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||||
return [unmount, task];
|
return [unmount, task];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITask OnGroundTarget(ElementId questId, uint dataId, uint itemId,
|
|
||||||
List<QuestWorkValue?> completionQuestVariablesFlags)
|
|
||||||
{
|
|
||||||
return new UseOnGround(questId, dataId, itemId, completionQuestVariablesFlags, gameFunctions,
|
|
||||||
questFunctions, condition, loggerFactory.CreateLogger<UseOnGround>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITask OnPosition(ElementId questId, Vector3 position, uint itemId,
|
|
||||||
List<QuestWorkValue?> completionQuestVariablesFlags)
|
|
||||||
{
|
|
||||||
return new UseOnPosition(questId, position, itemId, completionQuestVariablesFlags, gameFunctions,
|
|
||||||
questFunctions, condition, loggerFactory.CreateLogger<UseOnPosition>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITask OnObject(ElementId questId, uint dataId, uint itemId,
|
|
||||||
List<QuestWorkValue?> completionQuestVariablesFlags, bool startingCombat = false)
|
|
||||||
{
|
|
||||||
return new UseOnObject(questId, dataId, itemId, completionQuestVariablesFlags, startingCombat,
|
|
||||||
questFunctions, gameFunctions, condition, loggerFactory.CreateLogger<UseOnObject>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITask OnSelf(ElementId questId, uint itemId, List<QuestWorkValue?> completionQuestVariablesFlags)
|
|
||||||
{
|
|
||||||
return new Use(questId, itemId, completionQuestVariablesFlags, gameFunctions, questFunctions, condition,
|
|
||||||
loggerFactory.CreateLogger<Use>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<ITask> CreateVesperBayFallbackTask()
|
private IEnumerable<ITask> CreateVesperBayFallbackTask()
|
||||||
{
|
{
|
||||||
logger.LogWarning("No vesper bay aetheryte tickets in inventory, navigating via ferry in Limsa instead");
|
logger.LogWarning("No vesper bay aetheryte tickets in inventory, navigating via ferry in Limsa instead");
|
||||||
@ -139,39 +102,40 @@ internal static class UseItem
|
|||||||
uint npcId = 1003540;
|
uint npcId = 1003540;
|
||||||
ushort territoryId = 129;
|
ushort territoryId = 129;
|
||||||
Vector3 destination = new(-360.9217f, 8f, 38.92566f);
|
Vector3 destination = new(-360.9217f, 8f, 38.92566f);
|
||||||
yield return aetheryteShortcutFactory.Use(null, null, EAetheryteLocation.Limsa, territoryId);
|
yield return new AetheryteShortcut.Task(null, null, EAetheryteLocation.Limsa, territoryId);
|
||||||
yield return aethernetShortcutFactory.Use(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist);
|
yield return new AethernetShortcut.Task(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist);
|
||||||
yield return new WaitAtEnd.WaitDelay();
|
yield return new WaitAtEnd.WaitDelay();
|
||||||
yield return
|
yield return new MoveTo.MoveTask(territoryId, destination, DataId: npcId, Sprint: false);
|
||||||
moveFactory.Move(new MoveTo.MoveParams(territoryId, destination, DataId: npcId, Sprint: false));
|
yield return new Interact.Task(npcId, null, EInteractionType.None, true);
|
||||||
yield return interactFactory.Interact(npcId, null, EInteractionType.None, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class UseItemBase(
|
internal interface IUseItemBase : ITask
|
||||||
ElementId? questId,
|
{
|
||||||
uint itemId,
|
ElementId? QuestId { get; }
|
||||||
IList<QuestWorkValue?> completionQuestVariablesFlags,
|
uint ItemId { get; }
|
||||||
bool startingCombat,
|
IList<QuestWorkValue?> CompletionQuestVariablesFlags { get; }
|
||||||
|
bool StartingCombat { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal abstract class UseItemExecutorBase<T>(
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger logger) : ITask
|
ILogger logger) : TaskExecutor<T>
|
||||||
|
where T : class, IUseItemBase
|
||||||
{
|
{
|
||||||
private bool _usedItem;
|
private bool _usedItem;
|
||||||
private DateTime _continueAt;
|
private DateTime _continueAt;
|
||||||
private int _itemCount;
|
private int _itemCount;
|
||||||
private InteractionProgressContext? _progressContext;
|
|
||||||
|
|
||||||
public InteractionProgressContext? ProgressContext() => _progressContext;
|
private ElementId? QuestId => Task.QuestId;
|
||||||
|
protected uint ItemId => Task.ItemId;
|
||||||
public ElementId? QuestId => questId;
|
private IList<QuestWorkValue?> CompletionQuestVariablesFlags => Task.CompletionQuestVariablesFlags;
|
||||||
public uint ItemId => itemId;
|
private bool StartingCombat => Task.StartingCombat;
|
||||||
public IList<QuestWorkValue?> CompletionQuestVariablesFlags => completionQuestVariablesFlags;
|
|
||||||
public bool StartingCombat => startingCombat;
|
|
||||||
|
|
||||||
protected abstract bool UseItem();
|
protected abstract bool UseItem();
|
||||||
|
|
||||||
public unsafe bool Start()
|
protected override unsafe bool Start()
|
||||||
{
|
{
|
||||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||||
if (inventoryManager == null)
|
if (inventoryManager == null)
|
||||||
@ -181,12 +145,12 @@ internal static class UseItem
|
|||||||
if (_itemCount == 0)
|
if (_itemCount == 0)
|
||||||
throw new TaskException($"Don't have any {ItemId} in inventory (checks NQ only)");
|
throw new TaskException($"Don't have any {ItemId} in inventory (checks NQ only)");
|
||||||
|
|
||||||
_progressContext = InteractionProgressContext.FromActionUseOrDefault(() => _usedItem = UseItem());
|
ProgressContext = InteractionProgressContext.FromActionUseOrDefault(() => _usedItem = UseItem());
|
||||||
_continueAt = DateTime.Now.Add(GetRetryDelay());
|
_continueAt = DateTime.Now.Add(GetRetryDelay());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe ETaskResult Update()
|
public override unsafe ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (QuestId is QuestId realQuestId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
|
if (QuestId is QuestId realQuestId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
|
||||||
{
|
{
|
||||||
@ -224,7 +188,7 @@ internal static class UseItem
|
|||||||
|
|
||||||
if (!_usedItem)
|
if (!_usedItem)
|
||||||
{
|
{
|
||||||
_progressContext = InteractionProgressContext.FromActionUseOrDefault(() => _usedItem = UseItem());
|
ProgressContext = InteractionProgressContext.FromActionUseOrDefault(() => _usedItem = UseItem());
|
||||||
_continueAt = DateTime.Now.Add(GetRetryDelay());
|
_continueAt = DateTime.Now.Add(GetRetryDelay());
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
@ -241,69 +205,85 @@ internal static class UseItem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed record UseOnGround(
|
||||||
|
ElementId? QuestId,
|
||||||
|
uint DataId,
|
||||||
|
uint ItemId,
|
||||||
|
IList<QuestWorkValue?> CompletionQuestVariablesFlags) : IUseItemBase
|
||||||
|
{
|
||||||
|
public bool StartingCombat => false;
|
||||||
|
public override string ToString() => $"UseItem({ItemId} on ground at {DataId})";
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class UseOnGround(
|
internal sealed class UseOnGroundExecutor(
|
||||||
ElementId? questId,
|
|
||||||
uint dataId,
|
|
||||||
uint itemId,
|
|
||||||
IList<QuestWorkValue?> completionQuestVariablesFlags,
|
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger<UseOnGround> logger)
|
ILogger<UseOnGroundExecutor> logger)
|
||||||
: UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger)
|
: UseItemExecutorBase<UseOnGround>(questFunctions, condition, logger)
|
||||||
{
|
{
|
||||||
protected override bool UseItem() => gameFunctions.UseItemOnGround(dataId, ItemId);
|
protected override bool UseItem() => gameFunctions.UseItemOnGround(Task.DataId, ItemId);
|
||||||
|
|
||||||
public override string ToString() => $"UseItem({ItemId} on ground at {dataId})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class UseOnPosition(
|
internal sealed record UseOnPosition(
|
||||||
ElementId? questId,
|
ElementId? QuestId,
|
||||||
Vector3 position,
|
Vector3 Position,
|
||||||
uint itemId,
|
uint ItemId,
|
||||||
IList<QuestWorkValue?> completionQuestVariablesFlags,
|
IList<QuestWorkValue?> CompletionQuestVariablesFlags)
|
||||||
|
: IUseItemBase
|
||||||
|
{
|
||||||
|
public bool StartingCombat => false;
|
||||||
|
|
||||||
|
public override string ToString() =>
|
||||||
|
$"UseItem({ItemId} on ground at {Position.ToString("G", CultureInfo.InvariantCulture)})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class UseOnPositionExecutor(
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger<UseOnPosition> logger)
|
ILogger<UseOnPosition> logger)
|
||||||
: UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger)
|
: UseItemExecutorBase<UseOnPosition>(questFunctions, condition, logger)
|
||||||
{
|
{
|
||||||
protected override bool UseItem() => gameFunctions.UseItemOnPosition(position, ItemId);
|
protected override bool UseItem() => gameFunctions.UseItemOnPosition(Task.Position, ItemId);
|
||||||
|
|
||||||
public override string ToString() =>
|
|
||||||
$"UseItem({ItemId} on ground at {position.ToString("G", CultureInfo.InvariantCulture)})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class UseOnObject(
|
internal sealed record UseOnObject(
|
||||||
ElementId? questId,
|
ElementId? QuestId,
|
||||||
uint dataId,
|
uint DataId,
|
||||||
uint itemId,
|
uint ItemId,
|
||||||
IList<QuestWorkValue?> completionQuestVariablesFlags,
|
IList<QuestWorkValue?> CompletionQuestVariablesFlags,
|
||||||
bool startingCombat,
|
bool StartingCombat = false) : IUseItemBase
|
||||||
|
{
|
||||||
|
public override string ToString() => $"UseItem({ItemId} on {DataId})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class UseOnObjectExecutor(
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger<UseOnObject> logger)
|
ILogger<UseOnObject> logger)
|
||||||
: UseItemBase(questId, itemId, completionQuestVariablesFlags, startingCombat, questFunctions, condition, logger)
|
: UseItemExecutorBase<UseOnObject>(questFunctions, condition, logger)
|
||||||
{
|
{
|
||||||
protected override bool UseItem() => gameFunctions.UseItem(dataId, ItemId);
|
protected override bool UseItem() => gameFunctions.UseItem(Task.DataId, ItemId);
|
||||||
|
|
||||||
public override string ToString() => $"UseItem({ItemId} on {dataId})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class Use(
|
internal sealed record UseOnSelf(
|
||||||
ElementId? questId,
|
ElementId? QuestId,
|
||||||
uint itemId,
|
uint ItemId,
|
||||||
IList<QuestWorkValue?> completionQuestVariablesFlags,
|
IList<QuestWorkValue?> CompletionQuestVariablesFlags) : IUseItemBase
|
||||||
|
{
|
||||||
|
public bool StartingCombat => false;
|
||||||
|
public override string ToString() => $"UseItem({ItemId})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class UseOnSelfExecutor(
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger<Use> logger)
|
ILogger<UseOnSelf> logger)
|
||||||
: UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger)
|
: UseItemExecutorBase<UseOnSelf>(questFunctions, condition, logger)
|
||||||
{
|
{
|
||||||
protected override bool UseItem() => gameFunctions.UseItem(ItemId);
|
protected override bool UseItem() => gameFunctions.UseItem(ItemId);
|
||||||
|
|
||||||
public override string ToString() => $"UseItem({ItemId})";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
|||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using LLib.GameUI;
|
using LLib.GameUI;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
using Questionable.Functions;
|
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||||
@ -18,7 +16,7 @@ namespace Questionable.Controller.Steps.Leves;
|
|||||||
|
|
||||||
internal static class InitiateLeve
|
internal static class InitiateLeve
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IGameGui gameGui, ICondition condition) : ITaskFactory
|
internal sealed class Factory(ICondition condition) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -27,75 +25,86 @@ internal static class InitiateLeve
|
|||||||
|
|
||||||
yield return new SkipInitiateIfActive(quest.Id);
|
yield return new SkipInitiateIfActive(quest.Id);
|
||||||
yield return new OpenJournal(quest.Id);
|
yield return new OpenJournal(quest.Id);
|
||||||
yield return new Initiate(quest.Id, gameGui);
|
yield return new Initiate(quest.Id);
|
||||||
yield return new SelectDifficulty(gameGui);
|
yield return new SelectDifficulty();
|
||||||
yield return new WaitConditionTask(() => condition[ConditionFlag.BoundByDuty], "Wait(BoundByDuty)");
|
yield return new WaitCondition.Task(() => condition[ConditionFlag.BoundByDuty], "Wait(BoundByDuty)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed unsafe class SkipInitiateIfActive(ElementId elementId) : ITask
|
internal sealed record SkipInitiateIfActive(ElementId ElementId) : ITask
|
||||||
{
|
{
|
||||||
public bool Start() => true;
|
public override string ToString() => $"CheckIfAlreadyActive({ElementId})";
|
||||||
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
internal sealed unsafe class SkipInitiateIfActiveExecutor : TaskExecutor<SkipInitiateIfActive>
|
||||||
|
{
|
||||||
|
protected override bool Start() => true;
|
||||||
|
|
||||||
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
var director = UIState.Instance()->DirectorTodo.Director;
|
var director = UIState.Instance()->DirectorTodo.Director;
|
||||||
if (director != null &&
|
if (director != null &&
|
||||||
director->EventHandlerInfo != null &&
|
director->EventHandlerInfo != null &&
|
||||||
director->EventHandlerInfo->EventId.ContentId == EventHandlerType.GatheringLeveDirector &&
|
director->EventHandlerInfo->EventId.ContentId == EventHandlerType.GatheringLeveDirector &&
|
||||||
director->ContentId == elementId.Value)
|
director->ContentId == Task.ElementId.Value)
|
||||||
return ETaskResult.SkipRemainingTasksForStep;
|
return ETaskResult.SkipRemainingTasksForStep;
|
||||||
|
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"CheckIfAlreadyActive({elementId})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed unsafe class OpenJournal(ElementId elementId) : ITask
|
internal sealed record OpenJournal(ElementId ElementId) : ITask
|
||||||
|
{
|
||||||
|
public uint QuestType => ElementId is LeveId ? 2u : 1u;
|
||||||
|
public override string ToString() => $"OpenJournal({ElementId})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed unsafe class OpenJournalExecutor : TaskExecutor<OpenJournal>
|
||||||
{
|
{
|
||||||
private readonly uint _questType = elementId is LeveId ? 2u : 1u;
|
|
||||||
private DateTime _openedAt = DateTime.MinValue;
|
private DateTime _openedAt = DateTime.MinValue;
|
||||||
|
|
||||||
public bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
AgentQuestJournal.Instance()->OpenForQuest(elementId.Value, _questType);
|
AgentQuestJournal.Instance()->OpenForQuest(Task.ElementId.Value, Task.QuestType);
|
||||||
_openedAt = DateTime.Now;
|
_openedAt = DateTime.Now;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
AgentQuestJournal* agentQuestJournal = AgentQuestJournal.Instance();
|
AgentQuestJournal* agentQuestJournal = AgentQuestJournal.Instance();
|
||||||
if (agentQuestJournal->IsAgentActive() &&
|
if (agentQuestJournal->IsAgentActive() &&
|
||||||
agentQuestJournal->SelectedQuestId == elementId.Value &&
|
agentQuestJournal->SelectedQuestId == Task.ElementId.Value &&
|
||||||
agentQuestJournal->SelectedQuestType == _questType)
|
agentQuestJournal->SelectedQuestType == Task.QuestType)
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
if (DateTime.Now > _openedAt.AddSeconds(3))
|
if (DateTime.Now > _openedAt.AddSeconds(3))
|
||||||
{
|
{
|
||||||
AgentQuestJournal.Instance()->OpenForQuest(elementId.Value, _questType);
|
AgentQuestJournal.Instance()->OpenForQuest(Task.ElementId.Value, Task.QuestType);
|
||||||
_openedAt = DateTime.Now;
|
_openedAt = DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"OpenJournal({elementId})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed unsafe class Initiate(ElementId elementId, IGameGui gameGui) : ITask
|
internal sealed record Initiate(ElementId ElementId) : ITask
|
||||||
{
|
{
|
||||||
public bool Start() => true;
|
public override string ToString() => $"InitiateLeve({ElementId})";
|
||||||
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
internal sealed unsafe class InitiateExecutor(IGameGui gameGui) : TaskExecutor<Initiate>
|
||||||
|
{
|
||||||
|
protected override bool Start() => true;
|
||||||
|
|
||||||
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (gameGui.TryGetAddonByName("JournalDetail", out AtkUnitBase* addonJournalDetail))
|
if (gameGui.TryGetAddonByName("JournalDetail", out AtkUnitBase* addonJournalDetail))
|
||||||
{
|
{
|
||||||
var pickQuest = stackalloc AtkValue[]
|
var pickQuest = stackalloc AtkValue[]
|
||||||
{
|
{
|
||||||
new() { Type = ValueType.Int, Int = 4 },
|
new() { Type = ValueType.Int, Int = 4 },
|
||||||
new() { Type = ValueType.UInt, Int = elementId.Value }
|
new() { Type = ValueType.UInt, Int = Task.ElementId.Value }
|
||||||
};
|
};
|
||||||
addonJournalDetail->FireCallback(2, pickQuest);
|
addonJournalDetail->FireCallback(2, pickQuest);
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
@ -103,21 +112,22 @@ internal static class InitiateLeve
|
|||||||
|
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"InitiateLeve({elementId})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed unsafe class SelectDifficulty(IGameGui gameGui) : ITask
|
internal sealed class SelectDifficulty : ITask
|
||||||
{
|
{
|
||||||
public bool Start() => true;
|
public override string ToString() => "SelectLeveDifficulty";
|
||||||
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
internal sealed unsafe class SelectDifficultyExecutor(IGameGui gameGui) : TaskExecutor<SelectDifficulty>
|
||||||
|
{
|
||||||
|
protected override bool Start() => true;
|
||||||
|
|
||||||
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (gameGui.TryGetAddonByName("GuildLeveDifficulty", out AtkUnitBase* addon))
|
if (gameGui.TryGetAddonByName("GuildLeveDifficulty", out AtkUnitBase* addon))
|
||||||
{
|
{
|
||||||
// atkvalues: 1 → default difficulty, 2 → min, 3 → max
|
// atkvalues: 1 → default difficulty, 2 → min, 3 → max
|
||||||
|
|
||||||
|
|
||||||
var pickDifficulty = stackalloc AtkValue[]
|
var pickDifficulty = stackalloc AtkValue[]
|
||||||
{
|
{
|
||||||
new() { Type = ValueType.Int, Int = 0 },
|
new() { Type = ValueType.Int, Int = 0 },
|
||||||
@ -129,7 +139,5 @@ internal static class InitiateLeve
|
|||||||
|
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => "SelectLeveDifficulty";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using System.Numerics;
|
|||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
@ -20,17 +19,7 @@ namespace Questionable.Controller.Steps.Shared;
|
|||||||
|
|
||||||
internal static class AethernetShortcut
|
internal static class AethernetShortcut
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory(MovementController movementController)
|
||||||
MovementController movementController,
|
|
||||||
AetheryteFunctions aetheryteFunctions,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
QuestFunctions questFunctions,
|
|
||||||
IClientState clientState,
|
|
||||||
AetheryteData aetheryteData,
|
|
||||||
TerritoryData territoryData,
|
|
||||||
LifestreamIpc lifestreamIpc,
|
|
||||||
ICondition condition,
|
|
||||||
ILoggerFactory loggerFactory)
|
|
||||||
: ITaskFactory
|
: ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
@ -38,24 +27,28 @@ internal static class AethernetShortcut
|
|||||||
if (step.AethernetShortcut == null)
|
if (step.AethernetShortcut == null)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
|
yield return new WaitCondition.Task(() => movementController.IsNavmeshReady,
|
||||||
"Wait(navmesh ready)");
|
"Wait(navmesh ready)");
|
||||||
yield return Use(step.AethernetShortcut.From, step.AethernetShortcut.To,
|
yield return new Task(step.AethernetShortcut.From, step.AethernetShortcut.To,
|
||||||
step.SkipConditions?.AethernetShortcutIf);
|
step.SkipConditions?.AethernetShortcutIf ?? new());
|
||||||
}
|
|
||||||
|
|
||||||
public ITask Use(EAetheryteLocation from, EAetheryteLocation to, SkipAetheryteCondition? skipConditions = null)
|
|
||||||
{
|
|
||||||
return new UseAethernetShortcut(from, to, skipConditions ?? new(),
|
|
||||||
loggerFactory.CreateLogger<UseAethernetShortcut>(), aetheryteFunctions, gameFunctions, questFunctions,
|
|
||||||
clientState, aetheryteData, territoryData, lifestreamIpc, movementController, condition);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed record Task(
|
||||||
|
EAetheryteLocation From,
|
||||||
|
EAetheryteLocation To,
|
||||||
|
SkipAetheryteCondition SkipConditions) : ISkippableTask
|
||||||
|
{
|
||||||
|
public Task(EAetheryteLocation from,
|
||||||
|
EAetheryteLocation to)
|
||||||
|
: this(from, to, new())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"UseAethernet({From} -> {To})";
|
||||||
|
}
|
||||||
|
|
||||||
internal sealed class UseAethernetShortcut(
|
internal sealed class UseAethernetShortcut(
|
||||||
EAetheryteLocation from,
|
|
||||||
EAetheryteLocation to,
|
|
||||||
SkipAetheryteCondition skipConditions,
|
|
||||||
ILogger<UseAethernetShortcut> logger,
|
ILogger<UseAethernetShortcut> logger,
|
||||||
AetheryteFunctions aetheryteFunctions,
|
AetheryteFunctions aetheryteFunctions,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
@ -65,79 +58,80 @@ internal static class AethernetShortcut
|
|||||||
TerritoryData territoryData,
|
TerritoryData territoryData,
|
||||||
LifestreamIpc lifestreamIpc,
|
LifestreamIpc lifestreamIpc,
|
||||||
MovementController movementController,
|
MovementController movementController,
|
||||||
ICondition condition) : ISkippableTask
|
ICondition condition) : TaskExecutor<Task>
|
||||||
{
|
{
|
||||||
private bool _moving;
|
private bool _moving;
|
||||||
private bool _teleported;
|
private bool _teleported;
|
||||||
private bool _triedMounting;
|
private bool _triedMounting;
|
||||||
private DateTime _continueAt = DateTime.MinValue;
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
|
|
||||||
public EAetheryteLocation From => from;
|
public EAetheryteLocation From => Task.From;
|
||||||
public EAetheryteLocation To => to;
|
public EAetheryteLocation To => Task.To;
|
||||||
|
|
||||||
public bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
if (!skipConditions.Never)
|
if (!Task.SkipConditions.Never)
|
||||||
{
|
{
|
||||||
if (skipConditions.InSameTerritory && clientState.TerritoryType == aetheryteData.TerritoryIds[to])
|
if (Task.SkipConditions.InSameTerritory &&
|
||||||
|
clientState.TerritoryType == aetheryteData.TerritoryIds[Task.To])
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aethernet shortcut because the target is in the same territory");
|
logger.LogInformation("Skipping aethernet shortcut because the target is in the same territory");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skipConditions.InTerritory.Contains(clientState.TerritoryType))
|
if (Task.SkipConditions.InTerritory.Contains(clientState.TerritoryType))
|
||||||
{
|
{
|
||||||
logger.LogInformation(
|
logger.LogInformation(
|
||||||
"Skipping aethernet shortcut because the target is in the specified territory");
|
"Skipping aethernet shortcut because the target is in the specified territory");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skipConditions.QuestsCompleted.Count > 0 &&
|
if (Task.SkipConditions.QuestsCompleted.Count > 0 &&
|
||||||
skipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete))
|
Task.SkipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aethernet shortcut, all prequisite quests are complete");
|
logger.LogInformation("Skipping aethernet shortcut, all prequisite quests are complete");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skipConditions.QuestsAccepted.Count > 0 &&
|
if (Task.SkipConditions.QuestsAccepted.Count > 0 &&
|
||||||
skipConditions.QuestsAccepted.All(questFunctions.IsQuestAccepted))
|
Task.SkipConditions.QuestsAccepted.All(questFunctions.IsQuestAccepted))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aethernet shortcut, all prequisite quests are accepted");
|
logger.LogInformation("Skipping aethernet shortcut, all prequisite quests are accepted");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skipConditions.AetheryteLocked != null &&
|
if (Task.SkipConditions.AetheryteLocked != null &&
|
||||||
!aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteLocked.Value))
|
!aetheryteFunctions.IsAetheryteUnlocked(Task.SkipConditions.AetheryteLocked.Value))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is locked");
|
logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is locked");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skipConditions.AetheryteUnlocked != null &&
|
if (Task.SkipConditions.AetheryteUnlocked != null &&
|
||||||
aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteUnlocked.Value))
|
aetheryteFunctions.IsAetheryteUnlocked(Task.SkipConditions.AetheryteUnlocked.Value))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is unlocked");
|
logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is unlocked");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aetheryteFunctions.IsAetheryteUnlocked(from) &&
|
if (aetheryteFunctions.IsAetheryteUnlocked(Task.From) &&
|
||||||
aetheryteFunctions.IsAetheryteUnlocked(to))
|
aetheryteFunctions.IsAetheryteUnlocked(Task.To))
|
||||||
{
|
{
|
||||||
ushort territoryType = clientState.TerritoryType;
|
ushort territoryType = clientState.TerritoryType;
|
||||||
Vector3 playerPosition = clientState.LocalPlayer!.Position;
|
Vector3 playerPosition = clientState.LocalPlayer!.Position;
|
||||||
|
|
||||||
// closer to the source
|
// closer to the source
|
||||||
if (aetheryteData.CalculateDistance(playerPosition, territoryType, from) <
|
if (aetheryteData.CalculateDistance(playerPosition, territoryType, Task.From) <
|
||||||
aetheryteData.CalculateDistance(playerPosition, territoryType, to))
|
aetheryteData.CalculateDistance(playerPosition, territoryType, Task.To))
|
||||||
{
|
{
|
||||||
if (aetheryteData.CalculateDistance(playerPosition, territoryType, from) <
|
if (aetheryteData.CalculateDistance(playerPosition, territoryType, Task.From) <
|
||||||
(from.IsFirmamentAetheryte() ? 11f : 4f))
|
(Task.From.IsFirmamentAetheryte() ? 11f : 4f))
|
||||||
{
|
{
|
||||||
DoTeleport();
|
DoTeleport();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (from == EAetheryteLocation.SolutionNine)
|
else if (Task.From == EAetheryteLocation.SolutionNine)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Moving to S9 aetheryte");
|
logger.LogInformation("Moving to S9 aetheryte");
|
||||||
List<Vector3> nearbyPoints =
|
List<Vector3> nearbyPoints =
|
||||||
@ -150,14 +144,14 @@ internal static class AethernetShortcut
|
|||||||
|
|
||||||
Vector3 closestPoint = nearbyPoints.MinBy(x => (playerPosition - x).Length());
|
Vector3 closestPoint = nearbyPoints.MinBy(x => (playerPosition - x).Length());
|
||||||
_moving = true;
|
_moving = true;
|
||||||
movementController.NavigateTo(EMovementType.Quest, (uint)from, closestPoint, false, true,
|
movementController.NavigateTo(EMovementType.Quest, (uint)Task.From, closestPoint, false, true,
|
||||||
0.25f);
|
0.25f);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (territoryData.CanUseMount(territoryType) &&
|
if (territoryData.CanUseMount(territoryType) &&
|
||||||
aetheryteData.CalculateDistance(playerPosition, territoryType, from) > 30 &&
|
aetheryteData.CalculateDistance(playerPosition, territoryType, Task.From) > 30 &&
|
||||||
!gameFunctions.HasStatusPreventingMount())
|
!gameFunctions.HasStatusPreventingMount())
|
||||||
{
|
{
|
||||||
_triedMounting = gameFunctions.Mount();
|
_triedMounting = gameFunctions.Mount();
|
||||||
@ -176,7 +170,7 @@ internal static class AethernetShortcut
|
|||||||
else
|
else
|
||||||
logger.LogWarning(
|
logger.LogWarning(
|
||||||
"Aethernet shortcut not unlocked (from: {FromAetheryte}, to: {ToAetheryte}), walking manually",
|
"Aethernet shortcut not unlocked (from: {FromAetheryte}, to: {ToAetheryte}), walking manually",
|
||||||
from, to);
|
Task.From, Task.To);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -185,34 +179,34 @@ internal static class AethernetShortcut
|
|||||||
{
|
{
|
||||||
logger.LogInformation("Moving to aethernet shortcut");
|
logger.LogInformation("Moving to aethernet shortcut");
|
||||||
_moving = true;
|
_moving = true;
|
||||||
float distance = from switch
|
float distance = Task.From switch
|
||||||
{
|
{
|
||||||
_ when from.IsFirmamentAetheryte() => 4.4f,
|
_ when Task.From.IsFirmamentAetheryte() => 4.4f,
|
||||||
EAetheryteLocation.UldahChamberOfRule => 5f,
|
EAetheryteLocation.UldahChamberOfRule => 5f,
|
||||||
_ when AetheryteConverter.IsLargeAetheryte(from) => 10.9f,
|
_ when AetheryteConverter.IsLargeAetheryte(Task.From) => 10.9f,
|
||||||
_ => 6.9f,
|
_ => 6.9f,
|
||||||
};
|
};
|
||||||
movementController.NavigateTo(EMovementType.Quest, (uint)from, aetheryteData.Locations[from],
|
movementController.NavigateTo(EMovementType.Quest, (uint)Task.From, aetheryteData.Locations[Task.From],
|
||||||
false, true,
|
false, true,
|
||||||
distance);
|
distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DoTeleport()
|
private void DoTeleport()
|
||||||
{
|
{
|
||||||
if (from.IsFirmamentAetheryte())
|
if (Task.From.IsFirmamentAetheryte())
|
||||||
{
|
{
|
||||||
logger.LogInformation("Using manual teleport interaction");
|
logger.LogInformation("Using manual teleport interaction");
|
||||||
_teleported = gameFunctions.InteractWith((uint)from, ObjectKind.EventObj);
|
_teleported = gameFunctions.InteractWith((uint)Task.From, ObjectKind.EventObj);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogInformation("Using lifestream to teleport to {Destination}", to);
|
logger.LogInformation("Using lifestream to teleport to {Destination}", Task.To);
|
||||||
lifestreamIpc.Teleport(to);
|
lifestreamIpc.Teleport(Task.To);
|
||||||
_teleported = true;
|
_teleported = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (DateTime.Now < _continueAt)
|
if (DateTime.Now < _continueAt)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
@ -247,29 +241,27 @@ internal static class AethernetShortcut
|
|||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aetheryteData.IsAirshipLanding(to))
|
if (aetheryteData.IsAirshipLanding(Task.To))
|
||||||
{
|
{
|
||||||
if (aetheryteData.CalculateAirshipLandingDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
|
if (aetheryteData.CalculateAirshipLandingDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
|
||||||
clientState.TerritoryType, to) > 5)
|
clientState.TerritoryType, Task.To) > 5)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
else if (aetheryteData.IsCityAetheryte(to))
|
else if (aetheryteData.IsCityAetheryte(Task.To))
|
||||||
{
|
{
|
||||||
if (aetheryteData.CalculateDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
|
if (aetheryteData.CalculateDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
|
||||||
clientState.TerritoryType, to) > 20)
|
clientState.TerritoryType, Task.To) > 20)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// some overworld location (e.g. 'Tesselation (Lakeland)' would end up here
|
// some overworld location (e.g. 'Tesselation (Lakeland)' would end up here
|
||||||
if (clientState.TerritoryType != aetheryteData.TerritoryIds[to])
|
if (clientState.TerritoryType != aetheryteData.TerritoryIds[Task.To])
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"UseAethernet({from} -> {to})";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Common;
|
|
||||||
using Questionable.Controller.Utils;
|
using Questionable.Controller.Utils;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
@ -18,55 +15,42 @@ namespace Questionable.Controller.Steps.Shared;
|
|||||||
|
|
||||||
internal static class AetheryteShortcut
|
internal static class AetheryteShortcut
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory(AetheryteData aetheryteData) : ITaskFactory
|
||||||
AetheryteData aetheryteData,
|
|
||||||
AetheryteFunctions aetheryteFunctions,
|
|
||||||
QuestFunctions questFunctions,
|
|
||||||
IClientState clientState,
|
|
||||||
IChatGui chatGui,
|
|
||||||
ILoggerFactory loggerFactory) : ITaskFactory
|
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
if (step.AetheryteShortcut == null)
|
if (step.AetheryteShortcut == null)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
yield return Use(step, quest.Id, step.AetheryteShortcut.Value,
|
yield return new Task(step, quest.Id, step.AetheryteShortcut.Value,
|
||||||
aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]);
|
aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]);
|
||||||
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(0.5));
|
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITask Use(QuestStep? step, ElementId? elementId, EAetheryteLocation targetAetheryte,
|
|
||||||
ushort expectedTerritoryId)
|
|
||||||
{
|
|
||||||
return new UseAetheryteShortcut(step, elementId, targetAetheryte, expectedTerritoryId,
|
|
||||||
loggerFactory.CreateLogger<UseAetheryteShortcut>(), aetheryteFunctions, questFunctions, clientState,
|
|
||||||
chatGui, aetheryteData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <param name="expectedTerritoryId">If using an aethernet shortcut after, the aetheryte's territory-id and the step's territory-id can differ, we always use the aetheryte's territory-id.</param>
|
/// <param name="ExpectedTerritoryId">If using an aethernet shortcut after, the aetheryte's territory-id and the step's territory-id can differ, we always use the aetheryte's territory-id.</param>
|
||||||
private sealed class UseAetheryteShortcut(
|
internal sealed record Task(
|
||||||
QuestStep? step,
|
QuestStep? Step,
|
||||||
ElementId? elementId,
|
ElementId? ElementId,
|
||||||
EAetheryteLocation targetAetheryte,
|
EAetheryteLocation TargetAetheryte,
|
||||||
ushort expectedTerritoryId,
|
ushort ExpectedTerritoryId) : ISkippableTask
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class UseAetheryteShortcut(
|
||||||
ILogger<UseAetheryteShortcut> logger,
|
ILogger<UseAetheryteShortcut> logger,
|
||||||
AetheryteFunctions aetheryteFunctions,
|
AetheryteFunctions aetheryteFunctions,
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
IChatGui chatGui,
|
IChatGui chatGui,
|
||||||
AetheryteData aetheryteData) : ISkippableTask
|
AetheryteData aetheryteData) : TaskExecutor<Task>
|
||||||
{
|
{
|
||||||
private bool _teleported;
|
private bool _teleported;
|
||||||
private DateTime _continueAt;
|
private DateTime _continueAt;
|
||||||
private InteractionProgressContext? _progressContext;
|
|
||||||
|
|
||||||
public InteractionProgressContext? ProgressContext() => _progressContext;
|
protected override bool Start() => !ShouldSkipTeleport();
|
||||||
|
|
||||||
public bool Start() => !ShouldSkipTeleport();
|
public override ETaskResult Update()
|
||||||
|
|
||||||
public ETaskResult Update()
|
|
||||||
{
|
{
|
||||||
if (DateTime.Now < _continueAt)
|
if (DateTime.Now < _continueAt)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
@ -77,7 +61,7 @@ internal static class AetheryteShortcut
|
|||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientState.TerritoryType == expectedTerritoryId)
|
if (clientState.TerritoryType == Task.ExpectedTerritoryId)
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
@ -86,9 +70,9 @@ internal static class AetheryteShortcut
|
|||||||
private bool ShouldSkipTeleport()
|
private bool ShouldSkipTeleport()
|
||||||
{
|
{
|
||||||
ushort territoryType = clientState.TerritoryType;
|
ushort territoryType = clientState.TerritoryType;
|
||||||
if (step != null)
|
if (Task.Step != null)
|
||||||
{
|
{
|
||||||
var skipConditions = step.SkipConditions?.AetheryteShortcutIf ?? new();
|
var skipConditions = Task.Step.SkipConditions?.AetheryteShortcutIf ?? new();
|
||||||
if (skipConditions is { Never: false })
|
if (skipConditions is { Never: false })
|
||||||
{
|
{
|
||||||
if (skipConditions.InTerritory.Contains(territoryType))
|
if (skipConditions.InTerritory.Contains(territoryType))
|
||||||
@ -125,12 +109,12 @@ internal static class AetheryteShortcut
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elementId != null)
|
if (Task.ElementId != null)
|
||||||
{
|
{
|
||||||
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(elementId);
|
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(Task.ElementId);
|
||||||
if (skipConditions.RequiredQuestVariablesNotMet &&
|
if (skipConditions.RequiredQuestVariablesNotMet &&
|
||||||
questWork != null &&
|
questWork != null &&
|
||||||
!QuestWorkUtils.MatchesRequiredQuestWorkConfig(step.RequiredQuestVariables, questWork,
|
!QuestWorkUtils.MatchesRequiredQuestWorkConfig(Task.Step.RequiredQuestVariables, questWork,
|
||||||
logger))
|
logger))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aetheryte teleport, as required variables do not match");
|
logger.LogInformation("Skipping aetheryte teleport, as required variables do not match");
|
||||||
@ -151,7 +135,7 @@ internal static class AetheryteShortcut
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expectedTerritoryId == territoryType)
|
if (Task.ExpectedTerritoryId == territoryType)
|
||||||
{
|
{
|
||||||
if (!skipConditions.Never)
|
if (!skipConditions.Never)
|
||||||
{
|
{
|
||||||
@ -162,17 +146,19 @@ internal static class AetheryteShortcut
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vector3 pos = clientState.LocalPlayer!.Position;
|
Vector3 pos = clientState.LocalPlayer!.Position;
|
||||||
if (step.Position != null &&
|
if (Task.Step.Position != null &&
|
||||||
(pos - step.Position.Value).Length() < step.CalculateActualStopDistance())
|
(pos - Task.Step.Position.Value).Length() < Task.Step.CalculateActualStopDistance())
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aetheryte teleport, we're near the target");
|
logger.LogInformation("Skipping aetheryte teleport, we're near the target");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aetheryteData.CalculateDistance(pos, territoryType, targetAetheryte) < 20 ||
|
if (aetheryteData.CalculateDistance(pos, territoryType, Task.TargetAetheryte) < 20 ||
|
||||||
(step.AethernetShortcut != null &&
|
(Task.Step.AethernetShortcut != null &&
|
||||||
(aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.From) < 20 ||
|
(aetheryteData.CalculateDistance(pos, territoryType, Task.Step.AethernetShortcut.From) <
|
||||||
aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.To) < 20)))
|
20 ||
|
||||||
|
aetheryteData.CalculateDistance(pos, territoryType, Task.Step.AethernetShortcut.To) <
|
||||||
|
20)))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aetheryte teleport");
|
logger.LogInformation("Skipping aetheryte teleport");
|
||||||
return true;
|
return true;
|
||||||
@ -186,7 +172,7 @@ internal static class AetheryteShortcut
|
|||||||
|
|
||||||
private bool DoTeleport()
|
private bool DoTeleport()
|
||||||
{
|
{
|
||||||
if (!aetheryteFunctions.CanTeleport(targetAetheryte))
|
if (!aetheryteFunctions.CanTeleport(Task.TargetAetheryte))
|
||||||
{
|
{
|
||||||
if (!aetheryteFunctions.IsTeleportUnlocked())
|
if (!aetheryteFunctions.IsTeleportUnlocked())
|
||||||
throw new TaskException("Teleport is not unlocked, attune to any aetheryte first.");
|
throw new TaskException("Teleport is not unlocked, attune to any aetheryte first.");
|
||||||
@ -198,29 +184,27 @@ internal static class AetheryteShortcut
|
|||||||
|
|
||||||
_continueAt = DateTime.Now.AddSeconds(8);
|
_continueAt = DateTime.Now.AddSeconds(8);
|
||||||
|
|
||||||
if (!aetheryteFunctions.IsAetheryteUnlocked(targetAetheryte))
|
if (!aetheryteFunctions.IsAetheryteUnlocked(Task.TargetAetheryte))
|
||||||
{
|
{
|
||||||
chatGui.PrintError($"[Questionable] Aetheryte {targetAetheryte} is not unlocked.");
|
chatGui.PrintError($"[Questionable] Aetheryte {Task.TargetAetheryte} is not unlocked.");
|
||||||
throw new TaskException("Aetheryte is not unlocked");
|
throw new TaskException("Aetheryte is not unlocked");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProgressContext =
|
||||||
|
InteractionProgressContext.FromActionUseOrDefault(() =>
|
||||||
|
aetheryteFunctions.TeleportAetheryte(Task.TargetAetheryte));
|
||||||
|
if (ProgressContext != null)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Travelling via aetheryte...");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_progressContext =
|
chatGui.Print("[Questionable] Unable to teleport to aetheryte.");
|
||||||
InteractionProgressContext.FromActionUseOrDefault(() => aetheryteFunctions.TeleportAetheryte(targetAetheryte));
|
throw new TaskException("Unable to teleport to aetheryte");
|
||||||
logger.LogInformation("Ctx = {C}", _progressContext);
|
|
||||||
if (_progressContext != null)
|
|
||||||
{
|
|
||||||
logger.LogInformation("Travelling via aetheryte...");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
chatGui.Print("[Questionable] Unable to teleport to aetheryte.");
|
|
||||||
throw new TaskException("Unable to teleport to aetheryte");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"UseAetheryte({targetAetheryte})";
|
public override string ToString() => $"UseAetheryte({Task.TargetAetheryte})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,7 @@ namespace Questionable.Controller.Steps.Shared;
|
|||||||
|
|
||||||
internal static class Craft
|
internal static class Craft
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory : ITaskFactory
|
||||||
IDataManager dataManager,
|
|
||||||
IClientState clientState,
|
|
||||||
ArtisanIpc artisanIpc,
|
|
||||||
Mount.Factory mountFactory,
|
|
||||||
ILoggerFactory loggerFactory) : ITaskFactory
|
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -33,34 +28,36 @@ internal static class Craft
|
|||||||
ArgumentNullException.ThrowIfNull(step.ItemCount);
|
ArgumentNullException.ThrowIfNull(step.ItemCount);
|
||||||
return
|
return
|
||||||
[
|
[
|
||||||
mountFactory.Unmount(),
|
new Mount.UnmountTask(),
|
||||||
Craft(step.ItemId.Value, step.ItemCount.Value)
|
new CraftTask(step.ItemId.Value, step.ItemCount.Value)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITask Craft(uint itemId, int itemCount) =>
|
|
||||||
new DoCraft(itemId, itemCount, dataManager, clientState, artisanIpc, loggerFactory.CreateLogger<DoCraft>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class DoCraft(
|
internal sealed record CraftTask(
|
||||||
uint itemId,
|
uint ItemId,
|
||||||
int itemCount,
|
int ItemCount) : ITask
|
||||||
|
{
|
||||||
|
public override string ToString() => $"Craft {ItemCount}x {ItemId} (with Artisan)";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DoCraft(
|
||||||
IDataManager dataManager,
|
IDataManager dataManager,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
ArtisanIpc artisanIpc,
|
ArtisanIpc artisanIpc,
|
||||||
ILogger<DoCraft> logger) : ITask
|
ILogger<DoCraft> logger) : TaskExecutor<CraftTask>
|
||||||
{
|
{
|
||||||
public bool Start()
|
protected override bool Start()
|
||||||
{
|
{
|
||||||
if (HasRequestedItems())
|
if (HasRequestedItems())
|
||||||
{
|
{
|
||||||
logger.LogInformation("Already own {ItemCount}x {ItemId}", itemCount, itemId);
|
logger.LogInformation("Already own {ItemCount}x {ItemId}", Task.ItemCount, Task.ItemId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RecipeLookup? recipeLookup = dataManager.GetExcelSheet<RecipeLookup>()!.GetRow(itemId);
|
RecipeLookup? recipeLookup = dataManager.GetExcelSheet<RecipeLookup>()!.GetRow(Task.ItemId);
|
||||||
if (recipeLookup == null)
|
if (recipeLookup == null)
|
||||||
throw new TaskException($"Item {itemId} is not craftable");
|
throw new TaskException($"Item {Task.ItemId} is not craftable");
|
||||||
|
|
||||||
uint recipeId = (EClassJob)clientState.LocalPlayer!.ClassJob.Id switch
|
uint recipeId = (EClassJob)clientState.LocalPlayer!.ClassJob.Id switch
|
||||||
{
|
{
|
||||||
@ -92,19 +89,19 @@ internal static class Craft
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (recipeId == 0)
|
if (recipeId == 0)
|
||||||
throw new TaskException($"Unable to determine recipe for item {itemId}");
|
throw new TaskException($"Unable to determine recipe for item {Task.ItemId}");
|
||||||
|
|
||||||
int remainingItemCount = itemCount - GetOwnedItemCount();
|
int remainingItemCount = Task.ItemCount - GetOwnedItemCount();
|
||||||
logger.LogInformation(
|
logger.LogInformation(
|
||||||
"Starting craft for item {ItemId} with recipe {RecipeId} for {RemainingItemCount} items",
|
"Starting craft for item {ItemId} with recipe {RecipeId} for {RemainingItemCount} items",
|
||||||
itemId, recipeId, remainingItemCount);
|
Task.ItemId, recipeId, remainingItemCount);
|
||||||
if (!artisanIpc.CraftItem((ushort)recipeId, remainingItemCount))
|
if (!artisanIpc.CraftItem((ushort)recipeId, remainingItemCount))
|
||||||
throw new TaskException($"Failed to start Artisan craft for recipe {recipeId}");
|
throw new TaskException($"Failed to start Artisan craft for recipe {recipeId}");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe ETaskResult Update()
|
public override unsafe ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (HasRequestedItems() && !artisanIpc.IsCrafting())
|
if (HasRequestedItems() && !artisanIpc.IsCrafting())
|
||||||
{
|
{
|
||||||
@ -128,15 +125,13 @@ internal static class Craft
|
|||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HasRequestedItems() => GetOwnedItemCount() >= itemCount;
|
private bool HasRequestedItems() => GetOwnedItemCount() >= Task.ItemCount;
|
||||||
|
|
||||||
private unsafe int GetOwnedItemCount()
|
private unsafe int GetOwnedItemCount()
|
||||||
{
|
{
|
||||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||||
return inventoryManager->GetInventoryItemCount(itemId, isHq: false, checkEquipped: false)
|
return inventoryManager->GetInventoryItemCount(Task.ItemId, isHq: false, checkEquipped: false)
|
||||||
+ inventoryManager->GetInventoryItemCount(itemId, isHq: true, checkEquipped: false);
|
+ inventoryManager->GetInventoryItemCount(Task.ItemId, isHq: true, checkEquipped: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Craft {itemCount}x {itemId} (with Artisan)";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ internal static class Gather
|
|||||||
internal sealed class Factory(
|
internal sealed class Factory(
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
MovementController movementController,
|
MovementController movementController,
|
||||||
GatheringController gatheringController,
|
|
||||||
GatheringPointRegistry gatheringPointRegistry,
|
GatheringPointRegistry gatheringPointRegistry,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
GatheringData gatheringData,
|
GatheringData gatheringData,
|
||||||
@ -53,7 +52,7 @@ internal static class Gather
|
|||||||
|
|
||||||
if (classJob != currentClassJob)
|
if (classJob != currentClassJob)
|
||||||
{
|
{
|
||||||
yield return new SwitchClassJob(classJob, clientState);
|
yield return new SwitchClassJob.Task(classJob);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasRequiredItems(itemToGather))
|
if (HasRequiredItems(itemToGather))
|
||||||
@ -71,20 +70,20 @@ internal static class Gather
|
|||||||
foreach (var task in serviceProvider.GetRequiredService<TaskCreator>()
|
foreach (var task in serviceProvider.GetRequiredService<TaskCreator>()
|
||||||
.CreateTasks(quest, gatheringSequence, gatheringStep))
|
.CreateTasks(quest, gatheringSequence, gatheringStep))
|
||||||
if (task is WaitAtEnd.NextStep)
|
if (task is WaitAtEnd.NextStep)
|
||||||
yield return CreateSkipMarkerTask();
|
yield return new SkipMarker();
|
||||||
else
|
else
|
||||||
yield return task;
|
yield return task;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ushort territoryId = gatheringRoot.Steps.Last().TerritoryId;
|
ushort territoryId = gatheringRoot.Steps.Last().TerritoryId;
|
||||||
yield return new WaitConditionTask(() => clientState.TerritoryType == territoryId,
|
yield return new WaitCondition.Task(() => clientState.TerritoryType == territoryId,
|
||||||
$"Wait(territory: {territoryData.GetNameAndId(territoryId)})");
|
$"Wait(territory: {territoryData.GetNameAndId(territoryId)})");
|
||||||
|
|
||||||
yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
|
yield return new WaitCondition.Task(() => movementController.IsNavmeshReady,
|
||||||
"Wait(navmesh ready)");
|
"Wait(navmesh ready)");
|
||||||
|
|
||||||
yield return CreateStartGatheringTask(gatheringPointId, itemToGather);
|
yield return new GatheringTask(gatheringPointId, itemToGather);
|
||||||
yield return new WaitAtEnd.WaitDelay();
|
yield return new WaitAtEnd.WaitDelay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,38 +108,12 @@ internal static class Gather
|
|||||||
minCollectability: (short)itemToGather.Collectability) >=
|
minCollectability: (short)itemToGather.Collectability) >=
|
||||||
itemToGather.ItemCount;
|
itemToGather.ItemCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private StartGathering CreateStartGatheringTask(GatheringPointId gatheringPointId, GatheredItem gatheredItem)
|
|
||||||
{
|
|
||||||
return new StartGathering(gatheringPointId, gatheredItem, gatheringController);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SkipMarker CreateSkipMarkerTask()
|
|
||||||
{
|
|
||||||
return new SkipMarker();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class StartGathering(
|
internal sealed record GatheringTask(
|
||||||
GatheringPointId gatheringPointId,
|
GatheringPointId gatheringPointId,
|
||||||
GatheredItem gatheredItem,
|
GatheredItem gatheredItem) : ITask
|
||||||
GatheringController gatheringController) : ITask
|
|
||||||
{
|
{
|
||||||
public bool Start()
|
|
||||||
{
|
|
||||||
return gatheringController.Start(new GatheringController.GatheringRequest(gatheringPointId,
|
|
||||||
gatheredItem.ItemId, gatheredItem.AlternativeItemId, gatheredItem.ItemCount,
|
|
||||||
gatheredItem.Collectability));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ETaskResult Update()
|
|
||||||
{
|
|
||||||
if (gatheringController.Update() == GatheringController.EStatus.Complete)
|
|
||||||
return ETaskResult.TaskComplete;
|
|
||||||
|
|
||||||
return ETaskResult.StillRunning;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (gatheredItem.Collectability == 0)
|
if (gatheredItem.Collectability == 0)
|
||||||
@ -151,13 +124,35 @@ internal static class Gather
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed class StartGathering(GatheringController gatheringController) : TaskExecutor<GatheringTask>
|
||||||
|
{
|
||||||
|
protected override bool Start()
|
||||||
|
{
|
||||||
|
return gatheringController.Start(new GatheringController.GatheringRequest(Task.gatheringPointId,
|
||||||
|
Task.gatheredItem.ItemId, Task.gatheredItem.AlternativeItemId, Task.gatheredItem.ItemCount,
|
||||||
|
Task.gatheredItem.Collectability));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ETaskResult Update()
|
||||||
|
{
|
||||||
|
if (gatheringController.Update() == GatheringController.EStatus.Complete)
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A task that does nothing, but if we're skipping a step, this will be the task next in queue to be executed (instead of progressing to the next step) if gathering.
|
/// A task that does nothing, but if we're skipping a step, this will be the task next in queue to be executed (instead of progressing to the next step) if gathering.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class SkipMarker : ITask
|
internal sealed class SkipMarker : ITask
|
||||||
{
|
{
|
||||||
public bool Start() => true;
|
|
||||||
public ETaskResult Update() => ETaskResult.TaskComplete;
|
|
||||||
public override string ToString() => "Gather/SkipMarker";
|
public override string ToString() => "Gather/SkipMarker";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed class DoSkip : TaskExecutor<SkipMarker>
|
||||||
|
{
|
||||||
|
protected override bool Start() => true;
|
||||||
|
public override ETaskResult Update() => ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ using FFXIVClientStructs.FFXIV.Client.Game;
|
|||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using LLib;
|
using LLib;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
@ -28,14 +27,9 @@ internal static class MoveTo
|
|||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory(
|
||||||
MovementController movementController,
|
MovementController movementController,
|
||||||
GameFunctions gameFunctions,
|
|
||||||
ICondition condition,
|
|
||||||
IDataManager dataManager,
|
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
AetheryteData aetheryteData,
|
AetheryteData aetheryteData,
|
||||||
TerritoryData territoryData,
|
TerritoryData territoryData,
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
Mount.Factory mountFactory,
|
|
||||||
ILogger<Factory> logger) : ITaskFactory
|
ILogger<Factory> logger) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
@ -46,7 +40,7 @@ internal static class MoveTo
|
|||||||
}
|
}
|
||||||
else if (step is { DataId: not null, StopDistance: not null })
|
else if (step is { DataId: not null, StopDistance: not null })
|
||||||
{
|
{
|
||||||
return [ExpectToBeNearDataId(step.DataId.Value, step.StopDistance.Value)];
|
return [new WaitForNearDataId(step.DataId.Value, step.StopDistance.Value)];
|
||||||
}
|
}
|
||||||
else if (step is { InteractionType: EInteractionType.AttuneAetheryte, Aetheryte: not null })
|
else if (step is { InteractionType: EInteractionType.AttuneAetheryte, Aetheryte: not null })
|
||||||
{
|
{
|
||||||
@ -60,27 +54,6 @@ internal static class MoveTo
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITask Move(QuestStep step, Vector3 destination)
|
|
||||||
{
|
|
||||||
return Move(new MoveParams(step, destination));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITask Move(MoveParams moveParams)
|
|
||||||
{
|
|
||||||
return new MoveInternal(moveParams, movementController, mountFactory, gameFunctions,
|
|
||||||
loggerFactory.CreateLogger<MoveInternal>(), clientState, dataManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITask Land()
|
|
||||||
{
|
|
||||||
return new LandTask(clientState, condition, loggerFactory.CreateLogger<LandTask>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITask ExpectToBeNearDataId(uint dataId, float stopDistance)
|
|
||||||
{
|
|
||||||
return new WaitForNearDataId(dataId, stopDistance, gameFunctions, clientState);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<ITask> CreateMountTasks(ElementId questId, QuestStep step, Vector3 destination)
|
public IEnumerable<ITask> CreateMountTasks(ElementId questId, QuestStep step, Vector3 destination)
|
||||||
{
|
{
|
||||||
if (step.InteractionType == EInteractionType.Jump && step.JumpDestination != null &&
|
if (step.InteractionType == EInteractionType.Jump && step.JumpDestination != null &&
|
||||||
@ -91,146 +64,149 @@ internal static class MoveTo
|
|||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return new WaitConditionTask(() => clientState.TerritoryType == step.TerritoryId,
|
yield return new WaitCondition.Task(() => clientState.TerritoryType == step.TerritoryId,
|
||||||
$"Wait(territory: {territoryData.GetNameAndId(step.TerritoryId)})");
|
$"Wait(territory: {territoryData.GetNameAndId(step.TerritoryId)})");
|
||||||
|
|
||||||
if (!step.DisableNavmesh)
|
if (!step.DisableNavmesh)
|
||||||
{
|
{
|
||||||
yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
|
yield return new WaitCondition.Task(() => movementController.IsNavmeshReady,
|
||||||
"Wait(navmesh ready)");
|
"Wait(navmesh ready)");
|
||||||
|
|
||||||
yield return Move(step, destination);
|
yield return new MoveTask(step, destination);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
yield return Move(step, destination);
|
yield return new MoveTask(step, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (step is { Fly: true, Land: true })
|
if (step is { Fly: true, Land: true })
|
||||||
yield return Land();
|
yield return new LandTask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class MoveInternal : ITask, IToastAware
|
internal sealed class MoveExecutor : TaskExecutor<MoveTask>, IToastAware
|
||||||
{
|
{
|
||||||
private readonly string _cannotExecuteAtThisTime;
|
private readonly string _cannotExecuteAtThisTime;
|
||||||
private readonly MovementController _movementController;
|
private readonly MovementController _movementController;
|
||||||
private readonly Mount.Factory _mountFactory;
|
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly GameFunctions _gameFunctions;
|
||||||
private readonly ILogger<MoveInternal> _logger;
|
private readonly ILogger<MoveExecutor> _logger;
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
private readonly Mount.MountExecutor _mountExecutor;
|
||||||
|
private readonly Mount.UnmountExecutor _unmountExecutor;
|
||||||
|
|
||||||
private readonly Action _startAction;
|
private Action _startAction = null!;
|
||||||
private readonly Vector3 _destination;
|
private Vector3 _destination;
|
||||||
private readonly MoveParams _moveParams;
|
|
||||||
private bool _canRestart;
|
private bool _canRestart;
|
||||||
private ITask? _mountTask;
|
private ITaskExecutor? _nestedExecutor;
|
||||||
|
|
||||||
public MoveInternal(MoveParams moveParams,
|
public MoveExecutor(
|
||||||
MovementController movementController,
|
MovementController movementController,
|
||||||
Mount.Factory mountFactory,
|
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
ILogger<MoveInternal> logger,
|
ILogger<MoveExecutor> logger,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
IDataManager dataManager)
|
IDataManager dataManager,
|
||||||
|
Mount.MountExecutor mountExecutor,
|
||||||
|
Mount.UnmountExecutor unmountExecutor)
|
||||||
{
|
{
|
||||||
_movementController = movementController;
|
_movementController = movementController;
|
||||||
_mountFactory = mountFactory;
|
|
||||||
_gameFunctions = gameFunctions;
|
_gameFunctions = gameFunctions;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
|
_mountExecutor = mountExecutor;
|
||||||
|
_unmountExecutor = unmountExecutor;
|
||||||
_cannotExecuteAtThisTime = dataManager.GetString<LogMessage>(579, x => x.Text)!;
|
_cannotExecuteAtThisTime = dataManager.GetString<LogMessage>(579, x => x.Text)!;
|
||||||
|
|
||||||
_destination = moveParams.Destination;
|
}
|
||||||
|
|
||||||
if (!gameFunctions.IsFlyingUnlocked(moveParams.TerritoryId))
|
private void Initialize()
|
||||||
|
{
|
||||||
|
_destination = Task.Destination;
|
||||||
|
|
||||||
|
if (!_gameFunctions.IsFlyingUnlocked(Task.TerritoryId))
|
||||||
{
|
{
|
||||||
moveParams = moveParams with { Fly = false, Land = false };
|
Task = Task with { Fly = false, Land = false };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!moveParams.DisableNavMesh)
|
if (!Task.DisableNavmesh)
|
||||||
{
|
{
|
||||||
_startAction = () =>
|
_startAction = () =>
|
||||||
_movementController.NavigateTo(EMovementType.Quest, moveParams.DataId, _destination,
|
_movementController.NavigateTo(EMovementType.Quest, Task.DataId, _destination,
|
||||||
fly: moveParams.Fly,
|
fly: Task.Fly,
|
||||||
sprint: moveParams.Sprint,
|
sprint: Task.Sprint,
|
||||||
stopDistance: moveParams.StopDistance,
|
stopDistance: Task.StopDistance,
|
||||||
ignoreDistanceToObject: moveParams.IgnoreDistanceToObject,
|
ignoreDistanceToObject: Task.IgnoreDistanceToObject,
|
||||||
land: moveParams.Land);
|
land: Task.Land);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_startAction = () =>
|
_startAction = () =>
|
||||||
_movementController.NavigateTo(EMovementType.Quest, moveParams.DataId, [_destination],
|
_movementController.NavigateTo(EMovementType.Quest, Task.DataId, [_destination],
|
||||||
fly: moveParams.Fly,
|
fly: Task.Fly,
|
||||||
sprint: moveParams.Sprint,
|
sprint: Task.Sprint,
|
||||||
stopDistance: moveParams.StopDistance,
|
stopDistance: Task.StopDistance,
|
||||||
ignoreDistanceToObject: moveParams.IgnoreDistanceToObject,
|
ignoreDistanceToObject: Task.IgnoreDistanceToObject,
|
||||||
land: moveParams.Land);
|
land: Task.Land);
|
||||||
}
|
}
|
||||||
|
|
||||||
_moveParams = moveParams;
|
_canRestart = Task.RestartNavigation;
|
||||||
_canRestart = moveParams.RestartNavigation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public InteractionProgressContext? ProgressContext() => _mountTask?.ProgressContext();
|
protected override bool Start()
|
||||||
|
|
||||||
public bool ShouldRedoOnInterrupt() => true;
|
|
||||||
|
|
||||||
public bool Start()
|
|
||||||
{
|
{
|
||||||
float stopDistance = _moveParams.StopDistance ?? QuestStep.DefaultStopDistance;
|
Initialize();
|
||||||
|
|
||||||
|
float stopDistance = Task.StopDistance ?? QuestStep.DefaultStopDistance;
|
||||||
Vector3? position = _clientState.LocalPlayer?.Position;
|
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 (_moveParams.Mount == true)
|
if (Task.Mount == true)
|
||||||
{
|
{
|
||||||
var mountTask = _mountFactory.Mount(_moveParams.TerritoryId, Mount.EMountIf.Always);
|
var mountTask = new Mount.MountTask(Task.TerritoryId, Mount.EMountIf.Always);
|
||||||
if (mountTask.Start())
|
if (_mountExecutor.Start(mountTask))
|
||||||
{
|
{
|
||||||
_mountTask = mountTask;
|
_nestedExecutor = _mountExecutor;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (_moveParams.Mount == false)
|
else if (Task.Mount == false)
|
||||||
{
|
{
|
||||||
var mountTask = _mountFactory.Unmount();
|
var mountTask = new Mount.UnmountTask();
|
||||||
if (mountTask.Start())
|
if (_unmountExecutor.Start(mountTask))
|
||||||
{
|
{
|
||||||
_mountTask = mountTask;
|
_nestedExecutor = _unmountExecutor;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_moveParams.DisableNavMesh)
|
if (!Task.DisableNavmesh)
|
||||||
{
|
{
|
||||||
if (_moveParams.Mount == null)
|
if (Task.Mount == null)
|
||||||
{
|
{
|
||||||
Mount.EMountIf mountIf =
|
Mount.EMountIf mountIf =
|
||||||
actualDistance > stopDistance && _moveParams.Fly &&
|
actualDistance > stopDistance && Task.Fly &&
|
||||||
_gameFunctions.IsFlyingUnlocked(_moveParams.TerritoryId)
|
_gameFunctions.IsFlyingUnlocked(Task.TerritoryId)
|
||||||
? Mount.EMountIf.Always
|
? Mount.EMountIf.Always
|
||||||
: Mount.EMountIf.AwayFromPosition;
|
: Mount.EMountIf.AwayFromPosition;
|
||||||
var mountTask = _mountFactory.Mount(_moveParams.TerritoryId, mountIf, _destination);
|
var mountTask = new Mount.MountTask(Task.TerritoryId, mountIf, _destination);
|
||||||
if (mountTask.Start())
|
if (_mountExecutor.Start(mountTask))
|
||||||
{
|
{
|
||||||
_mountTask = mountTask;
|
_nestedExecutor = _mountExecutor;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_mountTask = new NoOpTask();
|
_nestedExecutor = new NoOpTaskExecutor();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (_mountTask != null)
|
if (_nestedExecutor != null)
|
||||||
{
|
{
|
||||||
if (_mountTask.Update() == ETaskResult.TaskComplete)
|
if (_nestedExecutor.Update() == ETaskResult.TaskComplete)
|
||||||
{
|
{
|
||||||
_mountTask = null;
|
_nestedExecutor = null;
|
||||||
|
|
||||||
_logger.LogInformation("Moving to {Destination}", _destination.ToString("G", CultureInfo.InvariantCulture));
|
_logger.LogInformation("Moving to {Destination}", _destination.ToString("G", CultureInfo.InvariantCulture));
|
||||||
_startAction();
|
_startAction();
|
||||||
@ -247,10 +223,10 @@ internal static class MoveTo
|
|||||||
|
|
||||||
if (_canRestart &&
|
if (_canRestart &&
|
||||||
Vector3.Distance(_clientState.LocalPlayer!.Position, _destination) >
|
Vector3.Distance(_clientState.LocalPlayer!.Position, _destination) >
|
||||||
(_moveParams.StopDistance ?? QuestStep.DefaultStopDistance) + 5f)
|
(Task.StopDistance ?? QuestStep.DefaultStopDistance) + 5f)
|
||||||
{
|
{
|
||||||
_canRestart = false;
|
_canRestart = false;
|
||||||
if (_clientState.TerritoryType == _moveParams.TerritoryId)
|
if (_clientState.TerritoryType == Task.TerritoryId)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Looks like movement was interrupted, re-attempting to move");
|
_logger.LogInformation("Looks like movement was interrupted, re-attempting to move");
|
||||||
_startAction();
|
_startAction();
|
||||||
@ -264,7 +240,6 @@ internal static class MoveTo
|
|||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"MoveTo({_destination.ToString("G", CultureInfo.InvariantCulture)})";
|
|
||||||
|
|
||||||
public bool OnErrorToast(SeString message)
|
public bool OnErrorToast(SeString message)
|
||||||
{
|
{
|
||||||
@ -275,27 +250,27 @@ internal static class MoveTo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class NoOpTask : ITask
|
private sealed class NoOpTaskExecutor : TaskExecutor<ITask>
|
||||||
{
|
{
|
||||||
public bool Start() => true;
|
protected override bool Start() => true;
|
||||||
|
|
||||||
public ETaskResult Update() => ETaskResult.TaskComplete;
|
public override ETaskResult Update() => ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed record MoveParams(
|
internal sealed record MoveTask(
|
||||||
ushort TerritoryId,
|
ushort TerritoryId,
|
||||||
Vector3 Destination,
|
Vector3 Destination,
|
||||||
bool? Mount = null,
|
bool? Mount = null,
|
||||||
float? StopDistance = null,
|
float? StopDistance = null,
|
||||||
uint? DataId = null,
|
uint? DataId = null,
|
||||||
bool DisableNavMesh = false,
|
bool DisableNavmesh = false,
|
||||||
bool Sprint = true,
|
bool Sprint = true,
|
||||||
bool Fly = false,
|
bool Fly = false,
|
||||||
bool Land = false,
|
bool Land = false,
|
||||||
bool IgnoreDistanceToObject = false,
|
bool IgnoreDistanceToObject = false,
|
||||||
bool RestartNavigation = true)
|
bool RestartNavigation = true) : ITask
|
||||||
{
|
{
|
||||||
public MoveParams(QuestStep step, Vector3 destination)
|
public MoveTask(QuestStep step, Vector3 destination)
|
||||||
: this(step.TerritoryId,
|
: this(step.TerritoryId,
|
||||||
destination,
|
destination,
|
||||||
step.Mount,
|
step.Mount,
|
||||||
@ -309,23 +284,27 @@ internal static class MoveTo
|
|||||||
step.RestartNavigationIfCancelled != false)
|
step.RestartNavigationIfCancelled != false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"MoveTo({Destination.ToString("G", CultureInfo.InvariantCulture)})";
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class WaitForNearDataId(
|
internal sealed record WaitForNearDataId(uint DataId, float StopDistance) : ITask
|
||||||
uint dataId,
|
|
||||||
float stopDistance,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
IClientState clientState) : ITask
|
|
||||||
{
|
{
|
||||||
public bool ShouldRedoOnInterrupt() => true;
|
public bool ShouldRedoOnInterrupt() => true;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Start() => true;
|
internal sealed class WaitForNearDataIdExecutor(
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
IClientState clientState) : TaskExecutor<WaitForNearDataId>
|
||||||
|
{
|
||||||
|
|
||||||
public ETaskResult Update()
|
protected override bool Start() => true;
|
||||||
|
|
||||||
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId);
|
IGameObject? gameObject = gameFunctions.FindObjectByDataId(Task.DataId);
|
||||||
if (gameObject == null ||
|
if (gameObject == null ||
|
||||||
(gameObject.Position - clientState.LocalPlayer!.Position).Length() > stopDistance)
|
(gameObject.Position - clientState.LocalPlayer!.Position).Length() > Task.StopDistance)
|
||||||
{
|
{
|
||||||
throw new TaskException("Object not found or too far away, no position so we can't move");
|
throw new TaskException("Object not found or too far away, no position so we can't move");
|
||||||
}
|
}
|
||||||
@ -334,14 +313,17 @@ internal static class MoveTo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class LandTask(IClientState clientState, ICondition condition, ILogger<LandTask> logger) : ITask
|
internal sealed class LandTask : ITask
|
||||||
|
{
|
||||||
|
public bool ShouldRedoOnInterrupt() => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class LandExecutor(IClientState clientState, ICondition condition, ILogger<LandExecutor> logger) : TaskExecutor<LandTask>
|
||||||
{
|
{
|
||||||
private bool _landing;
|
private bool _landing;
|
||||||
private DateTime _continueAt;
|
private DateTime _continueAt;
|
||||||
|
|
||||||
public bool ShouldRedoOnInterrupt() => true;
|
protected override bool Start()
|
||||||
|
|
||||||
public bool Start()
|
|
||||||
{
|
{
|
||||||
if (!condition[ConditionFlag.InFlight])
|
if (!condition[ConditionFlag.InFlight])
|
||||||
{
|
{
|
||||||
@ -354,7 +336,7 @@ internal static class MoveTo
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (DateTime.Now < _continueAt)
|
if (DateTime.Now < _continueAt)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
using System;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Utils;
|
using Questionable.Controller.Utils;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
@ -20,12 +15,7 @@ namespace Questionable.Controller.Steps.Shared;
|
|||||||
|
|
||||||
internal static class SkipCondition
|
internal static class SkipCondition
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory : SimpleTaskFactory
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
AetheryteFunctions aetheryteFunctions,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
QuestFunctions questFunctions,
|
|
||||||
IClientState clientState) : SimpleTaskFactory
|
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -40,28 +30,31 @@ internal static class SkipCondition
|
|||||||
step.NextQuestId == null)
|
step.NextQuestId == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return Check(step, skipConditions, quest.Id);
|
return new SkipTask(step, skipConditions ?? new(), quest.Id);
|
||||||
}
|
|
||||||
|
|
||||||
private CheckSkip Check(QuestStep step, SkipStepConditions? skipConditions, ElementId questId)
|
|
||||||
{
|
|
||||||
return new CheckSkip(step, skipConditions ?? new(), questId, loggerFactory.CreateLogger<CheckSkip>(),
|
|
||||||
aetheryteFunctions, gameFunctions, questFunctions, clientState);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class CheckSkip(
|
internal sealed record SkipTask(
|
||||||
QuestStep step,
|
QuestStep Step,
|
||||||
SkipStepConditions skipConditions,
|
SkipStepConditions SkipConditions,
|
||||||
ElementId elementId,
|
ElementId ElementId) : ITask
|
||||||
|
{
|
||||||
|
public override string ToString() => "CheckSkip";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class CheckSkip(
|
||||||
ILogger<CheckSkip> logger,
|
ILogger<CheckSkip> logger,
|
||||||
AetheryteFunctions aetheryteFunctions,
|
AetheryteFunctions aetheryteFunctions,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
IClientState clientState) : ITask
|
IClientState clientState) : TaskExecutor<SkipTask>
|
||||||
{
|
{
|
||||||
public unsafe bool Start()
|
protected override unsafe bool Start()
|
||||||
{
|
{
|
||||||
|
var skipConditions = Task.SkipConditions;
|
||||||
|
var step = Task.Step;
|
||||||
|
var elementId = Task.ElementId;
|
||||||
|
|
||||||
logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", skipConditions));
|
logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", skipConditions));
|
||||||
|
|
||||||
if (skipConditions.Flying == ELockedSkipCondition.Unlocked &&
|
if (skipConditions.Flying == ELockedSkipCondition.Unlocked &&
|
||||||
@ -204,7 +197,8 @@ internal static class SkipCondition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skipConditions.NearPosition is { } nearPosition && clientState.TerritoryType == nearPosition.TerritoryId)
|
if (skipConditions.NearPosition is { } nearPosition &&
|
||||||
|
clientState.TerritoryType == nearPosition.TerritoryId)
|
||||||
{
|
{
|
||||||
if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <=
|
if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <=
|
||||||
nearPosition.MaximumDistance)
|
nearPosition.MaximumDistance)
|
||||||
@ -251,8 +245,6 @@ internal static class SkipCondition
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() => ETaskResult.SkipRemainingTasksForStep;
|
public override ETaskResult Update() => ETaskResult.SkipRemainingTasksForStep;
|
||||||
|
|
||||||
public override string ToString() => "CheckSkip";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,27 +6,30 @@ namespace Questionable.Controller.Steps.Shared;
|
|||||||
|
|
||||||
internal static class StepDisabled
|
internal static class StepDisabled
|
||||||
{
|
{
|
||||||
internal sealed class Factory(ILoggerFactory loggerFactory) : SimpleTaskFactory
|
internal sealed class Factory : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
if (!step.Disabled)
|
if (!step.Disabled)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new Task(loggerFactory.CreateLogger<Task>());
|
return new SkipRemainingTasks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class Task(ILogger<Task> logger) : ITask
|
internal sealed class SkipRemainingTasks : ITask
|
||||||
{
|
{
|
||||||
public bool Start() => true;
|
public override string ToString() => "StepDisabled";
|
||||||
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
internal sealed class Executor(ILogger<SkipRemainingTasks> logger) : TaskExecutor<SkipRemainingTasks>
|
||||||
|
{
|
||||||
|
protected override bool Start() => true;
|
||||||
|
|
||||||
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as it is disabled");
|
logger.LogInformation("Skipping step, as it is disabled");
|
||||||
return ETaskResult.SkipRemainingTasksForStep;
|
return ETaskResult.SkipRemainingTasksForStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => "StepDisabled";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,31 +6,37 @@ using Questionable.Controller.Steps.Common;
|
|||||||
|
|
||||||
namespace Questionable.Controller.Steps.Shared;
|
namespace Questionable.Controller.Steps.Shared;
|
||||||
|
|
||||||
internal sealed class SwitchClassJob(EClassJob classJob, IClientState clientState) : AbstractDelayedTask
|
internal static class SwitchClassJob
|
||||||
{
|
{
|
||||||
protected override unsafe bool StartInternal()
|
internal sealed record Task(EClassJob ClassJob) : ITask
|
||||||
{
|
{
|
||||||
if (clientState.LocalPlayer!.ClassJob.Id == (uint)classJob)
|
public override string ToString() => $"SwitchJob({ClassJob})";
|
||||||
return false;
|
|
||||||
|
|
||||||
var gearsetModule = RaptureGearsetModule.Instance();
|
|
||||||
if (gearsetModule != null)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 100; ++i)
|
|
||||||
{
|
|
||||||
var gearset = gearsetModule->GetGearset(i);
|
|
||||||
if (gearset->ClassJob == (byte)classJob)
|
|
||||||
{
|
|
||||||
gearsetModule->EquipGearset(gearset->Id);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TaskException($"No gearset found for {classJob}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ETaskResult UpdateInternal() => ETaskResult.TaskComplete;
|
internal sealed class Executor(IClientState clientState) : AbstractDelayedTaskExecutor<Task>
|
||||||
|
{
|
||||||
|
protected override unsafe bool StartInternal()
|
||||||
|
{
|
||||||
|
if (clientState.LocalPlayer!.ClassJob.Id == (uint)Task.ClassJob)
|
||||||
|
return false;
|
||||||
|
|
||||||
public override string ToString() => $"SwitchJob({classJob})";
|
var gearsetModule = RaptureGearsetModule.Instance();
|
||||||
|
if (gearsetModule != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 100; ++i)
|
||||||
|
{
|
||||||
|
var gearset = gearsetModule->GetGearset(i);
|
||||||
|
if (gearset->ClassJob == (byte)Task.ClassJob)
|
||||||
|
{
|
||||||
|
gearsetModule->EquipGearset(gearset->Id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TaskException($"No gearset found for {Task.ClassJob}");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ETaskResult UpdateInternal() => ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,7 @@ internal static class WaitAtEnd
|
|||||||
internal sealed class Factory(
|
internal sealed class Factory(
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
TerritoryData territoryData,
|
TerritoryData territoryData)
|
||||||
QuestFunctions questFunctions,
|
|
||||||
GameFunctions gameFunctions)
|
|
||||||
: ITaskFactory
|
: ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
@ -29,7 +27,7 @@ internal static class WaitAtEnd
|
|||||||
if (step.CompletionQuestVariablesFlags.Count == 6 &&
|
if (step.CompletionQuestVariablesFlags.Count == 6 &&
|
||||||
QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
|
QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
|
||||||
{
|
{
|
||||||
var task = new WaitForCompletionFlags((QuestId)quest.Id, step, questFunctions);
|
var task = new WaitForCompletionFlags((QuestId)quest.Id, step);
|
||||||
var delay = new WaitDelay();
|
var delay = new WaitDelay();
|
||||||
return [task, delay, Next(quest, sequence)];
|
return [task, delay, Next(quest, sequence)];
|
||||||
}
|
}
|
||||||
@ -38,7 +36,7 @@ internal static class WaitAtEnd
|
|||||||
{
|
{
|
||||||
case EInteractionType.Combat:
|
case EInteractionType.Combat:
|
||||||
var notInCombat =
|
var notInCombat =
|
||||||
new WaitConditionTask(() => !condition[ConditionFlag.InCombat], "Wait(not in combat)");
|
new WaitCondition.Task(() => !condition[ConditionFlag.InCombat], "Wait(not in combat)");
|
||||||
return
|
return
|
||||||
[
|
[
|
||||||
new WaitDelay(),
|
new WaitDelay(),
|
||||||
@ -67,8 +65,7 @@ internal static class WaitAtEnd
|
|||||||
|
|
||||||
return
|
return
|
||||||
[
|
[
|
||||||
new WaitObjectAtPosition(step.DataId.Value, step.Position.Value, step.NpcWaitDistance ?? 0.5f,
|
new WaitObjectAtPosition(step.DataId.Value, step.Position.Value, step.NpcWaitDistance ?? 0.5f),
|
||||||
gameFunctions),
|
|
||||||
new WaitDelay(),
|
new WaitDelay(),
|
||||||
Next(quest, sequence)
|
Next(quest, sequence)
|
||||||
];
|
];
|
||||||
@ -79,14 +76,14 @@ internal static class WaitAtEnd
|
|||||||
if (step.TerritoryId != step.TargetTerritoryId)
|
if (step.TerritoryId != step.TargetTerritoryId)
|
||||||
{
|
{
|
||||||
// interaction moves to a different territory
|
// interaction moves to a different territory
|
||||||
waitInteraction = new WaitConditionTask(
|
waitInteraction = new WaitCondition.Task(
|
||||||
() => clientState.TerritoryType == step.TargetTerritoryId,
|
() => clientState.TerritoryType == step.TargetTerritoryId,
|
||||||
$"Wait(tp to territory: {territoryData.GetNameAndId(step.TargetTerritoryId.Value)})");
|
$"Wait(tp to territory: {territoryData.GetNameAndId(step.TargetTerritoryId.Value)})");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Vector3 lastPosition = step.Position ?? clientState.LocalPlayer?.Position ?? Vector3.Zero;
|
Vector3 lastPosition = step.Position ?? clientState.LocalPlayer?.Position ?? Vector3.Zero;
|
||||||
waitInteraction = new WaitConditionTask(() =>
|
waitInteraction = new WaitCondition.Task(() =>
|
||||||
{
|
{
|
||||||
Vector3? currentPosition = clientState.LocalPlayer?.Position;
|
Vector3? currentPosition = clientState.LocalPlayer?.Position;
|
||||||
if (currentPosition == null)
|
if (currentPosition == null)
|
||||||
@ -109,7 +106,7 @@ internal static class WaitAtEnd
|
|||||||
|
|
||||||
case EInteractionType.AcceptQuest:
|
case EInteractionType.AcceptQuest:
|
||||||
{
|
{
|
||||||
var accept = new WaitQuestAccepted(step.PickUpQuestId ?? quest.Id, questFunctions);
|
var accept = new WaitQuestAccepted(step.PickUpQuestId ?? quest.Id);
|
||||||
var delay = new WaitDelay();
|
var delay = new WaitDelay();
|
||||||
if (step.PickUpQuestId != null)
|
if (step.PickUpQuestId != null)
|
||||||
return [accept, delay, Next(quest, sequence)];
|
return [accept, delay, Next(quest, sequence)];
|
||||||
@ -119,7 +116,7 @@ internal static class WaitAtEnd
|
|||||||
|
|
||||||
case EInteractionType.CompleteQuest:
|
case EInteractionType.CompleteQuest:
|
||||||
{
|
{
|
||||||
var complete = new WaitQuestCompleted(step.TurnInQuestId ?? quest.Id, questFunctions);
|
var complete = new WaitQuestCompleted(step.TurnInQuestId ?? quest.Id);
|
||||||
var delay = new WaitDelay();
|
var delay = new WaitDelay();
|
||||||
if (step.TurnInQuestId != null)
|
if (step.TurnInQuestId != null)
|
||||||
return [complete, delay, Next(quest, sequence)];
|
return [complete, delay, Next(quest, sequence)];
|
||||||
@ -139,103 +136,133 @@ internal static class WaitAtEnd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class WaitDelay(TimeSpan? delay = null) : AbstractDelayedTask(delay ?? TimeSpan.FromSeconds(1))
|
internal sealed record WaitDelay(TimeSpan Delay) : ITask
|
||||||
{
|
{
|
||||||
protected override bool StartInternal() => true;
|
public WaitDelay()
|
||||||
|
: this(TimeSpan.FromSeconds(1))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Wait(seconds: {Delay.TotalSeconds})";
|
public override string ToString() => $"Wait(seconds: {Delay.TotalSeconds})";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed class WaitDelayExecutor : AbstractDelayedTaskExecutor<WaitDelay>
|
||||||
|
{
|
||||||
|
protected override bool StartInternal()
|
||||||
|
{
|
||||||
|
Delay = Task.Delay;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal sealed class WaitNextStepOrSequence : ITask
|
internal sealed class WaitNextStepOrSequence : ITask
|
||||||
{
|
{
|
||||||
public bool Start() => true;
|
|
||||||
|
|
||||||
public ETaskResult Update() => ETaskResult.StillRunning;
|
|
||||||
|
|
||||||
public override string ToString() => "Wait(next step or sequence)";
|
public override string ToString() => "Wait(next step or sequence)";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class WaitForCompletionFlags(QuestId quest, QuestStep step, QuestFunctions questFunctions) : ITask
|
internal sealed class WaitNextStepOrSequenceExecutor : TaskExecutor<WaitNextStepOrSequence>
|
||||||
{
|
{
|
||||||
public bool Start() => true;
|
protected override bool Start() => true;
|
||||||
|
|
||||||
public ETaskResult Update()
|
public override ETaskResult Update() => ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed record WaitForCompletionFlags(QuestId Quest, QuestStep Step) : ITask
|
||||||
|
{
|
||||||
|
public override string ToString() =>
|
||||||
|
$"Wait(QW: {string.Join(", ", Step.CompletionQuestVariablesFlags.Select(x => x?.ToString() ?? "-"))})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class WaitForCompletionFlagsExecutor(QuestFunctions questFunctions)
|
||||||
|
: TaskExecutor<WaitForCompletionFlags>
|
||||||
|
{
|
||||||
|
protected override bool Start() => true;
|
||||||
|
|
||||||
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(quest);
|
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(Task.Quest);
|
||||||
return questWork != null &&
|
return questWork != null &&
|
||||||
QuestWorkUtils.MatchesQuestWork(step.CompletionQuestVariablesFlags, questWork)
|
QuestWorkUtils.MatchesQuestWork(Task.Step.CompletionQuestVariablesFlags, questWork)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed record WaitObjectAtPosition(
|
||||||
|
uint DataId,
|
||||||
|
Vector3 Destination,
|
||||||
|
float Distance) : ITask
|
||||||
|
{
|
||||||
public override string ToString() =>
|
public override string ToString() =>
|
||||||
$"Wait(QW: {string.Join(", ", step.CompletionQuestVariablesFlags.Select(x => x?.ToString() ?? "-"))})";
|
$"WaitObj({DataId} at {Destination.ToString("G", CultureInfo.InvariantCulture)} < {Distance})";
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class WaitObjectAtPosition(
|
internal sealed class WaitObjectAtPositionExecutor(GameFunctions gameFunctions) : TaskExecutor<WaitObjectAtPosition>
|
||||||
uint dataId,
|
|
||||||
Vector3 destination,
|
|
||||||
float distance,
|
|
||||||
GameFunctions gameFunctions) : ITask
|
|
||||||
{
|
{
|
||||||
public bool Start() => true;
|
protected override bool Start() => true;
|
||||||
|
|
||||||
public ETaskResult Update() =>
|
public override ETaskResult Update() =>
|
||||||
gameFunctions.IsObjectAtPosition(dataId, destination, distance)
|
gameFunctions.IsObjectAtPosition(Task.DataId, Task.Destination, Task.Distance)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
|
|
||||||
public override string ToString() =>
|
|
||||||
$"WaitObj({dataId} at {destination.ToString("G", CultureInfo.InvariantCulture)} < {distance})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class WaitQuestAccepted(ElementId elementId, QuestFunctions questFunctions) : ITask
|
internal sealed record WaitQuestAccepted(ElementId ElementId) : ITask
|
||||||
{
|
{
|
||||||
public bool Start() => true;
|
public override string ToString() => $"WaitQuestAccepted({ElementId})";
|
||||||
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
internal sealed class WaitQuestAcceptedExecutor(QuestFunctions questFunctions) : TaskExecutor<WaitQuestAccepted>
|
||||||
|
{
|
||||||
|
protected override bool Start() => true;
|
||||||
|
|
||||||
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
return questFunctions.IsQuestAccepted(elementId)
|
return questFunctions.IsQuestAccepted(Task.ElementId)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"WaitQuestAccepted({elementId})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class WaitQuestCompleted(ElementId elementId, QuestFunctions questFunctions) : ITask
|
internal sealed record WaitQuestCompleted(ElementId ElementId) : ITask
|
||||||
{
|
{
|
||||||
public bool Start() => true;
|
public override string ToString() => $"WaitQuestComplete({ElementId})";
|
||||||
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
internal sealed class WaitQuestCompletedExecutor(QuestFunctions questFunctions) : TaskExecutor<WaitQuestCompleted>
|
||||||
|
{
|
||||||
|
protected override bool Start() => true;
|
||||||
|
|
||||||
|
public override ETaskResult Update()
|
||||||
{
|
{
|
||||||
return questFunctions.IsQuestComplete(elementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
|
return questFunctions.IsQuestComplete(Task.ElementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"WaitQuestComplete({elementId})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class NextStep(ElementId elementId, int sequence) : ILastTask
|
internal sealed record NextStep(ElementId ElementId, int Sequence) : ILastTask
|
||||||
{
|
{
|
||||||
public ElementId ElementId { get; } = elementId;
|
|
||||||
public int Sequence { get; } = sequence;
|
|
||||||
|
|
||||||
public bool Start() => true;
|
|
||||||
|
|
||||||
public ETaskResult Update() => ETaskResult.NextStep;
|
|
||||||
|
|
||||||
public override string ToString() => "NextStep";
|
public override string ToString() => "NextStep";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed class NextStepExecutor : TaskExecutor<NextStep>
|
||||||
|
{
|
||||||
|
protected override bool Start() => true;
|
||||||
|
|
||||||
|
public override ETaskResult Update() => ETaskResult.NextStep;
|
||||||
|
}
|
||||||
|
|
||||||
internal sealed class EndAutomation : ILastTask
|
internal sealed class EndAutomation : ILastTask
|
||||||
{
|
{
|
||||||
public ElementId ElementId => throw new InvalidOperationException();
|
public ElementId ElementId => throw new InvalidOperationException();
|
||||||
public int Sequence => throw new InvalidOperationException();
|
public int Sequence => throw new InvalidOperationException();
|
||||||
|
|
||||||
public bool Start() => true;
|
|
||||||
|
|
||||||
public ETaskResult Update() => ETaskResult.End;
|
|
||||||
|
|
||||||
public override string ToString() => "EndAutomation";
|
public override string ToString() => "EndAutomation";
|
||||||
}
|
}
|
||||||
|
internal sealed class EndAutomationExecutor : TaskExecutor<EndAutomation>
|
||||||
|
{
|
||||||
|
|
||||||
|
protected override bool Start() => true;
|
||||||
|
|
||||||
|
public override ETaskResult Update() => ETaskResult.End;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,19 @@ internal static class WaitAtStart
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class WaitDelay(TimeSpan delay) : AbstractDelayedTask(delay)
|
|
||||||
{
|
|
||||||
protected override bool StartInternal() => true;
|
|
||||||
|
|
||||||
|
internal sealed record WaitDelay(TimeSpan Delay) : ITask
|
||||||
|
{
|
||||||
public override string ToString() => $"Wait[S](seconds: {Delay.TotalSeconds})";
|
public override string ToString() => $"Wait[S](seconds: {Delay.TotalSeconds})";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed class WaitDelayExecutor : AbstractDelayedTaskExecutor<WaitDelay>
|
||||||
|
{
|
||||||
|
protected override bool StartInternal()
|
||||||
|
{
|
||||||
|
Delay = Task.Delay;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
51
Questionable/Controller/Steps/TaskExecutor.cs
Normal file
51
Questionable/Controller/Steps/TaskExecutor.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.Steps;
|
||||||
|
|
||||||
|
internal interface ITaskExecutor
|
||||||
|
{
|
||||||
|
ITask CurrentTask { get; }
|
||||||
|
|
||||||
|
Type GetTaskType();
|
||||||
|
|
||||||
|
bool Start(ITask task);
|
||||||
|
|
||||||
|
bool WasInterrupted();
|
||||||
|
|
||||||
|
ETaskResult Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal abstract class TaskExecutor<T> : ITaskExecutor
|
||||||
|
where T : class, ITask
|
||||||
|
{
|
||||||
|
protected T Task { get; set; } = null!;
|
||||||
|
protected InteractionProgressContext? ProgressContext { get; set; }
|
||||||
|
ITask ITaskExecutor.CurrentTask => Task;
|
||||||
|
|
||||||
|
public bool WasInterrupted()
|
||||||
|
{
|
||||||
|
if (ProgressContext is {} progressContext)
|
||||||
|
{
|
||||||
|
progressContext.Update();
|
||||||
|
return progressContext.WasInterrupted();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type GetTaskType() => typeof(T);
|
||||||
|
|
||||||
|
protected abstract bool Start();
|
||||||
|
|
||||||
|
public bool Start(ITask task)
|
||||||
|
{
|
||||||
|
if (task is T t)
|
||||||
|
{
|
||||||
|
Task = t;
|
||||||
|
return Start();
|
||||||
|
}
|
||||||
|
throw new TaskException($"Unable to cast {task.GetType()} to {typeof(T)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract ETaskResult Update();
|
||||||
|
}
|
@ -8,10 +8,10 @@ internal sealed class TaskQueue
|
|||||||
{
|
{
|
||||||
private readonly List<ITask> _completedTasks = [];
|
private readonly List<ITask> _completedTasks = [];
|
||||||
private readonly List<ITask> _tasks = [];
|
private readonly List<ITask> _tasks = [];
|
||||||
public ITask? CurrentTask { get; set; }
|
public ITaskExecutor? CurrentTaskExecutor { get; set; }
|
||||||
|
|
||||||
public IEnumerable<ITask> RemainingTasks => _tasks;
|
public IEnumerable<ITask> RemainingTasks => _tasks;
|
||||||
public bool AllTasksComplete => CurrentTask == null && _tasks.Count == 0;
|
public bool AllTasksComplete => CurrentTaskExecutor == null && _tasks.Count == 0;
|
||||||
|
|
||||||
public void Enqueue(ITask task)
|
public void Enqueue(ITask task)
|
||||||
{
|
{
|
||||||
@ -41,7 +41,7 @@ internal sealed class TaskQueue
|
|||||||
{
|
{
|
||||||
_tasks.Clear();
|
_tasks.Clear();
|
||||||
_completedTasks.Clear();
|
_completedTasks.Clear();
|
||||||
CurrentTask = null;
|
CurrentTaskExecutor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InterruptWith(List<ITask> interruptionTasks)
|
public void InterruptWith(List<ITask> interruptionTasks)
|
||||||
@ -49,8 +49,8 @@ internal sealed class TaskQueue
|
|||||||
List<ITask?> newTasks =
|
List<ITask?> newTasks =
|
||||||
[
|
[
|
||||||
..interruptionTasks,
|
..interruptionTasks,
|
||||||
.._completedTasks.Where(x => !ReferenceEquals(x, CurrentTask)).ToList(),
|
.._completedTasks.Where(x => !ReferenceEquals(x, CurrentTaskExecutor?.CurrentTask)).ToList(),
|
||||||
CurrentTask,
|
CurrentTaskExecutor?.CurrentTask,
|
||||||
.._tasks
|
.._tasks
|
||||||
];
|
];
|
||||||
Reset();
|
Reset();
|
||||||
|
@ -56,6 +56,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(pluginInterface);
|
ArgumentNullException.ThrowIfNull(pluginInterface);
|
||||||
ArgumentNullException.ThrowIfNull(chatGui);
|
ArgumentNullException.ThrowIfNull(chatGui);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ServiceCollection serviceCollection = new();
|
ServiceCollection serviceCollection = new();
|
||||||
@ -128,44 +129,81 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
private static void AddTaskFactories(ServiceCollection serviceCollection)
|
private static void AddTaskFactories(ServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
// individual tasks
|
// individual tasks
|
||||||
serviceCollection.AddTransient<MoveToLandingLocation>();
|
serviceCollection.AddTaskExecutor<MoveToLandingLocation.Task, MoveToLandingLocation.Executor>();
|
||||||
serviceCollection.AddTransient<DoGather>();
|
serviceCollection.AddTaskExecutor<DoGather.Task, DoGather.Executor>();
|
||||||
serviceCollection.AddTransient<DoGatherCollectable>();
|
serviceCollection.AddTaskExecutor<DoGatherCollectable.Task, DoGatherCollectable.Executor>();
|
||||||
serviceCollection.AddTransient<SwitchClassJob>();
|
serviceCollection.AddTaskExecutor<SwitchClassJob.Task, SwitchClassJob.Executor>();
|
||||||
serviceCollection.AddSingleton<Mount.Factory>();
|
serviceCollection.AddTaskExecutor<Mount.MountTask, Mount.MountExecutor>();
|
||||||
|
serviceCollection.AddTaskExecutor<Mount.UnmountTask, Mount.UnmountExecutor>();
|
||||||
|
|
||||||
// task factories
|
// task factories
|
||||||
serviceCollection.AddTaskFactory<StepDisabled.Factory>();
|
serviceCollection
|
||||||
|
.AddTaskFactoryAndExecutor<StepDisabled.SkipRemainingTasks, StepDisabled.Factory, StepDisabled.Executor>();
|
||||||
serviceCollection.AddTaskFactory<EquipRecommended.BeforeDutyOrInstance>();
|
serviceCollection.AddTaskFactory<EquipRecommended.BeforeDutyOrInstance>();
|
||||||
serviceCollection.AddTaskFactory<Gather.Factory>();
|
serviceCollection.AddTaskFactoryAndExecutor<Gather.GatheringTask, Gather.Factory, Gather.StartGathering>();
|
||||||
serviceCollection.AddTaskFactory<AetheryteShortcut.Factory>();
|
serviceCollection.AddTaskExecutor<Gather.SkipMarker, Gather.DoSkip>();
|
||||||
serviceCollection.AddTaskFactory<SkipCondition.Factory>();
|
serviceCollection
|
||||||
serviceCollection.AddTaskFactory<AethernetShortcut.Factory>();
|
.AddTaskFactoryAndExecutor<AetheryteShortcut.Task, AetheryteShortcut.Factory,
|
||||||
serviceCollection.AddTaskFactory<WaitAtStart.Factory>();
|
AetheryteShortcut.UseAetheryteShortcut>();
|
||||||
serviceCollection.AddTaskFactory<MoveTo.Factory>();
|
serviceCollection
|
||||||
|
.AddTaskFactoryAndExecutor<SkipCondition.SkipTask, SkipCondition.Factory, SkipCondition.CheckSkip>();
|
||||||
|
serviceCollection
|
||||||
|
.AddTaskFactoryAndExecutor<AethernetShortcut.Task, AethernetShortcut.Factory,
|
||||||
|
AethernetShortcut.UseAethernetShortcut>();
|
||||||
|
serviceCollection
|
||||||
|
.AddTaskFactoryAndExecutor<WaitAtStart.WaitDelay, WaitAtStart.Factory, WaitAtStart.WaitDelayExecutor>();
|
||||||
|
serviceCollection.AddTaskFactoryAndExecutor<MoveTo.MoveTask, MoveTo.Factory, MoveTo.MoveExecutor>();
|
||||||
|
serviceCollection.AddTaskExecutor<MoveTo.WaitForNearDataId, MoveTo.WaitForNearDataIdExecutor>();
|
||||||
|
serviceCollection.AddTaskExecutor<MoveTo.LandTask, MoveTo.LandExecutor>();
|
||||||
|
|
||||||
serviceCollection.AddTaskFactory<NextQuest.Factory>();
|
serviceCollection.AddTaskFactoryAndExecutor<NextQuest.SetQuestTask, NextQuest.Factory, NextQuest.Executor>();
|
||||||
serviceCollection.AddTaskFactory<AetherCurrent.Factory>();
|
serviceCollection
|
||||||
serviceCollection.AddTaskFactory<AethernetShard.Factory>();
|
.AddTaskFactoryAndExecutor<AetherCurrent.Attune, AetherCurrent.Factory, AetherCurrent.DoAttune>();
|
||||||
serviceCollection.AddTaskFactory<Aetheryte.Factory>();
|
serviceCollection
|
||||||
serviceCollection.AddTaskFactory<Combat.Factory>();
|
.AddTaskFactoryAndExecutor<AethernetShard.Attune, AethernetShard.Factory, AethernetShard.DoAttune>();
|
||||||
serviceCollection.AddTaskFactory<Duty.Factory>();
|
serviceCollection.AddTaskFactoryAndExecutor<Aetheryte.Attune, Aetheryte.Factory, Aetheryte.DoAttune>();
|
||||||
|
serviceCollection.AddTaskFactoryAndExecutor<Combat.Task, Combat.Factory, Combat.HandleCombat>();
|
||||||
|
serviceCollection.AddTaskFactoryAndExecutor<Duty.Task, Duty.Factory, Duty.Executor>();
|
||||||
serviceCollection.AddTaskFactory<Emote.Factory>();
|
serviceCollection.AddTaskFactory<Emote.Factory>();
|
||||||
serviceCollection.AddTaskFactory<Action.Factory>();
|
serviceCollection.AddTaskExecutor<Emote.UseOnObject, Emote.UseOnObjectExecutor>();
|
||||||
serviceCollection.AddTaskFactory<Interact.Factory>();
|
serviceCollection.AddTaskExecutor<Emote.UseOnSelf, Emote.UseOnSelfExecutor>();
|
||||||
|
serviceCollection.AddTaskFactoryAndExecutor<Action.UseOnObject, Action.Factory, Action.UseOnObjectExecutor>();
|
||||||
|
serviceCollection.AddTaskFactoryAndExecutor<Interact.Task, Interact.Factory, Interact.DoInteract>();
|
||||||
serviceCollection.AddTaskFactory<Jump.Factory>();
|
serviceCollection.AddTaskFactory<Jump.Factory>();
|
||||||
serviceCollection.AddTaskFactory<Dive.Factory>();
|
serviceCollection.AddTaskExecutor<Jump.SingleJumpTask, Jump.DoSingleJump>();
|
||||||
serviceCollection.AddTaskFactory<Say.Factory>();
|
serviceCollection.AddTaskExecutor<Jump.RepeatedJumpTask, Jump.DoRepeatedJumps>();
|
||||||
|
serviceCollection.AddTaskFactoryAndExecutor<Dive.Task, Dive.Factory, Dive.DoDive>();
|
||||||
|
serviceCollection.AddTaskFactoryAndExecutor<Say.Task, Say.Factory, Say.UseChat>();
|
||||||
serviceCollection.AddTaskFactory<UseItem.Factory>();
|
serviceCollection.AddTaskFactory<UseItem.Factory>();
|
||||||
serviceCollection.AddTaskFactory<EquipItem.Factory>();
|
serviceCollection.AddTaskExecutor<UseItem.UseOnGround, UseItem.UseOnGroundExecutor>();
|
||||||
serviceCollection.AddTaskFactory<EquipRecommended.Factory>();
|
serviceCollection.AddTaskExecutor<UseItem.UseOnPosition, UseItem.UseOnPositionExecutor>();
|
||||||
serviceCollection.AddTaskFactory<Craft.Factory>();
|
serviceCollection.AddTaskExecutor<UseItem.UseOnObject, UseItem.UseOnObjectExecutor>();
|
||||||
serviceCollection.AddTaskFactory<TurnInDelivery.Factory>();
|
serviceCollection.AddTaskExecutor<UseItem.UseOnSelf, UseItem.UseOnSelfExecutor>();
|
||||||
serviceCollection.AddTaskFactory<InitiateLeve.Factory>();
|
serviceCollection.AddTaskFactoryAndExecutor<EquipItem.Task, EquipItem.Factory, EquipItem.Executor>();
|
||||||
|
serviceCollection
|
||||||
|
.AddTaskFactoryAndExecutor<EquipRecommended.EquipTask, EquipRecommended.Factory,
|
||||||
|
EquipRecommended.DoEquipRecommended>();
|
||||||
|
serviceCollection.AddTaskFactoryAndExecutor<Craft.CraftTask, Craft.Factory, Craft.DoCraft>();
|
||||||
|
serviceCollection
|
||||||
|
.AddTaskFactoryAndExecutor<TurnInDelivery.Task, TurnInDelivery.Factory,
|
||||||
|
TurnInDelivery.SatisfactionSupplyTurnIn>();
|
||||||
|
|
||||||
|
serviceCollection.AddTaskFactory<InitiateLeve.Factory>();
|
||||||
|
serviceCollection.AddTaskExecutor<InitiateLeve.SkipInitiateIfActive, InitiateLeve.SkipInitiateIfActiveExecutor>();
|
||||||
|
serviceCollection.AddTaskExecutor<InitiateLeve.OpenJournal, InitiateLeve.OpenJournalExecutor>();
|
||||||
|
serviceCollection.AddTaskExecutor<InitiateLeve.Initiate, InitiateLeve.InitiateExecutor>();
|
||||||
|
serviceCollection.AddTaskExecutor<InitiateLeve.SelectDifficulty, InitiateLeve.SelectDifficultyExecutor>();
|
||||||
|
|
||||||
|
serviceCollection.AddTaskExecutor<WaitCondition.Task, WaitCondition.Executor>();
|
||||||
serviceCollection.AddTaskFactory<WaitAtEnd.Factory>();
|
serviceCollection.AddTaskFactory<WaitAtEnd.Factory>();
|
||||||
serviceCollection.AddTransient<WaitAtEnd.WaitQuestAccepted>();
|
serviceCollection.AddTaskExecutor<WaitAtEnd.WaitDelay, WaitAtEnd.WaitDelayExecutor>();
|
||||||
serviceCollection.AddTransient<WaitAtEnd.WaitQuestCompleted>();
|
serviceCollection.AddTaskExecutor<WaitAtEnd.WaitNextStepOrSequence, WaitAtEnd.WaitNextStepOrSequenceExecutor>();
|
||||||
|
serviceCollection.AddTaskExecutor<WaitAtEnd.WaitForCompletionFlags, WaitAtEnd.WaitForCompletionFlagsExecutor>();
|
||||||
|
serviceCollection.AddTaskExecutor<WaitAtEnd.WaitObjectAtPosition, WaitAtEnd.WaitObjectAtPositionExecutor>();
|
||||||
|
serviceCollection.AddTaskExecutor<WaitAtEnd.WaitQuestAccepted, WaitAtEnd.WaitQuestAcceptedExecutor>();
|
||||||
|
serviceCollection.AddTaskExecutor<WaitAtEnd.WaitQuestCompleted, WaitAtEnd.WaitQuestCompletedExecutor>();
|
||||||
|
serviceCollection.AddTaskExecutor<WaitAtEnd.NextStep, WaitAtEnd.NextStepExecutor>();
|
||||||
|
serviceCollection.AddTaskExecutor<WaitAtEnd.EndAutomation, WaitAtEnd.EndAutomationExecutor>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<TaskCreator>();
|
serviceCollection.AddSingleton<TaskCreator>();
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using JetBrains.Annotations;
|
using Dalamud.Plugin.Services;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Questionable.Controller.Steps;
|
using Questionable.Controller.Steps;
|
||||||
|
|
||||||
@ -7,11 +8,37 @@ namespace Questionable;
|
|||||||
internal static class ServiceCollectionExtensions
|
internal static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
public static void AddTaskFactory<
|
public static void AddTaskFactory<
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] TFactory>(
|
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
||||||
|
TFactory>(
|
||||||
this IServiceCollection serviceCollection)
|
this IServiceCollection serviceCollection)
|
||||||
where TFactory : class, ITaskFactory
|
where TFactory : class, ITaskFactory
|
||||||
{
|
{
|
||||||
serviceCollection.AddSingleton<ITaskFactory, TFactory>();
|
serviceCollection.AddSingleton<ITaskFactory, TFactory>();
|
||||||
serviceCollection.AddSingleton<TFactory>();
|
serviceCollection.AddSingleton<TFactory>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AddTaskExecutor<T,
|
||||||
|
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
||||||
|
TExecutor>(
|
||||||
|
this IServiceCollection serviceCollection)
|
||||||
|
where T : class, ITask
|
||||||
|
where TExecutor : TaskExecutor<T>
|
||||||
|
{
|
||||||
|
serviceCollection.AddKeyedTransient<ITaskExecutor, TExecutor>(typeof(T));
|
||||||
|
serviceCollection.AddTransient<TExecutor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddTaskFactoryAndExecutor<T,
|
||||||
|
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
||||||
|
TFactory,
|
||||||
|
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
||||||
|
TExecutor>(
|
||||||
|
this IServiceCollection serviceCollection)
|
||||||
|
where TFactory : class, ITaskFactory
|
||||||
|
where T : class, ITask
|
||||||
|
where TExecutor : TaskExecutor<T>
|
||||||
|
{
|
||||||
|
serviceCollection.AddTaskFactory<TFactory>();
|
||||||
|
serviceCollection.AddTaskExecutor<T, TExecutor>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user