Update how task factories work
This commit is contained in:
parent
9be1579f99
commit
2d22657d41
@ -92,6 +92,14 @@ internal sealed class InteractionUiController : IDisposable
|
|||||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
|
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
|
||||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
|
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
|
||||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "TelepotTown", TeleportTownPostSetup);
|
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "TelepotTown", TeleportTownPostSetup);
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
if (_gameGui.TryGetAddonByName("RhythmAction", out AtkUnitBase* addon))
|
||||||
|
{
|
||||||
|
addon->Close(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldHandleUiInteractions => _isInitialCheck || _questController.IsRunning;
|
private bool ShouldHandleUiInteractions => _isInitialCheck || _questController.IsRunning;
|
||||||
@ -778,7 +786,7 @@ internal sealed class InteractionUiController : IDisposable
|
|||||||
{
|
{
|
||||||
if (ShouldHandleUiInteractions &&
|
if (ShouldHandleUiInteractions &&
|
||||||
_questController.HasCurrentTaskMatching(out AethernetShortcut.UseAethernetShortcut? aethernetShortcut) &&
|
_questController.HasCurrentTaskMatching(out AethernetShortcut.UseAethernetShortcut? aethernetShortcut) &&
|
||||||
EAetheryteLocationExtensions.IsFirmamentAetheryte(aethernetShortcut.From))
|
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
|
||||||
uint toIndex = aethernetShortcut.To switch
|
uint toIndex = aethernetShortcut.To switch
|
||||||
|
@ -25,24 +25,34 @@ using Questionable.Functions;
|
|||||||
using Questionable.GatheringPaths;
|
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;
|
||||||
|
|
||||||
namespace Questionable.Controller;
|
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 IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly ICondition _condition;
|
private readonly ICondition _condition;
|
||||||
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
private readonly IGameGui _gameGui;
|
||||||
|
private readonly IClientState _clientState;
|
||||||
private readonly Regex _revisitRegex;
|
private readonly Regex _revisitRegex;
|
||||||
|
|
||||||
private CurrentRequest? _currentRequest;
|
private CurrentRequest? _currentRequest;
|
||||||
|
|
||||||
public GatheringController(
|
public GatheringController(
|
||||||
MovementController movementController,
|
MovementController movementController,
|
||||||
|
MoveTo.Factory moveFactory,
|
||||||
|
Mount.Factory mountFactory,
|
||||||
|
Interact.Factory interactFactory,
|
||||||
GatheringPointRegistry gatheringPointRegistry,
|
GatheringPointRegistry gatheringPointRegistry,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
NavmeshIpc navmeshIpc,
|
NavmeshIpc navmeshIpc,
|
||||||
@ -52,16 +62,25 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
IDataManager dataManager,
|
IDataManager dataManager,
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
IGameGui gameGui,
|
||||||
|
IClientState clientState,
|
||||||
IPluginLog pluginLog)
|
IPluginLog pluginLog)
|
||||||
: base(chatGui, logger)
|
: base(chatGui, 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;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
_condition = condition;
|
_condition = condition;
|
||||||
|
_loggerFactory = loggerFactory;
|
||||||
|
_gameGui = gameGui;
|
||||||
|
_clientState = clientState;
|
||||||
|
|
||||||
_revisitRegex = dataManager.GetRegex<LogMessage>(5574, x => x.Text, pluginLog)
|
_revisitRegex = dataManager.GetRegex<LogMessage>(5574, x => x.Text, pluginLog)
|
||||||
?? throw new InvalidDataException("No regex found for revisit message");
|
?? throw new InvalidDataException("No regex found for revisit message");
|
||||||
@ -152,8 +171,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(_serviceProvider.GetRequiredService<MountTask>()
|
_taskQueue.Enqueue(_mountFactory.Mount(territoryId, Mount.EMountIf.Always));
|
||||||
.With(territoryId, MountTask.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);
|
||||||
@ -170,24 +188,28 @@ 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(_serviceProvider.GetRequiredService<Move.MoveInternal>()
|
_taskQueue.Enqueue(_moveFactory.Move(new MoveTo.MoveParams(territoryId, pointOnFloor ?? averagePosition,
|
||||||
.With(territoryId, pointOnFloor ?? averagePosition, 50f, fly: fly,
|
50f,
|
||||||
ignoreDistanceToObject: true));
|
Fly: fly, IgnoreDistanceToObject: true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<MoveToLandingLocation>()
|
_taskQueue.Enqueue(new MoveToLandingLocation(territoryId, fly, currentNode, _moveFactory, _gameFunctions,
|
||||||
.With(territoryId, fly, currentNode));
|
_objectTable, _loggerFactory.CreateLogger<MoveToLandingLocation>()));
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<Interact.DoInteract>()
|
_taskQueue.Enqueue(_interactFactory.Interact(currentNode.DataId, null, EInteractionType.InternalGather, true));
|
||||||
.With(currentNode.DataId, null, EInteractionType.InternalGather, true));
|
|
||||||
|
|
||||||
|
QueueGatherNode(currentNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void QueueGatherNode(GatheringNode currentNode)
|
||||||
|
{
|
||||||
foreach (bool revisitRequired in new[] { false, true })
|
foreach (bool revisitRequired in new[] { false, true })
|
||||||
{
|
{
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGather>()
|
_taskQueue.Enqueue(new DoGather(_currentRequest!.Data, currentNode, revisitRequired, this, _gameFunctions,
|
||||||
.With(_currentRequest.Data, currentNode, revisitRequired));
|
_gameGui, _clientState, _condition, _loggerFactory.CreateLogger<DoGather>()));
|
||||||
if (_currentRequest.Data.Collectability > 0)
|
if (_currentRequest.Data.Collectability > 0)
|
||||||
{
|
{
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGatherCollectable>()
|
_taskQueue.Enqueue(new DoGatherCollectable(_currentRequest.Data, currentNode, revisitRequired, this,
|
||||||
.With(_currentRequest.Data, currentNode, revisitRequired));
|
_gameFunctions, _clientState, _gameGui, _loggerFactory.CreateLogger<DoGatherCollectable>()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
183
Questionable/Controller/Steps/Common/Mount.cs
Normal file
183
Questionable/Controller/Steps/Common/Mount.cs
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
using System;
|
||||||
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using FFXIVClientStructs.FFXIV.Common.Math;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Questionable.Data;
|
||||||
|
using Questionable.Functions;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.Steps.Common;
|
||||||
|
|
||||||
|
internal static class Mount
|
||||||
|
{
|
||||||
|
internal sealed class Factory(
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
ICondition condition,
|
||||||
|
TerritoryData territoryData,
|
||||||
|
IClientState clientState,
|
||||||
|
ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
public ITask Mount(ushort territoryId, EMountIf mountIf, Vector3? position = null)
|
||||||
|
{
|
||||||
|
if (mountIf == EMountIf.AwayFromPosition)
|
||||||
|
ArgumentNullException.ThrowIfNull(position);
|
||||||
|
|
||||||
|
return new MountTask(territoryId, mountIf, position, gameFunctions, condition, territoryData, clientState,
|
||||||
|
loggerFactory.CreateLogger<MountTask>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITask Unmount()
|
||||||
|
{
|
||||||
|
return new UnmountTask(condition, loggerFactory.CreateLogger<UnmountTask>(), gameFunctions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class MountTask(
|
||||||
|
ushort territoryId,
|
||||||
|
EMountIf mountIf,
|
||||||
|
Vector3? position,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
ICondition condition,
|
||||||
|
TerritoryData territoryData,
|
||||||
|
IClientState clientState,
|
||||||
|
ILogger<MountTask> logger) : ITask
|
||||||
|
{
|
||||||
|
private bool _mountTriggered;
|
||||||
|
private DateTime _retryAt = DateTime.MinValue;
|
||||||
|
|
||||||
|
public bool Start()
|
||||||
|
{
|
||||||
|
if (condition[ConditionFlag.Mounted])
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!territoryData.CanUseMount(territoryId))
|
||||||
|
{
|
||||||
|
logger.LogInformation("Can't use mount in current territory {Id}", territoryId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameFunctions.HasStatusPreventingMount())
|
||||||
|
{
|
||||||
|
logger.LogInformation("Can't mount due to status preventing sprint or mount");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mountIf == EMountIf.AwayFromPosition)
|
||||||
|
{
|
||||||
|
Vector3 playerPosition = clientState.LocalPlayer?.Position ?? Vector3.Zero;
|
||||||
|
float distance = System.Numerics.Vector3.Distance(playerPosition, position.GetValueOrDefault());
|
||||||
|
if (territoryId == clientState.TerritoryType && distance < 30f && !Conditions.IsDiving)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Not using mount, as we're close to the target");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation(
|
||||||
|
"Want to use mount if away from destination ({Distance} yalms), trying (in territory {Id})...",
|
||||||
|
distance, territoryId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
logger.LogInformation("Want to use mount, trying (in territory {Id})...", territoryId);
|
||||||
|
|
||||||
|
if (!condition[ConditionFlag.InCombat])
|
||||||
|
{
|
||||||
|
_retryAt = DateTime.Now.AddSeconds(0.5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ETaskResult Update()
|
||||||
|
{
|
||||||
|
if (_mountTriggered && !condition[ConditionFlag.Mounted] && DateTime.Now > _retryAt)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Not mounted, retrying...");
|
||||||
|
_mountTriggered = false;
|
||||||
|
_retryAt = DateTime.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_mountTriggered)
|
||||||
|
{
|
||||||
|
if (gameFunctions.HasStatusPreventingMount())
|
||||||
|
{
|
||||||
|
logger.LogInformation("Can't mount due to status preventing sprint or mount");
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
_mountTriggered = gameFunctions.Mount();
|
||||||
|
_retryAt = DateTime.Now.AddSeconds(5);
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
return condition[ConditionFlag.Mounted]
|
||||||
|
? ETaskResult.TaskComplete
|
||||||
|
: ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => "Mount";
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class UnmountTask(ICondition condition, ILogger<UnmountTask> logger, GameFunctions gameFunctions)
|
||||||
|
: ITask
|
||||||
|
{
|
||||||
|
private bool _unmountTriggered;
|
||||||
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
|
|
||||||
|
public bool Start()
|
||||||
|
{
|
||||||
|
if (!condition[ConditionFlag.Mounted])
|
||||||
|
return false;
|
||||||
|
|
||||||
|
logger.LogInformation("Step explicitly wants no mount, trying to unmount...");
|
||||||
|
if (condition[ConditionFlag.InFlight])
|
||||||
|
{
|
||||||
|
gameFunctions.Unmount();
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_unmountTriggered = gameFunctions.Unmount();
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ETaskResult Update()
|
||||||
|
{
|
||||||
|
if (_continueAt >= DateTime.Now)
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
|
if (!_unmountTriggered)
|
||||||
|
{
|
||||||
|
// if still flying, we still need to land
|
||||||
|
if (condition[ConditionFlag.InFlight])
|
||||||
|
gameFunctions.Unmount();
|
||||||
|
else
|
||||||
|
_unmountTriggered = gameFunctions.Unmount();
|
||||||
|
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(1);
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (condition[ConditionFlag.Mounted] && condition[ConditionFlag.InCombat])
|
||||||
|
{
|
||||||
|
_unmountTriggered = gameFunctions.Unmount();
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(1);
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
return condition[ConditionFlag.Mounted]
|
||||||
|
? ETaskResult.StillRunning
|
||||||
|
: ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => "Unmount";
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EMountIf
|
||||||
|
{
|
||||||
|
Always,
|
||||||
|
AwayFromPosition,
|
||||||
|
}
|
||||||
|
}
|
@ -1,114 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Numerics;
|
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Questionable.Data;
|
|
||||||
using Questionable.Functions;
|
|
||||||
|
|
||||||
namespace Questionable.Controller.Steps.Common;
|
|
||||||
|
|
||||||
internal sealed class MountTask(
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
ICondition condition,
|
|
||||||
TerritoryData territoryData,
|
|
||||||
IClientState clientState,
|
|
||||||
ILogger<MountTask> logger) : ITask
|
|
||||||
{
|
|
||||||
private ushort _territoryId;
|
|
||||||
private EMountIf _mountIf;
|
|
||||||
private Vector3? _position;
|
|
||||||
|
|
||||||
private bool _mountTriggered;
|
|
||||||
private DateTime _retryAt = DateTime.MinValue;
|
|
||||||
|
|
||||||
public ITask With(ushort territoryId, EMountIf mountIf, Vector3? position = null)
|
|
||||||
{
|
|
||||||
_territoryId = territoryId;
|
|
||||||
_mountIf = mountIf;
|
|
||||||
_position = position;
|
|
||||||
|
|
||||||
if (_mountIf == EMountIf.AwayFromPosition)
|
|
||||||
ArgumentNullException.ThrowIfNull(position);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start()
|
|
||||||
{
|
|
||||||
if (condition[ConditionFlag.Mounted])
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!territoryData.CanUseMount(_territoryId))
|
|
||||||
{
|
|
||||||
logger.LogInformation("Can't use mount in current territory {Id}", _territoryId);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameFunctions.HasStatusPreventingMount())
|
|
||||||
{
|
|
||||||
logger.LogInformation("Can't mount due to status preventing sprint or mount");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_mountIf == EMountIf.AwayFromPosition)
|
|
||||||
{
|
|
||||||
Vector3 playerPosition = clientState.LocalPlayer?.Position ?? Vector3.Zero;
|
|
||||||
float distance = (playerPosition - _position.GetValueOrDefault()).Length();
|
|
||||||
if (_territoryId == clientState.TerritoryType && distance < 30f && !Conditions.IsDiving)
|
|
||||||
{
|
|
||||||
logger.LogInformation("Not using mount, as we're close to the target");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.LogInformation(
|
|
||||||
"Want to use mount if away from destination ({Distance} yalms), trying (in territory {Id})...",
|
|
||||||
distance, _territoryId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
logger.LogInformation("Want to use mount, trying (in territory {Id})...", _territoryId);
|
|
||||||
|
|
||||||
if (!condition[ConditionFlag.InCombat])
|
|
||||||
{
|
|
||||||
_retryAt = DateTime.Now.AddSeconds(0.5);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ETaskResult Update()
|
|
||||||
{
|
|
||||||
if (_mountTriggered && !condition[ConditionFlag.Mounted] && DateTime.Now > _retryAt)
|
|
||||||
{
|
|
||||||
logger.LogInformation("Not mounted, retrying...");
|
|
||||||
_mountTriggered = false;
|
|
||||||
_retryAt = DateTime.MaxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_mountTriggered)
|
|
||||||
{
|
|
||||||
if (gameFunctions.HasStatusPreventingMount())
|
|
||||||
{
|
|
||||||
logger.LogInformation("Can't mount due to status preventing sprint or mount");
|
|
||||||
return ETaskResult.TaskComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
_mountTriggered = gameFunctions.Mount();
|
|
||||||
_retryAt = DateTime.Now.AddSeconds(5);
|
|
||||||
return ETaskResult.StillRunning;
|
|
||||||
}
|
|
||||||
|
|
||||||
return condition[ConditionFlag.Mounted]
|
|
||||||
? ETaskResult.TaskComplete
|
|
||||||
: ETaskResult.StillRunning;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() => "Mount";
|
|
||||||
|
|
||||||
public enum EMountIf
|
|
||||||
{
|
|
||||||
Always,
|
|
||||||
AwayFromPosition,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,4 @@
|
|||||||
using System;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
@ -9,7 +7,7 @@ namespace Questionable.Controller.Steps.Common;
|
|||||||
|
|
||||||
internal static class NextQuest
|
internal static class NextQuest
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
|
internal sealed class Factory(QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILoggerFactory loggerFactory) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -22,37 +20,26 @@ internal static class NextQuest
|
|||||||
if (step.NextQuestId == quest.Id)
|
if (step.NextQuestId == quest.Id)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return serviceProvider.GetRequiredService<SetQuest>()
|
return new SetQuest(step.NextQuestId, quest.Id, questRegistry, questController, questFunctions, loggerFactory.CreateLogger<SetQuest>());
|
||||||
.With(step.NextQuestId, quest.Id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class SetQuest(QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILogger<SetQuest> logger) : ITask
|
private sealed class SetQuest(ElementId nextQuestId, ElementId currentQuestId, QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILogger<SetQuest> logger) : ITask
|
||||||
{
|
{
|
||||||
public ElementId NextQuestId { get; set; } = null!;
|
|
||||||
public ElementId CurrentQuestId { get; set; } = null!;
|
|
||||||
|
|
||||||
public ITask With(ElementId nextQuestId, ElementId currentQuestId)
|
|
||||||
{
|
|
||||||
NextQuestId = nextQuestId;
|
|
||||||
CurrentQuestId = currentQuestId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
if (questFunctions.IsQuestLocked(NextQuestId, CurrentQuestId))
|
if (questFunctions.IsQuestLocked(nextQuestId, currentQuestId))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Can't set next quest to {QuestId}, quest is locked", NextQuestId);
|
logger.LogInformation("Can't set next quest to {QuestId}, quest is locked", nextQuestId);
|
||||||
}
|
}
|
||||||
else if (questRegistry.TryGetQuest(NextQuestId, out Quest? quest))
|
else if (questRegistry.TryGetQuest(nextQuestId, out Quest? quest))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Setting next quest to {QuestId}: '{QuestName}'", NextQuestId, quest.Info.Name);
|
logger.LogInformation("Setting next quest to {QuestId}: '{QuestName}'", nextQuestId, quest.Info.Name);
|
||||||
questController.SetNextQuest(quest);
|
questController.SetNextQuest(quest);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogInformation("Next quest with id {QuestId} not found", NextQuestId);
|
logger.LogInformation("Next quest with id {QuestId} not found", nextQuestId);
|
||||||
questController.SetNextQuest(null);
|
questController.SetNextQuest(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +48,6 @@ internal static class NextQuest
|
|||||||
|
|
||||||
public ETaskResult Update() => ETaskResult.TaskComplete;
|
public ETaskResult Update() => ETaskResult.TaskComplete;
|
||||||
|
|
||||||
public override string ToString() => $"SetNextQuest({NextQuestId})";
|
public override string ToString() => $"SetNextQuest({nextQuestId})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Questionable.Functions;
|
|
||||||
|
|
||||||
namespace Questionable.Controller.Steps.Common;
|
|
||||||
|
|
||||||
internal sealed class UnmountTask(ICondition condition, ILogger<UnmountTask> logger, GameFunctions gameFunctions)
|
|
||||||
: ITask
|
|
||||||
{
|
|
||||||
private bool _unmountTriggered;
|
|
||||||
private DateTime _continueAt = DateTime.MinValue;
|
|
||||||
|
|
||||||
public bool Start()
|
|
||||||
{
|
|
||||||
if (!condition[ConditionFlag.Mounted])
|
|
||||||
return false;
|
|
||||||
|
|
||||||
logger.LogInformation("Step explicitly wants no mount, trying to unmount...");
|
|
||||||
if (condition[ConditionFlag.InFlight])
|
|
||||||
{
|
|
||||||
gameFunctions.Unmount();
|
|
||||||
_continueAt = DateTime.Now.AddSeconds(1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_unmountTriggered = gameFunctions.Unmount();
|
|
||||||
_continueAt = DateTime.Now.AddSeconds(1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ETaskResult Update()
|
|
||||||
{
|
|
||||||
if (_continueAt >= DateTime.Now)
|
|
||||||
return ETaskResult.StillRunning;
|
|
||||||
|
|
||||||
if (!_unmountTriggered)
|
|
||||||
{
|
|
||||||
// if still flying, we still need to land
|
|
||||||
if (condition[ConditionFlag.InFlight])
|
|
||||||
gameFunctions.Unmount();
|
|
||||||
else
|
|
||||||
_unmountTriggered = gameFunctions.Unmount();
|
|
||||||
|
|
||||||
_continueAt = DateTime.Now.AddSeconds(1);
|
|
||||||
return ETaskResult.StillRunning;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (condition[ConditionFlag.Mounted] && condition[ConditionFlag.InCombat])
|
|
||||||
{
|
|
||||||
_unmountTriggered = gameFunctions.Unmount();
|
|
||||||
_continueAt = DateTime.Now.AddSeconds(1);
|
|
||||||
return ETaskResult.StillRunning;
|
|
||||||
}
|
|
||||||
|
|
||||||
return condition[ConditionFlag.Mounted]
|
|
||||||
? ETaskResult.StillRunning
|
|
||||||
: ETaskResult.TaskComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() => "Unmount";
|
|
||||||
}
|
|
@ -17,6 +17,9 @@ using Questionable.Model.Questing;
|
|||||||
namespace Questionable.Controller.Steps.Gathering;
|
namespace Questionable.Controller.Steps.Gathering;
|
||||||
|
|
||||||
internal sealed class DoGather(
|
internal sealed class DoGather(
|
||||||
|
GatheringController.GatheringRequest currentRequest,
|
||||||
|
GatheringNode currentNode,
|
||||||
|
bool revisitRequired,
|
||||||
GatheringController gatheringController,
|
GatheringController gatheringController,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
IGameGui gameGui,
|
IGameGui gameGui,
|
||||||
@ -26,34 +29,22 @@ internal sealed class DoGather(
|
|||||||
{
|
{
|
||||||
private const uint StatusGatheringRateUp = 218;
|
private const uint StatusGatheringRateUp = 218;
|
||||||
|
|
||||||
private GatheringController.GatheringRequest _currentRequest = null!;
|
|
||||||
private GatheringNode _currentNode = null!;
|
|
||||||
private bool _revisitRequired;
|
|
||||||
private bool _revisitTriggered;
|
private bool _revisitTriggered;
|
||||||
private bool _wasGathering;
|
private bool _wasGathering;
|
||||||
private SlotInfo? _slotToGather;
|
private SlotInfo? _slotToGather;
|
||||||
private Queue<EAction>? _actionQueue;
|
private Queue<EAction>? _actionQueue;
|
||||||
|
|
||||||
public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode,
|
|
||||||
bool revisitRequired)
|
|
||||||
{
|
|
||||||
_currentRequest = currentRequest;
|
|
||||||
_currentNode = currentNode;
|
|
||||||
_revisitRequired = revisitRequired;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start() => true;
|
public bool Start() => true;
|
||||||
|
|
||||||
public unsafe ETaskResult Update()
|
public unsafe ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (_revisitRequired && !_revisitTriggered)
|
if (revisitRequired && !_revisitTriggered)
|
||||||
{
|
{
|
||||||
logger.LogInformation("No revisit");
|
logger.LogInformation("No revisit");
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gatheringController.HasNodeDisappeared(_currentNode))
|
if (gatheringController.HasNodeDisappeared(currentNode))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Node disappeared");
|
logger.LogInformation("Node disappeared");
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
@ -78,9 +69,9 @@ internal sealed class DoGather(
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var slots = ReadSlots(addonGathering);
|
var slots = ReadSlots(addonGathering);
|
||||||
if (_currentRequest.Collectability > 0)
|
if (currentRequest.Collectability > 0)
|
||||||
{
|
{
|
||||||
var slot = slots.Single(x => x.ItemId == _currentRequest.ItemId);
|
var slot = slots.Single(x => x.ItemId == currentRequest.ItemId);
|
||||||
addonGathering->FireCallbackInt(slot.Index);
|
addonGathering->FireCallbackInt(slot.Index);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -103,7 +94,7 @@ internal sealed class DoGather(
|
|||||||
_actionQueue = GetNextActions(nodeCondition, slots);
|
_actionQueue = GetNextActions(nodeCondition, slots);
|
||||||
if (_actionQueue.Count == 0)
|
if (_actionQueue.Count == 0)
|
||||||
{
|
{
|
||||||
var slot = _slotToGather ?? slots.Single(x => x.ItemId == _currentRequest.ItemId);
|
var slot = _slotToGather ?? slots.Single(x => x.ItemId == currentRequest.ItemId);
|
||||||
addonGathering->FireCallbackInt(slot.Index);
|
addonGathering->FireCallbackInt(slot.Index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,9 +148,9 @@ internal sealed class DoGather(
|
|||||||
if (!gameFunctions.HasStatus(StatusGatheringRateUp))
|
if (!gameFunctions.HasStatus(StatusGatheringRateUp))
|
||||||
{
|
{
|
||||||
// do we have an alternative item? only happens for 'evaluation' leve quests
|
// do we have an alternative item? only happens for 'evaluation' leve quests
|
||||||
if (_currentRequest.AlternativeItemId != 0)
|
if (currentRequest.AlternativeItemId != 0)
|
||||||
{
|
{
|
||||||
var alternativeSlot = slots.Single(x => x.ItemId == _currentRequest.AlternativeItemId);
|
var alternativeSlot = slots.Single(x => x.ItemId == currentRequest.AlternativeItemId);
|
||||||
|
|
||||||
if (alternativeSlot.GatheringChance == 100)
|
if (alternativeSlot.GatheringChance == 100)
|
||||||
{
|
{
|
||||||
@ -195,7 +186,7 @@ internal sealed class DoGather(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var slot = slots.Single(x => x.ItemId == _currentRequest.ItemId);
|
var slot = slots.Single(x => x.ItemId == currentRequest.ItemId);
|
||||||
if (slot.GatheringChance > 0 && slot.GatheringChance < 100)
|
if (slot.GatheringChance > 0 && slot.GatheringChance < 100)
|
||||||
{
|
{
|
||||||
if (slot.GatheringChance >= 95 &&
|
if (slot.GatheringChance >= 95 &&
|
||||||
@ -243,7 +234,7 @@ internal sealed class DoGather(
|
|||||||
_revisitTriggered = true;
|
_revisitTriggered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"DoGather{(_revisitRequired ? " if revist" : "")}";
|
public override string ToString() => $"DoGather{(revisitRequired ? " if revist" : "")}";
|
||||||
|
|
||||||
private sealed record SlotInfo(int Index, uint ItemId, int GatheringChance, int BoonChance, int Quantity);
|
private sealed record SlotInfo(int Index, uint ItemId, int GatheringChance, int BoonChance, int Quantity);
|
||||||
|
|
||||||
|
@ -14,40 +14,31 @@ using Questionable.Model.Questing;
|
|||||||
namespace Questionable.Controller.Steps.Gathering;
|
namespace Questionable.Controller.Steps.Gathering;
|
||||||
|
|
||||||
internal sealed class DoGatherCollectable(
|
internal sealed class DoGatherCollectable(
|
||||||
|
GatheringController.GatheringRequest currentRequest,
|
||||||
|
GatheringNode currentNode,
|
||||||
|
bool revisitRequired,
|
||||||
GatheringController gatheringController,
|
GatheringController gatheringController,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
IGameGui gameGui,
|
IGameGui gameGui,
|
||||||
ILogger<DoGatherCollectable> logger) : ITask, IRevisitAware
|
ILogger<DoGatherCollectable> logger) : ITask, IRevisitAware
|
||||||
{
|
{
|
||||||
private GatheringController.GatheringRequest _currentRequest = null!;
|
|
||||||
private GatheringNode _currentNode = null!;
|
|
||||||
private bool _revisitRequired;
|
|
||||||
private bool _revisitTriggered;
|
private bool _revisitTriggered;
|
||||||
private Queue<EAction>? _actionQueue;
|
private Queue<EAction>? _actionQueue;
|
||||||
|
|
||||||
private bool? _expectedScrutiny;
|
private bool? _expectedScrutiny;
|
||||||
|
|
||||||
public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode,
|
|
||||||
bool revisitRequired)
|
|
||||||
{
|
|
||||||
_currentRequest = currentRequest;
|
|
||||||
_currentNode = currentNode;
|
|
||||||
_revisitRequired = revisitRequired;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start() => true;
|
public bool Start() => true;
|
||||||
|
|
||||||
public unsafe ETaskResult Update()
|
public unsafe ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (_revisitRequired && !_revisitTriggered)
|
if (revisitRequired && !_revisitTriggered)
|
||||||
{
|
{
|
||||||
logger.LogInformation("No revisit");
|
logger.LogInformation("No revisit");
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gatheringController.HasNodeDisappeared(_currentNode))
|
if (gatheringController.HasNodeDisappeared(currentNode))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Node disappeared");
|
logger.LogInformation("Node disappeared");
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
@ -103,7 +94,7 @@ internal sealed class DoGatherCollectable(
|
|||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeCondition.CollectabilityToGoal(_currentRequest.Collectability) > 0)
|
if (nodeCondition.CollectabilityToGoal(currentRequest.Collectability) > 0)
|
||||||
{
|
{
|
||||||
_actionQueue = GetNextActions(nodeCondition);
|
_actionQueue = GetNextActions(nodeCondition);
|
||||||
if (_actionQueue != null)
|
if (_actionQueue != null)
|
||||||
@ -147,7 +138,7 @@ internal sealed class DoGatherCollectable(
|
|||||||
|
|
||||||
Queue<EAction> actions = new();
|
Queue<EAction> actions = new();
|
||||||
|
|
||||||
uint neededCollectability = nodeCondition.CollectabilityToGoal(_currentRequest.Collectability);
|
uint neededCollectability = nodeCondition.CollectabilityToGoal(currentRequest.Collectability);
|
||||||
if (neededCollectability <= nodeCondition.CollectabilityFromMeticulous)
|
if (neededCollectability <= nodeCondition.CollectabilityFromMeticulous)
|
||||||
{
|
{
|
||||||
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ meticulous",
|
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ meticulous",
|
||||||
@ -203,7 +194,7 @@ internal sealed class DoGatherCollectable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() =>
|
public override string ToString() =>
|
||||||
$"DoGatherCollectable({SeIconChar.Collectible.ToIconString()}/{_expectedScrutiny} {_currentRequest.Collectability}){(_revisitRequired ? " if revist" : "")}";
|
$"DoGatherCollectable({SeIconChar.Collectible.ToIconString()}/{_expectedScrutiny} {currentRequest.Collectability}){(revisitRequired ? " if revist" : "")}";
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]
|
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]
|
||||||
private sealed record NodeCondition(
|
private sealed record NodeCondition(
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Globalization;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
@ -14,49 +13,40 @@ using Questionable.Model.Gathering;
|
|||||||
namespace Questionable.Controller.Steps.Gathering;
|
namespace Questionable.Controller.Steps.Gathering;
|
||||||
|
|
||||||
internal sealed class MoveToLandingLocation(
|
internal sealed class MoveToLandingLocation(
|
||||||
IServiceProvider serviceProvider,
|
ushort territoryId,
|
||||||
|
bool flyBetweenNodes,
|
||||||
|
GatheringNode gatheringNode,
|
||||||
|
MoveTo.Factory moveFactory,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
IObjectTable objectTable,
|
IObjectTable objectTable,
|
||||||
ILogger<MoveToLandingLocation> logger) : ITask
|
ILogger<MoveToLandingLocation> logger) : ITask
|
||||||
{
|
{
|
||||||
private ushort _territoryId;
|
|
||||||
private bool _flyBetweenNodes;
|
|
||||||
private GatheringNode _gatheringNode = null!;
|
|
||||||
private ITask _moveTask = null!;
|
private ITask _moveTask = null!;
|
||||||
|
|
||||||
public ITask With(ushort territoryId, bool flyBetweenNodes, GatheringNode gatheringNode)
|
|
||||||
{
|
|
||||||
_territoryId = territoryId;
|
|
||||||
_flyBetweenNodes = flyBetweenNodes;
|
|
||||||
_gatheringNode = gatheringNode;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
var location = _gatheringNode.Locations.First();
|
var location = gatheringNode.Locations.First();
|
||||||
if (_gatheringNode.Locations.Count > 1)
|
if (gatheringNode.Locations.Count > 1)
|
||||||
{
|
{
|
||||||
var gameObject = objectTable.SingleOrDefault(x =>
|
var gameObject = objectTable.SingleOrDefault(x =>
|
||||||
x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == _gatheringNode.DataId && x.IsTargetable);
|
x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == gatheringNode.DataId && x.IsTargetable);
|
||||||
if (gameObject == null)
|
if (gameObject == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
location = _gatheringNode.Locations.Single(x => Vector3.Distance(x.Position, gameObject.Position) < 0.1f);
|
location = gatheringNode.Locations.Single(x => Vector3.Distance(x.Position, gameObject.Position) < 0.1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
var (target, degrees, range) = GatheringMath.CalculateLandingLocation(location);
|
var (target, degrees, range) = GatheringMath.CalculateLandingLocation(location);
|
||||||
logger.LogInformation("Preliminary landing location: {Location}, with degrees = {Degrees}, range = {Range}",
|
logger.LogInformation("Preliminary landing location: {Location}, with degrees = {Degrees}, range = {Range}",
|
||||||
target.ToString("G", CultureInfo.InvariantCulture), degrees, range);
|
target.ToString("G", CultureInfo.InvariantCulture), degrees, range);
|
||||||
|
|
||||||
bool fly = _flyBetweenNodes && gameFunctions.IsFlyingUnlocked(_territoryId);
|
bool fly = flyBetweenNodes && gameFunctions.IsFlyingUnlocked(territoryId);
|
||||||
_moveTask = serviceProvider.GetRequiredService<Move.MoveInternal>()
|
_moveTask = moveFactory.Move(new MoveTo.MoveParams(territoryId, target, 0.25f, DataId: gatheringNode.DataId,
|
||||||
.With(_territoryId, target, 0.25f, dataId: _gatheringNode.DataId, fly: fly,
|
Fly: fly, IgnoreDistanceToObject: true));
|
||||||
ignoreDistanceToObject: true);
|
|
||||||
return _moveTask.Start();
|
return _moveTask.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() => _moveTask.Update();
|
public ETaskResult Update() => _moveTask.Update();
|
||||||
|
|
||||||
public override string ToString() => $"Land/{_moveTask}/{_flyBetweenNodes}";
|
public override string ToString() => $"Land/{_moveTask}/{flyBetweenNodes}";
|
||||||
}
|
}
|
||||||
|
@ -13,18 +13,18 @@ namespace Questionable.Controller.Steps.Gathering;
|
|||||||
|
|
||||||
internal static class TurnInDelivery
|
internal static class TurnInDelivery
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
|
internal sealed class Factory(ILoggerFactory loggerFactory) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
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 serviceProvider.GetRequiredService<SatisfactionSupplyTurnIn>();
|
return new SatisfactionSupplyTurnIn(loggerFactory.CreateLogger<SatisfactionSupplyTurnIn>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class SatisfactionSupplyTurnIn(ILogger<SatisfactionSupplyTurnIn> logger) : ITask
|
private sealed class SatisfactionSupplyTurnIn(ILogger<SatisfactionSupplyTurnIn> logger) : ITask
|
||||||
{
|
{
|
||||||
private ushort? _remainingAllowances;
|
private ushort? _remainingAllowances;
|
||||||
|
|
||||||
|
@ -14,7 +14,8 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Action
|
internal static class Action
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
|
internal sealed class Factory(GameFunctions gameFunctions, Mount.Factory mountFactory, ILoggerFactory loggerFactory)
|
||||||
|
: ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -23,54 +24,45 @@ internal static class Action
|
|||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(step.Action);
|
ArgumentNullException.ThrowIfNull(step.Action);
|
||||||
|
|
||||||
var task = serviceProvider.GetRequiredService<UseOnObject>()
|
var task = new UseOnObject(step.DataId, step.Action.Value, gameFunctions,
|
||||||
.With(step.DataId, step.Action.Value);
|
loggerFactory.CreateLogger<UseOnObject>());
|
||||||
if (step.Action.Value.RequiresMount())
|
if (step.Action.Value.RequiresMount())
|
||||||
return [task];
|
return [task];
|
||||||
else
|
else
|
||||||
{
|
return [mountFactory.Unmount(), task];
|
||||||
var unmount = serviceProvider.GetRequiredService<UnmountTask>();
|
|
||||||
return [unmount, task];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class UseOnObject(GameFunctions gameFunctions, ILogger<UseOnObject> logger) : ITask
|
private sealed class UseOnObject(
|
||||||
|
uint? dataId,
|
||||||
|
EAction action,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
ILogger<UseOnObject> logger) : ITask
|
||||||
{
|
{
|
||||||
private bool _usedAction;
|
private bool _usedAction;
|
||||||
private DateTime _continueAt = DateTime.MinValue;
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
|
|
||||||
public uint? DataId { get; set; }
|
|
||||||
public EAction Action { get; set; }
|
|
||||||
|
|
||||||
public ITask With(uint? dataId, EAction action)
|
|
||||||
{
|
|
||||||
DataId = dataId;
|
|
||||||
Action = action;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
if (DataId != null)
|
if (dataId != null)
|
||||||
{
|
{
|
||||||
IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId.Value);
|
IGameObject? gameObject = gameFunctions.FindObjectByDataId(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}", dataId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameObject.IsTargetable)
|
if (gameObject.IsTargetable)
|
||||||
{
|
{
|
||||||
_usedAction = gameFunctions.UseAction(gameObject, Action);
|
_usedAction = gameFunctions.UseAction(gameObject, 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(action);
|
||||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -85,18 +77,18 @@ internal static class Action
|
|||||||
|
|
||||||
if (!_usedAction)
|
if (!_usedAction)
|
||||||
{
|
{
|
||||||
if (DataId != null)
|
if (dataId != null)
|
||||||
{
|
{
|
||||||
IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId.Value);
|
IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId.Value);
|
||||||
if (gameObject == null || !gameObject.IsTargetable)
|
if (gameObject == null || !gameObject.IsTargetable)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
_usedAction = gameFunctions.UseAction(gameObject, Action);
|
_usedAction = gameFunctions.UseAction(gameObject, action);
|
||||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_usedAction = gameFunctions.UseAction(Action);
|
_usedAction = gameFunctions.UseAction(action);
|
||||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +98,6 @@ internal static class Action
|
|||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Action({Action})";
|
public override string ToString() => $"Action({action})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,11 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class AetherCurrent
|
internal static class AetherCurrent
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider, AetherCurrentData aetherCurrentData, IChatGui chatGui) : SimpleTaskFactory
|
internal sealed class Factory(
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
AetherCurrentData aetherCurrentData,
|
||||||
|
IChatGui chatGui,
|
||||||
|
ILoggerFactory loggerFactory) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -23,47 +27,37 @@ internal static class AetherCurrent
|
|||||||
|
|
||||||
if (!aetherCurrentData.IsValidAetherCurrent(step.TerritoryId, step.AetherCurrentId.Value))
|
if (!aetherCurrentData.IsValidAetherCurrent(step.TerritoryId, step.AetherCurrentId.Value))
|
||||||
{
|
{
|
||||||
chatGui.PrintError($"[Questionable] Aether current with id {step.AetherCurrentId} is referencing an invalid aether current, will skip attunement");
|
chatGui.PrintError(
|
||||||
|
$"[Questionable] Aether current with id {step.AetherCurrentId} is referencing an invalid aether current, will skip attunement");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return serviceProvider.GetRequiredService<DoAttune>()
|
return new DoAttune(step.DataId.Value, step.AetherCurrentId.Value, gameFunctions, loggerFactory.CreateLogger<DoAttune>());
|
||||||
.With(step.DataId.Value, step.AetherCurrentId.Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DoAttune(GameFunctions gameFunctions, ILogger<DoAttune> logger) : ITask
|
private sealed class DoAttune(uint dataId, uint aetherCurrentId, GameFunctions gameFunctions, ILogger<DoAttune> logger) : ITask
|
||||||
{
|
{
|
||||||
public uint DataId { get; set; }
|
|
||||||
public uint AetherCurrentId { get; set; }
|
|
||||||
|
|
||||||
public ITask With(uint dataId, uint aetherCurrentId)
|
|
||||||
{
|
|
||||||
DataId = dataId;
|
|
||||||
AetherCurrentId = aetherCurrentId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
if (!gameFunctions.IsAetherCurrentUnlocked(AetherCurrentId))
|
if (!gameFunctions.IsAetherCurrentUnlocked(aetherCurrentId))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Attuning to aether current {AetherCurrentId} / {DataId}", AetherCurrentId,
|
logger.LogInformation("Attuning to aether current {AetherCurrentId} / {DataId}", aetherCurrentId,
|
||||||
DataId);
|
dataId);
|
||||||
gameFunctions.InteractWith(DataId);
|
gameFunctions.InteractWith(dataId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation("Already attuned to aether current {AetherCurrentId} / {DataId}", AetherCurrentId,
|
logger.LogInformation("Already attuned to aether current {AetherCurrentId} / {DataId}", aetherCurrentId,
|
||||||
DataId);
|
dataId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() =>
|
public ETaskResult Update() =>
|
||||||
gameFunctions.IsAetherCurrentUnlocked(AetherCurrentId)
|
gameFunctions.IsAetherCurrentUnlocked(aetherCurrentId)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
|
|
||||||
public override string ToString() => $"AttuneAetherCurrent({AetherCurrentId})";
|
public override string ToString() => $"AttuneAetherCurrent({aetherCurrentId})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,10 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class AethernetShard
|
internal static class AethernetShard
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
|
internal sealed class Factory(
|
||||||
|
AetheryteFunctions aetheryteFunctions,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
ILoggerFactory loggerFactory) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -20,42 +23,35 @@ internal static class AethernetShard
|
|||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(step.AethernetShard);
|
ArgumentNullException.ThrowIfNull(step.AethernetShard);
|
||||||
|
|
||||||
return serviceProvider.GetRequiredService<DoAttune>()
|
return new DoAttune(step.AethernetShard.Value, aetheryteFunctions, gameFunctions,
|
||||||
.With(step.AethernetShard.Value);
|
loggerFactory.CreateLogger<DoAttune>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DoAttune(
|
private sealed class DoAttune(
|
||||||
|
EAetheryteLocation aetheryteLocation,
|
||||||
AetheryteFunctions aetheryteFunctions,
|
AetheryteFunctions aetheryteFunctions,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
ILogger<DoAttune> logger) : ITask
|
ILogger<DoAttune> logger) : ITask
|
||||||
{
|
{
|
||||||
public EAetheryteLocation AetheryteLocation { get; set; }
|
|
||||||
|
|
||||||
public ITask With(EAetheryteLocation aetheryteLocation)
|
|
||||||
{
|
|
||||||
AetheryteLocation = aetheryteLocation;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
if (!aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation))
|
if (!aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Attuning to aethernet shard {AethernetShard}", AetheryteLocation);
|
logger.LogInformation("Attuning to aethernet shard {AethernetShard}", aetheryteLocation);
|
||||||
gameFunctions.InteractWith((uint)AetheryteLocation, ObjectKind.Aetheryte);
|
gameFunctions.InteractWith((uint)aetheryteLocation, ObjectKind.Aetheryte);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation("Already attuned to aethernet shard {AethernetShard}", AetheryteLocation);
|
logger.LogInformation("Already attuned to aethernet shard {AethernetShard}", aetheryteLocation);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() =>
|
public ETaskResult Update() =>
|
||||||
aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation)
|
aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
|
|
||||||
public override string ToString() => $"AttuneAethernetShard({AetheryteLocation})";
|
public override string ToString() => $"AttuneAethernetShard({aetheryteLocation})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,10 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Aetheryte
|
internal static class Aetheryte
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
|
internal sealed class Factory(
|
||||||
|
AetheryteFunctions aetheryteFunctions,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
ILoggerFactory loggerFactory) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -19,42 +22,35 @@ internal static class Aetheryte
|
|||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(step.Aetheryte);
|
ArgumentNullException.ThrowIfNull(step.Aetheryte);
|
||||||
|
|
||||||
return serviceProvider.GetRequiredService<DoAttune>()
|
return new DoAttune(step.Aetheryte.Value, aetheryteFunctions, gameFunctions,
|
||||||
.With(step.Aetheryte.Value);
|
loggerFactory.CreateLogger<DoAttune>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DoAttune(
|
private sealed class DoAttune(
|
||||||
|
EAetheryteLocation aetheryteLocation,
|
||||||
AetheryteFunctions aetheryteFunctions,
|
AetheryteFunctions aetheryteFunctions,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
ILogger<DoAttune> logger) : ITask
|
ILogger<DoAttune> logger) : ITask
|
||||||
{
|
{
|
||||||
public EAetheryteLocation AetheryteLocation { get; set; }
|
|
||||||
|
|
||||||
public ITask With(EAetheryteLocation aetheryteLocation)
|
|
||||||
{
|
|
||||||
AetheryteLocation = aetheryteLocation;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
if (!aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation))
|
if (!aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Attuning to aetheryte {Aetheryte}", AetheryteLocation);
|
logger.LogInformation("Attuning to aetheryte {Aetheryte}", aetheryteLocation);
|
||||||
gameFunctions.InteractWith((uint)AetheryteLocation);
|
gameFunctions.InteractWith((uint)aetheryteLocation);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation("Already attuned to aetheryte {Aetheryte}", AetheryteLocation);
|
logger.LogInformation("Already attuned to aetheryte {Aetheryte}", aetheryteLocation);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() =>
|
public ETaskResult Update() =>
|
||||||
aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation)
|
aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
|
|
||||||
public override string ToString() => $"AttuneAetheryte({AetheryteLocation})";
|
public override string ToString() => $"AttuneAetheryte({aetheryteLocation})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,12 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Combat
|
internal static class Combat
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
|
internal sealed class Factory(
|
||||||
|
CombatController combatController,
|
||||||
|
Interact.Factory interactFactory,
|
||||||
|
Mount.Factory mountFactory,
|
||||||
|
UseItem.Factory useItemFactory,
|
||||||
|
QuestFunctions questFunctions) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -22,12 +27,11 @@ internal static class Combat
|
|||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
|
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
|
||||||
|
|
||||||
yield return serviceProvider.GetRequiredService<UnmountTask>();
|
yield return mountFactory.Unmount();
|
||||||
|
|
||||||
if (step.CombatDelaySecondsAtStart != null)
|
if (step.CombatDelaySecondsAtStart != null)
|
||||||
{
|
{
|
||||||
yield return serviceProvider.GetRequiredService<WaitAtStart.WaitDelay>()
|
yield return new WaitAtStart.WaitDelay(TimeSpan.FromSeconds(step.CombatDelaySecondsAtStart.Value));
|
||||||
.With(TimeSpan.FromSeconds(step.CombatDelaySecondsAtStart.Value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (step.EnemySpawnType)
|
switch (step.EnemySpawnType)
|
||||||
@ -36,8 +40,7 @@ internal static class Combat
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(step.DataId);
|
ArgumentNullException.ThrowIfNull(step.DataId);
|
||||||
|
|
||||||
yield return serviceProvider.GetRequiredService<Interact.DoInteract>()
|
yield return interactFactory.Interact(step.DataId.Value, quest, EInteractionType.None, true);
|
||||||
.With(step.DataId.Value, quest, EInteractionType.None, true);
|
|
||||||
yield return CreateTask(quest, sequence, step);
|
yield return CreateTask(quest, sequence, step);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -47,9 +50,8 @@ internal static class Combat
|
|||||||
ArgumentNullException.ThrowIfNull(step.DataId);
|
ArgumentNullException.ThrowIfNull(step.DataId);
|
||||||
ArgumentNullException.ThrowIfNull(step.ItemId);
|
ArgumentNullException.ThrowIfNull(step.ItemId);
|
||||||
|
|
||||||
yield return serviceProvider.GetRequiredService<UseItem.UseOnObject>()
|
yield return useItemFactory.OnObject(quest.Id, step.DataId.Value, step.ItemId.Value,
|
||||||
.With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags,
|
step.CompletionQuestVariablesFlags, true);
|
||||||
true);
|
|
||||||
yield return CreateTask(quest, sequence, step);
|
yield return CreateTask(quest, sequence, step);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -73,34 +75,32 @@ internal static class Combat
|
|||||||
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
|
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
|
||||||
|
|
||||||
bool isLastStep = sequence.Steps.Last() == step;
|
bool isLastStep = sequence.Steps.Last() == step;
|
||||||
return serviceProvider.GetRequiredService<HandleCombat>()
|
return CreateTask(quest.Id, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds,
|
||||||
.With(quest.Id, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds,
|
step.CompletionQuestVariablesFlags, step.ComplexCombatData);
|
||||||
step.CompletionQuestVariablesFlags, step.ComplexCombatData);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class HandleCombat(CombatController combatController, QuestFunctions questFunctions) : ITask
|
private HandleCombat CreateTask(ElementId elementId, bool isLastStep, EEnemySpawnType enemySpawnType,
|
||||||
{
|
IList<uint> killEnemyDataIds, IList<QuestWorkValue?> completionQuestVariablesFlags,
|
||||||
private bool _isLastStep;
|
IList<ComplexCombatData> complexCombatData)
|
||||||
private CombatController.CombatData _combatData = null!;
|
|
||||||
private IList<QuestWorkValue?> _completionQuestVariableFlags = null!;
|
|
||||||
|
|
||||||
public ITask With(ElementId elementId, bool isLastStep, EEnemySpawnType enemySpawnType, IList<uint> killEnemyDataIds,
|
|
||||||
IList<QuestWorkValue?> completionQuestVariablesFlags, IList<ComplexCombatData> complexCombatData)
|
|
||||||
{
|
{
|
||||||
_isLastStep = isLastStep;
|
return new HandleCombat(isLastStep, new CombatController.CombatData
|
||||||
_combatData = 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);
|
||||||
_completionQuestVariableFlags = completionQuestVariablesFlags;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool Start() => combatController.Start(_combatData);
|
private sealed class HandleCombat(
|
||||||
|
bool isLastStep,
|
||||||
|
CombatController.CombatData combatData,
|
||||||
|
IList<QuestWorkValue?> completionQuestVariableFlags,
|
||||||
|
CombatController combatController,
|
||||||
|
QuestFunctions questFunctions) : ITask
|
||||||
|
{
|
||||||
|
public bool Start() => combatController.Start(combatData);
|
||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
{
|
{
|
||||||
@ -108,13 +108,14 @@ internal static class Combat
|
|||||||
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) && _combatData.ElementId is QuestId questId)
|
if (QuestWorkUtils.HasCompletionFlags(completionQuestVariableFlags) &&
|
||||||
|
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(completionQuestVariableFlags, questWork))
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
else
|
else
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
@ -122,7 +123,7 @@ internal static class Combat
|
|||||||
|
|
||||||
// the last step, by definition, can only be progressed by the game recognizing we're in a new sequence,
|
// 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 (isLastStep)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -133,9 +134,9 @@ internal static class Combat
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (QuestWorkUtils.HasCompletionFlags(_completionQuestVariableFlags))
|
if (QuestWorkUtils.HasCompletionFlags(completionQuestVariableFlags))
|
||||||
return "HandleCombat(wait: QW flags)";
|
return "HandleCombat(wait: QW flags)";
|
||||||
else if (_isLastStep)
|
else if (isLastStep)
|
||||||
return "HandleCombat(wait: next sequence)";
|
return "HandleCombat(wait: next sequence)";
|
||||||
else
|
else
|
||||||
return "HandleCombat(wait: not in combat)";
|
return "HandleCombat(wait: not in combat)";
|
||||||
|
@ -18,18 +18,23 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Dive
|
internal static class Dive
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
|
internal sealed class Factory(ICondition condition, ILoggerFactory loggerFactory) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
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 serviceProvider.GetRequiredService<DoDive>();
|
return Dive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITask Dive()
|
||||||
|
{
|
||||||
|
return new DoDive(condition, loggerFactory.CreateLogger<DoDive>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DoDive(ICondition condition, ILogger<DoDive> logger)
|
private sealed class DoDive(ICondition condition, ILogger<DoDive> logger)
|
||||||
: AbstractDelayedTask(TimeSpan.FromSeconds(5))
|
: AbstractDelayedTask(TimeSpan.FromSeconds(5))
|
||||||
{
|
{
|
||||||
private readonly Queue<(uint Type, nint Key)> _keysToPress = [];
|
private readonly Queue<(uint Type, nint Key)> _keysToPress = [];
|
||||||
|
@ -10,7 +10,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Duty
|
internal static class Duty
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
|
internal sealed class Factory(GameFunctions gameFunctions, ICondition condition) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -18,33 +18,26 @@ 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 serviceProvider.GetRequiredService<OpenDutyFinder>()
|
|
||||||
.With(step.ContentFinderConditionId.Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class OpenDutyFinder(GameFunctions gameFunctions, ICondition condition) : ITask
|
private sealed class OpenDutyFinder(
|
||||||
|
uint contentFinderConditionId,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
ICondition condition) : ITask
|
||||||
{
|
{
|
||||||
public uint ContentFinderConditionId { get; set; }
|
|
||||||
|
|
||||||
public ITask With(uint contentFinderConditionId)
|
|
||||||
{
|
|
||||||
ContentFinderConditionId = contentFinderConditionId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
if (condition[ConditionFlag.InDutyQueue])
|
if (condition[ConditionFlag.InDutyQueue])
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
gameFunctions.OpenDutyFinder(ContentFinderConditionId);
|
gameFunctions.OpenDutyFinder(contentFinderConditionId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() => ETaskResult.TaskComplete;
|
public ETaskResult Update() => ETaskResult.TaskComplete;
|
||||||
|
|
||||||
public override string ToString() => $"OpenDutyFinder({ContentFinderConditionId})";
|
public override string ToString() => $"OpenDutyFinder({contentFinderConditionId})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Emote
|
internal static class Emote
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
|
internal sealed class Factory(ChatFunctions chatFunctions, Mount.Factory mountFactory) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -24,57 +24,39 @@ internal static class Emote
|
|||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(step.Emote);
|
ArgumentNullException.ThrowIfNull(step.Emote);
|
||||||
|
|
||||||
var unmount = serviceProvider.GetRequiredService<UnmountTask>();
|
var unmount = mountFactory.Unmount();
|
||||||
if (step.DataId != null)
|
if (step.DataId != null)
|
||||||
{
|
{
|
||||||
var task = serviceProvider.GetRequiredService<UseOnObject>().With(step.Emote.Value, step.DataId.Value);
|
var task = new UseOnObject(step.Emote.Value, step.DataId.Value, chatFunctions);
|
||||||
return [unmount, task];
|
return [unmount, task];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var task = serviceProvider.GetRequiredService<Use>().With(step.Emote.Value);
|
var task = new UseOnSelf(step.Emote.Value, chatFunctions);
|
||||||
return [unmount, task];
|
return [unmount, task];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class UseOnObject(ChatFunctions chatFunctions) : AbstractDelayedTask
|
private sealed class UseOnObject(EEmote emote, uint dataId, ChatFunctions chatFunctions) : AbstractDelayedTask
|
||||||
{
|
{
|
||||||
public EEmote Emote { get; set; }
|
|
||||||
public uint DataId { get; set; }
|
|
||||||
|
|
||||||
public ITask With(EEmote emote, uint dataId)
|
|
||||||
{
|
|
||||||
Emote = emote;
|
|
||||||
DataId = dataId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool StartInternal()
|
protected override bool StartInternal()
|
||||||
{
|
{
|
||||||
chatFunctions.UseEmote(DataId, Emote);
|
chatFunctions.UseEmote(dataId, emote);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Emote({Emote} on {DataId})";
|
public override string ToString() => $"Emote({emote} on {dataId})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class Use(ChatFunctions chatFunctions) : AbstractDelayedTask
|
private sealed class UseOnSelf(EEmote emote, ChatFunctions chatFunctions) : AbstractDelayedTask
|
||||||
{
|
{
|
||||||
public EEmote Emote { get; set; }
|
|
||||||
|
|
||||||
public ITask With(EEmote emote)
|
|
||||||
{
|
|
||||||
Emote = emote;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool StartInternal()
|
protected override bool StartInternal()
|
||||||
{
|
{
|
||||||
chatFunctions.UseEmote(Emote);
|
chatFunctions.UseEmote(emote);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Emote({Emote})";
|
public override string ToString() => $"Emote({emote})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class EquipItem
|
internal static class EquipItem
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
|
internal sealed class Factory(IDataManager dataManager, ILoggerFactory loggerFactory) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -24,14 +24,39 @@ internal static class EquipItem
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(step.ItemId);
|
ArgumentNullException.ThrowIfNull(step.ItemId);
|
||||||
return serviceProvider.GetRequiredService<DoEquip>()
|
return Equip(step.ItemId.Value);
|
||||||
.With(step.ItemId.Value);
|
}
|
||||||
|
|
||||||
|
private DoEquip Equip(uint itemId)
|
||||||
|
{
|
||||||
|
var item = dataManager.GetExcelSheet<Item>()!.GetRow(itemId) ??
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(itemId));
|
||||||
|
var targetSlots = GetEquipSlot(item) ?? throw new InvalidOperationException("Not a piece of equipment");
|
||||||
|
return new DoEquip(itemId, item, targetSlots, dataManager, loggerFactory.CreateLogger<DoEquip>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ushort>? GetEquipSlot(Item item)
|
||||||
|
{
|
||||||
|
return item.EquipSlotCategory.Row switch
|
||||||
|
{
|
||||||
|
>= 1 and <= 11 => [(ushort)(item.EquipSlotCategory.Row - 1)],
|
||||||
|
12 => [11, 12], // rings
|
||||||
|
13 => [0],
|
||||||
|
17 => [13], // soul crystal
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DoEquip(IDataManager dataManager, ILogger<DoEquip> logger) : ITask, IToastAware
|
private sealed class DoEquip(
|
||||||
|
uint itemId,
|
||||||
|
Item item,
|
||||||
|
List<ushort> targetSlots,
|
||||||
|
IDataManager dataManager,
|
||||||
|
ILogger<DoEquip> logger) : ITask, IToastAware
|
||||||
{
|
{
|
||||||
private const int MaxAttempts = 3;
|
private const int MaxAttempts = 3;
|
||||||
|
|
||||||
private static readonly IReadOnlyList<InventoryType> SourceInventoryTypes =
|
private static readonly IReadOnlyList<InventoryType> SourceInventoryTypes =
|
||||||
[
|
[
|
||||||
InventoryType.ArmoryMainHand,
|
InventoryType.ArmoryMainHand,
|
||||||
@ -55,22 +80,9 @@ internal static class EquipItem
|
|||||||
InventoryType.Inventory4,
|
InventoryType.Inventory4,
|
||||||
];
|
];
|
||||||
|
|
||||||
private uint _itemId;
|
|
||||||
private Item _item = null!;
|
|
||||||
private List<ushort> _targetSlots = [];
|
|
||||||
private int _attempts;
|
private int _attempts;
|
||||||
|
|
||||||
private DateTime _continueAt = DateTime.MaxValue;
|
private DateTime _continueAt = DateTime.MaxValue;
|
||||||
|
|
||||||
public ITask With(uint itemId)
|
|
||||||
{
|
|
||||||
_itemId = itemId;
|
|
||||||
_item = dataManager.GetExcelSheet<Item>()!.GetRow(itemId) ??
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(itemId));
|
|
||||||
_targetSlots = GetEquipSlot(_item) ?? throw new InvalidOperationException("Not a piece of equipment");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
Equip();
|
Equip();
|
||||||
@ -87,10 +99,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 == itemId)
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,12 +125,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 == itemId)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Already equipped {Item}, skipping step", _item.Name?.ToString());
|
logger.LogInformation("Already equipped {Item}, skipping step", item.Name?.ToString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,24 +141,24 @@ internal static class EquipItem
|
|||||||
if (sourceContainer == null)
|
if (sourceContainer == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (inventoryManager->GetItemCountInContainer(_itemId, sourceInventoryType, true) == 0 &&
|
if (inventoryManager->GetItemCountInContainer(itemId, sourceInventoryType, true) == 0 &&
|
||||||
inventoryManager->GetItemCountInContainer(_itemId, sourceInventoryType) == 0)
|
inventoryManager->GetItemCountInContainer(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 != 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}",
|
||||||
@ -160,19 +172,7 @@ internal static class EquipItem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<ushort>? GetEquipSlot(Item item)
|
public override string ToString() => $"Equip({item.Name})";
|
||||||
{
|
|
||||||
return item.EquipSlotCategory.Row switch
|
|
||||||
{
|
|
||||||
>= 1 and <= 11 => [(ushort)(item.EquipSlotCategory.Row - 1)],
|
|
||||||
12 => [11, 12], // rings
|
|
||||||
13 => [0],
|
|
||||||
17 => [13], // soul crystal
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() => $"Equip({_item.Name})";
|
|
||||||
|
|
||||||
public bool OnErrorToast(SeString message)
|
public bool OnErrorToast(SeString message)
|
||||||
{
|
{
|
||||||
|
@ -10,18 +10,23 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class EquipRecommended
|
internal static class EquipRecommended
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
|
internal sealed class Factory(IClientState clientState, IChatGui chatGui) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
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 serviceProvider.GetRequiredService<DoEquipRecommended>();
|
return DoEquip();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITask DoEquip()
|
||||||
|
{
|
||||||
|
return new DoEquipRecommended(clientState, chatGui);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class BeforeDutyOrInstance(IServiceProvider serviceProvider) : SimpleTaskFactory
|
internal sealed class BeforeDutyOrInstance(IClientState clientState, IChatGui chatGui) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -30,11 +35,11 @@ internal static class EquipRecommended
|
|||||||
step.InteractionType != EInteractionType.Combat)
|
step.InteractionType != EInteractionType.Combat)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return serviceProvider.GetRequiredService<DoEquipRecommended>();
|
return new DoEquipRecommended(clientState, chatGui);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed unsafe class DoEquipRecommended(IClientState clientState, IChatGui chatGui) : ITask
|
private sealed unsafe class DoEquipRecommended(IClientState clientState, IChatGui chatGui) : ITask
|
||||||
{
|
{
|
||||||
private bool _equipped;
|
private bool _equipped;
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Interact
|
internal static class Interact
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
|
internal sealed class Factory(GameFunctions gameFunctions, ICondition condition, ILoggerFactory loggerFactory) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -36,41 +36,46 @@ internal static class Interact
|
|||||||
|
|
||||||
// if we're fast enough, it is possible to get the smalltalk prompt
|
// if we're fast enough, it is possible to get the smalltalk prompt
|
||||||
if (sequence.Sequence == 0 && sequence.Steps.IndexOf(step) == 0)
|
if (sequence.Sequence == 0 && sequence.Steps.IndexOf(step) == 0)
|
||||||
yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>();
|
yield return new WaitAtEnd.WaitDelay();
|
||||||
|
|
||||||
yield return serviceProvider.GetRequiredService<DoInteract>()
|
yield return Interact(step.DataId.Value, quest, step.InteractionType,
|
||||||
.With(step.DataId.Value, quest, step.InteractionType,
|
|
||||||
step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId);
|
step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal ITask Interact(uint dataId, Quest? quest, EInteractionType interactionType, bool skipMarkerCheck = false)
|
||||||
|
{
|
||||||
|
return new DoInteract(dataId, quest, interactionType, skipMarkerCheck, gameFunctions, condition,
|
||||||
|
loggerFactory.CreateLogger<DoInteract>());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DoInteract(GameFunctions gameFunctions, ICondition condition, ILogger<DoInteract> logger)
|
internal sealed class DoInteract(
|
||||||
|
uint dataId,
|
||||||
|
Quest? quest,
|
||||||
|
EInteractionType interactionType,
|
||||||
|
bool skipMarkerCheck,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
ICondition condition,
|
||||||
|
ILogger<DoInteract> logger)
|
||||||
: ITask, IConditionChangeAware
|
: ITask, IConditionChangeAware
|
||||||
{
|
{
|
||||||
private bool _needsUnmount;
|
private bool _needsUnmount;
|
||||||
private EInteractionState _interactionState = EInteractionState.None;
|
private EInteractionState _interactionState = EInteractionState.None;
|
||||||
private DateTime _continueAt = DateTime.MinValue;
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
|
|
||||||
private uint DataId { get; set; }
|
public Quest? Quest => quest;
|
||||||
public Quest? Quest { get; private set; }
|
public EInteractionType InteractionType
|
||||||
public EInteractionType InteractionType { get; set; }
|
|
||||||
private bool SkipMarkerCheck { get; set; }
|
|
||||||
|
|
||||||
public DoInteract With(uint dataId, Quest? quest, EInteractionType interactionType, bool skipMarkerCheck)
|
|
||||||
{
|
{
|
||||||
DataId = dataId;
|
get => interactionType;
|
||||||
Quest = quest;
|
set => interactionType = value;
|
||||||
InteractionType = interactionType;
|
|
||||||
SkipMarkerCheck = skipMarkerCheck;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId);
|
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}", dataId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +83,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", dataId);
|
||||||
_needsUnmount = true;
|
_needsUnmount = true;
|
||||||
gameFunctions.Unmount();
|
gameFunctions.Unmount();
|
||||||
_continueAt = DateTime.Now.AddSeconds(1);
|
_continueAt = DateTime.Now.AddSeconds(1);
|
||||||
@ -117,10 +122,10 @@ internal static class Interact
|
|||||||
if (_interactionState == EInteractionState.InteractionConfirmed)
|
if (_interactionState == EInteractionState.InteractionConfirmed)
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
if (InteractionType == EInteractionType.InternalGather && condition[ConditionFlag.Gathering])
|
if (interactionType == EInteractionType.InternalGather && condition[ConditionFlag.Gathering])
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId);
|
IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId);
|
||||||
if (gameObject == null || !gameObject.IsTargetable || !HasAnyMarker(gameObject))
|
if (gameObject == null || !gameObject.IsTargetable || !HasAnyMarker(gameObject))
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
@ -133,14 +138,14 @@ internal static class Interact
|
|||||||
|
|
||||||
private unsafe bool HasAnyMarker(IGameObject gameObject)
|
private unsafe bool HasAnyMarker(IGameObject gameObject)
|
||||||
{
|
{
|
||||||
if (SkipMarkerCheck || gameObject.ObjectKind != ObjectKind.EventNpc)
|
if (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})";
|
public override string ToString() => $"Interact({dataId})";
|
||||||
|
|
||||||
public void OnConditionChange(ConditionFlag flag, bool value)
|
public void OnConditionChange(ConditionFlag flag, bool value)
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,11 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Jump
|
internal static class Jump
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
|
internal sealed class Factory(
|
||||||
|
MovementController movementController,
|
||||||
|
IClientState clientState,
|
||||||
|
IFramework framework,
|
||||||
|
ILoggerFactory loggerFactory) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -21,43 +25,39 @@ 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 serviceProvider.GetRequiredService<SingleJump>()
|
|
||||||
.With(step.DataId, step.JumpDestination, step.Comment);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
return RepeatedJumps(step.DataId, step.JumpDestination, step.Comment);
|
||||||
return serviceProvider.GetRequiredService<RepeatedJumps>()
|
}
|
||||||
.With(step.DataId, step.JumpDestination, step.Comment);
|
|
||||||
}
|
public ITask SingleJump(uint? dataId, JumpDestination jumpDestination, string? comment)
|
||||||
|
{
|
||||||
|
return new DoSingleJump(dataId, jumpDestination, comment, movementController, clientState, framework);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITask RepeatedJumps(uint? dataId, JumpDestination jumpDestination, string? comment)
|
||||||
|
{
|
||||||
|
return new DoRepeatedJumps(dataId, jumpDestination, comment, movementController, clientState, framework,
|
||||||
|
loggerFactory.CreateLogger<DoRepeatedJumps>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class SingleJump(
|
private class DoSingleJump(
|
||||||
|
uint? dataId,
|
||||||
|
JumpDestination jumpDestination,
|
||||||
|
string? comment,
|
||||||
MovementController movementController,
|
MovementController movementController,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
IFramework framework) : ITask
|
IFramework framework) : ITask
|
||||||
{
|
{
|
||||||
public uint? DataId { get; set; }
|
|
||||||
public JumpDestination JumpDestination { get; set; } = null!;
|
|
||||||
public string? Comment { get; set; }
|
|
||||||
|
|
||||||
public ITask With(uint? dataId, JumpDestination jumpDestination, string? comment)
|
|
||||||
{
|
|
||||||
DataId = dataId;
|
|
||||||
JumpDestination = jumpDestination;
|
|
||||||
Comment = comment ?? string.Empty;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool Start()
|
public virtual bool Start()
|
||||||
{
|
{
|
||||||
float stopDistance = JumpDestination.CalculateStopDistance();
|
float stopDistance = jumpDestination.CalculateStopDistance();
|
||||||
if ((clientState.LocalPlayer!.Position - JumpDestination.Position).Length() <= stopDistance)
|
if ((clientState.LocalPlayer!.Position - jumpDestination.Position).Length() <= stopDistance)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
movementController.NavigateTo(EMovementType.Quest, DataId, [JumpDestination.Position], false, false,
|
movementController.NavigateTo(EMovementType.Quest, dataId, [jumpDestination.Position], false, false,
|
||||||
JumpDestination.StopDistance ?? stopDistance);
|
jumpDestination.StopDistance ?? stopDistance);
|
||||||
framework.RunOnTick(() =>
|
framework.RunOnTick(() =>
|
||||||
{
|
{
|
||||||
unsafe
|
unsafe
|
||||||
@ -65,7 +65,7 @@ internal static class Jump
|
|||||||
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2);
|
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TimeSpan.FromSeconds(JumpDestination.DelaySeconds ?? 0.5f));
|
TimeSpan.FromSeconds(jumpDestination.DelaySeconds ?? 0.5f));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,22 +81,28 @@ internal static class Jump
|
|||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Jump({Comment})";
|
public override string ToString() => $"Jump({comment})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class RepeatedJumps(
|
private sealed class DoRepeatedJumps(
|
||||||
|
uint? dataId,
|
||||||
|
JumpDestination jumpDestination,
|
||||||
|
string? comment,
|
||||||
MovementController movementController,
|
MovementController movementController,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
IFramework framework,
|
IFramework framework,
|
||||||
ILogger<RepeatedJumps> logger) : SingleJump(movementController, clientState, framework)
|
ILogger<DoRepeatedJumps> logger)
|
||||||
|
: DoSingleJump(dataId, jumpDestination, comment, movementController, clientState, framework)
|
||||||
{
|
{
|
||||||
|
private readonly JumpDestination _jumpDestination = jumpDestination;
|
||||||
|
private readonly string? _comment = comment;
|
||||||
private readonly IClientState _clientState = clientState;
|
private readonly IClientState _clientState = clientState;
|
||||||
private DateTime _continueAt = DateTime.MinValue;
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
private int _attempts;
|
private int _attempts;
|
||||||
|
|
||||||
public override bool Start()
|
public override bool Start()
|
||||||
{
|
{
|
||||||
_continueAt = DateTime.Now + TimeSpan.FromSeconds(2 * (JumpDestination.DelaySeconds ?? 0.5f));
|
_continueAt = DateTime.Now + TimeSpan.FromSeconds(2 * (_jumpDestination.DelaySeconds ?? 0.5f));
|
||||||
return base.Start();
|
return base.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,13 +111,13 @@ internal static class Jump
|
|||||||
if (DateTime.Now < _continueAt)
|
if (DateTime.Now < _continueAt)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
float stopDistance = JumpDestination.CalculateStopDistance();
|
float stopDistance = _jumpDestination.CalculateStopDistance();
|
||||||
if ((_clientState.LocalPlayer!.Position - JumpDestination.Position).Length() <= stopDistance ||
|
if ((_clientState.LocalPlayer!.Position - _jumpDestination.Position).Length() <= stopDistance ||
|
||||||
_clientState.LocalPlayer.Position.Y >= JumpDestination.Position.Y - 0.5f)
|
_clientState.LocalPlayer.Position.Y >= _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);
|
_jumpDestination.Position.Y - 0.5f);
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2);
|
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2);
|
||||||
@ -121,10 +127,10 @@ 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(_jumpDestination.DelaySeconds ?? 0.5f);
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"RepeatedJump({Comment})";
|
public override string ToString() => $"RepeatedJump({_comment})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,10 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class Say
|
internal static class Say
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider, ExcelFunctions excelFunctions) : ITaskFactory
|
internal sealed class Factory(
|
||||||
|
ChatFunctions chatFunctions,
|
||||||
|
Mount.Factory mountFactory,
|
||||||
|
ExcelFunctions excelFunctions) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -21,31 +24,24 @@ internal static class Say
|
|||||||
ArgumentNullException.ThrowIfNull(step.ChatMessage);
|
ArgumentNullException.ThrowIfNull(step.ChatMessage);
|
||||||
|
|
||||||
string? excelString =
|
string? excelString =
|
||||||
excelFunctions.GetDialogueText(quest, step.ChatMessage.ExcelSheet, step.ChatMessage.Key, false).GetString();
|
excelFunctions.GetDialogueText(quest, step.ChatMessage.ExcelSheet, step.ChatMessage.Key, false)
|
||||||
|
.GetString();
|
||||||
ArgumentNullException.ThrowIfNull(excelString);
|
ArgumentNullException.ThrowIfNull(excelString);
|
||||||
|
|
||||||
var unmount = serviceProvider.GetRequiredService<UnmountTask>();
|
var unmount = mountFactory.Unmount();
|
||||||
var task = serviceProvider.GetRequiredService<UseChat>().With(excelString);
|
var task = new UseChat(excelString, chatFunctions);
|
||||||
return [unmount, task];
|
return [unmount, task];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class UseChat(ChatFunctions chatFunctions) : AbstractDelayedTask
|
private sealed class UseChat(string chatMessage, ChatFunctions chatFunctions) : AbstractDelayedTask
|
||||||
{
|
{
|
||||||
public string ChatMessage { get; set; } = null!;
|
|
||||||
|
|
||||||
public ITask With(string chatMessage)
|
|
||||||
{
|
|
||||||
ChatMessage = chatMessage;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool StartInternal()
|
protected override bool StartInternal()
|
||||||
{
|
{
|
||||||
chatFunctions.ExecuteCommand($"/say {ChatMessage}");
|
chatFunctions.ExecuteCommand($"/say {chatMessage}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Say({ChatMessage})";
|
public override string ToString() => $"Say({chatMessage})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,17 @@ internal static class UseItem
|
|||||||
public const int VesperBayAetheryteTicket = 30362;
|
public const int VesperBayAetheryteTicket = 30362;
|
||||||
|
|
||||||
internal sealed class Factory(
|
internal sealed class Factory(
|
||||||
IServiceProvider serviceProvider,
|
Mount.Factory mountFactory,
|
||||||
|
MoveTo.Factory moveFactory,
|
||||||
|
Interact.Factory interactFactory,
|
||||||
|
AetheryteShortcut.Factory aetheryteShortcutFactory,
|
||||||
|
AethernetShortcut.Factory aethernetShortcutFactory,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
QuestFunctions questFunctions,
|
||||||
|
ICondition condition,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
TerritoryData territoryData,
|
TerritoryData territoryData,
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
ILogger<Factory> logger)
|
ILogger<Factory> logger)
|
||||||
: ITaskFactory
|
: ITaskFactory
|
||||||
{
|
{
|
||||||
@ -48,8 +56,7 @@ internal static class UseItem
|
|||||||
return CreateVesperBayFallbackTask();
|
return CreateVesperBayFallbackTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
var task = serviceProvider.GetRequiredService<Use>()
|
var task = OnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||||
.With(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();
|
||||||
@ -59,47 +66,69 @@ internal static class UseItem
|
|||||||
task,
|
task,
|
||||||
new WaitConditionTask(() => clientState.TerritoryType == 140,
|
new WaitConditionTask(() => clientState.TerritoryType == 140,
|
||||||
$"Wait(territory: {territoryData.GetNameAndId(140)})"),
|
$"Wait(territory: {territoryData.GetNameAndId(140)})"),
|
||||||
serviceProvider.GetRequiredService<MountTask>()
|
mountFactory.Mount(140,
|
||||||
.With(140,
|
nextPosition != null ? Mount.EMountIf.AwayFromPosition : Mount.EMountIf.Always,
|
||||||
nextPosition != null ? MountTask.EMountIf.AwayFromPosition : MountTask.EMountIf.Always,
|
nextPosition),
|
||||||
nextPosition),
|
moveFactory.Move(new MoveTo.MoveParams(140, new(-408.92343f, 23.167036f, -351.16223f), 0.25f,
|
||||||
serviceProvider.GetRequiredService<Move.MoveInternal>()
|
DataId: null, DisableNavMesh: true, Sprint: false, Fly: false))
|
||||||
.With(140, new(-408.92343f, 23.167036f, -351.16223f), 0.25f, dataId: null, disableNavMesh: true,
|
|
||||||
sprint: false, fly: false)
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
var unmount = serviceProvider.GetRequiredService<UnmountTask>();
|
var unmount = mountFactory.Unmount();
|
||||||
if (step.GroundTarget == true)
|
if (step.GroundTarget == true)
|
||||||
{
|
{
|
||||||
ITask task;
|
ITask task;
|
||||||
if (step.DataId != null)
|
if (step.DataId != null)
|
||||||
task = serviceProvider.GetRequiredService<UseOnGround>()
|
task = OnGroundTarget(quest.Id, step.DataId.Value, step.ItemId.Value,
|
||||||
.With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
step.CompletionQuestVariablesFlags);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(step.Position);
|
ArgumentNullException.ThrowIfNull(step.Position);
|
||||||
task = serviceProvider.GetRequiredService<UseOnPosition>()
|
task = OnPosition(quest.Id, step.Position.Value, step.ItemId.Value,
|
||||||
.With(quest.Id, step.Position.Value, step.ItemId.Value,
|
step.CompletionQuestVariablesFlags);
|
||||||
step.CompletionQuestVariablesFlags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [unmount, task];
|
return [unmount, task];
|
||||||
}
|
}
|
||||||
else if (step.DataId != null)
|
else if (step.DataId != null)
|
||||||
{
|
{
|
||||||
var task = serviceProvider.GetRequiredService<UseOnObject>()
|
var task = OnObject(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||||
.With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
|
||||||
return [unmount, task];
|
return [unmount, task];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var task = serviceProvider.GetRequiredService<Use>()
|
var task = OnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||||
.With(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");
|
||||||
@ -107,28 +136,32 @@ 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 serviceProvider.GetRequiredService<AetheryteShortcut.UseAetheryteShortcut>()
|
yield return aetheryteShortcutFactory.Use(null, null, EAetheryteLocation.Limsa, territoryId);
|
||||||
.With(null, null, EAetheryteLocation.Limsa, territoryId);
|
yield return aethernetShortcutFactory.Use(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist);
|
||||||
yield return serviceProvider.GetRequiredService<AethernetShortcut.UseAethernetShortcut>()
|
yield return new WaitAtEnd.WaitDelay();
|
||||||
.With(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist);
|
yield return
|
||||||
yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>();
|
moveFactory.Move(new MoveTo.MoveParams(territoryId, destination, DataId: npcId, Sprint: false));
|
||||||
yield return serviceProvider.GetRequiredService<Move.MoveInternal>()
|
yield return interactFactory.Interact(npcId, null, EInteractionType.None, true);
|
||||||
.With(territoryId, destination, dataId: npcId, sprint: false);
|
|
||||||
yield return serviceProvider.GetRequiredService<Interact.DoInteract>()
|
|
||||||
.With(npcId, null, EInteractionType.None, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal abstract class UseItemBase(QuestFunctions questFunctions, ICondition condition, ILogger logger) : ITask
|
private abstract class UseItemBase(
|
||||||
|
ElementId? questId,
|
||||||
|
uint itemId,
|
||||||
|
IList<QuestWorkValue?> completionQuestVariablesFlags,
|
||||||
|
bool startingCombat,
|
||||||
|
QuestFunctions questFunctions,
|
||||||
|
ICondition condition,
|
||||||
|
ILogger logger) : ITask
|
||||||
{
|
{
|
||||||
private bool _usedItem;
|
private bool _usedItem;
|
||||||
private DateTime _continueAt;
|
private DateTime _continueAt;
|
||||||
private int _itemCount;
|
private int _itemCount;
|
||||||
|
|
||||||
public ElementId? QuestId { get; set; }
|
public ElementId? QuestId => questId;
|
||||||
public uint ItemId { get; set; }
|
public uint ItemId => itemId;
|
||||||
public IList<QuestWorkValue?> CompletionQuestVariablesFlags { get; set; } = new List<QuestWorkValue?>();
|
public IList<QuestWorkValue?> CompletionQuestVariablesFlags => completionQuestVariablesFlags;
|
||||||
public bool StartingCombat { get; set; }
|
public bool StartingCombat => startingCombat;
|
||||||
|
|
||||||
protected abstract bool UseItem();
|
protected abstract bool UseItem();
|
||||||
|
|
||||||
@ -149,9 +182,9 @@ internal static class UseItem
|
|||||||
|
|
||||||
public unsafe ETaskResult Update()
|
public unsafe ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (QuestId is QuestId questId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
|
if (QuestId is QuestId realQuestId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
|
||||||
{
|
{
|
||||||
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(questId);
|
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(realQuestId);
|
||||||
if (questWork != null &&
|
if (questWork != null &&
|
||||||
QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork))
|
QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork))
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
@ -203,96 +236,66 @@ internal static class UseItem
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal sealed class UseOnGround(
|
private sealed class UseOnGround(
|
||||||
|
ElementId? questId,
|
||||||
|
uint dataId,
|
||||||
|
uint itemId,
|
||||||
|
IList<QuestWorkValue?> completionQuestVariablesFlags,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger<UseOnGround> logger)
|
ILogger<UseOnGround> logger)
|
||||||
: UseItemBase(questFunctions, condition, logger)
|
: UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger)
|
||||||
{
|
{
|
||||||
public uint DataId { get; set; }
|
protected override bool UseItem() => gameFunctions.UseItemOnGround(dataId, ItemId);
|
||||||
|
|
||||||
public ITask With(ElementId? questId, uint dataId, uint itemId,
|
public override string ToString() => $"UseItem({ItemId} on ground at {dataId})";
|
||||||
IList<QuestWorkValue?> completionQuestVariablesFlags)
|
|
||||||
{
|
|
||||||
QuestId = questId;
|
|
||||||
DataId = dataId;
|
|
||||||
ItemId = itemId;
|
|
||||||
CompletionQuestVariablesFlags = completionQuestVariablesFlags;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool UseItem() => gameFunctions.UseItemOnGround(DataId, ItemId);
|
|
||||||
|
|
||||||
public override string ToString() => $"UseItem({ItemId} on ground at {DataId})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class UseOnPosition(
|
private sealed class UseOnPosition(
|
||||||
|
ElementId? questId,
|
||||||
|
Vector3 position,
|
||||||
|
uint itemId,
|
||||||
|
IList<QuestWorkValue?> completionQuestVariablesFlags,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger<UseOnPosition> logger)
|
ILogger<UseOnPosition> logger)
|
||||||
: UseItemBase(questFunctions, condition, logger)
|
: UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger)
|
||||||
{
|
{
|
||||||
public Vector3 Position { get; set; }
|
protected override bool UseItem() => gameFunctions.UseItemOnPosition(position, ItemId);
|
||||||
|
|
||||||
public ITask With(ElementId? questId, Vector3 position, uint itemId,
|
|
||||||
IList<QuestWorkValue?> completionQuestVariablesFlags)
|
|
||||||
{
|
|
||||||
QuestId = questId;
|
|
||||||
Position = position;
|
|
||||||
ItemId = itemId;
|
|
||||||
CompletionQuestVariablesFlags = completionQuestVariablesFlags;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool UseItem() => gameFunctions.UseItemOnPosition(Position, ItemId);
|
|
||||||
|
|
||||||
public override string ToString() =>
|
public override string ToString() =>
|
||||||
$"UseItem({ItemId} on ground at {Position.ToString("G", CultureInfo.InvariantCulture)})";
|
$"UseItem({ItemId} on ground at {position.ToString("G", CultureInfo.InvariantCulture)})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class UseOnObject(
|
private sealed class UseOnObject(
|
||||||
|
ElementId? questId,
|
||||||
|
uint dataId,
|
||||||
|
uint itemId,
|
||||||
|
IList<QuestWorkValue?> completionQuestVariablesFlags,
|
||||||
|
bool startingCombat,
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger<UseOnObject> logger)
|
ILogger<UseOnObject> logger)
|
||||||
: UseItemBase(questFunctions, condition, logger)
|
: UseItemBase(questId, itemId, completionQuestVariablesFlags, startingCombat, questFunctions, condition, logger)
|
||||||
{
|
{
|
||||||
public uint DataId { get; set; }
|
protected override bool UseItem() => gameFunctions.UseItem(dataId, ItemId);
|
||||||
|
|
||||||
public ITask With(ElementId? questId, uint dataId, uint itemId,
|
public override string ToString() => $"UseItem({ItemId} on {dataId})";
|
||||||
IList<QuestWorkValue?> completionQuestVariablesFlags,
|
|
||||||
bool startingCombat = false)
|
|
||||||
{
|
|
||||||
QuestId = questId;
|
|
||||||
DataId = dataId;
|
|
||||||
ItemId = itemId;
|
|
||||||
StartingCombat = startingCombat;
|
|
||||||
CompletionQuestVariablesFlags = completionQuestVariablesFlags;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool UseItem() => gameFunctions.UseItem(DataId, ItemId);
|
|
||||||
|
|
||||||
public override string ToString() => $"UseItem({ItemId} on {DataId})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class Use(
|
private sealed class Use(
|
||||||
|
ElementId? questId,
|
||||||
|
uint itemId,
|
||||||
|
IList<QuestWorkValue?> completionQuestVariablesFlags,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger<Use> logger)
|
ILogger<Use> logger)
|
||||||
: UseItemBase(questFunctions, condition, logger)
|
: UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger)
|
||||||
{
|
{
|
||||||
public ITask With(ElementId? questId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
|
|
||||||
{
|
|
||||||
QuestId = questId;
|
|
||||||
ItemId = itemId;
|
|
||||||
CompletionQuestVariablesFlags = completionQuestVariablesFlags;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool UseItem() => gameFunctions.UseItem(ItemId);
|
protected override bool UseItem() => gameFunctions.UseItem(ItemId);
|
||||||
|
|
||||||
public override string ToString() => $"UseItem({ItemId})";
|
public override string ToString() => $"UseItem({ItemId})";
|
||||||
|
@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
|
|||||||
using LLib.GameUI;
|
using LLib.GameUI;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
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;
|
||||||
@ -17,31 +18,23 @@ namespace Questionable.Controller.Steps.Leves;
|
|||||||
|
|
||||||
internal static class InitiateLeve
|
internal static class InitiateLeve
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider, ICondition condition) : ITaskFactory
|
internal sealed class Factory(IGameGui gameGui, ICondition condition) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
if (step.InteractionType != EInteractionType.InitiateLeve)
|
if (step.InteractionType != EInteractionType.InitiateLeve)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
yield return serviceProvider.GetRequiredService<SkipInitiateIfActive>().With(quest.Id);
|
yield return new SkipInitiateIfActive(quest.Id);
|
||||||
yield return serviceProvider.GetRequiredService<OpenJournal>().With(quest.Id);
|
yield return new OpenJournal(quest.Id);
|
||||||
yield return serviceProvider.GetRequiredService<Initiate>().With(quest.Id);
|
yield return new Initiate(quest.Id, gameGui);
|
||||||
yield return serviceProvider.GetRequiredService<SelectDifficulty>();
|
yield return new SelectDifficulty(gameGui);
|
||||||
yield return new WaitConditionTask(() => condition[ConditionFlag.BoundByDuty], "Wait(BoundByDuty)");
|
yield return new WaitConditionTask(() => condition[ConditionFlag.BoundByDuty], "Wait(BoundByDuty)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed unsafe class SkipInitiateIfActive : ITask
|
internal sealed unsafe class SkipInitiateIfActive(ElementId elementId) : ITask
|
||||||
{
|
{
|
||||||
private ElementId _elementId = null!;
|
|
||||||
|
|
||||||
public ITask With(ElementId elementId)
|
|
||||||
{
|
|
||||||
_elementId = elementId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start() => true;
|
public bool Start() => true;
|
||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
@ -50,31 +43,23 @@ internal static class InitiateLeve
|
|||||||
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 == elementId.Value)
|
||||||
return ETaskResult.SkipRemainingTasksForStep;
|
return ETaskResult.SkipRemainingTasksForStep;
|
||||||
|
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"CheckIfAlreadyActive({_elementId})";
|
public override string ToString() => $"CheckIfAlreadyActive({elementId})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed unsafe class OpenJournal : ITask
|
internal sealed unsafe class OpenJournal(ElementId elementId) : ITask
|
||||||
{
|
{
|
||||||
private ElementId _elementId = null!;
|
private readonly uint _questType = elementId is LeveId ? 2u : 1u;
|
||||||
private uint _questType;
|
|
||||||
private DateTime _openedAt = DateTime.MinValue;
|
private DateTime _openedAt = DateTime.MinValue;
|
||||||
|
|
||||||
public ITask With(ElementId elementId)
|
|
||||||
{
|
|
||||||
_elementId = elementId;
|
|
||||||
_questType = _elementId is LeveId ? 2u : 1u;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
AgentQuestJournal.Instance()->OpenForQuest(_elementId.Value, _questType);
|
AgentQuestJournal.Instance()->OpenForQuest(elementId.Value, _questType);
|
||||||
_openedAt = DateTime.Now;
|
_openedAt = DateTime.Now;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -83,32 +68,24 @@ internal static class InitiateLeve
|
|||||||
{
|
{
|
||||||
AgentQuestJournal* agentQuestJournal = AgentQuestJournal.Instance();
|
AgentQuestJournal* agentQuestJournal = AgentQuestJournal.Instance();
|
||||||
if (agentQuestJournal->IsAgentActive() &&
|
if (agentQuestJournal->IsAgentActive() &&
|
||||||
agentQuestJournal->SelectedQuestId == _elementId.Value &&
|
agentQuestJournal->SelectedQuestId == elementId.Value &&
|
||||||
agentQuestJournal->SelectedQuestType == _questType)
|
agentQuestJournal->SelectedQuestType == _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(elementId.Value, _questType);
|
||||||
_openedAt = DateTime.Now;
|
_openedAt = DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"OpenJournal({_elementId})";
|
public override string ToString() => $"OpenJournal({elementId})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed unsafe class Initiate(IGameGui gameGui) : ITask
|
internal sealed unsafe class Initiate(ElementId elementId, IGameGui gameGui) : ITask
|
||||||
{
|
{
|
||||||
private ElementId _elementId = null!;
|
|
||||||
|
|
||||||
public ITask With(ElementId elementId)
|
|
||||||
{
|
|
||||||
_elementId = elementId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start() => true;
|
public bool Start() => true;
|
||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
@ -118,7 +95,7 @@ internal static class InitiateLeve
|
|||||||
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 = elementId.Value }
|
||||||
};
|
};
|
||||||
addonJournalDetail->FireCallback(2, pickQuest);
|
addonJournalDetail->FireCallback(2, pickQuest);
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
@ -127,7 +104,7 @@ internal static class InitiateLeve
|
|||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"InitiateLeve({_elementId})";
|
public override string ToString() => $"InitiateLeve({elementId})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed unsafe class SelectDifficulty(IGameGui gameGui) : ITask
|
internal sealed unsafe class SelectDifficulty(IGameGui gameGui) : ITask
|
||||||
|
@ -20,7 +20,16 @@ namespace Questionable.Controller.Steps.Shared;
|
|||||||
|
|
||||||
internal static class AethernetShortcut
|
internal static class AethernetShortcut
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider, MovementController movementController)
|
internal sealed class Factory(
|
||||||
|
MovementController movementController,
|
||||||
|
AetheryteFunctions aetheryteFunctions,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
IClientState clientState,
|
||||||
|
AetheryteData aetheryteData,
|
||||||
|
TerritoryData territoryData,
|
||||||
|
LifestreamIpc lifestreamIpc,
|
||||||
|
ICondition condition,
|
||||||
|
ILoggerFactory loggerFactory)
|
||||||
: ITaskFactory
|
: ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
@ -30,12 +39,22 @@ internal static class AethernetShortcut
|
|||||||
|
|
||||||
yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
|
yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
|
||||||
"Wait(navmesh ready)");
|
"Wait(navmesh ready)");
|
||||||
yield return serviceProvider.GetRequiredService<UseAethernetShortcut>()
|
yield return Use(step.AethernetShortcut.From, step.AethernetShortcut.To,
|
||||||
.With(step.AethernetShortcut.From, step.AethernetShortcut.To, step.SkipConditions?.AethernetShortcutIf);
|
step.SkipConditions?.AethernetShortcutIf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITask Use(EAetheryteLocation from, EAetheryteLocation to, SkipAetheryteCondition? skipConditions = null)
|
||||||
|
{
|
||||||
|
return new UseAethernetShortcut(from, to, skipConditions ?? new(),
|
||||||
|
loggerFactory.CreateLogger<UseAethernetShortcut>(), aetheryteFunctions, gameFunctions, clientState,
|
||||||
|
aetheryteData, territoryData, lifestreamIpc, movementController, condition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class UseAethernetShortcut(
|
internal sealed class UseAethernetShortcut(
|
||||||
|
EAetheryteLocation from,
|
||||||
|
EAetheryteLocation to,
|
||||||
|
SkipAetheryteCondition skipConditions,
|
||||||
ILogger<UseAethernetShortcut> logger,
|
ILogger<UseAethernetShortcut> logger,
|
||||||
AetheryteFunctions aetheryteFunctions,
|
AetheryteFunctions aetheryteFunctions,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
@ -51,68 +70,58 @@ internal static class AethernetShortcut
|
|||||||
private bool _triedMounting;
|
private bool _triedMounting;
|
||||||
private DateTime _continueAt = DateTime.MinValue;
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
|
|
||||||
public EAetheryteLocation From { get; set; }
|
public EAetheryteLocation From => from;
|
||||||
public EAetheryteLocation To { get; set; }
|
public EAetheryteLocation To => to;
|
||||||
public SkipAetheryteCondition SkipConditions { get; set; } = null!;
|
|
||||||
|
|
||||||
public ITask With(EAetheryteLocation from, EAetheryteLocation to,
|
|
||||||
SkipAetheryteCondition? skipConditions = null)
|
|
||||||
{
|
|
||||||
From = from;
|
|
||||||
To = to;
|
|
||||||
SkipConditions = skipConditions ?? new();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
if (!SkipConditions.Never)
|
if (!skipConditions.Never)
|
||||||
{
|
{
|
||||||
if (SkipConditions.InSameTerritory && clientState.TerritoryType == aetheryteData.TerritoryIds[To])
|
if (skipConditions.InSameTerritory && clientState.TerritoryType == aetheryteData.TerritoryIds[to])
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aethernet shortcut because the target is in the same territory");
|
logger.LogInformation("Skipping aethernet shortcut because the target is in the same territory");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.InTerritory.Contains(clientState.TerritoryType))
|
if (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.AetheryteLocked != null &&
|
if (skipConditions.AetheryteLocked != null &&
|
||||||
!aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteLocked.Value))
|
!aetheryteFunctions.IsAetheryteUnlocked(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 (skipConditions.AetheryteUnlocked != null &&
|
||||||
aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteUnlocked.Value))
|
aetheryteFunctions.IsAetheryteUnlocked(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(from) &&
|
||||||
aetheryteFunctions.IsAetheryteUnlocked(To))
|
aetheryteFunctions.IsAetheryteUnlocked(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, from) <
|
||||||
aetheryteData.CalculateDistance(playerPosition, territoryType, To))
|
aetheryteData.CalculateDistance(playerPosition, territoryType, to))
|
||||||
{
|
{
|
||||||
if (aetheryteData.CalculateDistance(playerPosition, territoryType, From) <
|
if (aetheryteData.CalculateDistance(playerPosition, territoryType, from) <
|
||||||
(From.IsFirmamentAetheryte() ? 11f : 4f))
|
(from.IsFirmamentAetheryte() ? 11f : 4f))
|
||||||
{
|
{
|
||||||
DoTeleport();
|
DoTeleport();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (From == EAetheryteLocation.SolutionNine)
|
else if (from == EAetheryteLocation.SolutionNine)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Moving to S9 aetheryte");
|
logger.LogInformation("Moving to S9 aetheryte");
|
||||||
List<Vector3> nearbyPoints =
|
List<Vector3> nearbyPoints =
|
||||||
@ -125,14 +134,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)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, from) > 30 &&
|
||||||
!gameFunctions.HasStatusPreventingMount())
|
!gameFunctions.HasStatusPreventingMount())
|
||||||
{
|
{
|
||||||
_triedMounting = gameFunctions.Mount();
|
_triedMounting = gameFunctions.Mount();
|
||||||
@ -151,7 +160,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);
|
from, to);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -160,26 +169,26 @@ internal static class AethernetShortcut
|
|||||||
{
|
{
|
||||||
logger.LogInformation("Moving to aethernet shortcut");
|
logger.LogInformation("Moving to aethernet shortcut");
|
||||||
_moving = true;
|
_moving = true;
|
||||||
movementController.NavigateTo(EMovementType.Quest, (uint)From, aetheryteData.Locations[From],
|
movementController.NavigateTo(EMovementType.Quest, (uint)from, aetheryteData.Locations[from],
|
||||||
false, true,
|
false, true,
|
||||||
From.IsFirmamentAetheryte()
|
from.IsFirmamentAetheryte()
|
||||||
? 4.4f
|
? 4.4f
|
||||||
: AetheryteConverter.IsLargeAetheryte(From)
|
: AetheryteConverter.IsLargeAetheryte(from)
|
||||||
? 10.9f
|
? 10.9f
|
||||||
: 6.9f);
|
: 6.9f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DoTeleport()
|
private void DoTeleport()
|
||||||
{
|
{
|
||||||
if (From.IsFirmamentAetheryte())
|
if (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)from, ObjectKind.EventObj);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogInformation("Using lifestream to teleport to {Destination}", To);
|
logger.LogInformation("Using lifestream to teleport to {Destination}", to);
|
||||||
lifestreamIpc.Teleport(To);
|
lifestreamIpc.Teleport(to);
|
||||||
_teleported = true;
|
_teleported = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,22 +228,22 @@ internal static class AethernetShortcut
|
|||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aetheryteData.IsAirshipLanding(To))
|
if (aetheryteData.IsAirshipLanding(to))
|
||||||
{
|
{
|
||||||
if (aetheryteData.CalculateAirshipLandingDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
|
if (aetheryteData.CalculateAirshipLandingDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
|
||||||
clientState.TerritoryType, To) > 5)
|
clientState.TerritoryType, to) > 5)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
else if (aetheryteData.IsCityAetheryte(To))
|
else if (aetheryteData.IsCityAetheryte(to))
|
||||||
{
|
{
|
||||||
if (aetheryteData.CalculateDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
|
if (aetheryteData.CalculateDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
|
||||||
clientState.TerritoryType, To) > 20)
|
clientState.TerritoryType, 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[to])
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,6 +251,6 @@ internal static class AethernetShortcut
|
|||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"UseAethernet({From} -> {To})";
|
public override string ToString() => $"UseAethernet({from} -> {to})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,23 +18,38 @@ namespace Questionable.Controller.Steps.Shared;
|
|||||||
internal static class AetheryteShortcut
|
internal static class AetheryteShortcut
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory(
|
||||||
IServiceProvider serviceProvider,
|
AetheryteData aetheryteData,
|
||||||
AetheryteData aetheryteData) : ITaskFactory
|
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 serviceProvider.GetRequiredService<UseAetheryteShortcut>()
|
yield return Use(step, quest.Id, step.AetheryteShortcut.Value,
|
||||||
.With(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 serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>()
|
}
|
||||||
.With(TimeSpan.FromSeconds(0.5));
|
|
||||||
|
public ITask Use(QuestStep? step, ElementId? elementId, EAetheryteLocation targetAetheryte,
|
||||||
|
ushort expectedTerritoryId)
|
||||||
|
{
|
||||||
|
return new UseAetheryteShortcut(step, elementId, targetAetheryte, expectedTerritoryId,
|
||||||
|
loggerFactory.CreateLogger<UseAetheryteShortcut>(), aetheryteFunctions, questFunctions, clientState,
|
||||||
|
chatGui, aetheryteData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class UseAetheryteShortcut(
|
/// <param name="expectedTerritoryId">If using an aethernet shortcut after, the aetheryte's territory-id and the step's territory-id can differ, we always use the aetheryte's territory-id.</param>
|
||||||
|
private sealed class UseAetheryteShortcut(
|
||||||
|
QuestStep? step,
|
||||||
|
ElementId? elementId,
|
||||||
|
EAetheryteLocation targetAetheryte,
|
||||||
|
ushort expectedTerritoryId,
|
||||||
ILogger<UseAetheryteShortcut> logger,
|
ILogger<UseAetheryteShortcut> logger,
|
||||||
AetheryteFunctions aetheryteFunctions,
|
AetheryteFunctions aetheryteFunctions,
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
@ -45,26 +60,6 @@ internal static class AetheryteShortcut
|
|||||||
private bool _teleported;
|
private bool _teleported;
|
||||||
private DateTime _continueAt;
|
private DateTime _continueAt;
|
||||||
|
|
||||||
public QuestStep? Step { get; set; }
|
|
||||||
public ElementId? ElementId { get; set; }
|
|
||||||
public EAetheryteLocation TargetAetheryte { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If using an aethernet shortcut after, the aetheryte's territory-id and the step's territory-id can differ,
|
|
||||||
/// we always use the aetheryte's territory-id.
|
|
||||||
/// </summary>
|
|
||||||
public ushort ExpectedTerritoryId { get; set; }
|
|
||||||
|
|
||||||
public ITask With(QuestStep? step, ElementId? elementId, EAetheryteLocation targetAetheryte,
|
|
||||||
ushort expectedTerritoryId)
|
|
||||||
{
|
|
||||||
Step = step;
|
|
||||||
ElementId = elementId;
|
|
||||||
TargetAetheryte = targetAetheryte;
|
|
||||||
ExpectedTerritoryId = expectedTerritoryId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start() => !ShouldSkipTeleport();
|
public bool Start() => !ShouldSkipTeleport();
|
||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
@ -78,7 +73,7 @@ internal static class AetheryteShortcut
|
|||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientState.TerritoryType == ExpectedTerritoryId)
|
if (clientState.TerritoryType == expectedTerritoryId)
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
@ -87,9 +82,9 @@ internal static class AetheryteShortcut
|
|||||||
private bool ShouldSkipTeleport()
|
private bool ShouldSkipTeleport()
|
||||||
{
|
{
|
||||||
ushort territoryType = clientState.TerritoryType;
|
ushort territoryType = clientState.TerritoryType;
|
||||||
if (Step != null)
|
if (step != null)
|
||||||
{
|
{
|
||||||
var skipConditions = Step.SkipConditions?.AetheryteShortcutIf ?? new();
|
var skipConditions = step.SkipConditions?.AetheryteShortcutIf ?? new();
|
||||||
if (skipConditions is { Never: false })
|
if (skipConditions is { Never: false })
|
||||||
{
|
{
|
||||||
if (skipConditions.InTerritory.Contains(territoryType))
|
if (skipConditions.InTerritory.Contains(territoryType))
|
||||||
@ -112,12 +107,12 @@ internal static class AetheryteShortcut
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ElementId != null)
|
if (elementId != null)
|
||||||
{
|
{
|
||||||
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(ElementId);
|
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(elementId);
|
||||||
if (skipConditions.RequiredQuestVariablesNotMet &&
|
if (skipConditions.RequiredQuestVariablesNotMet &&
|
||||||
questWork != null &&
|
questWork != null &&
|
||||||
!QuestWorkUtils.MatchesRequiredQuestWorkConfig(Step.RequiredQuestVariables, questWork,
|
!QuestWorkUtils.MatchesRequiredQuestWorkConfig(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");
|
||||||
@ -126,10 +121,11 @@ internal static class AetheryteShortcut
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (skipConditions.NearPosition is { } nearPosition &&
|
||||||
if (skipConditions.NearPosition is { } nearPosition && clientState.TerritoryType == Step.TerritoryId)
|
clientState.TerritoryType == step.TerritoryId)
|
||||||
{
|
{
|
||||||
if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <= nearPosition.MaximumDistance)
|
if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <=
|
||||||
|
nearPosition.MaximumDistance)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aetheryte shortcut, as we're near the position");
|
logger.LogInformation("Skipping aetheryte shortcut, as we're near the position");
|
||||||
return true;
|
return true;
|
||||||
@ -137,7 +133,7 @@ internal static class AetheryteShortcut
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ExpectedTerritoryId == territoryType)
|
if (expectedTerritoryId == territoryType)
|
||||||
{
|
{
|
||||||
if (!skipConditions.Never)
|
if (!skipConditions.Never)
|
||||||
{
|
{
|
||||||
@ -148,17 +144,17 @@ internal static class AetheryteShortcut
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vector3 pos = clientState.LocalPlayer!.Position;
|
Vector3 pos = clientState.LocalPlayer!.Position;
|
||||||
if (Step.Position != null &&
|
if (step.Position != null &&
|
||||||
(pos - Step.Position.Value).Length() < Step.CalculateActualStopDistance())
|
(pos - step.Position.Value).Length() < 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, targetAetheryte) < 20 ||
|
||||||
(Step.AethernetShortcut != null &&
|
(step.AethernetShortcut != null &&
|
||||||
(aetheryteData.CalculateDistance(pos, territoryType, Step.AethernetShortcut.From) < 20 ||
|
(aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.From) < 20 ||
|
||||||
aetheryteData.CalculateDistance(pos, territoryType, Step.AethernetShortcut.To) < 20)))
|
aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.To) < 20)))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aetheryte teleport");
|
logger.LogInformation("Skipping aetheryte teleport");
|
||||||
return true;
|
return true;
|
||||||
@ -172,7 +168,7 @@ internal static class AetheryteShortcut
|
|||||||
|
|
||||||
private bool DoTeleport()
|
private bool DoTeleport()
|
||||||
{
|
{
|
||||||
if (!aetheryteFunctions.CanTeleport(TargetAetheryte))
|
if (!aetheryteFunctions.CanTeleport(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.");
|
||||||
@ -184,12 +180,12 @@ internal static class AetheryteShortcut
|
|||||||
|
|
||||||
_continueAt = DateTime.Now.AddSeconds(8);
|
_continueAt = DateTime.Now.AddSeconds(8);
|
||||||
|
|
||||||
if (!aetheryteFunctions.IsAetheryteUnlocked(TargetAetheryte))
|
if (!aetheryteFunctions.IsAetheryteUnlocked(targetAetheryte))
|
||||||
{
|
{
|
||||||
chatGui.PrintError($"[Questionable] Aetheryte {TargetAetheryte} is not unlocked.");
|
chatGui.PrintError($"[Questionable] Aetheryte {targetAetheryte} is not unlocked.");
|
||||||
throw new TaskException("Aetheryte is not unlocked");
|
throw new TaskException("Aetheryte is not unlocked");
|
||||||
}
|
}
|
||||||
else if (aetheryteFunctions.TeleportAetheryte(TargetAetheryte))
|
else if (aetheryteFunctions.TeleportAetheryte(targetAetheryte))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Travelling via aetheryte...");
|
logger.LogInformation("Travelling via aetheryte...");
|
||||||
return true;
|
return true;
|
||||||
@ -201,6 +197,6 @@ internal static class AetheryteShortcut
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"UseAetheryte({TargetAetheryte})";
|
public override string ToString() => $"UseAetheryte({targetAetheryte})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,18 +7,22 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using LLib.GameData;
|
using LLib.GameData;
|
||||||
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.External;
|
using Questionable.External;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
using Mount = Questionable.Controller.Steps.Common.Mount;
|
||||||
using Quest = Questionable.Model.Quest;
|
using Quest = Questionable.Model.Quest;
|
||||||
|
|
||||||
namespace Questionable.Controller.Steps.Shared;
|
namespace Questionable.Controller.Steps.Shared;
|
||||||
|
|
||||||
internal static class Craft
|
internal static class Craft
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
|
internal sealed class Factory(
|
||||||
|
IDataManager dataManager,
|
||||||
|
IClientState clientState,
|
||||||
|
ArtisanIpc artisanIpc,
|
||||||
|
Mount.Factory mountFactory,
|
||||||
|
ILoggerFactory loggerFactory) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -29,40 +33,34 @@ internal static class Craft
|
|||||||
ArgumentNullException.ThrowIfNull(step.ItemCount);
|
ArgumentNullException.ThrowIfNull(step.ItemCount);
|
||||||
return
|
return
|
||||||
[
|
[
|
||||||
serviceProvider.GetRequiredService<UnmountTask>(),
|
mountFactory.Unmount(),
|
||||||
serviceProvider.GetRequiredService<DoCraft>()
|
Craft(step.ItemId.Value, step.ItemCount.Value)
|
||||||
.With(step.ItemId.Value, step.ItemCount.Value)
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ITask Craft(uint itemId, int itemCount) =>
|
||||||
|
new DoCraft(itemId, itemCount, dataManager, clientState, artisanIpc, loggerFactory.CreateLogger<DoCraft>());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DoCraft(
|
private sealed class DoCraft(
|
||||||
|
uint itemId,
|
||||||
|
int itemCount,
|
||||||
IDataManager dataManager,
|
IDataManager dataManager,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
ArtisanIpc artisanIpc,
|
ArtisanIpc artisanIpc,
|
||||||
ILogger<DoCraft> logger) : ITask
|
ILogger<DoCraft> logger) : ITask
|
||||||
{
|
{
|
||||||
private uint _itemId;
|
|
||||||
private int _itemCount;
|
|
||||||
|
|
||||||
public ITask With(uint itemId, int itemCount)
|
|
||||||
{
|
|
||||||
_itemId = itemId;
|
|
||||||
_itemCount = itemCount;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
if (HasRequestedItems())
|
if (HasRequestedItems())
|
||||||
{
|
{
|
||||||
logger.LogInformation("Already own {ItemCount}x {ItemId}", _itemCount, _itemId);
|
logger.LogInformation("Already own {ItemCount}x {ItemId}", itemCount, itemId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RecipeLookup? recipeLookup = dataManager.GetExcelSheet<RecipeLookup>()!.GetRow(_itemId);
|
RecipeLookup? recipeLookup = dataManager.GetExcelSheet<RecipeLookup>()!.GetRow(itemId);
|
||||||
if (recipeLookup == null)
|
if (recipeLookup == null)
|
||||||
throw new TaskException($"Item {_itemId} is not craftable");
|
throw new TaskException($"Item {itemId} is not craftable");
|
||||||
|
|
||||||
uint recipeId = (EClassJob)clientState.LocalPlayer!.ClassJob.Id switch
|
uint recipeId = (EClassJob)clientState.LocalPlayer!.ClassJob.Id switch
|
||||||
{
|
{
|
||||||
@ -94,12 +92,12 @@ 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 {itemId}");
|
||||||
|
|
||||||
int remainingItemCount = _itemCount - GetOwnedItemCount();
|
int remainingItemCount = 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);
|
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}");
|
||||||
|
|
||||||
@ -130,15 +128,15 @@ internal static class Craft
|
|||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HasRequestedItems() => GetOwnedItemCount() >= _itemCount;
|
private bool HasRequestedItems() => GetOwnedItemCount() >= 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(itemId, isHq: false, checkEquipped: false)
|
||||||
+ inventoryManager->GetInventoryItemCount(_itemId, isHq: true, checkEquipped: false);
|
+ inventoryManager->GetInventoryItemCount(itemId, isHq: true, checkEquipped: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Craft {_itemCount}x {_itemId} (with Artisan)";
|
public override string ToString() => $"Craft {itemCount}x {itemId} (with Artisan)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ 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;
|
||||||
using Questionable.Functions;
|
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Gathering;
|
using Questionable.Model.Gathering;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
@ -22,6 +21,7 @@ internal static class GatheringRequiredItems
|
|||||||
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,
|
||||||
@ -50,8 +50,7 @@ internal static class GatheringRequiredItems
|
|||||||
|
|
||||||
if (classJob != currentClassJob)
|
if (classJob != currentClassJob)
|
||||||
{
|
{
|
||||||
yield return serviceProvider.GetRequiredService<SwitchClassJob>()
|
yield return new SwitchClassJob(classJob, clientState);
|
||||||
.With(classJob);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasRequiredItems(requiredGatheredItems))
|
if (HasRequiredItems(requiredGatheredItems))
|
||||||
@ -69,7 +68,7 @@ internal static class GatheringRequiredItems
|
|||||||
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 serviceProvider.GetRequiredService<SkipMarker>();
|
yield return CreateSkipMarkerTask();
|
||||||
else
|
else
|
||||||
yield return task;
|
yield return task;
|
||||||
}
|
}
|
||||||
@ -82,8 +81,7 @@ internal static class GatheringRequiredItems
|
|||||||
yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
|
yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
|
||||||
"Wait(navmesh ready)");
|
"Wait(navmesh ready)");
|
||||||
|
|
||||||
yield return serviceProvider.GetRequiredService<StartGathering>()
|
yield return CreateStartGatheringTask(gatheringPointId, requiredGatheredItems);
|
||||||
.With(gatheringPointId, requiredGatheredItems);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,25 +105,28 @@ internal static class GatheringRequiredItems
|
|||||||
minCollectability: (short)requiredGatheredItems.Collectability) >=
|
minCollectability: (short)requiredGatheredItems.Collectability) >=
|
||||||
requiredGatheredItems.ItemCount;
|
requiredGatheredItems.ItemCount;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class StartGathering(GatheringController gatheringController) : ITask
|
private StartGathering CreateStartGatheringTask(GatheringPointId gatheringPointId, GatheredItem gatheredItem)
|
||||||
{
|
|
||||||
private GatheringPointId _gatheringPointId = null!;
|
|
||||||
private GatheredItem _gatheredItem = null!;
|
|
||||||
|
|
||||||
public ITask With(GatheringPointId gatheringPointId, GatheredItem gatheredItem)
|
|
||||||
{
|
{
|
||||||
_gatheringPointId = gatheringPointId;
|
return new StartGathering(gatheringPointId, gatheredItem, gatheringController);
|
||||||
_gatheredItem = gatheredItem;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static SkipMarker CreateSkipMarkerTask()
|
||||||
|
{
|
||||||
|
return new SkipMarker();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class StartGathering(
|
||||||
|
GatheringPointId gatheringPointId,
|
||||||
|
GatheredItem gatheredItem,
|
||||||
|
GatheringController gatheringController) : ITask
|
||||||
|
{
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
return gatheringController.Start(new GatheringController.GatheringRequest(_gatheringPointId,
|
return gatheringController.Start(new GatheringController.GatheringRequest(gatheringPointId,
|
||||||
_gatheredItem.ItemId, _gatheredItem.AlternativeItemId, _gatheredItem.ItemCount,
|
gatheredItem.ItemId, gatheredItem.AlternativeItemId, gatheredItem.ItemCount,
|
||||||
_gatheredItem.Collectability));
|
gatheredItem.Collectability));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
@ -138,11 +139,11 @@ internal static class GatheringRequiredItems
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (_gatheredItem.Collectability == 0)
|
if (gatheredItem.Collectability == 0)
|
||||||
return $"Gather({_gatheredItem.ItemCount}x {_gatheredItem.ItemId})";
|
return $"Gather({gatheredItem.ItemCount}x {gatheredItem.ItemId})";
|
||||||
else
|
else
|
||||||
return
|
return
|
||||||
$"Gather({_gatheredItem.ItemCount}x {_gatheredItem.ItemId} {SeIconChar.Collectible.ToIconString()} {_gatheredItem.Collectability})";
|
$"Gather({gatheredItem.ItemCount}x {gatheredItem.ItemId} {SeIconChar.Collectible.ToIconString()} {gatheredItem.Collectability})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,87 +18,88 @@ using Questionable.Functions;
|
|||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
using Action = System.Action;
|
using Action = System.Action;
|
||||||
|
using Mount = Questionable.Controller.Steps.Common.Mount;
|
||||||
using Quest = Questionable.Model.Quest;
|
using Quest = Questionable.Model.Quest;
|
||||||
|
|
||||||
namespace Questionable.Controller.Steps.Shared;
|
namespace Questionable.Controller.Steps.Shared;
|
||||||
|
|
||||||
internal static class Move
|
internal static class MoveTo
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider, AetheryteData aetheryteData) : ITaskFactory
|
internal sealed class Factory(
|
||||||
|
MovementController movementController,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
ICondition condition,
|
||||||
|
IDataManager dataManager,
|
||||||
|
IClientState clientState,
|
||||||
|
AetheryteData aetheryteData,
|
||||||
|
TerritoryData territoryData,
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
Mount.Factory mountFactory,
|
||||||
|
ILogger<Factory> logger) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
if (step.Position != null)
|
if (step.Position != null)
|
||||||
{
|
{
|
||||||
var builder = serviceProvider.GetRequiredService<MoveBuilder>()
|
return CreateMountTasks(quest.Id, step, step.Position.Value);
|
||||||
.With(quest.Id, step, step.Position.Value);
|
|
||||||
return builder.Build();
|
|
||||||
}
|
}
|
||||||
else if (step is { DataId: not null, StopDistance: not null })
|
else if (step is { DataId: not null, StopDistance: not null })
|
||||||
{
|
{
|
||||||
var task = serviceProvider.GetRequiredService<ExpectToBeNearDataId>();
|
return [ExpectToBeNearDataId(step.DataId.Value, step.StopDistance.Value)];
|
||||||
task.DataId = step.DataId.Value;
|
|
||||||
task.StopDistance = step.StopDistance.Value;
|
|
||||||
return [task];
|
|
||||||
}
|
}
|
||||||
else if (step is { InteractionType: EInteractionType.AttuneAetheryte, Aetheryte: not null })
|
else if (step is { InteractionType: EInteractionType.AttuneAetheryte, Aetheryte: not null })
|
||||||
{
|
{
|
||||||
var builder = serviceProvider.GetRequiredService<MoveBuilder>()
|
return CreateMountTasks(quest.Id, step, aetheryteData.Locations[step.Aetheryte.Value]);
|
||||||
.With(quest.Id, step, aetheryteData.Locations[step.Aetheryte.Value]);
|
|
||||||
return builder.Build();
|
|
||||||
}
|
}
|
||||||
else if (step is { InteractionType: EInteractionType.AttuneAethernetShard, AethernetShard: not null })
|
else if (step is { InteractionType: EInteractionType.AttuneAethernetShard, AethernetShard: not null })
|
||||||
{
|
{
|
||||||
var builder = serviceProvider.GetRequiredService<MoveBuilder>().With(quest.Id, step,
|
return CreateMountTasks(quest.Id, step, aetheryteData.Locations[step.AethernetShard.Value]);
|
||||||
aetheryteData.Locations[step.AethernetShard.Value]);
|
|
||||||
return builder.Build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class MoveBuilder(
|
public ITask Move(QuestStep step, Vector3 destination)
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
ILogger<MoveBuilder> logger,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
IClientState clientState,
|
|
||||||
MovementController movementController,
|
|
||||||
TerritoryData territoryData,
|
|
||||||
AetheryteData aetheryteData)
|
|
||||||
{
|
|
||||||
private ElementId _questId = null!;
|
|
||||||
private QuestStep _step = null!;
|
|
||||||
private Vector3 _destination;
|
|
||||||
|
|
||||||
public MoveBuilder With(ElementId questId, QuestStep step, Vector3 destination)
|
|
||||||
{
|
{
|
||||||
_questId = questId;
|
return Move(new MoveParams(step, destination));
|
||||||
_step = step;
|
|
||||||
_destination = destination;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<ITask> Build()
|
public ITask Move(MoveParams moveParams)
|
||||||
{
|
{
|
||||||
if (_step.InteractionType == EInteractionType.Jump && _step.JumpDestination != null &&
|
return new MoveInternal(moveParams, movementController, gameFunctions,
|
||||||
(clientState.LocalPlayer!.Position - _step.JumpDestination.Position).Length() <=
|
loggerFactory.CreateLogger<MoveInternal>(), condition, dataManager);
|
||||||
(_step.JumpDestination.StopDistance ?? 1f))
|
}
|
||||||
|
|
||||||
|
public ITask Land()
|
||||||
|
{
|
||||||
|
return new LandTask(clientState, condition, loggerFactory.CreateLogger<LandTask>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITask ExpectToBeNearDataId(uint dataId, float stopDistance)
|
||||||
|
{
|
||||||
|
return new WaitForNearDataId(dataId, stopDistance, gameFunctions, clientState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ITask> CreateMountTasks(ElementId questId, QuestStep step, Vector3 destination)
|
||||||
|
{
|
||||||
|
if (step.InteractionType == EInteractionType.Jump && step.JumpDestination != null &&
|
||||||
|
(clientState.LocalPlayer!.Position - step.JumpDestination.Position).Length() <=
|
||||||
|
(step.JumpDestination.StopDistance ?? 1f))
|
||||||
{
|
{
|
||||||
logger.LogInformation("We're at the jump destination, skipping movement");
|
logger.LogInformation("We're at the jump destination, skipping movement");
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return new WaitConditionTask(() => clientState.TerritoryType == _step.TerritoryId,
|
yield return new WaitConditionTask(() => 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 WaitConditionTask(() => movementController.IsNavmeshReady,
|
||||||
"Wait(navmesh ready)");
|
"Wait(navmesh ready)");
|
||||||
|
|
||||||
float stopDistance = _step.CalculateActualStopDistance();
|
float stopDistance = step.CalculateActualStopDistance();
|
||||||
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 we teleport to a different zone, assume we always need to move; this is primarily relevant for cases
|
// if we teleport to a different zone, assume we always need to move; this is primarily relevant for cases
|
||||||
// where you're e.g. in Lakeland, and the step navigates via Crystarium → Tesselation back into the same
|
// where you're e.g. in Lakeland, and the step navigates via Crystarium → Tesselation back into the same
|
||||||
@ -107,8 +108,8 @@ internal static class Move
|
|||||||
// Side effects of this check being broken include:
|
// Side effects of this check being broken include:
|
||||||
// - mounting when near the target npc (if you spawn close enough for the next step)
|
// - mounting when near the target npc (if you spawn close enough for the next step)
|
||||||
// - trying to fly when near the target npc (if close enough where no movement is required)
|
// - trying to fly when near the target npc (if close enough where no movement is required)
|
||||||
if (_step.AetheryteShortcut != null &&
|
if (step.AetheryteShortcut != null &&
|
||||||
aetheryteData.TerritoryIds[_step.AetheryteShortcut.Value] != _step.TerritoryId)
|
aetheryteData.TerritoryIds[step.AetheryteShortcut.Value] != step.TerritoryId)
|
||||||
{
|
{
|
||||||
logger.LogDebug("Aetheryte: Changing distance to max, previous distance: {Distance}", actualDistance);
|
logger.LogDebug("Aetheryte: Changing distance to max, previous distance: {Distance}", actualDistance);
|
||||||
actualDistance = float.MaxValue;
|
actualDistance = float.MaxValue;
|
||||||
@ -116,36 +117,33 @@ internal static class Move
|
|||||||
|
|
||||||
// In particular, MoveBuilder is used so early that it'll have the position when you're starting gathering,
|
// In particular, MoveBuilder is used so early that it'll have the position when you're starting gathering,
|
||||||
// not when you're finished.
|
// not when you're finished.
|
||||||
if (_questId is SatisfactionSupplyNpcId)
|
if (questId is SatisfactionSupplyNpcId)
|
||||||
{
|
{
|
||||||
logger.LogDebug("SatisfactionSupply: Changing distance to max, previous distance: {Distance}",
|
logger.LogDebug("SatisfactionSupply: Changing distance to max, previous distance: {Distance}",
|
||||||
actualDistance);
|
actualDistance);
|
||||||
actualDistance = float.MaxValue;
|
actualDistance = float.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_step.Mount == true)
|
if (step.Mount == true)
|
||||||
yield return serviceProvider.GetRequiredService<MountTask>()
|
yield return mountFactory.Mount(step.TerritoryId, Mount.EMountIf.Always);
|
||||||
.With(_step.TerritoryId, MountTask.EMountIf.Always);
|
else if (step.Mount == false)
|
||||||
else if (_step.Mount == false)
|
yield return mountFactory.Unmount();
|
||||||
yield return serviceProvider.GetRequiredService<UnmountTask>();
|
|
||||||
|
|
||||||
if (!_step.DisableNavmesh)
|
if (!step.DisableNavmesh)
|
||||||
{
|
{
|
||||||
if (_step.Mount == null)
|
if (step.Mount == null)
|
||||||
{
|
{
|
||||||
MountTask.EMountIf mountIf =
|
Mount.EMountIf mountIf =
|
||||||
actualDistance > stopDistance && _step.Fly == true &&
|
actualDistance > stopDistance && step.Fly == true &&
|
||||||
gameFunctions.IsFlyingUnlocked(_step.TerritoryId)
|
gameFunctions.IsFlyingUnlocked(step.TerritoryId)
|
||||||
? MountTask.EMountIf.Always
|
? Mount.EMountIf.Always
|
||||||
: MountTask.EMountIf.AwayFromPosition;
|
: Mount.EMountIf.AwayFromPosition;
|
||||||
yield return serviceProvider.GetRequiredService<MountTask>()
|
yield return mountFactory.Mount(step.TerritoryId, mountIf, destination);
|
||||||
.With(_step.TerritoryId, mountIf, _destination);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actualDistance > stopDistance)
|
if (actualDistance > stopDistance)
|
||||||
{
|
{
|
||||||
yield return serviceProvider.GetRequiredService<MoveInternal>()
|
yield return Move(step, destination);
|
||||||
.With(_step, _destination);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
logger.LogInformation("Skipping move task, distance: {ActualDistance} < {StopDistance}",
|
logger.LogInformation("Skipping move task, distance: {ActualDistance} < {StopDistance}",
|
||||||
@ -156,124 +154,138 @@ internal static class Move
|
|||||||
// navmesh won't move close enough
|
// navmesh won't move close enough
|
||||||
if (actualDistance > stopDistance)
|
if (actualDistance > stopDistance)
|
||||||
{
|
{
|
||||||
yield return serviceProvider.GetRequiredService<MoveInternal>()
|
yield return Move(step, destination);
|
||||||
.With(_step, _destination);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
logger.LogInformation("Skipping move task, distance: {ActualDistance} < {StopDistance}",
|
logger.LogInformation("Skipping move task, distance: {ActualDistance} < {StopDistance}",
|
||||||
actualDistance, stopDistance);
|
actualDistance, stopDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_step.Fly == true && _step.Land == true)
|
if (step.Fly == true && step.Land == true)
|
||||||
yield return serviceProvider.GetRequiredService<Land>();
|
yield return Land();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class MoveInternal(
|
private sealed class MoveInternal : ITask, IToastAware
|
||||||
MovementController movementController,
|
|
||||||
GameFunctions gameFunctions,
|
|
||||||
ILogger<MoveInternal> logger,
|
|
||||||
ICondition condition,
|
|
||||||
IDataManager dataManager) : ITask, IToastAware
|
|
||||||
{
|
{
|
||||||
private string _cannotExecuteAtThisTime = dataManager.GetString<LogMessage>(579, x => x.Text)!;
|
private readonly string _cannotExecuteAtThisTime;
|
||||||
|
private readonly MovementController _movementController;
|
||||||
|
private readonly ILogger<MoveInternal> _logger;
|
||||||
|
private readonly ICondition _condition;
|
||||||
|
|
||||||
public Action StartAction { get; set; } = null!;
|
private readonly Action _startAction;
|
||||||
public Vector3 Destination { get; set; }
|
private readonly Vector3 _destination;
|
||||||
|
|
||||||
public ITask With(QuestStep step, Vector3 destination)
|
public MoveInternal(MoveParams moveParams,
|
||||||
|
MovementController movementController,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
ILogger<MoveInternal> logger,
|
||||||
|
ICondition condition,
|
||||||
|
IDataManager dataManager)
|
||||||
{
|
{
|
||||||
return With(
|
_movementController = movementController;
|
||||||
territoryId: step.TerritoryId,
|
_logger = logger;
|
||||||
destination: destination,
|
_condition = condition;
|
||||||
stopDistance: step.CalculateActualStopDistance(),
|
_cannotExecuteAtThisTime = dataManager.GetString<LogMessage>(579, x => x.Text)!;
|
||||||
dataId: step.DataId,
|
|
||||||
disableNavMesh: step.DisableNavmesh,
|
|
||||||
sprint: step.Sprint != false,
|
|
||||||
fly: step.Fly == true,
|
|
||||||
land: step.Land == true,
|
|
||||||
ignoreDistanceToObject: step.IgnoreDistanceToObject == true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITask With(ushort territoryId, Vector3 destination, float? stopDistance = null, uint? dataId = null,
|
_destination = moveParams.Destination;
|
||||||
bool disableNavMesh = false, bool sprint = true, bool fly = false, bool land = false,
|
|
||||||
bool ignoreDistanceToObject = false)
|
|
||||||
{
|
|
||||||
Destination = destination;
|
|
||||||
|
|
||||||
if (!gameFunctions.IsFlyingUnlocked(territoryId))
|
if (!gameFunctions.IsFlyingUnlocked(moveParams.TerritoryId))
|
||||||
{
|
{
|
||||||
fly = false;
|
moveParams = moveParams with { Fly = false, Land = false };
|
||||||
land = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!disableNavMesh)
|
if (!moveParams.DisableNavMesh)
|
||||||
{
|
{
|
||||||
StartAction = () =>
|
_startAction = () =>
|
||||||
movementController.NavigateTo(EMovementType.Quest, dataId, Destination,
|
_movementController.NavigateTo(EMovementType.Quest, moveParams.DataId, _destination,
|
||||||
fly: fly,
|
fly: moveParams.Fly,
|
||||||
sprint: sprint,
|
sprint: moveParams.Sprint,
|
||||||
stopDistance: stopDistance,
|
stopDistance: moveParams.StopDistance,
|
||||||
ignoreDistanceToObject: ignoreDistanceToObject,
|
ignoreDistanceToObject: moveParams.IgnoreDistanceToObject,
|
||||||
land: land);
|
land: moveParams.Land);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
StartAction = () =>
|
_startAction = () =>
|
||||||
movementController.NavigateTo(EMovementType.Quest, dataId, [Destination],
|
_movementController.NavigateTo(EMovementType.Quest, moveParams.DataId, [_destination],
|
||||||
fly: fly,
|
fly: moveParams.Fly,
|
||||||
sprint: sprint,
|
sprint: moveParams.Sprint,
|
||||||
stopDistance: stopDistance,
|
stopDistance: moveParams.StopDistance,
|
||||||
ignoreDistanceToObject: ignoreDistanceToObject,
|
ignoreDistanceToObject: moveParams.IgnoreDistanceToObject,
|
||||||
land: land);
|
land: moveParams.Land);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
logger.LogInformation("Moving to {Destination}", Destination.ToString("G", CultureInfo.InvariantCulture));
|
_logger.LogInformation("Moving to {Destination}", _destination.ToString("G", CultureInfo.InvariantCulture));
|
||||||
StartAction();
|
_startAction();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (movementController.IsPathfinding || movementController.IsPathRunning)
|
if (_movementController.IsPathfinding || _movementController.IsPathRunning)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
DateTime movementStartedAt = movementController.MovementStartedAt;
|
DateTime movementStartedAt = _movementController.MovementStartedAt;
|
||||||
if (movementStartedAt == DateTime.MaxValue || movementStartedAt.AddSeconds(2) >= DateTime.Now)
|
if (movementStartedAt == DateTime.MaxValue || movementStartedAt.AddSeconds(2) >= DateTime.Now)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"MoveTo({Destination.ToString("G", CultureInfo.InvariantCulture)})";
|
public override string ToString() => $"MoveTo({_destination.ToString("G", CultureInfo.InvariantCulture)})";
|
||||||
|
|
||||||
public bool OnErrorToast(SeString message)
|
public bool OnErrorToast(SeString message)
|
||||||
{
|
{
|
||||||
if (GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue) &&
|
if (GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue) &&
|
||||||
condition[ConditionFlag.Diving])
|
_condition[ConditionFlag.Diving])
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class ExpectToBeNearDataId(GameFunctions gameFunctions, IClientState clientState) : ITask
|
internal sealed record MoveParams(
|
||||||
|
ushort TerritoryId,
|
||||||
|
Vector3 Destination,
|
||||||
|
float? StopDistance = null,
|
||||||
|
uint? DataId = null,
|
||||||
|
bool DisableNavMesh = false,
|
||||||
|
bool Sprint = true,
|
||||||
|
bool Fly = false,
|
||||||
|
bool Land = false,
|
||||||
|
bool IgnoreDistanceToObject = false)
|
||||||
{
|
{
|
||||||
public uint DataId { get; set; }
|
public MoveParams(QuestStep step, Vector3 destination)
|
||||||
public float StopDistance { get; set; }
|
: this(step.TerritoryId,
|
||||||
|
destination,
|
||||||
|
step.CalculateActualStopDistance(),
|
||||||
|
step.DataId,
|
||||||
|
step.DisableNavmesh,
|
||||||
|
step.Sprint != false,
|
||||||
|
step.Fly == true,
|
||||||
|
step.Land == true,
|
||||||
|
step.IgnoreDistanceToObject == true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class WaitForNearDataId(
|
||||||
|
uint dataId,
|
||||||
|
float stopDistance,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
IClientState clientState) : ITask
|
||||||
|
{
|
||||||
public bool Start() => true;
|
public bool Start() => true;
|
||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
{
|
{
|
||||||
IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId);
|
IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId);
|
||||||
if (gameObject == null ||
|
if (gameObject == null ||
|
||||||
(gameObject.Position - clientState.LocalPlayer!.Position).Length() > StopDistance)
|
(gameObject.Position - clientState.LocalPlayer!.Position).Length() > stopDistance)
|
||||||
{
|
{
|
||||||
throw new TaskException("Object not found or too far away, no position so we can't move");
|
throw new TaskException("Object not found or too far away, no position so we can't move");
|
||||||
}
|
}
|
||||||
@ -282,7 +294,7 @@ internal static class Move
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class Land(IClientState clientState, ICondition condition, ILogger<Land> logger) : ITask
|
private sealed class LandTask(IClientState clientState, ICondition condition, ILogger<LandTask> logger) : ITask
|
||||||
{
|
{
|
||||||
private bool _landing;
|
private bool _landing;
|
||||||
private DateTime _continueAt;
|
private DateTime _continueAt;
|
@ -20,7 +20,12 @@ namespace Questionable.Controller.Steps.Shared;
|
|||||||
|
|
||||||
internal static class SkipCondition
|
internal static class SkipCondition
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
|
internal sealed class Factory(
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
AetheryteFunctions aetheryteFunctions,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
QuestFunctions questFunctions,
|
||||||
|
IClientState clientState) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
@ -35,90 +40,86 @@ internal static class SkipCondition
|
|||||||
step.NextQuestId == null)
|
step.NextQuestId == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return serviceProvider.GetRequiredService<CheckSkip>()
|
return Check(step, skipConditions, quest.Id);
|
||||||
.With(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class CheckSkip(
|
private sealed class CheckSkip(
|
||||||
|
QuestStep step,
|
||||||
|
SkipStepConditions skipConditions,
|
||||||
|
ElementId elementId,
|
||||||
ILogger<CheckSkip> logger,
|
ILogger<CheckSkip> logger,
|
||||||
AetheryteFunctions aetheryteFunctions,
|
AetheryteFunctions aetheryteFunctions,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
IClientState clientState) : ITask
|
IClientState clientState) : ITask
|
||||||
{
|
{
|
||||||
public QuestStep Step { get; set; } = null!;
|
|
||||||
public SkipStepConditions SkipConditions { get; set; } = null!;
|
|
||||||
public ElementId ElementId { get; set; } = null!;
|
|
||||||
|
|
||||||
public ITask With(QuestStep step, SkipStepConditions skipConditions, ElementId elementId)
|
|
||||||
{
|
|
||||||
Step = step;
|
|
||||||
SkipConditions = skipConditions;
|
|
||||||
ElementId = elementId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe bool Start()
|
public unsafe bool Start()
|
||||||
{
|
{
|
||||||
logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", SkipConditions));
|
logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", skipConditions));
|
||||||
|
|
||||||
if (SkipConditions.Flying == ELockedSkipCondition.Unlocked &&
|
if (skipConditions.Flying == ELockedSkipCondition.Unlocked &&
|
||||||
gameFunctions.IsFlyingUnlocked(Step.TerritoryId))
|
gameFunctions.IsFlyingUnlocked(step.TerritoryId))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as flying is unlocked");
|
logger.LogInformation("Skipping step, as flying is unlocked");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.Flying == ELockedSkipCondition.Locked &&
|
if (skipConditions.Flying == ELockedSkipCondition.Locked &&
|
||||||
!gameFunctions.IsFlyingUnlocked(Step.TerritoryId))
|
!gameFunctions.IsFlyingUnlocked(step.TerritoryId))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as flying is locked");
|
logger.LogInformation("Skipping step, as flying is locked");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.Chocobo == ELockedSkipCondition.Unlocked &&
|
if (skipConditions.Chocobo == ELockedSkipCondition.Unlocked &&
|
||||||
PlayerState.Instance()->IsMountUnlocked(1))
|
PlayerState.Instance()->IsMountUnlocked(1))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as chocobo is unlocked");
|
logger.LogInformation("Skipping step, as chocobo is unlocked");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.InTerritory.Count > 0 &&
|
if (skipConditions.InTerritory.Count > 0 &&
|
||||||
SkipConditions.InTerritory.Contains(clientState.TerritoryType))
|
skipConditions.InTerritory.Contains(clientState.TerritoryType))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as in a skip.InTerritory");
|
logger.LogInformation("Skipping step, as in a skip.InTerritory");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.NotInTerritory.Count > 0 &&
|
if (skipConditions.NotInTerritory.Count > 0 &&
|
||||||
!SkipConditions.NotInTerritory.Contains(clientState.TerritoryType))
|
!skipConditions.NotInTerritory.Contains(clientState.TerritoryType))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as not in a skip.NotInTerritory");
|
logger.LogInformation("Skipping step, as not in a skip.NotInTerritory");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.QuestsCompleted.Count > 0 &&
|
if (skipConditions.QuestsCompleted.Count > 0 &&
|
||||||
SkipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete))
|
skipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, all prequisite quests are complete");
|
logger.LogInformation("Skipping step, all prequisite quests are complete");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.QuestsAccepted.Count > 0 &&
|
if (skipConditions.QuestsAccepted.Count > 0 &&
|
||||||
SkipConditions.QuestsAccepted.All(questFunctions.IsQuestAccepted))
|
skipConditions.QuestsAccepted.All(questFunctions.IsQuestAccepted))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, all prequisite quests are accepted");
|
logger.LogInformation("Skipping step, all prequisite quests are accepted");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.NotTargetable &&
|
if (skipConditions.NotTargetable &&
|
||||||
Step is { DataId: not null })
|
step is { DataId: not null })
|
||||||
{
|
{
|
||||||
IGameObject? gameObject = gameFunctions.FindObjectByDataId(Step.DataId.Value);
|
IGameObject? gameObject = gameFunctions.FindObjectByDataId(step.DataId.Value);
|
||||||
if (gameObject == null)
|
if (gameObject == null)
|
||||||
{
|
{
|
||||||
if ((Step.Position.GetValueOrDefault() - clientState.LocalPlayer!.Position).Length() < 100)
|
if ((step.Position.GetValueOrDefault() - clientState.LocalPlayer!.Position).Length() < 100)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, object is not nearby (but we are)");
|
logger.LogInformation("Skipping step, object is not nearby (but we are)");
|
||||||
return true;
|
return true;
|
||||||
@ -131,60 +132,60 @@ internal static class SkipCondition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.Item is { NotInInventory: true } && Step is { ItemId: not null })
|
if (skipConditions.Item is { NotInInventory: true } && step is { ItemId: not null })
|
||||||
{
|
{
|
||||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||||
if (inventoryManager->GetInventoryItemCount(Step.ItemId.Value) == 0)
|
if (inventoryManager->GetInventoryItemCount(step.ItemId.Value) == 0)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, no item with itemId {ItemId} in inventory",
|
logger.LogInformation("Skipping step, no item with itemId {ItemId} in inventory",
|
||||||
Step.ItemId.Value);
|
step.ItemId.Value);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Step is
|
if (step is
|
||||||
{
|
{
|
||||||
DataId: not null,
|
DataId: not null,
|
||||||
InteractionType: EInteractionType.AttuneAetheryte or EInteractionType.AttuneAethernetShard
|
InteractionType: EInteractionType.AttuneAetheryte or EInteractionType.AttuneAethernetShard
|
||||||
} &&
|
} &&
|
||||||
aetheryteFunctions.IsAetheryteUnlocked((EAetheryteLocation)Step.DataId.Value))
|
aetheryteFunctions.IsAetheryteUnlocked((EAetheryteLocation)step.DataId.Value))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as aetheryte/aethernet shard is unlocked");
|
logger.LogInformation("Skipping step, as aetheryte/aethernet shard is unlocked");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.AetheryteLocked != null &&
|
if (skipConditions.AetheryteLocked != null &&
|
||||||
!aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteLocked.Value))
|
!aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteLocked.Value))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as aetheryte is locked");
|
logger.LogInformation("Skipping step, as aetheryte is locked");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.AetheryteUnlocked != null &&
|
if (skipConditions.AetheryteUnlocked != null &&
|
||||||
aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteUnlocked.Value))
|
aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteUnlocked.Value))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as aetheryte is unlocked");
|
logger.LogInformation("Skipping step, as aetheryte is unlocked");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Step is { DataId: not null, InteractionType: EInteractionType.AttuneAetherCurrent } &&
|
if (step is { DataId: not null, InteractionType: EInteractionType.AttuneAetherCurrent } &&
|
||||||
gameFunctions.IsAetherCurrentUnlocked(Step.DataId.Value))
|
gameFunctions.IsAetherCurrentUnlocked(step.DataId.Value))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as current is unlocked");
|
logger.LogInformation("Skipping step, as current is unlocked");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(ElementId);
|
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(elementId);
|
||||||
if (questWork != null)
|
if (questWork != null)
|
||||||
{
|
{
|
||||||
if (QuestWorkUtils.HasCompletionFlags(Step.CompletionQuestVariablesFlags) &&
|
if (QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags) &&
|
||||||
QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork))
|
QuestWorkUtils.MatchesQuestWork(step.CompletionQuestVariablesFlags, questWork))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as quest variables match (step is complete)");
|
logger.LogInformation("Skipping step, as quest variables match (step is complete)");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Step is { SkipConditions.StepIf: { } conditions })
|
if (step is { SkipConditions.StepIf: { } conditions })
|
||||||
{
|
{
|
||||||
if (QuestWorkUtils.MatchesQuestWork(conditions.CompletionQuestVariablesFlags, questWork))
|
if (QuestWorkUtils.MatchesQuestWork(conditions.CompletionQuestVariablesFlags, questWork))
|
||||||
{
|
{
|
||||||
@ -193,7 +194,7 @@ internal static class SkipCondition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Step is { RequiredQuestVariables: { } requiredQuestVariables })
|
if (step is { RequiredQuestVariables: { } requiredQuestVariables })
|
||||||
{
|
{
|
||||||
if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork, logger))
|
if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork, logger))
|
||||||
{
|
{
|
||||||
@ -203,16 +204,17 @@ internal static class SkipCondition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.NearPosition is { } nearPosition && clientState.TerritoryType == Step.TerritoryId)
|
if (skipConditions.NearPosition is { } nearPosition && clientState.TerritoryType == step.TerritoryId)
|
||||||
{
|
{
|
||||||
if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <= nearPosition.MaximumDistance)
|
if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <=
|
||||||
|
nearPosition.MaximumDistance)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as we're near the position");
|
logger.LogInformation("Skipping step, as we're near the position");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.ExtraCondition == EExtraSkipCondition.WakingSandsMainArea &&
|
if (skipConditions.ExtraCondition == EExtraSkipCondition.WakingSandsMainArea &&
|
||||||
clientState.TerritoryType == 212)
|
clientState.TerritoryType == 212)
|
||||||
{
|
{
|
||||||
var position = clientState.LocalPlayer!.Position;
|
var position = clientState.LocalPlayer!.Position;
|
||||||
@ -223,7 +225,7 @@ internal static class SkipCondition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.ExtraCondition == EExtraSkipCondition.RisingStonesSolar &&
|
if (skipConditions.ExtraCondition == EExtraSkipCondition.RisingStonesSolar &&
|
||||||
clientState.TerritoryType == 351)
|
clientState.TerritoryType == 351)
|
||||||
{
|
{
|
||||||
var position = clientState.LocalPlayer!.Position;
|
var position = clientState.LocalPlayer!.Position;
|
||||||
@ -234,13 +236,13 @@ internal static class SkipCondition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Step.PickUpQuestId != null && questFunctions.IsQuestAcceptedOrComplete(Step.PickUpQuestId))
|
if (step.PickUpQuestId != null && questFunctions.IsQuestAcceptedOrComplete(step.PickUpQuestId))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as we have already picked up the relevant quest");
|
logger.LogInformation("Skipping step, as we have already picked up the relevant quest");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Step.TurnInQuestId != null && questFunctions.IsQuestComplete(Step.TurnInQuestId))
|
if (step.TurnInQuestId != null && questFunctions.IsQuestComplete(step.TurnInQuestId))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as we have already completed the relevant quest");
|
logger.LogInformation("Skipping step, as we have already completed the relevant quest");
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
using System;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
@ -8,14 +6,14 @@ namespace Questionable.Controller.Steps.Shared;
|
|||||||
|
|
||||||
internal static class StepDisabled
|
internal static class StepDisabled
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
|
internal sealed class Factory(ILoggerFactory loggerFactory) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
if (!step.Disabled)
|
if (!step.Disabled)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return serviceProvider.GetRequiredService<Task>();
|
return new Task(loggerFactory.CreateLogger<Task>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,19 +6,11 @@ using Questionable.Controller.Steps.Common;
|
|||||||
|
|
||||||
namespace Questionable.Controller.Steps.Shared;
|
namespace Questionable.Controller.Steps.Shared;
|
||||||
|
|
||||||
internal sealed class SwitchClassJob(IClientState clientState) : AbstractDelayedTask
|
internal sealed class SwitchClassJob(EClassJob classJob, IClientState clientState) : AbstractDelayedTask
|
||||||
{
|
{
|
||||||
private EClassJob _classJob;
|
|
||||||
|
|
||||||
public ITask With(EClassJob classJob)
|
|
||||||
{
|
|
||||||
_classJob = classJob;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override unsafe bool StartInternal()
|
protected override unsafe bool StartInternal()
|
||||||
{
|
{
|
||||||
if (clientState.LocalPlayer!.ClassJob.Id == (uint)_classJob)
|
if (clientState.LocalPlayer!.ClassJob.Id == (uint)classJob)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var gearsetModule = RaptureGearsetModule.Instance();
|
var gearsetModule = RaptureGearsetModule.Instance();
|
||||||
@ -27,7 +19,7 @@ internal sealed class SwitchClassJob(IClientState clientState) : AbstractDelayed
|
|||||||
for (int i = 0; i < 100; ++i)
|
for (int i = 0; i < 100; ++i)
|
||||||
{
|
{
|
||||||
var gearset = gearsetModule->GetGearset(i);
|
var gearset = gearsetModule->GetGearset(i);
|
||||||
if (gearset->ClassJob == (byte)_classJob)
|
if (gearset->ClassJob == (byte)classJob)
|
||||||
{
|
{
|
||||||
gearsetModule->EquipGearset(gearset->Id, gearset->BannerIndex);
|
gearsetModule->EquipGearset(gearset->Id, gearset->BannerIndex);
|
||||||
return true;
|
return true;
|
||||||
@ -35,10 +27,10 @@ internal sealed class SwitchClassJob(IClientState clientState) : AbstractDelayed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new TaskException($"No gearset found for {_classJob}");
|
throw new TaskException($"No gearset found for {classJob}");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ETaskResult UpdateInternal() => ETaskResult.TaskComplete;
|
protected override ETaskResult UpdateInternal() => ETaskResult.TaskComplete;
|
||||||
|
|
||||||
public override string ToString() => $"SwitchJob({_classJob})";
|
public override string ToString() => $"SwitchJob({classJob})";
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,6 @@ 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 Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
using Questionable.Controller.Utils;
|
using Questionable.Controller.Utils;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
@ -20,19 +17,20 @@ namespace Questionable.Controller.Steps.Shared;
|
|||||||
internal static class WaitAtEnd
|
internal static class WaitAtEnd
|
||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory(
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
if (step.CompletionQuestVariablesFlags.Count == 6 && QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
|
if (step.CompletionQuestVariablesFlags.Count == 6 &&
|
||||||
|
QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
|
||||||
{
|
{
|
||||||
var task = serviceProvider.GetRequiredService<WaitForCompletionFlags>()
|
var task = new WaitForCompletionFlags((QuestId)quest.Id, step, questFunctions);
|
||||||
.With((QuestId)quest.Id, step);
|
var delay = new WaitDelay();
|
||||||
var delay = serviceProvider.GetRequiredService<WaitDelay>();
|
|
||||||
return [task, delay, Next(quest, sequence)];
|
return [task, delay, Next(quest, sequence)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,15 +41,15 @@ internal static class WaitAtEnd
|
|||||||
new WaitConditionTask(() => !condition[ConditionFlag.InCombat], "Wait(not in combat)");
|
new WaitConditionTask(() => !condition[ConditionFlag.InCombat], "Wait(not in combat)");
|
||||||
return
|
return
|
||||||
[
|
[
|
||||||
serviceProvider.GetRequiredService<WaitDelay>(),
|
new WaitDelay(),
|
||||||
notInCombat,
|
notInCombat,
|
||||||
serviceProvider.GetRequiredService<WaitDelay>(),
|
new WaitDelay(),
|
||||||
Next(quest, sequence)
|
Next(quest, sequence)
|
||||||
];
|
];
|
||||||
|
|
||||||
case EInteractionType.WaitForManualProgress:
|
case EInteractionType.WaitForManualProgress:
|
||||||
case EInteractionType.Instruction:
|
case EInteractionType.Instruction:
|
||||||
return [serviceProvider.GetRequiredService<WaitNextStepOrSequence>()];
|
return [new WaitNextStepOrSequence()];
|
||||||
|
|
||||||
case EInteractionType.Duty:
|
case EInteractionType.Duty:
|
||||||
case EInteractionType.SinglePlayerDuty:
|
case EInteractionType.SinglePlayerDuty:
|
||||||
@ -68,9 +66,9 @@ internal static class WaitAtEnd
|
|||||||
|
|
||||||
return
|
return
|
||||||
[
|
[
|
||||||
serviceProvider.GetRequiredService<WaitObjectAtPosition>()
|
new WaitObjectAtPosition(step.DataId.Value, step.Position.Value, step.NpcWaitDistance ?? 0.05f,
|
||||||
.With(step.DataId.Value, step.Position.Value, step.NpcWaitDistance ?? 0.05f),
|
gameFunctions),
|
||||||
serviceProvider.GetRequiredService<WaitDelay>(),
|
new WaitDelay(),
|
||||||
Next(quest, sequence)
|
Next(quest, sequence)
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -104,15 +102,14 @@ internal static class WaitAtEnd
|
|||||||
return
|
return
|
||||||
[
|
[
|
||||||
waitInteraction,
|
waitInteraction,
|
||||||
serviceProvider.GetRequiredService<WaitDelay>(),
|
new WaitDelay(),
|
||||||
Next(quest, sequence)
|
Next(quest, sequence)
|
||||||
];
|
];
|
||||||
|
|
||||||
case EInteractionType.AcceptQuest:
|
case EInteractionType.AcceptQuest:
|
||||||
{
|
{
|
||||||
var accept = serviceProvider.GetRequiredService<WaitQuestAccepted>()
|
var accept = new WaitQuestAccepted(step.PickUpQuestId ?? quest.Id, questFunctions);
|
||||||
.With(step.PickUpQuestId ?? quest.Id);
|
var delay = new WaitDelay();
|
||||||
var delay = serviceProvider.GetRequiredService<WaitDelay>();
|
|
||||||
if (step.PickUpQuestId != null)
|
if (step.PickUpQuestId != null)
|
||||||
return [accept, delay, Next(quest, sequence)];
|
return [accept, delay, Next(quest, sequence)];
|
||||||
else
|
else
|
||||||
@ -121,9 +118,8 @@ internal static class WaitAtEnd
|
|||||||
|
|
||||||
case EInteractionType.CompleteQuest:
|
case EInteractionType.CompleteQuest:
|
||||||
{
|
{
|
||||||
var complete = serviceProvider.GetRequiredService<WaitQuestCompleted>()
|
var complete = new WaitQuestCompleted(step.TurnInQuestId ?? quest.Id, questFunctions);
|
||||||
.With(step.TurnInQuestId ?? quest.Id);
|
var delay = new WaitDelay();
|
||||||
var delay = serviceProvider.GetRequiredService<WaitDelay>();
|
|
||||||
if (step.TurnInQuestId != null)
|
if (step.TurnInQuestId != null)
|
||||||
return [complete, delay, Next(quest, sequence)];
|
return [complete, delay, Next(quest, sequence)];
|
||||||
else
|
else
|
||||||
@ -132,7 +128,7 @@ internal static class WaitAtEnd
|
|||||||
|
|
||||||
case EInteractionType.Interact:
|
case EInteractionType.Interact:
|
||||||
default:
|
default:
|
||||||
return [serviceProvider.GetRequiredService<WaitDelay>(), Next(quest, sequence)];
|
return [new WaitDelay(), Next(quest, sequence)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,14 +138,8 @@ internal static class WaitAtEnd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class WaitDelay() : AbstractDelayedTask(TimeSpan.FromSeconds(1))
|
internal sealed class WaitDelay(TimeSpan? delay = null) : AbstractDelayedTask(delay ?? TimeSpan.FromSeconds(1))
|
||||||
{
|
{
|
||||||
public ITask With(TimeSpan delay)
|
|
||||||
{
|
|
||||||
Delay = delay;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool StartInternal() => true;
|
protected override bool StartInternal() => true;
|
||||||
|
|
||||||
public override string ToString() => $"Wait(seconds: {Delay.TotalSeconds})";
|
public override string ToString() => $"Wait(seconds: {Delay.TotalSeconds})";
|
||||||
@ -164,100 +154,64 @@ internal static class WaitAtEnd
|
|||||||
public override string ToString() => "Wait(next step or sequence)";
|
public override string ToString() => "Wait(next step or sequence)";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class WaitForCompletionFlags(QuestFunctions questFunctions) : ITask
|
internal sealed class WaitForCompletionFlags(QuestId quest, QuestStep step, QuestFunctions questFunctions) : ITask
|
||||||
{
|
{
|
||||||
public QuestId Quest { get; set; } = null!;
|
|
||||||
public QuestStep Step { get; set; } = null!;
|
|
||||||
public IList<QuestWorkValue?> Flags { get; set; } = null!;
|
|
||||||
|
|
||||||
public ITask With(QuestId quest, QuestStep step)
|
|
||||||
{
|
|
||||||
Quest = quest;
|
|
||||||
Step = step;
|
|
||||||
Flags = step.CompletionQuestVariablesFlags;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start() => true;
|
public bool Start() => true;
|
||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
{
|
{
|
||||||
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(Quest);
|
QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(quest);
|
||||||
return questWork != null &&
|
return questWork != null &&
|
||||||
QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork)
|
QuestWorkUtils.MatchesQuestWork(step.CompletionQuestVariablesFlags, questWork)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() =>
|
public override string ToString() =>
|
||||||
$"Wait(QW: {string.Join(", ", Flags.Select(x => x?.ToString() ?? "-"))})";
|
$"Wait(QW: {string.Join(", ", step.CompletionQuestVariablesFlags.Select(x => x?.ToString() ?? "-"))})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class WaitObjectAtPosition(GameFunctions gameFunctions) : ITask
|
private sealed class WaitObjectAtPosition(
|
||||||
|
uint dataId,
|
||||||
|
Vector3 destination,
|
||||||
|
float distance,
|
||||||
|
GameFunctions gameFunctions) : ITask
|
||||||
{
|
{
|
||||||
public uint DataId { get; set; }
|
|
||||||
public Vector3 Destination { get; set; }
|
|
||||||
public float Distance { get; set; }
|
|
||||||
|
|
||||||
public ITask With(uint dataId, Vector3 destination, float distance)
|
|
||||||
{
|
|
||||||
DataId = dataId;
|
|
||||||
Destination = destination;
|
|
||||||
Distance = distance;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start() => true;
|
public bool Start() => true;
|
||||||
|
|
||||||
public ETaskResult Update() =>
|
public ETaskResult Update() =>
|
||||||
gameFunctions.IsObjectAtPosition(DataId, Destination, Distance)
|
gameFunctions.IsObjectAtPosition(dataId, destination, distance)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
|
|
||||||
public override string ToString() =>
|
public override string ToString() =>
|
||||||
$"WaitObj({DataId} at {Destination.ToString("G", CultureInfo.InvariantCulture)} < {Distance})";
|
$"WaitObj({dataId} at {destination.ToString("G", CultureInfo.InvariantCulture)} < {distance})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class WaitQuestAccepted(QuestFunctions questFunctions) : ITask
|
internal sealed class WaitQuestAccepted(ElementId elementId, QuestFunctions questFunctions) : ITask
|
||||||
{
|
{
|
||||||
public ElementId ElementId { get; set; } = null!;
|
|
||||||
|
|
||||||
public ITask With(ElementId elementId)
|
|
||||||
{
|
|
||||||
ElementId = elementId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start() => true;
|
public bool Start() => true;
|
||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
{
|
{
|
||||||
return questFunctions.IsQuestAccepted(ElementId)
|
return questFunctions.IsQuestAccepted(elementId)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"WaitQuestAccepted({ElementId})";
|
public override string ToString() => $"WaitQuestAccepted({elementId})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class WaitQuestCompleted(QuestFunctions questFunctions) : ITask
|
internal sealed class WaitQuestCompleted(ElementId elementId, QuestFunctions questFunctions) : ITask
|
||||||
{
|
{
|
||||||
public ElementId ElementId { get; set; } = null!;
|
|
||||||
|
|
||||||
public ITask With(ElementId elementId)
|
|
||||||
{
|
|
||||||
ElementId = elementId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Start() => true;
|
public bool Start() => true;
|
||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
{
|
{
|
||||||
return questFunctions.IsQuestComplete(ElementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
|
return questFunctions.IsQuestComplete(elementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"WaitQuestComplete({ElementId})";
|
public override string ToString() => $"WaitQuestComplete({elementId})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class NextStep(ElementId elementId, int sequence) : ILastTask
|
internal sealed class NextStep(ElementId elementId, int sequence) : ILastTask
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Questionable.Controller.Steps.Common;
|
using Questionable.Controller.Steps.Common;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
@ -8,26 +7,19 @@ namespace Questionable.Controller.Steps.Shared;
|
|||||||
|
|
||||||
internal static class WaitAtStart
|
internal static class WaitAtStart
|
||||||
{
|
{
|
||||||
internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
|
internal sealed class Factory : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
if (step.DelaySecondsAtStart == null)
|
if (step.DelaySecondsAtStart == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return serviceProvider.GetRequiredService<WaitDelay>()
|
return new WaitDelay(TimeSpan.FromSeconds(step.DelaySecondsAtStart.Value));
|
||||||
.With(TimeSpan.FromSeconds(step.DelaySecondsAtStart.Value));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class WaitDelay : AbstractDelayedTask
|
internal sealed class WaitDelay(TimeSpan delay) : AbstractDelayedTask(delay)
|
||||||
{
|
{
|
||||||
public ITask With(TimeSpan delay)
|
|
||||||
{
|
|
||||||
Delay = delay;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool StartInternal() => true;
|
protected override bool StartInternal() => true;
|
||||||
|
|
||||||
public override string ToString() => $"Wait[S](seconds: {Delay.TotalSeconds})";
|
public override string ToString() => $"Wait[S](seconds: {Delay.TotalSeconds})";
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
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;
|
||||||
@ -9,18 +10,19 @@ namespace Questionable.Controller.Steps;
|
|||||||
|
|
||||||
internal sealed class TaskCreator
|
internal sealed class TaskCreator
|
||||||
{
|
{
|
||||||
private readonly IReadOnlyList<ITaskFactory> _taskFactories;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly ILogger<TaskCreator> _logger;
|
private readonly ILogger<TaskCreator> _logger;
|
||||||
|
|
||||||
public TaskCreator(IEnumerable<ITaskFactory> taskFactories, ILogger<TaskCreator> logger)
|
public TaskCreator(IServiceProvider serviceProvider, ILogger<TaskCreator> logger)
|
||||||
{
|
{
|
||||||
_taskFactories = taskFactories.ToList().AsReadOnly();
|
_serviceProvider = serviceProvider;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<ITask> CreateTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IReadOnlyList<ITask> CreateTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
var newTasks = _taskFactories
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var newTasks = scope.ServiceProvider.GetRequiredService<IEnumerable<ITaskFactory>>()
|
||||||
.SelectMany(x =>
|
.SelectMany(x =>
|
||||||
{
|
{
|
||||||
IList<ITask> tasks = x.CreateAllTasks(quest, sequence, step).ToList();
|
IList<ITask> tasks = x.CreateAllTasks(quest, sequence, step).ToList();
|
||||||
|
@ -125,56 +125,42 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
private static void AddTaskFactories(ServiceCollection serviceCollection)
|
private static void AddTaskFactories(ServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
// individual tasks
|
// individual tasks
|
||||||
serviceCollection.AddTransient<MountTask>();
|
|
||||||
serviceCollection.AddTransient<UnmountTask>();
|
|
||||||
serviceCollection.AddTransient<MoveToLandingLocation>();
|
serviceCollection.AddTransient<MoveToLandingLocation>();
|
||||||
serviceCollection.AddTransient<DoGather>();
|
serviceCollection.AddTransient<DoGather>();
|
||||||
serviceCollection.AddTransient<DoGatherCollectable>();
|
serviceCollection.AddTransient<DoGatherCollectable>();
|
||||||
serviceCollection.AddTransient<SwitchClassJob>();
|
serviceCollection.AddTransient<SwitchClassJob>();
|
||||||
|
serviceCollection.AddSingleton<Mount.Factory>();
|
||||||
|
|
||||||
// task factories
|
// task factories
|
||||||
serviceCollection.AddTaskWithFactory<StepDisabled.Factory, StepDisabled.Task>();
|
serviceCollection.AddTaskFactory<StepDisabled.Factory>();
|
||||||
serviceCollection.AddSingleton<ITaskFactory, EquipRecommended.BeforeDutyOrInstance>();
|
serviceCollection.AddTaskFactory<EquipRecommended.BeforeDutyOrInstance>();
|
||||||
serviceCollection.AddTaskWithFactory<GatheringRequiredItems.Factory, GatheringRequiredItems.StartGathering, GatheringRequiredItems.SkipMarker>();
|
serviceCollection.AddTaskFactory<GatheringRequiredItems.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<AetheryteShortcut.Factory, AetheryteShortcut.UseAetheryteShortcut>();
|
serviceCollection.AddTaskFactory<AetheryteShortcut.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<SkipCondition.Factory, SkipCondition.CheckSkip>();
|
serviceCollection.AddTaskFactory<SkipCondition.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<AethernetShortcut.Factory, AethernetShortcut.UseAethernetShortcut>();
|
serviceCollection.AddTaskFactory<AethernetShortcut.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<WaitAtStart.Factory, WaitAtStart.WaitDelay>();
|
serviceCollection.AddTaskFactory<WaitAtStart.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<Move.Factory, Move.MoveInternal, Move.ExpectToBeNearDataId, Move.Land>();
|
serviceCollection.AddTaskFactory<MoveTo.Factory>();
|
||||||
serviceCollection.AddTransient<Move.MoveBuilder>();
|
|
||||||
|
|
||||||
serviceCollection.AddTaskWithFactory<NextQuest.Factory, NextQuest.SetQuest>();
|
serviceCollection.AddTaskFactory<NextQuest.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<AetherCurrent.Factory, AetherCurrent.DoAttune>();
|
serviceCollection.AddTaskFactory<AetherCurrent.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<AethernetShard.Factory, AethernetShard.DoAttune>();
|
serviceCollection.AddTaskFactory<AethernetShard.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<Aetheryte.Factory, Aetheryte.DoAttune>();
|
serviceCollection.AddTaskFactory<Aetheryte.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<Combat.Factory, Combat.HandleCombat>();
|
serviceCollection.AddTaskFactory<Combat.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<Duty.Factory, Duty.OpenDutyFinder>();
|
serviceCollection.AddTaskFactory<Duty.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<Emote.Factory, Emote.UseOnObject, Emote.Use>();
|
serviceCollection.AddTaskFactory<Emote.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<Action.Factory, Action.UseOnObject>();
|
serviceCollection.AddTaskFactory<Action.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<Interact.Factory, Interact.DoInteract>();
|
serviceCollection.AddTaskFactory<Interact.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<Jump.Factory, Jump.SingleJump, Jump.RepeatedJumps>();
|
serviceCollection.AddTaskFactory<Jump.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<Dive.Factory, Dive.DoDive>();
|
serviceCollection.AddTaskFactory<Dive.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<Say.Factory, Say.UseChat>();
|
serviceCollection.AddTaskFactory<Say.Factory>();
|
||||||
serviceCollection
|
serviceCollection.AddTaskFactory<UseItem.Factory>();
|
||||||
.AddTaskWithFactory<UseItem.Factory, UseItem.UseOnGround, UseItem.UseOnObject, UseItem.Use,
|
serviceCollection.AddTaskFactory<EquipItem.Factory>();
|
||||||
UseItem.UseOnPosition>();
|
serviceCollection.AddTaskFactory<EquipRecommended.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<EquipItem.Factory, EquipItem.DoEquip>();
|
serviceCollection.AddTaskFactory<Craft.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<EquipRecommended.Factory, EquipRecommended.DoEquipRecommended>();
|
serviceCollection.AddTaskFactory<TurnInDelivery.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<Craft.Factory, Craft.DoCraft>();
|
serviceCollection.AddTaskFactory<InitiateLeve.Factory>();
|
||||||
serviceCollection.AddTaskWithFactory<TurnInDelivery.Factory, TurnInDelivery.SatisfactionSupplyTurnIn>();
|
|
||||||
serviceCollection
|
|
||||||
.AddTaskWithFactory<InitiateLeve.Factory,
|
|
||||||
InitiateLeve.SkipInitiateIfActive,
|
|
||||||
InitiateLeve.OpenJournal,
|
|
||||||
InitiateLeve.Initiate,
|
|
||||||
InitiateLeve.SelectDifficulty>();
|
|
||||||
|
|
||||||
serviceCollection
|
serviceCollection.AddTaskFactory<WaitAtEnd.Factory>();
|
||||||
.AddTaskWithFactory<WaitAtEnd.Factory,
|
|
||||||
WaitAtEnd.WaitDelay,
|
|
||||||
WaitAtEnd.WaitNextStepOrSequence,
|
|
||||||
WaitAtEnd.WaitForCompletionFlags,
|
|
||||||
WaitAtEnd.WaitObjectAtPosition>();
|
|
||||||
serviceCollection.AddTransient<WaitAtEnd.WaitQuestAccepted>();
|
serviceCollection.AddTransient<WaitAtEnd.WaitQuestAccepted>();
|
||||||
serviceCollection.AddTransient<WaitAtEnd.WaitQuestCompleted>();
|
serviceCollection.AddTransient<WaitAtEnd.WaitQuestCompleted>();
|
||||||
|
|
||||||
|
@ -6,79 +6,12 @@ namespace Questionable;
|
|||||||
|
|
||||||
internal static class ServiceCollectionExtensions
|
internal static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
public static void AddTaskWithFactory<
|
public static void AddTaskFactory<
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] TFactory>(
|
||||||
TFactory,
|
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
|
||||||
TTask>(
|
|
||||||
this IServiceCollection serviceCollection)
|
this IServiceCollection serviceCollection)
|
||||||
where TFactory : class, ITaskFactory
|
where TFactory : class, ITaskFactory
|
||||||
where TTask : class, ITask
|
|
||||||
{
|
{
|
||||||
serviceCollection.AddSingleton<ITaskFactory, TFactory>();
|
serviceCollection.AddSingleton<ITaskFactory, TFactory>();
|
||||||
serviceCollection.AddTransient<TTask>();
|
serviceCollection.AddSingleton<TFactory>();
|
||||||
}
|
|
||||||
|
|
||||||
public static void AddTaskWithFactory<
|
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
|
||||||
TFactory,
|
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
|
||||||
TTask1,
|
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
|
||||||
TTask2>(
|
|
||||||
this IServiceCollection serviceCollection)
|
|
||||||
where TFactory : class, ITaskFactory
|
|
||||||
where TTask1 : class, ITask
|
|
||||||
where TTask2 : class, ITask
|
|
||||||
{
|
|
||||||
serviceCollection.AddSingleton<ITaskFactory, TFactory>();
|
|
||||||
serviceCollection.AddTransient<TTask1>();
|
|
||||||
serviceCollection.AddTransient<TTask2>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void AddTaskWithFactory<
|
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
|
||||||
TFactory,
|
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
|
||||||
TTask1,
|
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
|
||||||
TTask2,
|
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
|
||||||
TTask3>(
|
|
||||||
this IServiceCollection serviceCollection)
|
|
||||||
where TFactory : class, ITaskFactory
|
|
||||||
where TTask1 : class, ITask
|
|
||||||
where TTask2 : class, ITask
|
|
||||||
where TTask3 : class, ITask
|
|
||||||
{
|
|
||||||
serviceCollection.AddSingleton<ITaskFactory, TFactory>();
|
|
||||||
serviceCollection.AddTransient<TTask1>();
|
|
||||||
serviceCollection.AddTransient<TTask2>();
|
|
||||||
serviceCollection.AddTransient<TTask3>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void AddTaskWithFactory<
|
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
|
||||||
TFactory,
|
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
|
||||||
TTask1,
|
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
|
||||||
TTask2,
|
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
|
||||||
TTask3,
|
|
||||||
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
|
|
||||||
TTask4>(
|
|
||||||
this IServiceCollection serviceCollection)
|
|
||||||
where TFactory : class, ITaskFactory
|
|
||||||
where TTask1 : class, ITask
|
|
||||||
where TTask2 : class, ITask
|
|
||||||
where TTask3 : class, ITask
|
|
||||||
where TTask4 : class, ITask
|
|
||||||
{
|
|
||||||
serviceCollection.AddSingleton<ITaskFactory, TFactory>();
|
|
||||||
serviceCollection.AddTransient<TTask1>();
|
|
||||||
serviceCollection.AddTransient<TTask2>();
|
|
||||||
serviceCollection.AddTransient<TTask3>();
|
|
||||||
serviceCollection.AddTransient<TTask4>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user