qstbak/Questionable/Questionable.Controller/MiniTaskController.cs
2025-10-09 07:47:19 +10:00

257 lines
8 KiB
C#

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<T> : 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<T> _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<T> 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<ITaskExecutor>(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<ITaskExecutor>(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<string> 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<ITask> list = new List<ITask>();
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<uint>(), new List<QuestWorkValue>(), new List<ComplexCombatData>(), null));
list.Add(new WaitAtEnd.WaitDelay());
_taskQueue.InterruptWith(list);
}
else
{
TaskQueue taskQueue = _taskQueue;
int num = 1;
List<ITask> list2 = new List<ITask>(num);
CollectionsMarshal.SetCount(list2, num);
Span<ITask> 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<ITask> list = new List<ITask>(num);
CollectionsMarshal.SetCount(list, num);
Span<ITask> 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;
}
}