Compare commits

..

6 Commits

Author SHA1 Message Date
Plogon Enjoyer
f4a70bde2e Updated a few daily quests:
- Rock 'n Ronka
- There's No Clean Like Qhoterl Clean
2024-09-19 01:34:12 +08:00
Plogon Enjoyer
3061802132 Added new story quests:
- Delving Deeper
- The Second Stela: The Hunters and the Hunted
- The Second Stela: Fast Friends
2024-09-19 01:33:29 +08:00
5288cc6e31
Handle 'Action canceled, you are under attack' while e.g. talking to an NPC 2024-09-17 19:37:28 +02:00
21fde119ba
Attune to the High Crucible in Radz-at-Han when first visiting the city 2024-09-16 23:10:14 +02:00
55e2cd300b
Add gathering point for Kai-Shirr 2024-09-16 21:46:10 +02:00
14ec91330a Merge pull request '[ShB][Allied Society][Qitari] Updated a few more daily quests' (#50) from plogon_enjoyer/Questionable:qitari into master
Reviewed-on: liza/Questionable#50
2024-09-16 18:38:50 +00:00
23 changed files with 698 additions and 78 deletions

View File

@ -1,5 +1,5 @@
<Project>
<PropertyGroup>
<Version>3.3</Version>
<Version>3.4</Version>
</PropertyGroup>
</Project>

View File

@ -40,7 +40,7 @@
"Z": 634.821
},
"MinimumAngle": 45,
"MaximumAngle": 90,
"MaximumAngle": 65,
"MinimumDistance": 1.6,
"MaximumDistance": 3
},
@ -140,4 +140,4 @@
]
}
]
}
}

View File

@ -0,0 +1,140 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
"Author": "liza",
"Steps": [
{
"Position": {
"X": 329.38184,
"Y": 9.586891,
"Z": 749.2314
},
"TerritoryId": 816,
"InteractionType": "WalkTo",
"AetheryteShortcut": "Il Mheg - Lydha Lran",
"Fly": true,
"SkipConditions": {
"AetheryteShortcutIf": {
"InSameTerritory": true
}
}
},
{
"Position": {
"X": 351.29465,
"Y": -38.275272,
"Z": 763.0457
},
"TerritoryId": 816,
"InteractionType": "WalkTo",
"Fly": true,
"DisableNavmesh": true
}
],
"Groups": [
{
"Nodes": [
{
"DataId": 32832,
"Locations": [
{
"Position": {
"X": 382.7488,
"Y": -72.47251,
"Z": 794.3513
}
},
{
"Position": {
"X": 388.7861,
"Y": -74.19925,
"Z": 801.0947
}
},
{
"Position": {
"X": 386.1797,
"Y": -73.5009,
"Z": 787.0967
}
}
]
},
{
"DataId": 32831,
"Locations": [
{
"Position": {
"X": 396.5799,
"Y": -76.29187,
"Z": 790.9022
}
}
]
}
]
},
{
"Nodes": [
{
"DataId": 32830,
"Locations": [
{
"Position": {
"X": 492.6384,
"Y": -82.73045,
"Z": 804.714
}
},
{
"Position": {
"X": 482.808,
"Y": -82.61642,
"Z": 802.591
}
}
]
},
{
"DataId": 32829,
"Locations": [
{
"Position": {
"X": 493.5814,
"Y": -82.43644,
"Z": 790.831
}
}
]
}
]
},
{
"Nodes": [
{
"DataId": 32827,
"Locations": [
{
"Position": {
"X": 490.9451,
"Y": -97.88062,
"Z": 636.6115
}
}
]
},
{
"DataId": 32828,
"Locations": [
{
"Position": {
"X": 491.5274,
"Y": -100.762,
"Z": 626.6958
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,158 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
"Author": "liza",
"Steps": [
{
"Position": {
"X": 491.82068,
"Y": 3.9304812,
"Z": 487.9401
},
"TerritoryId": 816,
"InteractionType": "WalkTo",
"AetheryteShortcut": "Il Mheg - Lydha Lran",
"Fly": true,
"SkipConditions": {
"AetheryteShortcutIf": {
"InSameTerritory": true
}
}
},
{
"Position": {
"X": 506.00256,
"Y": -37.76961,
"Z": 485.49347
},
"TerritoryId": 816,
"InteractionType": "WalkTo",
"Fly": true,
"DisableNavmesh": true
}
],
"Groups": [
{
"Nodes": [
{
"DataId": 32836,
"Locations": [
{
"Position": {
"X": 539.5437,
"Y": -81.62054,
"Z": 520.1647
},
"MinimumAngle": -30,
"MaximumAngle": 165
},
{
"Position": {
"X": 555.8599,
"Y": -73.65717,
"Z": 494.6164
},
"MinimumAngle": 35,
"MaximumAngle": 240
},
{
"Position": {
"X": 576.4164,
"Y": -69.75835,
"Z": 512.9263
},
"MinimumAngle": -75,
"MaximumAngle": 70
}
]
},
{
"DataId": 32835,
"Locations": [
{
"Position": {
"X": 552.5504,
"Y": -78.23183,
"Z": 512.429
},
"MinimumAngle": -30,
"MaximumAngle": 135
}
]
}
]
},
{
"Nodes": [
{
"DataId": 32838,
"Locations": [
{
"Position": {
"X": 652.7063,
"Y": -46.64378,
"Z": 488.4543
},
"MinimumAngle": 0,
"MaximumAngle": 120
},
{
"Position": {
"X": 669.2959,
"Y": -47.14824,
"Z": 513.9606
},
"MinimumAngle": -20,
"MaximumAngle": 105
}
]
},
{
"DataId": 32837,
"Locations": [
{
"Position": {
"X": 659.1685,
"Y": -46.65159,
"Z": 499.8015
},
"MinimumAngle": 0,
"MaximumAngle": 125
}
]
}
]
},
{
"Nodes": [
{
"DataId": 32834,
"Locations": [
{
"Position": {
"X": 576.1583,
"Y": -46.68682,
"Z": 375.5306
},
"MinimumAngle": -40,
"MaximumAngle": 150
}
]
},
{
"DataId": 32833,
"Locations": [
{
"Position": {
"X": 571.1834,
"Y": -46.41214,
"Z": 360.5112
},
"MinimumAngle": 0,
"MaximumAngle": 115
}
]
}
]
}
]
}

View File

@ -1,7 +1,6 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"Author": "liza",
"Disabled": true,
"Author": "plogon_enjoyer",
"QuestSequence": [
{
"Sequence": 0,
@ -21,6 +20,22 @@
{
"Sequence": 255,
"Steps": [
{
"TerritoryId": 817,
"InteractionType": "Gather",
"ItemsToGather": [
{
"QuestAcceptedAsClass": "Miner",
"ItemId": 29531,
"ItemCount": 3
},
{
"QuestAcceptedAsClass": "Botanist",
"ItemId": 29557,
"ItemCount": 3
}
]
},
{
"Position": {
"X": 788.1569,

View File

@ -1,7 +1,6 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"Author": "liza",
"Disabled": true,
"Author": "plogon_enjoyer",
"QuestSequence": [
{
"Sequence": 0,
@ -21,6 +20,22 @@
{
"Sequence": 255,
"Steps": [
{
"TerritoryId": 817,
"InteractionType": "Gather",
"ItemsToGather": [
{
"QuestAcceptedAsClass": "Miner",
"ItemId": 29532,
"ItemCount": 3
},
{
"QuestAcceptedAsClass": "Botanist",
"ItemId": 29558,
"ItemCount": 3
}
]
},
{
"Position": {
"X": 788.1569,

View File

@ -0,0 +1,100 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"Author": "plogon_enjoyer",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1032735,
"Position": {
"X": 803.7993,
"Y": -45.924515,
"Z": -217.94464
},
"TerritoryId": 817,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1032659,
"Position": {
"X": 802.79236,
"Y": -45.91779,
"Z": -218.58557
},
"TerritoryId": 817,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1027716,
"Position": {
"X": 494.9873,
"Y": -6.555339,
"Z": -224.93329
},
"TerritoryId": 817,
"InteractionType": "Interact",
"Fly": true,
"AetheryteShortcut": "Rak'tika - Fanow"
}
]
},
{
"Sequence": 3,
"Steps": [
{
"TerritoryId": 817,
"InteractionType": "Gather",
"ItemsToGather": [
{
"QuestAcceptedAsClass": "Miner",
"ItemId": 29514,
"ItemCount": 1
},
{
"QuestAcceptedAsClass": "Botanist",
"ItemId": 29540,
"ItemCount": 1
}
]
},
{
"DataId": 1032660,
"Position": {
"X": 804.4098,
"Y": -45.9255,
"Z": -216.41876
},
"TerritoryId": 817,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1032659,
"Position": {
"X": 802.79236,
"Y": -45.91779,
"Z": -218.58557
},
"TerritoryId": 817,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,36 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"Author": "plogon_enjoyer",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1032660,
"Position": {
"X": 804.4098,
"Y": -45.9255,
"Z": -216.41876
},
"TerritoryId": 817,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1032660,
"Position": {
"X": 804.4098,
"Y": -45.9255,
"Z": -216.41876
},
"TerritoryId": 817,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,36 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"Author": "plogon_enjoyer",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1032659,
"Position": {
"X": 802.79236,
"Y": -45.91779,
"Z": -218.58557
},
"TerritoryId": 817,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1032659,
"Position": {
"X": 802.79236,
"Y": -45.91779,
"Z": -218.58557
},
"TerritoryId": 817,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -85,6 +85,11 @@
{
"Sequence": 5,
"Steps": [
{
"TerritoryId": 963,
"InteractionType": "AttuneAethernetShard",
"AethernetShard": "[Radz-at-Han] The High Crucible of Al-Kimiya"
},
{
"DataId": 1040276,
"Position": {

View File

@ -49,12 +49,6 @@
{
"Sequence": 255,
"Steps": [
{
"TerritoryId": 963,
"InteractionType": "AttuneAethernetShard",
"AethernetShard": "[Radz-at-Han] The High Crucible of Al-Kimiya",
"$": "TODO This happens in Post-EW MSQ only; should maybe move this to when we're around the alchemist area earlier"
},
{
"Position": {
"X": -21.53136,

View File

@ -13,4 +13,5 @@ public enum EEnemySpawnType
AutoOnEnterArea,
OverworldEnemies,
FateEnemies,
QuestInterruption,
}

View File

@ -75,6 +75,7 @@ internal sealed class CombatController : IDisposable
Module = combatModule,
Data = combatData,
};
_wasInCombat = combatData.SpawnType == EEnemySpawnType.QuestInterruption;
return true;
}
else
@ -86,7 +87,9 @@ internal sealed class CombatController : IDisposable
if (_currentFight == null)
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;
var target = _targetManager.Target;
@ -111,6 +114,8 @@ internal sealed class CombatController : IDisposable
else
{
var nextTarget = FindNextTarget();
_logger.LogInformation("NT → {NT}", nextTarget);
if (nextTarget is { IsDead: false })
SetTarget(nextTarget);
}
@ -335,7 +340,7 @@ internal sealed class CombatController : IDisposable
public sealed class CombatData
{
public required ElementId ElementId { get; init; }
public required ElementId? ElementId { get; init; }
public required EEnemySpawnType SpawnType { get; init; }
public required List<uint> KillEnemyDataIds { get; init; }
public required List<ComplexCombatData> ComplexCombatDatas { get; init; }
@ -345,6 +350,7 @@ internal sealed class CombatController : IDisposable
public enum EStatus
{
NotStarted,
InCombat,
Moving,
Complete,

View File

@ -122,6 +122,10 @@ internal sealed class CommandHandler : IDisposable
PrintMountId();
break;
case "handle-interrupt":
_questController.InterruptQueueWithCombat();
break;
case "":
_questWindow.Toggle();
break;

View File

@ -129,7 +129,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
return EStatus.Complete;
}
if (_currentTask == null && _taskQueue.Count == 0)
if (_taskQueue.AllTasksComplete)
GoToNextNode();
UpdateCurrentTask();
@ -141,8 +141,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
public override void Stop(string label)
{
_currentRequest = null;
_currentTask = null;
_taskQueue.Clear();
_taskQueue.Reset();
}
private void GoToNextNode()
@ -150,7 +149,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
if (_currentRequest == null)
return;
if (_taskQueue.Count > 0)
if (!_taskQueue.AllTasksComplete)
return;
var director = UIState.Instance()->DirectorTodo.Director;
@ -267,8 +266,8 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
public override IList<string> GetRemainingTaskNames()
{
if (_currentTask != null)
return [_currentTask.ToString() ?? "?", .. base.GetRemainingTaskNames()];
if (_taskQueue.CurrentTask is {} currentTask)
return [currentTask.ToString() ?? "?", .. base.GetRemainingTaskNames()];
else
return base.GetRemainingTaskNames();
}
@ -277,10 +276,10 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
{
if (_revisitRegex.IsMatch(message.TextValue))
{
if (_currentTask is IRevisitAware currentTaskRevisitAware)
if (_taskQueue.CurrentTask is IRevisitAware currentTaskRevisitAware)
currentTaskRevisitAware.OnRevisit();
foreach (ITask task in _taskQueue)
foreach (ITask task in _taskQueue.RemainingTasks)
{
if (task is IRevisitAware taskRevisitAware)
taskRevisitAware.OnRevisit();

View File

@ -12,11 +12,9 @@ internal abstract class MiniTaskController<T>
{
protected readonly IChatGui _chatGui;
protected readonly ILogger<T> _logger;
protected readonly TaskQueue _taskQueue = new();
protected readonly Queue<ITask> _taskQueue = new();
protected ITask? _currentTask;
public MiniTaskController(IChatGui chatGui, ILogger<T> logger)
protected MiniTaskController(IChatGui chatGui, ILogger<T> logger)
{
_chatGui = chatGui;
_logger = logger;
@ -24,7 +22,7 @@ internal abstract class MiniTaskController<T>
protected virtual void UpdateCurrentTask()
{
if (_currentTask == null)
if (_taskQueue.CurrentTask == null)
{
if (_taskQueue.TryDequeue(out ITask? upcomingTask))
{
@ -33,7 +31,7 @@ internal abstract class MiniTaskController<T>
_logger.LogInformation("Starting task {TaskName}", upcomingTask.ToString());
if (upcomingTask.Start())
{
_currentTask = upcomingTask;
_taskQueue.CurrentTask = upcomingTask;
return;
}
else
@ -58,13 +56,13 @@ internal abstract class MiniTaskController<T>
ETaskResult result;
try
{
result = _currentTask.Update();
result = _taskQueue.CurrentTask.Update();
}
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(
$"[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");
return;
}
@ -76,14 +74,14 @@ internal abstract class MiniTaskController<T>
case ETaskResult.SkipRemainingTasksForStep:
_logger.LogInformation("{Task} → {Result}, skipping remaining tasks for step",
_currentTask, result);
_currentTask = null;
_taskQueue.CurrentTask, result);
_taskQueue.CurrentTask = null;
while (_taskQueue.TryDequeue(out ITask? nextTask))
{
if (nextTask is ILastTask or Gather.SkipMarker)
{
_currentTask = nextTask;
_taskQueue.CurrentTask = nextTask;
return;
}
}
@ -92,27 +90,27 @@ internal abstract class MiniTaskController<T>
case ETaskResult.TaskComplete:
_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
return;
case ETaskResult.NextStep:
_logger.LogInformation("{Task} → {Result}", _currentTask, result);
_logger.LogInformation("{Task} → {Result}", _taskQueue.CurrentTask, result);
var lastTask = (ILastTask)_currentTask;
_currentTask = null;
var lastTask = (ILastTask)_taskQueue.CurrentTask;
_taskQueue.CurrentTask = null;
OnNextStep(lastTask);
return;
case ETaskResult.End:
_logger.LogInformation("{Task} → {Result}", _currentTask, result);
_currentTask = null;
_logger.LogInformation("{Task} → {Result}", _taskQueue.CurrentTask, result);
_taskQueue.CurrentTask = null;
Stop("Task end");
return;
}
@ -130,5 +128,5 @@ internal abstract class MiniTaskController<T>
public abstract void Stop(string label);
public virtual IList<string> GetRemainingTaskNames() =>
_taskQueue.Select(x => x.ToString() ?? "?").ToList();
_taskQueue.RemainingTasks.Select(x => x.ToString() ?? "?").ToList();
}

View File

@ -85,7 +85,7 @@ internal sealed class MovementController : IDisposable
public bool IsPathfinding => _pathfindTask is { IsCompleted: false };
public DestinationData? Destination { get; set; }
public DateTime MovementStartedAt { get; private set; } = DateTime.MaxValue;
public DateTime MovementStartedAt { get; private set; } = DateTime.Now;
public void Update()
{

View File

@ -8,7 +8,9 @@ using Dalamud.Game.Gui.Toast;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using LLib;
using LLib.GameData;
using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps;
using Questionable.Controller.Steps.Interactions;
@ -18,6 +20,8 @@ using Questionable.External;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
using Quest = Questionable.Model.Quest;
using Mount = Questionable.Controller.Steps.Common.Mount;
namespace Questionable.Controller;
@ -36,6 +40,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
private readonly Configuration _configuration;
private readonly YesAlreadyIpc _yesAlreadyIpc;
private readonly TaskCreator _taskCreator;
private readonly Mount.Factory _mountFactory;
private readonly Combat.Factory _combatFactory;
private readonly string _actionCanceledText;
private readonly object _progressLock = new();
@ -73,7 +81,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
IToastGui toastGui,
Configuration configuration,
YesAlreadyIpc yesAlreadyIpc,
TaskCreator taskCreator)
TaskCreator taskCreator,
Mount.Factory mountFactory,
Combat.Factory combatFactory,
IDataManager dataManager)
: base(chatGui, logger)
{
_clientState = clientState;
@ -89,10 +100,14 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
_configuration = configuration;
_yesAlreadyIpc = yesAlreadyIpc;
_taskCreator = taskCreator;
_mountFactory = mountFactory;
_combatFactory = combatFactory;
_condition.ConditionChange += OnConditionChange;
_toastGui.Toast += OnNormalToast;
_toastGui.ErrorToast += OnErrorToast;
_actionCanceledText = dataManager.GetString<LogMessage>(1314, x => x.Text)!;
}
public EAutomationType AutomationType
@ -181,7 +196,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
if (!_clientState.IsLoggedIn || _condition[ConditionFlag.Unconscious])
{
if (_currentTask != null || _taskQueue.Count > 0)
if (!_taskQueue.AllTasksComplete)
{
Stop("HP = 0");
_movementController.Stop();
@ -191,7 +206,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
}
else if (_configuration.General.UseEscToCancelQuesting && _keyState[VirtualKey.ESCAPE])
{
if (_currentTask != null || _taskQueue.Count > 0)
if (!_taskQueue.AllTasksComplete)
{
Stop("ESC pressed");
_movementController.Stop();
@ -204,8 +219,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
return;
if (AutomationType == EAutomationType.Automatic &&
((_currentTask == null && _taskQueue.Count == 0) ||
_currentTask is WaitAtEnd.WaitQuestAccepted)
(_taskQueue.AllTasksComplete || _taskQueue.CurrentTask is WaitAtEnd.WaitQuestAccepted)
&& CurrentQuest is { Sequence: 0, Step: 0 } or { Sequence: 0, Step: 255 }
&& DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15))
{
@ -276,8 +290,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
questToRun = _nextQuest;
currentSequence = _nextQuest.Sequence; // by definition, this should always be 0
if (_nextQuest.Step == 0 &&
_currentTask == null &&
_taskQueue.Count == 0 &&
_taskQueue.AllTasksComplete &&
AutomationType == EAutomationType.Automatic)
ExecuteNextStep();
}
@ -286,8 +299,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
questToRun = _gatheringQuest;
currentSequence = _gatheringQuest.Sequence;
if (_gatheringQuest.Step == 0 &&
_currentTask == null &&
_taskQueue.Count == 0 &&
_taskQueue.AllTasksComplete &&
AutomationType == EAutomationType.Automatic)
ExecuteNextStep();
}
@ -392,7 +404,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
if (questToRun.Step == 255)
{
DebugState = "Step completed";
if (_currentTask != null || _taskQueue.Count > 0)
if (!_taskQueue.AllTasksComplete)
CheckNextTasks("Step complete");
return;
}
@ -465,10 +477,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
private void ClearTasksInternal()
{
//_logger.LogDebug("Clearing task (internally)");
_currentTask = null;
if (_taskQueue.Count > 0)
_taskQueue.Clear();
_taskQueue.Reset();
_yesAlreadyIpc.RestoreYesAlready();
_combatController.Stop("ClearTasksInternal");
@ -629,13 +638,15 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
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)
where T : class, ITask
{
if (_currentTask is T t)
if (_taskQueue.CurrentTask is T t)
{
task = t;
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
{
@ -687,19 +698,19 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
{
lock (_progressLock)
{
if (_currentTask is ISkippableTask)
_currentTask = null;
else if (_currentTask != null)
if (_taskQueue.CurrentTask is ISkippableTask)
_taskQueue.CurrentTask = null;
else if (_taskQueue.CurrentTask != null)
{
_currentTask = null;
while (_taskQueue.Count > 0)
_taskQueue.CurrentTask = null;
while (_taskQueue.TryPeek(out ITask? task))
{
var task = _taskQueue.Dequeue();
_taskQueue.TryDequeue(out _);
if (task is ISkippableTask)
return;
}
if (_taskQueue.Count == 0)
if (_taskQueue.AllTasksComplete)
{
Stop("Skip");
IncreaseStepCount(elementId, currentQuestSequence);
@ -715,7 +726,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
public void SkipSimulatedTask()
{
_currentTask = null;
_taskQueue.CurrentTask = null;
}
public bool IsInterruptible()
@ -774,7 +785,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
private void OnConditionChange(ConditionFlag flag, bool value)
{
if (_currentTask is IConditionChangeAware conditionChangeAware)
if (_taskQueue.CurrentTask is IConditionChangeAware conditionChangeAware)
conditionChangeAware.OnConditionChange(flag, value);
}
@ -785,13 +796,33 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
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))
{
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()

View File

@ -46,6 +46,8 @@ internal static class Mount
private bool _mountTriggered;
private DateTime _retryAt = DateTime.MinValue;
public bool ShouldRedoOnInterrupt() => true;
public bool Start()
{
if (condition[ConditionFlag.Mounted])
@ -129,6 +131,8 @@ internal static class Mount
private bool _unmountTriggered;
private DateTime _continueAt = DateTime.MinValue;
public bool ShouldRedoOnInterrupt() => true;
public bool Start()
{
if (!condition[ConditionFlag.Mounted])

View File

@ -5,6 +5,8 @@ namespace Questionable.Controller.Steps;
internal interface ITask
{
bool ShouldRedoOnInterrupt() => false;
bool Start();
ETaskResult Update();

View File

@ -101,7 +101,7 @@ internal static class Combat
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<ComplexCombatData> complexCombatData)
{
@ -115,18 +115,21 @@ internal static class Combat
}
}
private sealed class HandleCombat(
internal sealed class HandleCombat(
bool isLastStep,
CombatController.CombatData combatData,
IList<QuestWorkValue?> completionQuestVariableFlags,
CombatController combatController,
QuestFunctions questFunctions) : ITask
{
private CombatController.EStatus _status = CombatController.EStatus.NotStarted;
public bool Start() => combatController.Start(combatData);
public ETaskResult Update()
{
if (combatController.Update() != CombatController.EStatus.Complete)
_status = combatController.Update();
if (_status != CombatController.EStatus.Complete)
return ETaskResult.StillRunning;
// 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()
{
if (QuestWorkUtils.HasCompletionFlags(completionQuestVariableFlags))
return "HandleCombat(wait: QW flags)";
return $"HandleCombat(wait: QW flags, s: {_status})";
else if (isLastStep)
return "HandleCombat(wait: next sequence)";
return $"HandleCombat(wait: next sequence, s: {_status})";
else
return "HandleCombat(wait: not in combat)";
return $"HandleCombat(wait: not in combat, s: {_status})";
}
}
}

View File

@ -173,6 +173,8 @@ internal static class MoveTo
_canRestart = moveParams.RestartNavigation;
}
public bool ShouldRedoOnInterrupt() => true;
public bool Start()
{
float stopDistance = _moveParams.StopDistance ?? QuestStep.DefaultStopDistance;
@ -313,6 +315,8 @@ internal static class MoveTo
GameFunctions gameFunctions,
IClientState clientState) : ITask
{
public bool ShouldRedoOnInterrupt() => true;
public bool Start() => true;
public ETaskResult Update()
@ -333,6 +337,8 @@ internal static class MoveTo
private bool _landing;
private DateTime _continueAt;
public bool ShouldRedoOnInterrupt() => true;
public bool Start()
{
if (!condition[ConditionFlag.InFlight])

View 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);
}
}