using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin.Services; using LLib; using Lumina.Excel.Sheets; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps; using Questionable.Controller.Steps.Common; using Questionable.Controller.Steps.Interactions; using Questionable.Controller.Steps.Shared; using Questionable.Functions; using Questionable.Model.Questing; namespace Questionable.Controller; internal abstract class MiniTaskController : IDisposable { protected readonly TaskQueue _taskQueue = new TaskQueue(); private readonly IChatGui _chatGui; private readonly ICondition _condition; private readonly IServiceProvider _serviceProvider; private readonly InterruptHandler _interruptHandler; private readonly ILogger _logger; private readonly Regex _actionCanceledText; private readonly string _eventCanceledText; private readonly string _cantExecuteDueToStatusText; protected MiniTaskController(IChatGui chatGui, ICondition condition, IServiceProvider serviceProvider, InterruptHandler interruptHandler, IDataManager dataManager, ILogger logger) { _chatGui = chatGui; _logger = logger; _serviceProvider = serviceProvider; _interruptHandler = interruptHandler; _condition = condition; _eventCanceledText = dataManager.GetString(1318u, (LogMessage x) => x.Text); _actionCanceledText = dataManager.GetRegex(1314u, (LogMessage x) => x.Text); _cantExecuteDueToStatusText = dataManager.GetString(7728u, (LogMessage x) => x.Text); _interruptHandler.Interrupted += HandleInterruption; } protected virtual void UpdateCurrentTask() { if (_taskQueue.CurrentTaskExecutor == null) { if (!_taskQueue.TryDequeue(out ITask task)) { return; } try { _logger.LogInformation("Starting task {TaskName}", task.ToString()); ITaskExecutor requiredKeyedService = _serviceProvider.GetRequiredKeyedService(task.GetType()); if (requiredKeyedService.Start(task)) { _taskQueue.CurrentTaskExecutor = requiredKeyedService; return; } _logger.LogTrace("Task {TaskName} was skipped", task.ToString()); return; } catch (Exception exception) { _logger.LogError(exception, "Failed to start task {TaskName}", task.ToString()); _chatGui.PrintError($"Failed to start task '{task}', please check /xllog for details.", "Questionable", 576); Stop("Task failed to start"); return; } } ETaskResult eTaskResult; try { if (_taskQueue.CurrentTaskExecutor.WasInterrupted()) { InterruptQueueWithCombat(); return; } eTaskResult = _taskQueue.CurrentTaskExecutor.Update(); } catch (Exception exception2) { _logger.LogError(exception2, "Failed to update task {TaskName}", _taskQueue.CurrentTaskExecutor.CurrentTask.ToString()); _chatGui.PrintError($"Failed to update task '{_taskQueue.CurrentTaskExecutor.CurrentTask}', please check /xllog for details.", "Questionable", 576); Stop("Task failed to update"); return; } switch (eTaskResult) { case ETaskResult.StillRunning: break; case ETaskResult.SkipRemainingTasksForStep: { _logger.LogInformation("{Task} → {Result}, skipping remaining tasks for step", _taskQueue.CurrentTaskExecutor.CurrentTask, eTaskResult); _taskQueue.CurrentTaskExecutor = null; ITask task3; while (_taskQueue.TryDequeue(out task3)) { if ((task3 is ILastTask || task3 is Gather.SkipMarker) ? true : false) { ITaskExecutor requiredKeyedService2 = _serviceProvider.GetRequiredKeyedService(task3.GetType()); requiredKeyedService2.Start(task3); _taskQueue.CurrentTaskExecutor = requiredKeyedService2; break; } } break; } case ETaskResult.TaskComplete: case ETaskResult.CreateNewTasks: _logger.LogInformation("{Task} → {Result}, remaining tasks: {RemainingTaskCount}", _taskQueue.CurrentTaskExecutor.CurrentTask, eTaskResult, _taskQueue.RemainingTasks.Count()); OnTaskComplete(_taskQueue.CurrentTaskExecutor.CurrentTask); if (eTaskResult == ETaskResult.CreateNewTasks && _taskQueue.CurrentTaskExecutor is IExtraTaskCreator extraTaskCreator) { _taskQueue.EnqueueAll(extraTaskCreator.CreateExtraTasks()); } _taskQueue.CurrentTaskExecutor = null; break; case ETaskResult.NextStep: { _logger.LogInformation("{Task} → {Result}", _taskQueue.CurrentTaskExecutor.CurrentTask, eTaskResult); ILastTask task2 = (ILastTask)_taskQueue.CurrentTaskExecutor.CurrentTask; _taskQueue.CurrentTaskExecutor = null; OnNextStep(task2); break; } case ETaskResult.End: _logger.LogInformation("{Task} → {Result}", _taskQueue.CurrentTaskExecutor.CurrentTask, eTaskResult); _taskQueue.CurrentTaskExecutor = null; Stop("Task end"); break; } } protected virtual void OnTaskComplete(ITask task) { } protected virtual void OnNextStep(ILastTask task) { } public abstract void Stop(string label); public virtual IList GetRemainingTaskNames() { return _taskQueue.RemainingTasks.Select((ITask x) => x.ToString() ?? "?").ToList(); } public void InterruptQueueWithCombat() { _logger.LogWarning("Interrupted, attempting to resolve (if in combat)"); if (_condition[ConditionFlag.InCombat]) { List list = new List(); if (_condition[ConditionFlag.Mounted]) { list.Add(new Questionable.Controller.Steps.Common.Mount.UnmountTask()); } list.Add(Combat.Factory.CreateTask(null, -1, isLastStep: false, EEnemySpawnType.QuestInterruption, new List(), new List(), new List(), null)); list.Add(new WaitAtEnd.WaitDelay()); _taskQueue.InterruptWith(list); } else { TaskQueue taskQueue = _taskQueue; int num = 1; List list2 = new List(num); CollectionsMarshal.SetCount(list2, num); Span span = CollectionsMarshal.AsSpan(list2); int index = 0; span[index] = new WaitAtEnd.WaitDelay(); taskQueue.InterruptWith(list2); } LogTasksAfterInterruption(); } private void InterruptWithoutCombat() { if (!(_taskQueue.CurrentTaskExecutor is SinglePlayerDuty.WaitSinglePlayerDutyExecutor)) { _logger.LogWarning("Interrupted, attempting to redo previous tasks (not in combat)"); TaskQueue taskQueue = _taskQueue; int num = 1; List list = new List(num); CollectionsMarshal.SetCount(list, num); Span span = CollectionsMarshal.AsSpan(list); int index = 0; span[index] = new WaitAtEnd.WaitDelay(); taskQueue.InterruptWith(list); LogTasksAfterInterruption(); } } private void LogTasksAfterInterruption() { _logger.LogInformation("Remaining tasks after interruption:"); foreach (ITask remainingTask in _taskQueue.RemainingTasks) { _logger.LogInformation("- {TaskName}", remainingTask); } } public void OnErrorToast(ref SeString message, ref bool isHandled) { if (_taskQueue.CurrentTaskExecutor is IToastAware toastAware && toastAware.OnErrorToast(message)) { isHandled = true; } if (isHandled) { return; } if (_actionCanceledText.IsMatch(message.TextValue) && !_condition[ConditionFlag.InFlight]) { ITaskExecutor? currentTaskExecutor = _taskQueue.CurrentTaskExecutor; if (currentTaskExecutor != null && currentTaskExecutor.ShouldInterruptOnDamage()) { InterruptQueueWithCombat(); return; } } if (GameFunctions.GameStringEquals(_cantExecuteDueToStatusText, message.TextValue) || GameFunctions.GameStringEquals(_eventCanceledText, message.TextValue)) { InterruptWithoutCombat(); } } protected virtual void HandleInterruption(object? sender, EventArgs e) { if (!_condition[ConditionFlag.InFlight]) { ITaskExecutor? currentTaskExecutor = _taskQueue.CurrentTaskExecutor; if (currentTaskExecutor != null && currentTaskExecutor.ShouldInterruptOnDamage()) { InterruptQueueWithCombat(); } } } public virtual void Dispose() { _interruptHandler.Interrupted -= HandleInterruption; } }