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
Liza 5288cc6e31
Handle 'Action canceled, you are under attack' while e.g. talking to an NPC 2024-09-17 19:37:28 +02:00
Liza 21fde119ba
Attune to the High Crucible in Radz-at-Han when first visiting the city 2024-09-16 23:10:14 +02:00
Liza 55e2cd300b
Add gathering point for Kai-Shirr 2024-09-16 21:46:10 +02:00
Liza 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> <Project>
<PropertyGroup> <PropertyGroup>
<Version>3.3</Version> <Version>3.4</Version>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@ -40,7 +40,7 @@
"Z": 634.821 "Z": 634.821
}, },
"MinimumAngle": 45, "MinimumAngle": 45,
"MaximumAngle": 90, "MaximumAngle": 65,
"MinimumDistance": 1.6, "MinimumDistance": 1.6,
"MaximumDistance": 3 "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", "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"Author": "liza", "Author": "plogon_enjoyer",
"Disabled": true,
"QuestSequence": [ "QuestSequence": [
{ {
"Sequence": 0, "Sequence": 0,
@ -21,6 +20,22 @@
{ {
"Sequence": 255, "Sequence": 255,
"Steps": [ "Steps": [
{
"TerritoryId": 817,
"InteractionType": "Gather",
"ItemsToGather": [
{
"QuestAcceptedAsClass": "Miner",
"ItemId": 29531,
"ItemCount": 3
},
{
"QuestAcceptedAsClass": "Botanist",
"ItemId": 29557,
"ItemCount": 3
}
]
},
{ {
"Position": { "Position": {
"X": 788.1569, "X": 788.1569,

View File

@ -1,7 +1,6 @@
{ {
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"Author": "liza", "Author": "plogon_enjoyer",
"Disabled": true,
"QuestSequence": [ "QuestSequence": [
{ {
"Sequence": 0, "Sequence": 0,
@ -21,6 +20,22 @@
{ {
"Sequence": 255, "Sequence": 255,
"Steps": [ "Steps": [
{
"TerritoryId": 817,
"InteractionType": "Gather",
"ItemsToGather": [
{
"QuestAcceptedAsClass": "Miner",
"ItemId": 29532,
"ItemCount": 3
},
{
"QuestAcceptedAsClass": "Botanist",
"ItemId": 29558,
"ItemCount": 3
}
]
},
{ {
"Position": { "Position": {
"X": 788.1569, "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, "Sequence": 5,
"Steps": [ "Steps": [
{
"TerritoryId": 963,
"InteractionType": "AttuneAethernetShard",
"AethernetShard": "[Radz-at-Han] The High Crucible of Al-Kimiya"
},
{ {
"DataId": 1040276, "DataId": 1040276,
"Position": { "Position": {

View File

@ -49,12 +49,6 @@
{ {
"Sequence": 255, "Sequence": 255,
"Steps": [ "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": { "Position": {
"X": -21.53136, "X": -21.53136,

View File

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

View File

@ -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,

View File

@ -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;

View File

@ -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();

View File

@ -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();
} }

View File

@ -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()
{ {

View File

@ -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()

View File

@ -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])

View File

@ -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();

View File

@ -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})";
} }
} }
} }

View File

@ -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])

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