qstbak/Questionable/Questionable.Controller/QuestController.cs
2026-08-19 13:19:57 +10:00

1777 lines
52 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.Gui.Toast;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using LLib.GameData;
using LLib.Inventory;
using Microsoft.Extensions.Logging;
using Questionable.Controller.CombatModules;
using Questionable.Controller.Conditions;
using Questionable.Controller.Steps;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Interactions;
using Questionable.Controller.Steps.Shared;
using Questionable.Data;
using Questionable.External;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Gathering;
using Questionable.Model.Questing;
using Questionable.Navigation;
using Questionable.Notifications;
namespace Questionable.Controller;
internal sealed class QuestController : MiniTaskController<QuestController>
{
public delegate void AutomationTypeChangedEventHandler(object sender, EAutomationType e);
public sealed class QuestProgress
{
public Quest Quest { get; private set; }
public byte Sequence { get; private set; }
public int Step { get; private set; }
public StepProgress StepProgress { get; private set; } = new StepProgress(Environment.TickCount64);
public QuestProgressInfo? InitialQuestWork { get; private set; }
public QuestProgress(Quest quest, byte sequence = 0, int step = 0, QuestProgressInfo? initialQuestWork = null)
{
Quest = quest;
InitialQuestWork = initialQuestWork;
SetSequence(sequence, step);
}
internal void CaptureInitialQuestWork(QuestProgressInfo? questWork)
{
if (InitialQuestWork == null)
{
QuestProgressInfo questProgressInfo = (InitialQuestWork = questWork);
}
}
internal void ResetInitialQuestWork(QuestProgressInfo? questWork)
{
InitialQuestWork = questWork;
}
public void SetSequence(byte sequence, int step = 0)
{
Sequence = sequence;
SetStep(step);
}
public void SetStep(int step)
{
Step = step;
StepProgress = new StepProgress(Environment.TickCount64);
}
internal void ReplaceQuest(Quest quest)
{
Quest = quest;
}
public void IncreasePointMenuCounter()
{
StepProgress = StepProgress with
{
PointMenuCounter = StepProgress.PointMenuCounter + 1
};
}
}
public sealed record StepProgress(long StartedAt, int PointMenuCounter = 0);
public enum ECurrentQuestType
{
Normal,
Next,
Simulated
}
public enum EAutomationType
{
Manual,
Automatic,
SingleQuestA,
SingleQuestB
}
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
private readonly GameFunctions _gameFunctions;
private readonly QuestFunctions _questFunctions;
private readonly MovementController _movementController;
private readonly CombatController _combatController;
private readonly WrathComboModule _wrathComboModule;
private readonly RotationSolverRebornModule _rsrModule;
private readonly BossModIpc _bossModIpc;
private readonly GatheringController _gatheringController;
private readonly FishingController _fishingController;
private readonly FateController _fateController;
private readonly SeasonalDutyController _seasonalDutyController;
private readonly CustomDeliveryController _customDeliveryController;
private readonly AttunementController _attunementController;
private readonly QuestRegistry _questRegistry;
private readonly TerritoryData _territoryData;
private readonly JournalData _journalData;
private readonly IKeyState _keyState;
private readonly IChatGui _chatGui;
private readonly ICondition _condition;
private readonly IToastGui _toastGui;
private readonly Configuration _configuration;
private readonly TaskCreator _taskCreator;
private readonly GatheringPointRegistry _gatheringPointRegistry;
private readonly IFramework _framework;
private readonly AutoDutyIpc _autoDutyIpc;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly QuestPriorityResolver _priorityResolver;
private readonly SmartNavReRouteService _reRouteService;
private readonly SmartNavRouteEnqueuer _smartNavRouteEnqueuer;
private readonly GearRepairService _gearRepairService;
private readonly NotificationService _notificationService;
private readonly ICommandManager _commandManager;
private QuestProgress? _startedQuest;
private QuestProgress? _nextQuest;
private QuestProgress? _simulatedQuest;
private QuestProgress? _pendingQuest;
private (ElementId QuestId, QuestProgressInfo QuestWork)? _initialQuestWorkPreservedForReload;
private EAutomationType _automationType;
private bool _commandAfterStopFired;
private long _safeAnimationEnd;
private bool _lastEscDown;
private int _escPressCount;
private long _lastEscPressTime;
private const long EscDoublePressWindowMs = 1000L;
private (ElementId QuestId, byte Sequence, bool Result, long ComputedAt)? _interruptibleRouteCache;
private const long InterruptibleRouteCacheMs = 2000L;
private DateTime _automationStartedAt;
private int _questsCompletedThisSession;
private readonly HashSet<StopCondition> _conditionsMetAtStart = new HashSet<StopCondition>();
private bool _deathRecoveryPending;
private int _deathCount;
private bool _resumeAfterSideTasks;
private bool _resumeAfterDutyExit;
private bool _resumeAfterOccupiedQuestTransition;
private bool _runningDebugTasks;
private const char ClipboardSeparator = ';';
public string? LastStopReason { get; private set; }
public List<StopCondition> SessionConditions { get; } = new List<StopCondition>();
public EAutomationType AutomationType
{
get
{
return _automationType;
}
set
{
if (value != _automationType)
{
_logger.LogDebug("Setting automation type to {NewAutomationType} (previous: {OldAutomationType})", value, _automationType);
_automationType = value;
if (value != EAutomationType.Manual)
{
_commandAfterStopFired = false;
}
this.AutomationTypeChanged?.Invoke(this, value);
}
}
}
public (QuestProgress Progress, ECurrentQuestType Type)? CurrentQuestDetails
{
get
{
if (_simulatedQuest != null)
{
return (_simulatedQuest, ECurrentQuestType.Simulated);
}
if (_nextQuest != null && _questFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.Id))
{
return (_nextQuest, ECurrentQuestType.Next);
}
if (_startedQuest != null)
{
return (_startedQuest, ECurrentQuestType.Normal);
}
return null;
}
}
public QuestProgress? CurrentQuest => CurrentQuestDetails?.Progress;
public QuestProgress? StartedQuest => _startedQuest;
public QuestProgress? SimulatedQuest => _simulatedQuest;
public QuestProgress? NextQuest => _nextQuest;
private IPlayerCharacter? LocalPlayer => _objectTable.LocalPlayer;
public QuestProgress? PendingQuest => _pendingQuest;
public List<Quest> ManualPriorityQuests { get; } = new List<Quest>();
public string? DebugState { get; private set; }
public bool IsQuestWindowOpen => IsQuestWindowOpenFunction?.Invoke() ?? true;
public Func<bool>? IsQuestWindowOpenFunction { private get; set; } = () => true;
public bool IsRunning => !_taskQueue.AllTasksComplete;
public bool IsDeathRecoveryPending => _deathRecoveryPending;
public TaskQueue TaskQueue => _taskQueue;
public string? CurrentTaskState
{
get
{
if (_taskQueue.CurrentTaskExecutor is IDebugStateProvider debugStateProvider)
{
return debugStateProvider.GetDebugState();
}
return null;
}
}
public event AutomationTypeChangedEventHandler? AutomationTypeChanged;
public QuestController(IClientState clientState, IObjectTable objectTable, GameFunctions gameFunctions, QuestFunctions questFunctions, MovementController movementController, CombatController combatController, WrathComboModule wrathComboModule, RotationSolverRebornModule rsrModule, BossModIpc bossModIpc, GatheringController gatheringController, FishingController fishingController, FateController fateController, SeasonalDutyController seasonalDutyController, CustomDeliveryController customDeliveryController, AttunementController attunementController, ILogger<QuestController> logger, QuestRegistry questRegistry, TerritoryData territoryData, JournalData journalData, IKeyState keyState, IChatGui chatGui, ICondition condition, IToastGui toastGui, Configuration configuration, TaskCreator taskCreator, IServiceProvider serviceProvider, InterruptHandler interruptHandler, IDataManager dataManager, GatheringPointRegistry gatheringPointRegistry, IFramework framework, AutoDutyIpc autoDutyIpc, IDalamudPluginInterface pluginInterface, QuestPriorityResolver priorityResolver, SmartNavReRouteService reRouteService, SmartNavRouteEnqueuer smartNavRouteEnqueuer, GearRepairService gearRepairService, NotificationService notificationService, ICommandManager commandManager)
: base(chatGui, condition, serviceProvider, interruptHandler, dataManager, configuration, logger)
{
_clientState = clientState;
_objectTable = objectTable;
_gameFunctions = gameFunctions;
_questFunctions = questFunctions;
_movementController = movementController;
_combatController = combatController;
_wrathComboModule = wrathComboModule;
_rsrModule = rsrModule;
_bossModIpc = bossModIpc;
_gatheringController = gatheringController;
_fishingController = fishingController;
_fateController = fateController;
_seasonalDutyController = seasonalDutyController;
_customDeliveryController = customDeliveryController;
_attunementController = attunementController;
_questRegistry = questRegistry;
_territoryData = territoryData;
_journalData = journalData;
_keyState = keyState;
_chatGui = chatGui;
_condition = condition;
_toastGui = toastGui;
_configuration = configuration;
_taskCreator = taskCreator;
_gatheringPointRegistry = gatheringPointRegistry;
_framework = framework;
_autoDutyIpc = autoDutyIpc;
_pluginInterface = pluginInterface;
_priorityResolver = priorityResolver;
_reRouteService = reRouteService;
_smartNavRouteEnqueuer = smartNavRouteEnqueuer;
_gearRepairService = gearRepairService;
_notificationService = notificationService;
_commandManager = commandManager;
_toastGui.ErrorToast += base.OnErrorToast;
_toastGui.Toast += OnNormalToast;
_condition.ConditionChange += OnConditionChange;
_clientState.Logout += OnLogout;
_movementController.PlayerInputDetected += OnPlayerInputDetected;
_questRegistry.Reloaded += OnQuestRegistryReloaded;
}
private static ushort GetQuestIdValue(ElementId id)
{
if (!(id is QuestId questId))
{
return 0;
}
return questId.Value;
}
public void Reload()
{
_logger.LogInformation("Reload, resetting current quest progress");
QuestRegistry.Snapshot questSnapshot = _questRegistry.Build();
Dictionary<GatheringPointId, GatheringRoot> gatheringPoints = _gatheringPointRegistry.Build();
_framework.RunOnFrameworkThread(delegate
{
QuestProgress questProgress = CurrentQuestDetails?.Progress;
QuestProgressInfo questProgressInfo = questProgress?.InitialQuestWork;
if (questProgress != null && questProgressInfo == null)
{
questProgressInfo = _questFunctions.GetQuestProgressInfo(questProgress.Quest.Id);
}
_initialQuestWorkPreservedForReload = ((questProgress != null && questProgressInfo != null) ? new(ElementId, QuestProgressInfo)?((questProgress.Quest.Id, questProgressInfo)) : (((ElementId, QuestProgressInfo)?)null));
ResetInternalState();
_gatheringPointRegistry.Publish(gatheringPoints);
_questRegistry.Publish(questSnapshot);
}).GetAwaiter().GetResult();
}
private void ResetInternalState()
{
_startedQuest = null;
_nextQuest = null;
_pendingQuest = null;
_simulatedQuest = null;
_safeAnimationEnd = 0L;
_resumeAfterDutyExit = false;
_resumeAfterOccupiedQuestTransition = false;
DebugState = null;
}
public unsafe void Update()
{
ActionManager* ptr = ActionManager.Instance();
if (ptr != null)
{
float num = Math.Max(ptr->AnimationLock, (ptr->CastTimeElapsed > 0f) ? (ptr->CastTimeTotal - ptr->CastTimeElapsed) : 0f);
if (num > 0f)
{
_safeAnimationEnd = Environment.TickCount64 + (long)(1000f * (1f + num));
}
}
if (AutomationType == EAutomationType.Manual && !IsRunning && !IsQuestWindowOpen && !_deathRecoveryPending)
{
return;
}
UpdateCurrentQuest();
if (!_clientState.IsLoggedIn)
{
return;
}
if (_condition[ConditionFlag.Unconscious])
{
if (!_condition[ConditionFlag.SufferingStatusAffliction63] || _clientState.TerritoryType != 1052)
{
ITaskExecutor currentTaskExecutor = _taskQueue.CurrentTaskExecutor;
bool flag = ((currentTaskExecutor is Duty.WaitAutoDutyExecutor || currentTaskExecutor is Duty.WaitLevelingModeExecutor) ? true : false);
if (!flag && !_condition[ConditionFlag.BoundByDuty] && !_condition[ConditionFlag.BoundByDuty56] && !_condition[ConditionFlag.InDeepDungeon] && !_taskQueue.AllTasksComplete && !_deathRecoveryPending)
{
if (_configuration.General.DeathRecoveryEnabled)
{
_deathCount++;
if (_configuration.General.MaxDeathsBeforeStop > 0 && _deathCount >= _configuration.General.MaxDeathsBeforeStop)
{
_logger.LogWarning("Death recovery: max deaths ({Count}) reached, stopping", _deathCount);
_deathCount = 0;
StopAllDueToConditionFailed("HP = 0 (max deaths reached)");
_deathRecoveryPending = true;
}
else
{
_logger.LogInformation("Death recovery: died ({Count}), waiting for respawn", _deathCount);
_deathRecoveryPending = true;
_movementController.Stop();
_combatController.Stop("HP = 0");
}
}
else
{
StopAllDueToConditionFailed("HP = 0");
}
}
}
}
else
{
if (_deathRecoveryPending)
{
if (!ConditionHelper.IsBetweenAreas(_condition))
{
_deathRecoveryPending = false;
if (AutomationType != EAutomationType.Manual)
{
_logger.LogInformation("Death recovery: respawned, re-planning current step");
_gatheringController.Replan();
_fateController.Replan();
CheckNextTasks("Death recovery");
}
else
{
_logger.LogInformation("Death recovery: respawned (automation stopped)");
}
}
return;
}
if (_configuration.General.UseEscToCancelQuesting)
{
if (_keyState[VirtualKey.ESCAPE] && !_lastEscDown)
{
long tickCount = Environment.TickCount64;
if (tickCount - _lastEscPressTime <= 1000)
{
_escPressCount++;
}
else
{
_escPressCount = 1;
}
_lastEscPressTime = tickCount;
if (_escPressCount >= 2)
{
if (!_taskQueue.AllTasksComplete)
{
StopAllDueToConditionFailed("ESC pressed twice");
}
_escPressCount = 0;
}
}
if (!_keyState[VirtualKey.ESCAPE] && Environment.TickCount64 - _lastEscPressTime > 1000)
{
_escPressCount = 0;
}
_lastEscDown = _keyState[VirtualKey.ESCAPE];
}
else
{
_lastEscDown = _keyState[VirtualKey.ESCAPE];
_escPressCount = 0;
_lastEscPressTime = 0L;
}
}
if ((AutomationType == EAutomationType.Manual && !IsRunning) || !_configuration.Stop.Enabled || !CheckStopConditions(BuildEvaluationContext()))
{
UpdateCurrentTask();
}
}
private void UpdateCurrentQuest()
{
DebugState = null;
if (!_clientState.IsLoggedIn)
{
ResetInternalState();
DebugState = "Not logged in";
return;
}
if (_resumeAfterDutyExit)
{
if (ConditionHelper.IsBetweenAreas(_condition) || IsInsideDutyInstance())
{
DebugState = "Waiting for duty exit";
return;
}
_resumeAfterDutyExit = false;
CheckNextTasks("Resume after duty exit");
return;
}
if (_resumeAfterOccupiedQuestTransition)
{
if (_gameFunctions.IsOccupied())
{
DebugState = "Waiting for quest transition to finish";
return;
}
_resumeAfterOccupiedQuestTransition = false;
CheckNextTasks("Resume after occupied quest transition");
return;
}
if (_runningDebugTasks)
{
if (!_taskQueue.AllTasksComplete)
{
DebugState = "Running debug tasks";
return;
}
_runningDebugTasks = false;
}
if (_resumeAfterSideTasks)
{
if (!_taskQueue.AllTasksComplete)
{
DebugState = "Running side tasks (e.g. repair)";
return;
}
_resumeAfterSideTasks = false;
CheckNextTasks("Resume step after side tasks");
return;
}
if (_configuration.General.ClearPriorityQuestsOnCompletion && ManualPriorityQuests.Count > 0)
{
int num = ManualPriorityQuests.RemoveAll((Quest q) => _questFunctions.IsQuestComplete(q.Id) && !_questFunctions.IsQuestAccepted(q.Id));
if (num > 0)
{
_logger.LogDebug("Removed {Count} completed priority {QuestWord}", num, (num == 1) ? "quest" : "quests");
}
}
if (_pendingQuest != null)
{
if (!_questFunctions.IsQuestAccepted(_pendingQuest.Quest.Id))
{
DebugState = $"Waiting for Leve {_pendingQuest.Quest.Id}";
return;
}
_startedQuest = _pendingQuest;
_pendingQuest = null;
OnQuestAccepted(_startedQuest.Quest.Id);
if (AutomationType == EAutomationType.Manual)
{
return;
}
CheckNextTasks("Pending quest accepted");
}
if (_simulatedQuest == null && _nextQuest != null && !((!_nextQuest.Quest.Info.IsRepeatable) ? (!_questFunctions.IsQuestAcceptedOrComplete(_nextQuest.Quest.Id)) : (!_questFunctions.IsQuestAccepted(_nextQuest.Quest.Id))))
{
ElementId id = _nextQuest.Quest.Id;
_logger.LogInformation("Next quest {QuestId} accepted or completed", id);
if (AutomationType == EAutomationType.SingleQuestA)
{
_startedQuest = _nextQuest;
AutomationType = EAutomationType.SingleQuestB;
}
_logger.LogDebug("Started: {StartedQuest}", _startedQuest?.Quest.Id);
_nextQuest = null;
OnQuestAccepted(id);
if (AutomationType == EAutomationType.Manual)
{
return;
}
}
QuestResolution resolution = _priorityResolver.Resolve(new QuestSlots(_simulatedQuest, _nextQuest, ManualPriorityQuests, AutomationType));
byte b = resolution.Sequence;
QuestProgress questProgress;
if (resolution.IsSlotBased)
{
questProgress = resolution.Type switch
{
EQuestResolutionType.Simulated => _simulatedQuest,
EQuestResolutionType.NextQuest => _nextQuest,
_ => null,
};
if (resolution.Type == EQuestResolutionType.NextQuest && _questFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.Id) && _nextQuest.Step == 0 && _taskQueue.AllTasksComplete && AutomationType == EAutomationType.Automatic)
{
ExecuteNextStep();
}
}
else
{
if (resolution.Type == EQuestResolutionType.LevelingMode)
{
if (IsLevelingModeActive())
{
DebugState = "Leveling mode active";
}
else if (_taskQueue.AllTasksComplete)
{
_logger.LogInformation("Starting AutoDuty Leveling mode for level {RequiredLevel} (quest: {QuestName})", resolution.LevelingTargetLevel, resolution.LevelingQuestName);
EnqueueLevelingTasks(resolution.LevelingTargetLevel, resolution.LevelingQuestName);
}
return;
}
if (resolution.Type == EQuestResolutionType.NoQuest)
{
if (IsLevelingModeActive())
{
DebugState = "Leveling mode active";
return;
}
if (_startedQuest != null)
{
if (resolution.MsqState == MainScenarioQuestState.Unavailable)
{
_logger.LogWarning("MSQ information not available, doing nothing");
return;
}
if (resolution.MsqState == MainScenarioQuestState.LoadingScreen)
{
_logger.LogWarning("On loading screen, no MSQ - doing nothing");
return;
}
_logger.LogInformation("No current quest, resetting data (resolution: {Reason})", resolution.DebugReason);
_startedQuest = null;
Stop("Resetting current quest");
}
DebugState = "No quest active";
Stop("No quest active");
return;
}
(questProgress, b) = HandleGameStateTransition(resolution);
if (questProgress == null)
{
return;
}
}
if (questProgress == null)
{
if (IsLevelingModeActive())
{
DebugState = "Leveling mode active";
return;
}
DebugState = "No quest active";
Stop("No quest active");
}
else if (questProgress.Sequence != b)
{
if (IsDutyChainActive())
{
DebugState = "Waiting for duty to finish";
return;
}
questProgress.SetSequence(b);
QuestProgressInfo questProgressInfo = _questFunctions.GetQuestProgressInfo(questProgress.Quest.Id);
questProgress.ResetInitialQuestWork(questProgressInfo);
_logger.LogDebug("Captured initial quest variables for {QuestId} sequence {Sequence}: {QuestWork}", questProgress.Quest.Id, b, questProgressInfo);
bool flag = _gameFunctions.IsOccupied();
if (flag)
{
EAutomationType automationType = AutomationType;
bool flag2 = (uint)(automationType - 1) <= 2u;
flag = flag2;
}
if (flag)
{
ClearTasksInternal();
_resumeAfterOccupiedQuestTransition = true;
DebugState = "Waiting for quest transition to finish";
}
else
{
CheckNextTasks($"New sequence {questProgress == _startedQuest}");
}
}
else if (questProgress.Step == 255)
{
DebugState = $"Waiting for sequence update (current: {questProgress.Sequence})";
if (!_taskQueue.AllTasksComplete)
{
DebugState = "Step 255 - processing interrupted tasks";
}
else
{
if (CurrentQuest == null)
{
return;
}
long num2 = Environment.TickCount64 - CurrentQuest.StepProgress.StartedAt;
if (num2 > 3000)
{
_logger.LogWarning("Step 255 with no tasks for {WaitTime:F1}s, retrying step to ensure completion (quest: {QuestId}, sequence: {Sequence})", (float)num2 / 1000f, questProgress.Quest.Id, questProgress.Sequence);
QuestSequence questSequence = questProgress.Quest.FindSequence(questProgress.Sequence);
if (questSequence != null && questSequence.Steps.Count > 0)
{
CurrentQuest.SetStep(questSequence.Steps.Count - 1);
CheckNextTasks("Retry last step at 255");
}
}
}
}
else if (_gameFunctions.IsOccupied())
{
DebugState = "Occupied";
}
else if (_movementController.IsPathfinding)
{
DebugState = "Pathfinding is running";
}
else if (_movementController.IsPathRunning)
{
DebugState = "Path is running";
}
else if (Environment.TickCount64 < _safeAnimationEnd)
{
DebugState = "Waiting for Animation";
}
else
{
QuestSequence questSequence2 = questProgress.Quest.FindSequence(questProgress.Sequence);
if (questSequence2 == null)
{
DebugState = $"Sequence {questProgress.Sequence} not found";
Stop("Unknown sequence");
}
else if (questSequence2.Steps.Count > 0 && questProgress.Step >= questSequence2.Steps.Count)
{
DebugState = "Step not found";
Stop("Unknown step");
}
else
{
DebugState = null;
}
}
}
private (QuestProgress?, byte) HandleGameStateTransition(QuestResolution resolution)
{
ElementId questId = resolution.QuestId;
byte sequence = resolution.Sequence;
if (_startedQuest != null && _startedQuest.Quest.Id == questId)
{
return (_startedQuest, sequence);
}
if (_startedQuest != null && !_taskQueue.AllTasksComplete)
{
return (_startedQuest, _startedQuest.Sequence);
}
if (_startedQuest != null && _questFunctions.IsQuestComplete(_startedQuest.Quest.Id) && !_questFunctions.IsQuestAccepted(_startedQuest.Quest.Id) && CheckStopConditions(BuildEvaluationContext(_startedQuest.Quest.Id)))
{
_startedQuest = null;
return (null, 0);
}
if (_questRegistry.TryGetQuest(questId, out Quest quest))
{
_logger.LogDebug("New quest: {QuestName} (via {ResolutionType})", quest.Info.Name, resolution.Type);
EClassJob valueOrDefault = ((EClassJob?)LocalPlayer?.ClassJob.RowId).GetValueOrDefault();
if (valueOrDefault.IsLimitedJob() && quest.Info.ClassJobs.Count > 0 && !quest.Info.ClassJobs.Contains(valueOrDefault))
{
_logger.LogWarning("Quest {QuestName} does not allow limited job {Job}, stopping", quest.Info.Name, valueOrDefault.ToFriendlyString());
Stop("Quest '" + quest.Info.Name + "' cannot be progressed on " + valueOrDefault.ToFriendlyString());
return (null, 0);
}
_startedQuest = CreateQuestProgress(quest, sequence);
OnQuestAccepted(quest.Id);
if (AutomationType == EAutomationType.Manual)
{
return (null, 0);
}
IPlayerCharacter localPlayer = LocalPlayer;
if (localPlayer != null && localPlayer.Level < quest.Info.Level)
{
if (_priorityResolver.ShouldStartLevelingMode(localPlayer.Level, AutomationType))
{
_logger.LogInformation("Player level ({PlayerLevel}) < quest level ({QuestLevel}), starting AutoDuty Leveling mode", localPlayer.Level, quest.Info.Level);
ClearTasksInternal();
EnqueueLevelingTasks(quest.Info.Level, quest.Info.Name);
}
else
{
_logger.LogInformation("Stopping automation, player level ({PlayerLevel}) < quest level ({QuestLevel})", localPlayer.Level, quest.Info.Level);
Stop("Quest level too high");
}
return (null, 0);
}
if (AutomationType == EAutomationType.SingleQuestB)
{
_logger.LogInformation("Single quest is finished");
AutomationType = EAutomationType.Manual;
}
CheckNextTasks("Different Quest");
return (null, 0);
}
if (_startedQuest != null)
{
_logger.LogWarning("No active quest anymore? Not sure what happened...");
_startedQuest = null;
Stop("No active Quest");
return (null, 0);
}
return (null, 0);
}
private bool IsInsideDutyInstance()
{
uint territoryType = _clientState.TerritoryType;
if (_territoryData.IsDutyInstance(territoryType))
{
return !_territoryData.IsFieldOperation(territoryType);
}
return false;
}
private bool IsDutyChainActive()
{
if (!(_taskQueue.CurrentTaskExecutor?.CurrentTask is IDutyTask))
{
return _taskQueue.RemainingTasks.Any((ITask x) => x is IDutyTask);
}
return true;
}
private bool IsLevelingModeActive()
{
ITask task = _taskQueue.CurrentTaskExecutor?.CurrentTask;
if ((!(task is Duty.StartLevelingModeTask) && !(task is Duty.WaitLevelingModeTask)) || 1 == 0)
{
return _taskQueue.HasLevelingTasks;
}
return true;
}
private void EnqueueLevelingTasks(int targetLevel, string? questName)
{
_taskQueue.Enqueue(new Duty.StartLevelingModeTask(targetLevel, questName));
_taskQueue.Enqueue(new Duty.WaitLevelingModeTask(targetLevel));
}
public (QuestSequence? Sequence, QuestStep? Step, bool createTasks) GetNextStep()
{
if (CurrentQuest == null)
{
return (Sequence: null, Step: null, createTasks: false);
}
QuestSequence questSequence = CurrentQuest.Quest.FindSequence(CurrentQuest.Sequence);
if (questSequence == null)
{
return (Sequence: null, Step: null, createTasks: true);
}
if (questSequence.Steps.Count == 0)
{
return (Sequence: questSequence, Step: null, createTasks: true);
}
if (CurrentQuest.Step >= questSequence.Steps.Count)
{
return (Sequence: null, Step: null, createTasks: false);
}
return (Sequence: questSequence, Step: questSequence.Steps[CurrentQuest.Step], createTasks: true);
}
public void IncreaseStepCount(ElementId? questId, int? sequence, bool shouldContinue = false)
{
var (questSequence, questStep, _) = GetNextStep();
if (CurrentQuest == null || questSequence == null || questStep == null)
{
_logger.LogWarning("Unable to retrieve next quest step, not increasing step count");
return;
}
if (questId != null && CurrentQuest.Quest.Id != questId)
{
_logger.LogWarning("Ignoring 'increase step count' for different quest (expected {ExpectedQuestId}, but we are at {CurrentQuestId}", questId, CurrentQuest.Quest.Id);
return;
}
if (sequence.HasValue && questSequence.Sequence != sequence.Value)
{
_logger.LogWarning("Ignoring 'increase step count' for different sequence (expected {ExpectedSequence}, but we are at {CurrentSequence}", sequence, questSequence.Sequence);
return;
}
_logger.LogDebug("Increasing step count from {CurrentValue}", CurrentQuest.Step);
bool num = CurrentQuest.Step + 1 >= questSequence.Steps.Count;
if (num)
{
CurrentQuest.SetStep(255);
}
else
{
CurrentQuest.SetStep(CurrentQuest.Step + 1);
}
if (num)
{
_logger.LogDebug("Completed last step in sequence, waiting for game to update sequence");
return;
}
using (_logger.BeginScope("IncStepCt"))
{
if (shouldContinue && AutomationType != EAutomationType.Manual)
{
QuestResolution questResolution = _priorityResolver.Resolve(new QuestSlots(_simulatedQuest, _nextQuest, ManualPriorityQuests, AutomationType));
if (questResolution.HasQuest && questResolution.QuestId != CurrentQuest.Quest.Id)
{
_logger.LogDebug("Deferring next step: resolver selected {ResolvedQuest} over current {CurrentQuest}", questResolution.QuestId, CurrentQuest.Quest.Id);
}
else
{
ExecuteNextStep();
}
}
}
}
private void ClearTasksInternal()
{
_runningDebugTasks = false;
_reRouteService.ClearDestination();
if (_taskQueue.CurrentTaskExecutor is IStoppableTaskExecutor stoppableTaskExecutor)
{
stoppableTaskExecutor.StopNow();
}
_taskQueue.Reset();
_combatController.Stop("ClearTasksInternal");
_wrathComboModule.Stop();
_rsrModule.Stop();
_bossModIpc.DisableQuestBattleAi();
_gatheringController.Stop("ClearTasksInternal");
_fateController.Stop("ClearTasksInternal");
_seasonalDutyController.Stop("ClearTasksInternal");
}
public override void Stop(string label)
{
base.Stop(label);
using (_logger.BeginScope("Stop/" + label))
{
if (IsRunning || AutomationType != EAutomationType.Manual)
{
ClearTasksInternal();
_logger.LogInformation("Stopping automatic questing");
AutomationType = EAutomationType.Manual;
_nextQuest = null;
_deathCount = 0;
_deathRecoveryPending = false;
_resumeAfterSideTasks = false;
_resumeAfterDutyExit = false;
_resumeAfterOccupiedQuestTransition = false;
SessionConditions.Clear();
_conditionsMetAtStart.Clear();
}
}
}
public void RunDebugTasks(IReadOnlyList<ITask> tasks, string label)
{
Stop(label);
_runningDebugTasks = true;
foreach (ITask task in tasks)
{
_taskQueue.Enqueue(task);
}
}
private void StopAllDueToConditionFailed(string label)
{
Stop(label);
_movementController.Stop();
_combatController.Stop(label);
_gatheringController.Stop(label);
_fishingController.Stop(label);
_fateController.Stop(label);
_seasonalDutyController.Stop(label);
_customDeliveryController.Stop(label);
_attunementController.Stop(label);
}
private void CheckNextTasks(string label)
{
EAutomationType automationType = AutomationType;
if ((uint)(automationType - 1) <= 2u)
{
using (_logger.BeginScope(label))
{
ClearTasksInternal();
int? num = CurrentQuest?.Step;
if (num.HasValue)
{
int valueOrDefault = num.GetValueOrDefault();
if (valueOrDefault >= 0 && valueOrDefault < 255)
{
ExecuteNextStep();
return;
}
}
_logger.LogDebug("Couldn't execute next step during Stop() call");
return;
}
}
Stop(label);
}
public void SimulateQuest(Quest? quest, byte sequence, int step)
{
_logger.LogDebug("SimulateQuest: {QuestId}", quest?.Id);
if (quest != null)
{
_simulatedQuest = CreateQuestProgress(quest, sequence, step);
}
else
{
_simulatedQuest = null;
}
}
public void SetNextQuest(Quest? quest)
{
_logger.LogInformation("NextQuest: {QuestId}", quest?.Id);
if (quest != null)
{
_nextQuest = CreateQuestProgress(quest, 0);
}
else
{
_nextQuest = null;
}
}
public void SetPendingQuest(QuestProgress? quest)
{
_logger.LogDebug("PendingQuest: {QuestId}", quest?.Quest.Id);
_pendingQuest = quest;
}
protected override void UpdateCurrentTask()
{
bool flag = _gameFunctions.IsOccupied();
if (flag)
{
ITask task = _taskQueue.CurrentTaskExecutor?.CurrentTask;
bool flag2 = ((task is WaitAtEnd.WaitQuestAccepted || task is RepairGear.SelfRepairTask || task is RepairGear.MenderRepairTask) ? true : false);
flag = !flag2;
}
if (!flag)
{
base.UpdateCurrentTask();
}
}
protected override void OnTaskComplete(ITask task)
{
if (task is WaitAtEnd.WaitQuestCompleted)
{
_questsCompletedThisSession++;
_simulatedQuest = null;
}
}
protected override void OnRetryStep()
{
ExecuteNextStep();
}
protected override void OnNextStep(ILastTask task)
{
_deathCount = 0;
IncreaseStepCount(task.ElementId, task.Sequence, shouldContinue: true);
}
public void Start(string label)
{
using (_logger.BeginScope("Q/" + label))
{
if (!CheckAndBlockForStopConditions())
{
RecordStopConditionsMetAtStart();
_gearRepairService.ResetAttemptCooldown();
AutomationType = EAutomationType.Automatic;
ExecuteNextStep();
}
}
}
public void StartSingleQuest(string label)
{
using (_logger.BeginScope("SQ/" + label))
{
if (!CheckAndBlockForStopConditions())
{
RecordStopConditionsMetAtStart();
_gearRepairService.ResetAttemptCooldown();
AutomationType = EAutomationType.SingleQuestA;
ExecuteNextStep();
}
}
}
public void StartSingleStep(string label)
{
using (_logger.BeginScope("SS/" + label))
{
AutomationType = EAutomationType.Manual;
ExecuteNextStep();
}
}
private StopEvaluationContext BuildEvaluationContext(ElementId? completedQuestId = null, ElementId? acceptedQuestId = null, EInteractionType? upcomingInteractionType = null)
{
IPlayerCharacter localPlayer = LocalPlayer;
return new StopEvaluationContext
{
PlayerLevel = (localPlayer?.Level ?? 0),
CurrentQuestId = CurrentQuest?.Quest.Id,
CurrentSequence = (CurrentQuest?.Sequence ?? 0),
CompletedQuestId = completedQuestId,
AcceptedQuestId = acceptedQuestId,
Gil = GetCurrentGil(),
FreeInventorySlots = InventoryHelper.CountFreeInventorySlots(),
ElapsedSinceStart = DateTime.UtcNow - _automationStartedAt,
QuestsCompletedThisSession = _questsCompletedThisSession,
UpcomingInteractionType = upcomingInteractionType
};
}
private unsafe static int GetCurrentGil()
{
InventoryManager* ptr = InventoryManager.Instance();
if (ptr == null)
{
return 0;
}
return ptr->GetItemCountInContainer(1u, InventoryType.Currency, isHq: false, 0);
}
private bool CheckStopConditions(StopEvaluationContext context)
{
if (!_configuration.Stop.Enabled)
{
return false;
}
bool hasQuestSpecificSequence = false;
if (context.CurrentQuestId != null)
{
foreach (StopCondition condition in _configuration.Stop.Conditions)
{
if (condition is QuestCompleteCondition questCompleteCondition && questCompleteCondition.QuestId.Equals(context.CurrentQuestId) && questCompleteCondition.Sequence.HasValue)
{
hasQuestSpecificSequence = true;
break;
}
}
}
foreach (StopCondition condition2 in _configuration.Stop.Conditions)
{
if (EvaluateCondition(condition2, context, hasQuestSpecificSequence))
{
return true;
}
}
foreach (StopCondition sessionCondition in SessionConditions)
{
if (EvaluateCondition(sessionCondition, context, hasQuestSpecificSequence))
{
return true;
}
}
return false;
}
private bool EvaluateCondition(StopCondition condition, StopEvaluationContext context, bool hasQuestSpecificSequence)
{
if (condition.Mode == EStopConditionMode.Off)
{
return false;
}
if (condition is GlobalSequenceCondition && hasQuestSpecificSequence)
{
return false;
}
if (!condition.Evaluate(context))
{
return false;
}
if (condition.Mode == EStopConditionMode.Pause && _conditionsMetAtStart.Contains(condition))
{
return false;
}
_logger.LogInformation("Stop condition met: {Description} (mode={Mode})", condition.GetDescription(), condition.Mode);
LastStopReason = $"{condition.Mode}: {condition.GetDescription()}";
_notificationService.NotifyStopCondition("Stopped - " + condition.GetDescription());
if (condition.IsSession)
{
SessionConditions.Remove(condition);
}
bool flag = AutomationType != EAutomationType.Manual;
Stop("Stop condition: " + condition.GetDescription());
if (condition.Mode == EStopConditionMode.Stop && flag && !_commandAfterStopFired)
{
_commandAfterStopFired = true;
string text = _configuration.Stop.CommandAfterStop?.Trim() ?? string.Empty;
if (text.Length > 0)
{
if (!text.StartsWith('/'))
{
text = "/" + text;
}
try
{
_commandManager.ProcessCommand(text);
}
catch (Exception exception)
{
_logger.LogWarning(exception, "CommandAfterStop failed: {Command}", text);
}
}
}
return true;
}
public void OnQuestAccepted(ElementId questId)
{
CaptureInitialQuestWork(_startedQuest, questId);
CaptureInitialQuestWork(_nextQuest, questId);
CaptureInitialQuestWork(_pendingQuest, questId);
if (CheckStopConditions(BuildEvaluationContext(null, questId)))
{
_logger.LogInformation("Stopped after accepting quest {QuestId}", questId);
}
}
public QuestProgressInfo? GetInitialQuestProgressInfo(ElementId questId)
{
QuestProgress questProgress = CurrentQuestDetails?.Progress;
if (questProgress == null || !questProgress.Quest.Id.Equals(questId))
{
return null;
}
questProgress.CaptureInitialQuestWork(_questFunctions.GetQuestProgressInfo(questId));
return questProgress.InitialQuestWork;
}
private QuestProgress CreateQuestProgress(Quest quest, byte sequence = 0, int step = 0)
{
(ElementId, QuestProgressInfo)? initialQuestWorkPreservedForReload = _initialQuestWorkPreservedForReload;
QuestProgressInfo questProgressInfo;
if (initialQuestWorkPreservedForReload.HasValue)
{
(ElementId, QuestProgressInfo) valueOrDefault = initialQuestWorkPreservedForReload.GetValueOrDefault();
if (valueOrDefault.Item1.Equals(quest.Id))
{
questProgressInfo = valueOrDefault.Item2;
_initialQuestWorkPreservedForReload = null;
_logger.LogDebug("Restored initial quest variables for {QuestId} after data reload: {QuestWork}", quest.Id, questProgressInfo);
goto IL_0075;
}
}
questProgressInfo = _questFunctions.GetQuestProgressInfo(quest.Id);
goto IL_0075;
IL_0075:
return new QuestProgress(quest, sequence, step, questProgressInfo);
}
private void CaptureInitialQuestWork(QuestProgress? progress, ElementId questId)
{
if (progress != null && progress.Quest.Id.Equals(questId))
{
progress.CaptureInitialQuestWork(_questFunctions.GetQuestProgressInfo(questId));
}
}
private void RecordStopConditionsMetAtStart()
{
LastStopReason = null;
_conditionsMetAtStart.Clear();
_automationStartedAt = DateTime.UtcNow;
_questsCompletedThisSession = 0;
if (!_configuration.Stop.Enabled)
{
return;
}
StopEvaluationContext context = BuildEvaluationContext();
foreach (StopCondition item in _configuration.Stop.Conditions.Concat(SessionConditions))
{
if (item.Mode == EStopConditionMode.Pause && item.Evaluate(context))
{
_conditionsMetAtStart.Add(item);
}
}
}
private bool CheckAndBlockForStopConditions()
{
if (!_configuration.Stop.Enabled)
{
return false;
}
StopEvaluationContext context = BuildEvaluationContext();
foreach (StopCondition condition in _configuration.Stop.Conditions)
{
if (condition.Mode == EStopConditionMode.Stop && condition.Evaluate(context))
{
_logger.LogInformation("Blocking start: {Description}", condition.GetDescription());
_chatGui.PrintError("Cannot start: " + condition.GetDescription(), "Questionable", 576);
return true;
}
}
return false;
}
private void ExecuteNextStep()
{
_logger.LogDebug("ExecuteNextStep called. AutoType={AutoType}, CurrentQuest={CurrentQuest}", AutomationType, CurrentQuest?.Quest.Id);
ClearTasksInternal();
if (TryPickPriorityQuest())
{
_logger.LogDebug("Using priority quest over current quest");
}
var (questSequence, questStep, flag) = GetNextStep();
if (CurrentQuest != null && questSequence != null && questStep == null && questSequence.Steps.Count == 0)
{
_logger.LogDebug("Sequence {Sequence} has no steps, waiting for game to advance sequence", CurrentQuest.Sequence);
CurrentQuest.SetStep(255);
return;
}
if (CurrentQuest == null || questSequence == null)
{
_logger.LogWarning("Could not retrieve next quest step, not doing anything [{QuestId}, {Sequence}, {Step}]", CurrentQuest?.Quest.Id, CurrentQuest?.Sequence, CurrentQuest?.Step);
if (CurrentQuest == null || !flag)
{
return;
}
}
if (questStep != null && questStep.TerritoryId != _clientState.TerritoryType && IsInsideDutyInstance())
{
_resumeAfterDutyExit = true;
_logger.LogDebug("Deferring next step: step territory {StepTerritory} != current instance territory {CurrentTerritory}, waiting for duty exit", questStep.TerritoryId, _clientState.TerritoryType);
DebugState = "Waiting for duty exit";
return;
}
bool flag2 = questStep != null;
if (flag2)
{
EInteractionType interactionType = questStep.InteractionType;
bool flag3 = (uint)(interactionType - 19) <= 1u;
flag2 = flag3;
}
if (flag2 && CheckStopConditions(BuildEvaluationContext(null, null, questStep.InteractionType)))
{
return;
}
_movementController.Stop();
if (questStep != null)
{
switch (_gearRepairService.EvaluateAndEnqueue(questStep.InteractionType, _taskQueue))
{
case ERepairDecision.Enqueued:
_resumeAfterSideTasks = true;
return;
case ERepairDecision.BlockDuty:
_chatGui.PrintError("Gear is critically damaged and cannot be repaired - stopping before duty.", "Questionable", 576);
Stop("Gear critically damaged");
return;
}
}
try
{
(QuestProgress, ECurrentQuestType)? currentQuestDetails = CurrentQuestDetails;
bool isSimulation = currentQuestDetails.HasValue && currentQuestDetails.GetValueOrDefault().Item2 == ECurrentQuestType.Simulated;
foreach (ITask item in _taskCreator.CreateTasks(CurrentQuest.Quest, CurrentQuest.Sequence, questSequence, questStep, isSimulation))
{
_taskQueue.Enqueue(item);
}
}
catch (Exception exception)
{
_logger.LogError(exception, "Failed to create tasks");
_chatGui.PrintError("Failed to start next task sequence, please check /xllog for details.", "Questionable", 576);
Stop("Tasks failed to create");
}
}
public string ToStatString()
{
ITask task = _taskQueue.CurrentTaskExecutor?.CurrentTask;
if (task != null)
{
return $"{task} (+{_taskQueue.RemainingTasks.Count()})";
}
return $"- (+{_taskQueue.RemainingTasks.Count()})";
}
public bool HasCurrentTaskExecutorMatching<T>([NotNullWhen(true)] out T? task) where T : class, ITaskExecutor
{
if (_taskQueue.CurrentTaskExecutor is T val)
{
task = val;
return true;
}
task = null;
return false;
}
public bool HasCurrentTaskMatching<T>([NotNullWhen(true)] out T? task) where T : class, ITask
{
if (_taskQueue.CurrentTaskExecutor?.CurrentTask is T val)
{
task = val;
return true;
}
task = null;
return false;
}
public void Skip(ElementId elementId, byte currentQuestSequence)
{
if (_taskQueue.CurrentTaskExecutor?.CurrentTask is ISkippableTask)
{
_taskQueue.CurrentTaskExecutor = null;
}
else if (_taskQueue.CurrentTaskExecutor != null)
{
_taskQueue.CurrentTaskExecutor = null;
ITask task;
while (_taskQueue.TryPeek(out task))
{
_taskQueue.TryDequeue(out ITask _);
if (task is ISkippableTask)
{
return;
}
}
if (_taskQueue.AllTasksComplete)
{
Stop("Skip");
IncreaseStepCount(elementId, currentQuestSequence);
}
}
else
{
Stop("SkipNx");
IncreaseStepCount(elementId, currentQuestSequence);
}
}
public void SkipSimulatedTask()
{
_taskQueue.CurrentTaskExecutor = null;
}
public bool IsInterruptible()
{
EAutomationType automationType = AutomationType;
if ((uint)(automationType - 2) <= 1u)
{
return false;
}
(QuestProgress, ECurrentQuestType)? currentQuestDetails = CurrentQuestDetails;
if (!currentQuestDetails.HasValue)
{
return false;
}
(QuestProgress, ECurrentQuestType) value = currentQuestDetails.Value;
var (questProgress, _) = value;
if (value.Item2 != ECurrentQuestType.Normal || !questProgress.Quest.Root.Interruptible || questProgress.Sequence == 0)
{
return false;
}
if (ManualPriorityQuests.Contains(questProgress.Quest))
{
return false;
}
if (QuestData.HardModePrimals.Contains(questProgress.Quest.Id))
{
return false;
}
if (questProgress.Quest.Info.AlliedSociety != EAlliedSociety.None)
{
return false;
}
QuestSequence questSequence = questProgress.Quest.FindSequence(questProgress.Sequence);
if (questProgress.Step > 0)
{
return false;
}
QuestStep questStep = questSequence?.FindStep(questProgress.Step);
if (questStep == null || !questStep.Position.HasValue)
{
return false;
}
(ElementId, byte, bool, long)? interruptibleRouteCache = _interruptibleRouteCache;
if (interruptibleRouteCache.HasValue)
{
(ElementId, byte, bool, long) valueOrDefault = interruptibleRouteCache.GetValueOrDefault();
if (valueOrDefault.Item1 == questProgress.Quest.Id && valueOrDefault.Item2 == questProgress.Sequence && Environment.TickCount64 - valueOrDefault.Item4 < 2000)
{
return valueOrDefault.Item3;
}
}
bool flag = _smartNavRouteEnqueuer.RouteStartsWithTeleport(questStep.TerritoryId, questStep.Position.Value);
_interruptibleRouteCache = (questProgress.Quest.Id, questProgress.Sequence, flag, Environment.TickCount64);
return flag;
}
public bool TryPickPriorityQuest()
{
if (!IsInterruptible() || _nextQuest != null || _simulatedQuest != null)
{
return false;
}
ElementId elementId = (from x in _questFunctions.GetNextPriorityQuestsThatCanBeAccepted()
where x.IsAvailable
select x.QuestId).FirstOrDefault();
if (elementId == null)
{
return false;
}
if (_startedQuest != null && elementId == _startedQuest.Quest.Id)
{
return false;
}
if (_questRegistry.TryGetQuest(elementId, out Quest quest))
{
SetNextQuest(quest);
return true;
}
return false;
}
public bool ImportQuestPriority(List<ElementId> questElements)
{
List<Quest> list = new List<Quest>();
foreach (ElementId questElement in questElements)
{
if (_questRegistry.TryGetQuest(questElement, out Quest quest))
{
list.Add(quest);
continue;
}
_logger.LogWarning("Could not find quest {QuestId} during import", questElement);
}
if (list.Count == 0)
{
_logger.LogWarning("Priority import contained no known quests; keeping existing list");
return false;
}
ManualPriorityQuests.Clear();
ManualPriorityQuests.AddRange(list);
_logger.LogInformation("Imported {Count} priority quests", ManualPriorityQuests.Count);
SavePriorityQuests();
return true;
}
public string ExportQuestPriority()
{
return string.Join(';', ManualPriorityQuests.Select((Quest x) => x.Id.ToString()));
}
public void ClearQuestPriority()
{
ManualPriorityQuests.Clear();
SavePriorityQuests();
}
public bool AddQuestPriority(ElementId elementId)
{
if (_questRegistry.TryGetQuest(elementId, out Quest quest) && !ManualPriorityQuests.Contains(quest))
{
ManualPriorityQuests.Add(quest);
SavePriorityQuests();
return true;
}
return false;
}
public int AddAllAvailableQuests()
{
List<ElementId> list = (from q in _questRegistry.AllQuests.Where(delegate(Quest quest2)
{
if (quest2.Root.Disabled || _questFunctions.IsQuestRemoved(quest2.Id) || _questFunctions.IsQuestComplete(quest2.Id) || _questFunctions.IsQuestAccepted(quest2.Id) || quest2.Info.IsRepeatable)
{
return false;
}
if (quest2.Info is QuestInfo questInfo && _journalData.MoogleDeliveryGenreId.HasValue && questInfo.JournalGenre == _journalData.MoogleDeliveryGenreId.Value)
{
_logger.LogDebug("Excluding moogle delivery quest {QuestId} from bulk add", quest2.Id);
return false;
}
if (!_questFunctions.IsReadyToAcceptQuest(quest2.Id))
{
_logger.LogTrace("Quest {QuestId} not ready to accept", quest2.Id);
return false;
}
return true;
})
select q.Id).ToList();
_logger.LogInformation("Adding {Count} available quests to priority queue", list.Count);
int num = 0;
foreach (ElementId item in list)
{
if (_questRegistry.TryGetQuest(item, out Quest quest) && !ManualPriorityQuests.Contains(quest))
{
ManualPriorityQuests.Add(quest);
num++;
}
}
if (num > 0)
{
SavePriorityQuests();
}
return num;
}
public bool InsertQuestPriority(int index, ElementId elementId)
{
try
{
if (_questRegistry.TryGetQuest(elementId, out Quest quest) && !ManualPriorityQuests.Contains(quest))
{
ManualPriorityQuests.Insert(index, quest);
SavePriorityQuests();
}
return true;
}
catch (Exception exception)
{
_logger.LogError(exception, "Failed to insert quest in priority list");
_chatGui.PrintError("Failed to insert quest in priority list, please check /xllog for details.", "Questionable", 576);
return false;
}
}
public void LoadPriorityQuests()
{
if (!_configuration.General.PersistPriorityQuestsBetweenSessions)
{
_logger.LogDebug("Priority quest persistence is disabled, skipping load");
return;
}
ManualPriorityQuests.Clear();
foreach (ElementId priorityQuest in _configuration.General.PriorityQuests)
{
if (_questRegistry.TryGetQuest(priorityQuest, out Quest quest))
{
ManualPriorityQuests.Add(quest);
continue;
}
_logger.LogWarning("Could not find quest {QuestId} during load from config", priorityQuest);
}
if (ManualPriorityQuests.Count > 0)
{
_logger.LogDebug("Loaded {Count} priority quests from configuration", ManualPriorityQuests.Count);
}
}
public void SavePriorityQuests()
{
if (!_configuration.General.PersistPriorityQuestsBetweenSessions)
{
_logger.LogDebug("Priority quest persistence is disabled, skipping save");
return;
}
_configuration.General.PriorityQuests = ManualPriorityQuests.Select((Quest q) => q.Id).ToList();
_pluginInterface.SavePluginConfig(_configuration);
_logger.LogDebug("Saved {Count} priority quests to configuration", ManualPriorityQuests.Count);
}
private void OnConditionChange(ConditionFlag flag, bool value)
{
if (_taskQueue.CurrentTaskExecutor is IConditionChangeAware conditionChangeAware)
{
conditionChangeAware.OnConditionChange(flag, value);
}
}
private void OnNormalToast(ref SeString message, ref ToastOptions options, ref bool isHandled)
{
_gatheringController.OnNormalToast(message);
}
protected override void HandleInterruption(object? sender, EventArgs e)
{
if (IsRunning && AutomationType != EAutomationType.Manual)
{
base.HandleInterruption(sender, e);
}
}
private void OnLogout(int type, int code)
{
StopAllDueToConditionFailed("Logged out");
if (_configuration.General.ClearPriorityQuestsOnLogout)
{
_logger.LogDebug("Clearing priority quests on logout");
ManualPriorityQuests.Clear();
if (_configuration.General.PersistPriorityQuestsBetweenSessions)
{
_configuration.General.PriorityQuests.Clear();
_pluginInterface.SavePluginConfig(_configuration);
}
}
}
private void OnPlayerInputDetected(object? sender, EventArgs e)
{
if (AutomationType != EAutomationType.Manual && IsRunning)
{
_logger.LogInformation("Player input detected during movement, stopping quest automation");
_chatGui.Print("Player input detected - stopping quest automation.", "Questionable", 576);
Stop("Player input detected");
}
}
private void OnQuestRegistryReloaded(object? sender, EventArgs e)
{
RefreshQuestReference(_startedQuest);
RefreshQuestReference(_nextQuest);
RefreshQuestReference(_pendingQuest);
for (int i = 0; i < ManualPriorityQuests.Count; i++)
{
if (_questRegistry.TryGetQuest(ManualPriorityQuests[i].Id, out Quest quest))
{
ManualPriorityQuests[i] = quest;
}
}
_interruptibleRouteCache = null;
}
private void RefreshQuestReference(QuestProgress? progress)
{
if (progress != null && _questRegistry.TryGetQuest(progress.Quest.Id, out Quest quest))
{
progress.ReplaceQuest(quest);
}
}
public override void Dispose()
{
_questRegistry.Reloaded -= OnQuestRegistryReloaded;
_toastGui.ErrorToast -= base.OnErrorToast;
_toastGui.Toast -= OnNormalToast;
_condition.ConditionChange -= OnConditionChange;
_clientState.Logout -= OnLogout;
_movementController.PlayerInputDetected -= OnPlayerInputDetected;
base.Dispose();
}
}