Handle 'Action canceled, you are under attack' while e.g. talking to an NPC
This commit is contained in:
parent
21fde119ba
commit
5288cc6e31
@ -1,5 +1,5 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>3.3</Version>
|
<Version>3.4</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -13,4 +13,5 @@ public enum EEnemySpawnType
|
|||||||
AutoOnEnterArea,
|
AutoOnEnterArea,
|
||||||
OverworldEnemies,
|
OverworldEnemies,
|
||||||
FateEnemies,
|
FateEnemies,
|
||||||
|
QuestInterruption,
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,7 @@ internal sealed class CombatController : IDisposable
|
|||||||
Module = combatModule,
|
Module = combatModule,
|
||||||
Data = combatData,
|
Data = combatData,
|
||||||
};
|
};
|
||||||
|
_wasInCombat = combatData.SpawnType == EEnemySpawnType.QuestInterruption;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -86,7 +87,9 @@ internal sealed class CombatController : IDisposable
|
|||||||
if (_currentFight == null)
|
if (_currentFight == null)
|
||||||
return EStatus.Complete;
|
return EStatus.Complete;
|
||||||
|
|
||||||
if (_movementController.IsPathfinding || _movementController.IsPathRunning || _movementController.MovementStartedAt > DateTime.Now.AddSeconds(-1))
|
if (_movementController.IsPathfinding ||
|
||||||
|
_movementController.IsPathRunning ||
|
||||||
|
_movementController.MovementStartedAt > DateTime.Now.AddSeconds(-1))
|
||||||
return EStatus.Moving;
|
return EStatus.Moving;
|
||||||
|
|
||||||
var target = _targetManager.Target;
|
var target = _targetManager.Target;
|
||||||
@ -111,6 +114,8 @@ internal sealed class CombatController : IDisposable
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var nextTarget = FindNextTarget();
|
var nextTarget = FindNextTarget();
|
||||||
|
_logger.LogInformation("NT → {NT}", nextTarget);
|
||||||
|
|
||||||
if (nextTarget is { IsDead: false })
|
if (nextTarget is { IsDead: false })
|
||||||
SetTarget(nextTarget);
|
SetTarget(nextTarget);
|
||||||
}
|
}
|
||||||
@ -335,7 +340,7 @@ internal sealed class CombatController : IDisposable
|
|||||||
|
|
||||||
public sealed class CombatData
|
public sealed class CombatData
|
||||||
{
|
{
|
||||||
public required ElementId ElementId { get; init; }
|
public required ElementId? ElementId { get; init; }
|
||||||
public required EEnemySpawnType SpawnType { get; init; }
|
public required EEnemySpawnType SpawnType { get; init; }
|
||||||
public required List<uint> KillEnemyDataIds { get; init; }
|
public required List<uint> KillEnemyDataIds { get; init; }
|
||||||
public required List<ComplexCombatData> ComplexCombatDatas { get; init; }
|
public required List<ComplexCombatData> ComplexCombatDatas { get; init; }
|
||||||
@ -345,6 +350,7 @@ internal sealed class CombatController : IDisposable
|
|||||||
|
|
||||||
public enum EStatus
|
public enum EStatus
|
||||||
{
|
{
|
||||||
|
NotStarted,
|
||||||
InCombat,
|
InCombat,
|
||||||
Moving,
|
Moving,
|
||||||
Complete,
|
Complete,
|
||||||
|
@ -122,6 +122,10 @@ internal sealed class CommandHandler : IDisposable
|
|||||||
PrintMountId();
|
PrintMountId();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "handle-interrupt":
|
||||||
|
_questController.InterruptQueueWithCombat();
|
||||||
|
break;
|
||||||
|
|
||||||
case "":
|
case "":
|
||||||
_questWindow.Toggle();
|
_questWindow.Toggle();
|
||||||
break;
|
break;
|
||||||
|
@ -129,7 +129,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
return EStatus.Complete;
|
return EStatus.Complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_currentTask == null && _taskQueue.Count == 0)
|
if (_taskQueue.AllTasksComplete)
|
||||||
GoToNextNode();
|
GoToNextNode();
|
||||||
|
|
||||||
UpdateCurrentTask();
|
UpdateCurrentTask();
|
||||||
@ -141,8 +141,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
public override void Stop(string label)
|
public override void Stop(string label)
|
||||||
{
|
{
|
||||||
_currentRequest = null;
|
_currentRequest = null;
|
||||||
_currentTask = null;
|
_taskQueue.Reset();
|
||||||
_taskQueue.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GoToNextNode()
|
private void GoToNextNode()
|
||||||
@ -150,7 +149,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
if (_currentRequest == null)
|
if (_currentRequest == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_taskQueue.Count > 0)
|
if (!_taskQueue.AllTasksComplete)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var director = UIState.Instance()->DirectorTodo.Director;
|
var director = UIState.Instance()->DirectorTodo.Director;
|
||||||
@ -267,8 +266,8 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
|
|
||||||
public override IList<string> GetRemainingTaskNames()
|
public override IList<string> GetRemainingTaskNames()
|
||||||
{
|
{
|
||||||
if (_currentTask != null)
|
if (_taskQueue.CurrentTask is {} currentTask)
|
||||||
return [_currentTask.ToString() ?? "?", .. base.GetRemainingTaskNames()];
|
return [currentTask.ToString() ?? "?", .. base.GetRemainingTaskNames()];
|
||||||
else
|
else
|
||||||
return base.GetRemainingTaskNames();
|
return base.GetRemainingTaskNames();
|
||||||
}
|
}
|
||||||
@ -277,10 +276,10 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
{
|
{
|
||||||
if (_revisitRegex.IsMatch(message.TextValue))
|
if (_revisitRegex.IsMatch(message.TextValue))
|
||||||
{
|
{
|
||||||
if (_currentTask is IRevisitAware currentTaskRevisitAware)
|
if (_taskQueue.CurrentTask is IRevisitAware currentTaskRevisitAware)
|
||||||
currentTaskRevisitAware.OnRevisit();
|
currentTaskRevisitAware.OnRevisit();
|
||||||
|
|
||||||
foreach (ITask task in _taskQueue)
|
foreach (ITask task in _taskQueue.RemainingTasks)
|
||||||
{
|
{
|
||||||
if (task is IRevisitAware taskRevisitAware)
|
if (task is IRevisitAware taskRevisitAware)
|
||||||
taskRevisitAware.OnRevisit();
|
taskRevisitAware.OnRevisit();
|
||||||
|
@ -12,11 +12,9 @@ internal abstract class MiniTaskController<T>
|
|||||||
{
|
{
|
||||||
protected readonly IChatGui _chatGui;
|
protected readonly IChatGui _chatGui;
|
||||||
protected readonly ILogger<T> _logger;
|
protected readonly ILogger<T> _logger;
|
||||||
|
protected readonly TaskQueue _taskQueue = new();
|
||||||
|
|
||||||
protected readonly Queue<ITask> _taskQueue = new();
|
protected MiniTaskController(IChatGui chatGui, ILogger<T> logger)
|
||||||
protected ITask? _currentTask;
|
|
||||||
|
|
||||||
public MiniTaskController(IChatGui chatGui, ILogger<T> logger)
|
|
||||||
{
|
{
|
||||||
_chatGui = chatGui;
|
_chatGui = chatGui;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -24,7 +22,7 @@ internal abstract class MiniTaskController<T>
|
|||||||
|
|
||||||
protected virtual void UpdateCurrentTask()
|
protected virtual void UpdateCurrentTask()
|
||||||
{
|
{
|
||||||
if (_currentTask == null)
|
if (_taskQueue.CurrentTask == null)
|
||||||
{
|
{
|
||||||
if (_taskQueue.TryDequeue(out ITask? upcomingTask))
|
if (_taskQueue.TryDequeue(out ITask? upcomingTask))
|
||||||
{
|
{
|
||||||
@ -33,7 +31,7 @@ internal abstract class MiniTaskController<T>
|
|||||||
_logger.LogInformation("Starting task {TaskName}", upcomingTask.ToString());
|
_logger.LogInformation("Starting task {TaskName}", upcomingTask.ToString());
|
||||||
if (upcomingTask.Start())
|
if (upcomingTask.Start())
|
||||||
{
|
{
|
||||||
_currentTask = upcomingTask;
|
_taskQueue.CurrentTask = upcomingTask;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -58,13 +56,13 @@ internal abstract class MiniTaskController<T>
|
|||||||
ETaskResult result;
|
ETaskResult result;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
result = _currentTask.Update();
|
result = _taskQueue.CurrentTask.Update();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError(e, "Failed to update task {TaskName}", _currentTask.ToString());
|
_logger.LogError(e, "Failed to update task {TaskName}", _taskQueue.CurrentTask.ToString());
|
||||||
_chatGui.PrintError(
|
_chatGui.PrintError(
|
||||||
$"[Questionable] Failed to update task '{_currentTask}', please check /xllog for details.");
|
$"[Questionable] Failed to update task '{_taskQueue.CurrentTask}', please check /xllog for details.");
|
||||||
Stop("Task failed to update");
|
Stop("Task failed to update");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -76,14 +74,14 @@ internal abstract class MiniTaskController<T>
|
|||||||
|
|
||||||
case ETaskResult.SkipRemainingTasksForStep:
|
case ETaskResult.SkipRemainingTasksForStep:
|
||||||
_logger.LogInformation("{Task} → {Result}, skipping remaining tasks for step",
|
_logger.LogInformation("{Task} → {Result}, skipping remaining tasks for step",
|
||||||
_currentTask, result);
|
_taskQueue.CurrentTask, result);
|
||||||
_currentTask = null;
|
_taskQueue.CurrentTask = null;
|
||||||
|
|
||||||
while (_taskQueue.TryDequeue(out ITask? nextTask))
|
while (_taskQueue.TryDequeue(out ITask? nextTask))
|
||||||
{
|
{
|
||||||
if (nextTask is ILastTask or Gather.SkipMarker)
|
if (nextTask is ILastTask or Gather.SkipMarker)
|
||||||
{
|
{
|
||||||
_currentTask = nextTask;
|
_taskQueue.CurrentTask = nextTask;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,27 +90,27 @@ internal abstract class MiniTaskController<T>
|
|||||||
|
|
||||||
case ETaskResult.TaskComplete:
|
case ETaskResult.TaskComplete:
|
||||||
_logger.LogInformation("{Task} → {Result}, remaining tasks: {RemainingTaskCount}",
|
_logger.LogInformation("{Task} → {Result}, remaining tasks: {RemainingTaskCount}",
|
||||||
_currentTask, result, _taskQueue.Count);
|
_taskQueue.CurrentTask, result, _taskQueue.RemainingTasks.Count());
|
||||||
|
|
||||||
OnTaskComplete(_currentTask);
|
OnTaskComplete(_taskQueue.CurrentTask);
|
||||||
|
|
||||||
_currentTask = null;
|
_taskQueue.CurrentTask = null;
|
||||||
|
|
||||||
// handled in next update
|
// handled in next update
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case ETaskResult.NextStep:
|
case ETaskResult.NextStep:
|
||||||
_logger.LogInformation("{Task} → {Result}", _currentTask, result);
|
_logger.LogInformation("{Task} → {Result}", _taskQueue.CurrentTask, result);
|
||||||
|
|
||||||
var lastTask = (ILastTask)_currentTask;
|
var lastTask = (ILastTask)_taskQueue.CurrentTask;
|
||||||
_currentTask = null;
|
_taskQueue.CurrentTask = null;
|
||||||
|
|
||||||
OnNextStep(lastTask);
|
OnNextStep(lastTask);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case ETaskResult.End:
|
case ETaskResult.End:
|
||||||
_logger.LogInformation("{Task} → {Result}", _currentTask, result);
|
_logger.LogInformation("{Task} → {Result}", _taskQueue.CurrentTask, result);
|
||||||
_currentTask = null;
|
_taskQueue.CurrentTask = null;
|
||||||
Stop("Task end");
|
Stop("Task end");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -130,5 +128,5 @@ internal abstract class MiniTaskController<T>
|
|||||||
public abstract void Stop(string label);
|
public abstract void Stop(string label);
|
||||||
|
|
||||||
public virtual IList<string> GetRemainingTaskNames() =>
|
public virtual IList<string> GetRemainingTaskNames() =>
|
||||||
_taskQueue.Select(x => x.ToString() ?? "?").ToList();
|
_taskQueue.RemainingTasks.Select(x => x.ToString() ?? "?").ToList();
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ internal sealed class MovementController : IDisposable
|
|||||||
|
|
||||||
public bool IsPathfinding => _pathfindTask is { IsCompleted: false };
|
public bool IsPathfinding => _pathfindTask is { IsCompleted: false };
|
||||||
public DestinationData? Destination { get; set; }
|
public DestinationData? Destination { get; set; }
|
||||||
public DateTime MovementStartedAt { get; private set; } = DateTime.MaxValue;
|
public DateTime MovementStartedAt { get; private set; } = DateTime.Now;
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
|
@ -8,7 +8,9 @@ using Dalamud.Game.Gui.Toast;
|
|||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using LLib;
|
||||||
using LLib.GameData;
|
using LLib.GameData;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps;
|
using Questionable.Controller.Steps;
|
||||||
using Questionable.Controller.Steps.Interactions;
|
using Questionable.Controller.Steps.Interactions;
|
||||||
@ -18,6 +20,8 @@ using Questionable.External;
|
|||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
using Quest = Questionable.Model.Quest;
|
||||||
|
using Mount = Questionable.Controller.Steps.Common.Mount;
|
||||||
|
|
||||||
namespace Questionable.Controller;
|
namespace Questionable.Controller;
|
||||||
|
|
||||||
@ -36,6 +40,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
private readonly Configuration _configuration;
|
private readonly Configuration _configuration;
|
||||||
private readonly YesAlreadyIpc _yesAlreadyIpc;
|
private readonly YesAlreadyIpc _yesAlreadyIpc;
|
||||||
private readonly TaskCreator _taskCreator;
|
private readonly TaskCreator _taskCreator;
|
||||||
|
private readonly Mount.Factory _mountFactory;
|
||||||
|
private readonly Combat.Factory _combatFactory;
|
||||||
|
|
||||||
|
private readonly string _actionCanceledText;
|
||||||
|
|
||||||
private readonly object _progressLock = new();
|
private readonly object _progressLock = new();
|
||||||
|
|
||||||
@ -73,7 +81,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
IToastGui toastGui,
|
IToastGui toastGui,
|
||||||
Configuration configuration,
|
Configuration configuration,
|
||||||
YesAlreadyIpc yesAlreadyIpc,
|
YesAlreadyIpc yesAlreadyIpc,
|
||||||
TaskCreator taskCreator)
|
TaskCreator taskCreator,
|
||||||
|
Mount.Factory mountFactory,
|
||||||
|
Combat.Factory combatFactory,
|
||||||
|
IDataManager dataManager)
|
||||||
: base(chatGui, logger)
|
: base(chatGui, logger)
|
||||||
{
|
{
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
@ -89,10 +100,14 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_yesAlreadyIpc = yesAlreadyIpc;
|
_yesAlreadyIpc = yesAlreadyIpc;
|
||||||
_taskCreator = taskCreator;
|
_taskCreator = taskCreator;
|
||||||
|
_mountFactory = mountFactory;
|
||||||
|
_combatFactory = combatFactory;
|
||||||
|
|
||||||
_condition.ConditionChange += OnConditionChange;
|
_condition.ConditionChange += OnConditionChange;
|
||||||
_toastGui.Toast += OnNormalToast;
|
_toastGui.Toast += OnNormalToast;
|
||||||
_toastGui.ErrorToast += OnErrorToast;
|
_toastGui.ErrorToast += OnErrorToast;
|
||||||
|
|
||||||
|
_actionCanceledText = dataManager.GetString<LogMessage>(1314, x => x.Text)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EAutomationType AutomationType
|
public EAutomationType AutomationType
|
||||||
@ -181,7 +196,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
|
|
||||||
if (!_clientState.IsLoggedIn || _condition[ConditionFlag.Unconscious])
|
if (!_clientState.IsLoggedIn || _condition[ConditionFlag.Unconscious])
|
||||||
{
|
{
|
||||||
if (_currentTask != null || _taskQueue.Count > 0)
|
if (!_taskQueue.AllTasksComplete)
|
||||||
{
|
{
|
||||||
Stop("HP = 0");
|
Stop("HP = 0");
|
||||||
_movementController.Stop();
|
_movementController.Stop();
|
||||||
@ -191,7 +206,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
}
|
}
|
||||||
else if (_configuration.General.UseEscToCancelQuesting && _keyState[VirtualKey.ESCAPE])
|
else if (_configuration.General.UseEscToCancelQuesting && _keyState[VirtualKey.ESCAPE])
|
||||||
{
|
{
|
||||||
if (_currentTask != null || _taskQueue.Count > 0)
|
if (!_taskQueue.AllTasksComplete)
|
||||||
{
|
{
|
||||||
Stop("ESC pressed");
|
Stop("ESC pressed");
|
||||||
_movementController.Stop();
|
_movementController.Stop();
|
||||||
@ -204,8 +219,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (AutomationType == EAutomationType.Automatic &&
|
if (AutomationType == EAutomationType.Automatic &&
|
||||||
((_currentTask == null && _taskQueue.Count == 0) ||
|
(_taskQueue.AllTasksComplete || _taskQueue.CurrentTask is WaitAtEnd.WaitQuestAccepted)
|
||||||
_currentTask is WaitAtEnd.WaitQuestAccepted)
|
|
||||||
&& CurrentQuest is { Sequence: 0, Step: 0 } or { Sequence: 0, Step: 255 }
|
&& CurrentQuest is { Sequence: 0, Step: 0 } or { Sequence: 0, Step: 255 }
|
||||||
&& DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15))
|
&& DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15))
|
||||||
{
|
{
|
||||||
@ -276,8 +290,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
questToRun = _nextQuest;
|
questToRun = _nextQuest;
|
||||||
currentSequence = _nextQuest.Sequence; // by definition, this should always be 0
|
currentSequence = _nextQuest.Sequence; // by definition, this should always be 0
|
||||||
if (_nextQuest.Step == 0 &&
|
if (_nextQuest.Step == 0 &&
|
||||||
_currentTask == null &&
|
_taskQueue.AllTasksComplete &&
|
||||||
_taskQueue.Count == 0 &&
|
|
||||||
AutomationType == EAutomationType.Automatic)
|
AutomationType == EAutomationType.Automatic)
|
||||||
ExecuteNextStep();
|
ExecuteNextStep();
|
||||||
}
|
}
|
||||||
@ -286,8 +299,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
questToRun = _gatheringQuest;
|
questToRun = _gatheringQuest;
|
||||||
currentSequence = _gatheringQuest.Sequence;
|
currentSequence = _gatheringQuest.Sequence;
|
||||||
if (_gatheringQuest.Step == 0 &&
|
if (_gatheringQuest.Step == 0 &&
|
||||||
_currentTask == null &&
|
_taskQueue.AllTasksComplete &&
|
||||||
_taskQueue.Count == 0 &&
|
|
||||||
AutomationType == EAutomationType.Automatic)
|
AutomationType == EAutomationType.Automatic)
|
||||||
ExecuteNextStep();
|
ExecuteNextStep();
|
||||||
}
|
}
|
||||||
@ -392,7 +404,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
if (questToRun.Step == 255)
|
if (questToRun.Step == 255)
|
||||||
{
|
{
|
||||||
DebugState = "Step completed";
|
DebugState = "Step completed";
|
||||||
if (_currentTask != null || _taskQueue.Count > 0)
|
if (!_taskQueue.AllTasksComplete)
|
||||||
CheckNextTasks("Step complete");
|
CheckNextTasks("Step complete");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -465,10 +477,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
private void ClearTasksInternal()
|
private void ClearTasksInternal()
|
||||||
{
|
{
|
||||||
//_logger.LogDebug("Clearing task (internally)");
|
//_logger.LogDebug("Clearing task (internally)");
|
||||||
_currentTask = null;
|
_taskQueue.Reset();
|
||||||
|
|
||||||
if (_taskQueue.Count > 0)
|
|
||||||
_taskQueue.Clear();
|
|
||||||
|
|
||||||
_yesAlreadyIpc.RestoreYesAlready();
|
_yesAlreadyIpc.RestoreYesAlready();
|
||||||
_combatController.Stop("ClearTasksInternal");
|
_combatController.Stop("ClearTasksInternal");
|
||||||
@ -629,13 +638,15 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
|
|
||||||
public string ToStatString()
|
public string ToStatString()
|
||||||
{
|
{
|
||||||
return _currentTask == null ? $"- (+{_taskQueue.Count})" : $"{_currentTask} (+{_taskQueue.Count})";
|
return _taskQueue.CurrentTask is { } currentTask
|
||||||
|
? $"{currentTask} (+{_taskQueue.RemainingTasks.Count()})"
|
||||||
|
: $"- (+{_taskQueue.RemainingTasks.Count()})";
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasCurrentTaskMatching<T>([NotNullWhen(true)] out T? task)
|
public bool HasCurrentTaskMatching<T>([NotNullWhen(true)] out T? task)
|
||||||
where T : class, ITask
|
where T : class, ITask
|
||||||
{
|
{
|
||||||
if (_currentTask is T t)
|
if (_taskQueue.CurrentTask is T t)
|
||||||
{
|
{
|
||||||
task = t;
|
task = t;
|
||||||
return true;
|
return true;
|
||||||
@ -647,7 +658,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsRunning => _currentTask != null || _taskQueue.Count > 0;
|
public bool IsRunning => !_taskQueue.AllTasksComplete;
|
||||||
|
|
||||||
public sealed class QuestProgress
|
public sealed class QuestProgress
|
||||||
{
|
{
|
||||||
@ -687,19 +698,19 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
{
|
{
|
||||||
lock (_progressLock)
|
lock (_progressLock)
|
||||||
{
|
{
|
||||||
if (_currentTask is ISkippableTask)
|
if (_taskQueue.CurrentTask is ISkippableTask)
|
||||||
_currentTask = null;
|
_taskQueue.CurrentTask = null;
|
||||||
else if (_currentTask != null)
|
else if (_taskQueue.CurrentTask != null)
|
||||||
{
|
{
|
||||||
_currentTask = null;
|
_taskQueue.CurrentTask = null;
|
||||||
while (_taskQueue.Count > 0)
|
while (_taskQueue.TryPeek(out ITask? task))
|
||||||
{
|
{
|
||||||
var task = _taskQueue.Dequeue();
|
_taskQueue.TryDequeue(out _);
|
||||||
if (task is ISkippableTask)
|
if (task is ISkippableTask)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_taskQueue.Count == 0)
|
if (_taskQueue.AllTasksComplete)
|
||||||
{
|
{
|
||||||
Stop("Skip");
|
Stop("Skip");
|
||||||
IncreaseStepCount(elementId, currentQuestSequence);
|
IncreaseStepCount(elementId, currentQuestSequence);
|
||||||
@ -715,7 +726,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
|
|
||||||
public void SkipSimulatedTask()
|
public void SkipSimulatedTask()
|
||||||
{
|
{
|
||||||
_currentTask = null;
|
_taskQueue.CurrentTask = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsInterruptible()
|
public bool IsInterruptible()
|
||||||
@ -774,7 +785,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
|
|
||||||
private void OnConditionChange(ConditionFlag flag, bool value)
|
private void OnConditionChange(ConditionFlag flag, bool value)
|
||||||
{
|
{
|
||||||
if (_currentTask is IConditionChangeAware conditionChangeAware)
|
if (_taskQueue.CurrentTask is IConditionChangeAware conditionChangeAware)
|
||||||
conditionChangeAware.OnConditionChange(flag, value);
|
conditionChangeAware.OnConditionChange(flag, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -785,13 +796,33 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
|
|
||||||
private void OnErrorToast(ref SeString message, ref bool isHandled)
|
private void OnErrorToast(ref SeString message, ref bool isHandled)
|
||||||
{
|
{
|
||||||
if (_currentTask is IToastAware toastAware)
|
_logger.LogWarning("XXX {A} → {B} XXX", _actionCanceledText, message.TextValue);
|
||||||
|
if (_taskQueue.CurrentTask is IToastAware toastAware)
|
||||||
{
|
{
|
||||||
if (toastAware.OnErrorToast(message))
|
if (toastAware.OnErrorToast(message))
|
||||||
{
|
{
|
||||||
isHandled = true;
|
isHandled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isHandled)
|
||||||
|
{
|
||||||
|
if (GameFunctions.GameStringEquals(_actionCanceledText, message.TextValue) &&
|
||||||
|
!_condition[ConditionFlag.InFlight])
|
||||||
|
InterruptQueueWithCombat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InterruptQueueWithCombat()
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Interrupted with action canceled message, attempting to resolve");
|
||||||
|
List<ITask> tasks = [];
|
||||||
|
if (_condition[ConditionFlag.Mounted])
|
||||||
|
tasks.Add(_mountFactory.Unmount());
|
||||||
|
|
||||||
|
tasks.Add(_combatFactory.CreateTask(null, false, EEnemySpawnType.QuestInterruption, [], [], []));
|
||||||
|
tasks.Add(new WaitAtEnd.WaitDelay());
|
||||||
|
_taskQueue.InterruptWith(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -46,6 +46,8 @@ internal static class Mount
|
|||||||
private bool _mountTriggered;
|
private bool _mountTriggered;
|
||||||
private DateTime _retryAt = DateTime.MinValue;
|
private DateTime _retryAt = DateTime.MinValue;
|
||||||
|
|
||||||
|
public bool ShouldRedoOnInterrupt() => true;
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
if (condition[ConditionFlag.Mounted])
|
if (condition[ConditionFlag.Mounted])
|
||||||
@ -129,6 +131,8 @@ internal static class Mount
|
|||||||
private bool _unmountTriggered;
|
private bool _unmountTriggered;
|
||||||
private DateTime _continueAt = DateTime.MinValue;
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
|
|
||||||
|
public bool ShouldRedoOnInterrupt() => true;
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
if (!condition[ConditionFlag.Mounted])
|
if (!condition[ConditionFlag.Mounted])
|
||||||
|
@ -5,6 +5,8 @@ namespace Questionable.Controller.Steps;
|
|||||||
|
|
||||||
internal interface ITask
|
internal interface ITask
|
||||||
{
|
{
|
||||||
|
bool ShouldRedoOnInterrupt() => false;
|
||||||
|
|
||||||
bool Start();
|
bool Start();
|
||||||
|
|
||||||
ETaskResult Update();
|
ETaskResult Update();
|
||||||
|
@ -101,7 +101,7 @@ internal static class Combat
|
|||||||
step.CompletionQuestVariablesFlags, step.ComplexCombatData);
|
step.CompletionQuestVariablesFlags, step.ComplexCombatData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private HandleCombat CreateTask(ElementId elementId, bool isLastStep, EEnemySpawnType enemySpawnType,
|
internal HandleCombat CreateTask(ElementId? elementId, bool isLastStep, EEnemySpawnType enemySpawnType,
|
||||||
IList<uint> killEnemyDataIds, IList<QuestWorkValue?> completionQuestVariablesFlags,
|
IList<uint> killEnemyDataIds, IList<QuestWorkValue?> completionQuestVariablesFlags,
|
||||||
IList<ComplexCombatData> complexCombatData)
|
IList<ComplexCombatData> complexCombatData)
|
||||||
{
|
{
|
||||||
@ -115,18 +115,21 @@ internal static class Combat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class HandleCombat(
|
internal sealed class HandleCombat(
|
||||||
bool isLastStep,
|
bool isLastStep,
|
||||||
CombatController.CombatData combatData,
|
CombatController.CombatData combatData,
|
||||||
IList<QuestWorkValue?> completionQuestVariableFlags,
|
IList<QuestWorkValue?> completionQuestVariableFlags,
|
||||||
CombatController combatController,
|
CombatController combatController,
|
||||||
QuestFunctions questFunctions) : ITask
|
QuestFunctions questFunctions) : ITask
|
||||||
{
|
{
|
||||||
|
private CombatController.EStatus _status = CombatController.EStatus.NotStarted;
|
||||||
|
|
||||||
public bool Start() => combatController.Start(combatData);
|
public bool Start() => combatController.Start(combatData);
|
||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (combatController.Update() != CombatController.EStatus.Complete)
|
_status = combatController.Update();
|
||||||
|
if (_status != CombatController.EStatus.Complete)
|
||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
|
|
||||||
// if our quest step has any completion flags, we need to check if they are set
|
// if our quest step has any completion flags, we need to check if they are set
|
||||||
@ -157,11 +160,11 @@ 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, s: {_status})";
|
||||||
else if (isLastStep)
|
else if (isLastStep)
|
||||||
return "HandleCombat(wait: next sequence)";
|
return $"HandleCombat(wait: next sequence, s: {_status})";
|
||||||
else
|
else
|
||||||
return "HandleCombat(wait: not in combat)";
|
return $"HandleCombat(wait: not in combat, s: {_status})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,6 +173,8 @@ internal static class MoveTo
|
|||||||
_canRestart = moveParams.RestartNavigation;
|
_canRestart = moveParams.RestartNavigation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ShouldRedoOnInterrupt() => true;
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
float stopDistance = _moveParams.StopDistance ?? QuestStep.DefaultStopDistance;
|
float stopDistance = _moveParams.StopDistance ?? QuestStep.DefaultStopDistance;
|
||||||
@ -313,6 +315,8 @@ internal static class MoveTo
|
|||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
IClientState clientState) : ITask
|
IClientState clientState) : ITask
|
||||||
{
|
{
|
||||||
|
public bool ShouldRedoOnInterrupt() => true;
|
||||||
|
|
||||||
public bool Start() => true;
|
public bool Start() => true;
|
||||||
|
|
||||||
public ETaskResult Update()
|
public ETaskResult Update()
|
||||||
@ -333,6 +337,8 @@ internal static class MoveTo
|
|||||||
private bool _landing;
|
private bool _landing;
|
||||||
private DateTime _continueAt;
|
private DateTime _continueAt;
|
||||||
|
|
||||||
|
public bool ShouldRedoOnInterrupt() => true;
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
if (!condition[ConditionFlag.InFlight])
|
if (!condition[ConditionFlag.InFlight])
|
||||||
|
67
Questionable/Controller/Steps/TaskQueue.cs
Normal file
67
Questionable/Controller/Steps/TaskQueue.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.Steps;
|
||||||
|
|
||||||
|
internal sealed class TaskQueue
|
||||||
|
{
|
||||||
|
private readonly List<ITask> _tasks = [];
|
||||||
|
private int _currentTaskIndex;
|
||||||
|
public ITask? CurrentTask { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<ITask> RemainingTasks => _tasks.Skip(_currentTaskIndex);
|
||||||
|
public bool AllTasksComplete => CurrentTask == null && _currentTaskIndex >= _tasks.Count;
|
||||||
|
|
||||||
|
public void Enqueue(ITask task)
|
||||||
|
{
|
||||||
|
_tasks.Add(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryDequeue([NotNullWhen(true)] out ITask? task)
|
||||||
|
{
|
||||||
|
if (_currentTaskIndex >= _tasks.Count)
|
||||||
|
{
|
||||||
|
task = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
task = _tasks[_currentTaskIndex];
|
||||||
|
if (task.ShouldRedoOnInterrupt())
|
||||||
|
_currentTaskIndex++;
|
||||||
|
else
|
||||||
|
_tasks.RemoveAt(0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryPeek([NotNullWhen(true)] out ITask? task)
|
||||||
|
{
|
||||||
|
if (_currentTaskIndex >= _tasks.Count)
|
||||||
|
{
|
||||||
|
task = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
task = _tasks[_currentTaskIndex];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_tasks.Clear();
|
||||||
|
_currentTaskIndex = 0;
|
||||||
|
CurrentTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InterruptWith(List<ITask> interruptionTasks)
|
||||||
|
{
|
||||||
|
if (CurrentTask != null)
|
||||||
|
{
|
||||||
|
_tasks.Insert(0, CurrentTask);
|
||||||
|
CurrentTask = null;
|
||||||
|
_currentTaskIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_tasks.InsertRange(0, interruptionTasks);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user