1
0
forked from liza/Questionable

Various fixes

This commit is contained in:
Liza 2024-06-10 19:56:13 +02:00
parent 73fef2677e
commit 564f1adc01
Signed by: liza
GPG Key ID: 7199F8D727D55F67
13 changed files with 195 additions and 46 deletions

View File

@ -77,7 +77,7 @@
"Z": 365.7129 "Z": 365.7129
}, },
"TerritoryId": 958, "TerritoryId": 958,
"InteractionType": "WaitForManualProgress", "InteractionType": "SinglePlayerDuty",
"Comment": "Follow Alphinaud and Alisaie", "Comment": "Follow Alphinaud and Alisaie",
"DialogueChoices": [ "DialogueChoices": [
{ {

View File

@ -76,7 +76,7 @@
null, null,
null, null,
null, null,
128 -128
] ]
}, },
{ {

View File

@ -52,7 +52,15 @@
"Z": -503.2578 "Z": -503.2578
}, },
"TerritoryId": 958, "TerritoryId": 958,
"InteractionType": "Interact" "InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}, },
{ {
"DataId": 2012094, "DataId": 2012094,
@ -64,7 +72,15 @@
"TerritoryId": 958, "TerritoryId": 958,
"InteractionType": "Interact", "InteractionType": "Interact",
"Comment": "Map", "Comment": "Map",
"Mount": true "Mount": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
}, },
{ {
"DataId": 2012005, "DataId": 2012005,
@ -86,7 +102,15 @@
}, },
"TerritoryId": 958, "TerritoryId": 958,
"InteractionType": "Interact", "InteractionType": "Interact",
"Comment": "Warmachine Wreckage" "Comment": "Warmachine Wreckage",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
}, },
{ {
"DataId": 2012096, "DataId": 2012096,
@ -97,7 +121,15 @@
}, },
"TerritoryId": 958, "TerritoryId": 958,
"InteractionType": "Interact", "InteractionType": "Interact",
"Comment": "Children's Slide" "Comment": "Children's Slide",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
16
]
} }
] ]
}, },

View File

@ -21,6 +21,16 @@
{ {
"Sequence": 1, "Sequence": 1,
"Steps": [ "Steps": [
{
"Position": {
"X": -148.48793,
"Y": -10.30035,
"Z": -247.25652
},
"TerritoryId": 956,
"InteractionType": "WalkTo",
"Comment": "Avoids Combat"
},
{ {
"DataId": 2011987, "DataId": 2011987,
"Position": { "Position": {
@ -43,6 +53,16 @@
"Mount": true, "Mount": true,
"DisableNavmesh": true "DisableNavmesh": true
}, },
{
"Position": {
"X": -480.30975,
"Y": -22.946651,
"Z": -145.08534
},
"TerritoryId": 956,
"InteractionType": "WalkTo",
"Comment": "Avoids Combat (typically)"
},
{ {
"DataId": 2011988, "DataId": 2011988,
"Position": { "Position": {

View File

@ -71,7 +71,8 @@ internal sealed class QuestController
if (_keyState[VirtualKey.ESCAPE]) if (_keyState[VirtualKey.ESCAPE])
{ {
Stop(); if (_currentTask != null || _taskQueue.Count > 0)
Stop("ESC pressed");
_movementController.Stop(); _movementController.Stop();
} }
@ -92,7 +93,7 @@ internal sealed class QuestController
{ {
_logger.LogInformation("No current quest, resetting data"); _logger.LogInformation("No current quest, resetting data");
CurrentQuest = null; CurrentQuest = null;
Stop(); Stop("Resetting current quest");
} }
} }
else if (CurrentQuest == null || CurrentQuest.Quest.QuestId != currentQuestId) else if (CurrentQuest == null || CurrentQuest.Quest.QuestId != currentQuestId)
@ -101,14 +102,15 @@ internal sealed class QuestController
{ {
_logger.LogInformation("New quest: {QuestName}", quest.Name); _logger.LogInformation("New quest: {QuestName}", quest.Name);
CurrentQuest = new QuestProgress(quest, currentSequence, 0); CurrentQuest = new QuestProgress(quest, currentSequence, 0);
Stop("Different Quest");
} }
else if (CurrentQuest != null) else if (CurrentQuest != null)
{ {
_logger.LogInformation("No active quest anymore? Not sure what happened..."); _logger.LogInformation("No active quest anymore? Not sure what happened...");
CurrentQuest = null; CurrentQuest = null;
Stop("No active Quest");
} }
Stop();
return; return;
} }
@ -116,7 +118,7 @@ internal sealed class QuestController
{ {
DebugState = "No quest active"; DebugState = "No quest active";
Comment = null; Comment = null;
Stop(); Stop("No quest active");
return; return;
} }
@ -140,11 +142,7 @@ internal sealed class QuestController
if (CurrentQuest.Sequence != currentSequence) if (CurrentQuest.Sequence != currentSequence)
{ {
CurrentQuest = CurrentQuest with { Sequence = currentSequence, Step = 0 }; CurrentQuest = CurrentQuest with { Sequence = currentSequence, Step = 0 };
Stop("New sequence", continueIfAutomatic: true);
bool automatic = _automatic;
Stop();
if (automatic)
ExecuteNextStep(true);
} }
var q = CurrentQuest.Quest; var q = CurrentQuest.Quest;
@ -153,7 +151,7 @@ internal sealed class QuestController
{ {
DebugState = "Sequence not found"; DebugState = "Sequence not found";
Comment = null; Comment = null;
Stop(); Stop("Unknown sequence");
return; return;
} }
@ -161,7 +159,8 @@ internal sealed class QuestController
{ {
DebugState = "Step completed"; DebugState = "Step completed";
Comment = null; Comment = null;
Stop(); if (_currentTask != null || _taskQueue.Count > 0)
Stop("Step complete", continueIfAutomatic: true);
return; return;
} }
@ -169,7 +168,7 @@ internal sealed class QuestController
{ {
DebugState = "Step not found"; DebugState = "Step not found";
Comment = null; Comment = null;
Stop(); Stop("Unknown step");
return; return;
} }
@ -249,16 +248,32 @@ internal sealed class QuestController
*/ */
} }
public void Stop() private void ClearTasksInternal()
{ {
_currentTask = null; _currentTask = null;
if (_taskQueue.Count > 0) if (_taskQueue.Count > 0)
_taskQueue.Clear(); _taskQueue.Clear();
}
public void Stop(string label, bool continueIfAutomatic = false)
{
using var scope = _logger.BeginScope(label);
ClearTasksInternal();
// reset task queue // reset task queue
if (continueIfAutomatic && _automatic)
{
if (CurrentQuest?.Step is >= 0 and < 255)
ExecuteNextStep(true);
}
else
{
_logger.LogInformation("Stopping automatic questing");
_automatic = false; _automatic = false;
} }
}
private void UpdateCurrentTask() private void UpdateCurrentTask()
{ {
@ -286,7 +301,7 @@ internal sealed class QuestController
catch (Exception e) catch (Exception e)
{ {
_logger.LogError(e, "Failed to start task {TaskName}", upcomingTask.ToString()); _logger.LogError(e, "Failed to start task {TaskName}", upcomingTask.ToString());
Stop(); Stop("Task failed to start");
return; return;
} }
} }
@ -302,7 +317,7 @@ internal sealed class QuestController
catch (Exception e) catch (Exception e)
{ {
_logger.LogError(e, "Failed to update task {TaskName}", _currentTask.ToString()); _logger.LogError(e, "Failed to update task {TaskName}", _currentTask.ToString());
Stop(); Stop("Task failed to update");
return; return;
} }
@ -312,7 +327,8 @@ internal sealed class QuestController
return; return;
case ETaskResult.SkipRemainingTasksForStep: case ETaskResult.SkipRemainingTasksForStep:
_logger.LogInformation("Result: {Result}, skipping remaining tasks for step", result); _logger.LogInformation("{Task} → {Result}, skipping remaining tasks for step",
_currentTask, result);
_currentTask = null; _currentTask = null;
while (_taskQueue.TryDequeue(out ITask? nextTask)) while (_taskQueue.TryDequeue(out ITask? nextTask))
@ -327,28 +343,28 @@ internal sealed class QuestController
return; return;
case ETaskResult.TaskComplete: case ETaskResult.TaskComplete:
_logger.LogInformation("Result: {Result}, remaining tasks: {RemainingTaskCount}", result, _logger.LogInformation("{Task} → {Result}, remaining tasks: {RemainingTaskCount}",
_taskQueue.Count); _currentTask, result, _taskQueue.Count);
_currentTask = null; _currentTask = null;
// handled in next update // handled in next update
return; return;
case ETaskResult.NextStep: case ETaskResult.NextStep:
_logger.LogInformation("Result: {Result}", result); _logger.LogInformation("{Task} → {Result}", _currentTask, result);
IncreaseStepCount(true); IncreaseStepCount(true);
return; return;
case ETaskResult.End: case ETaskResult.End:
_logger.LogInformation("Result: {Result}", result); _logger.LogInformation("{Task} → {Result}", _currentTask, result);
Stop(); Stop("Task end");
return; return;
} }
} }
public void ExecuteNextStep(bool automatic) public void ExecuteNextStep(bool automatic)
{ {
Stop(); ClearTasksInternal();
_automatic = automatic; _automatic = automatic;
(QuestSequence? seq, QuestStep? step) = GetNextStep(); (QuestSequence? seq, QuestStep? step) = GetNextStep();
@ -382,6 +398,9 @@ internal sealed class QuestController
return; return;
} }
_logger.LogInformation("Tasks for {QuestId}, {Sequence}, {Step}: {Tasks}",
CurrentQuest.Quest.QuestId, seq.Sequence, seq.Steps.IndexOf(step),
string.Join(", ", newTasks.Select(x => x.ToString())));
foreach (var task in newTasks) foreach (var task in newTasks)
_taskQueue.Enqueue(task); _taskQueue.Enqueue(task);
} }
@ -394,6 +413,9 @@ internal sealed class QuestController
return _currentTask == null ? $"- (+{_taskQueue.Count})" : $"{_currentTask} (+{_taskQueue.Count})"; return _currentTask == null ? $"- (+{_taskQueue.Count})" : $"{_currentTask} (+{_taskQueue.Count})";
} }
public bool HasCurrentTaskMatching<T>() =>
_currentTask is T;
public sealed record QuestProgress( public sealed record QuestProgress(
Quest Quest, Quest Quest,
byte Sequence, byte Sequence,

View File

@ -14,10 +14,10 @@ internal static class SkipCondition
{ {
public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step) public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{ {
if (step.SkipIf.Count == 0) if (step.SkipIf.Contains(ESkipCondition.Never))
return null; return null;
if (step.SkipIf.Contains(ESkipCondition.Never)) if (step.SkipIf.Count == 0 && step.CompletionQuestVariablesFlags.Count == 0)
return null; return null;
return serviceProvider.GetRequiredService<CheckTask>() return serviceProvider.GetRequiredService<CheckTask>()

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions; using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Questionable.Controller.Steps.BaseTasks; using Questionable.Controller.Steps.BaseTasks;
@ -13,7 +14,7 @@ namespace Questionable.Controller.Steps.BaseFactory;
internal static class WaitAtEnd internal static class WaitAtEnd
{ {
internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory internal sealed class Factory(IServiceProvider serviceProvider, IClientState clientState) : ITaskFactory
{ {
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{ {
@ -54,6 +55,36 @@ internal static class WaitAtEnd
new NextStep() new NextStep()
]; ];
case EInteractionType.Interact when step.TargetTerritoryId != null:
ITask waitInteraction;
if (step.TerritoryId != step.TargetTerritoryId)
{
// interaction moves to a different territory
waitInteraction = new WaitConditionTask(() => clientState.TerritoryType == step.TerritoryId,
$"Wait(tp to territory: {step.TerritoryId})");
}
else
{
Vector3 lastPosition = step.Position ?? clientState.LocalPlayer?.Position ?? Vector3.Zero;
waitInteraction = new WaitConditionTask(() =>
{
Vector3? currentPosition = clientState.LocalPlayer?.Position;
if (currentPosition == null)
return false;
// interaction moved to elsewhere in the zone
return (lastPosition - currentPosition.Value).Length() > 20;
}, $"Wait(tp away from {lastPosition.ToString("G", CultureInfo.InvariantCulture)})");
}
return
[
waitInteraction,
serviceProvider.GetRequiredService<WaitDelay>(),
new NextStep()
];
case EInteractionType.Interact:
default: default:
return [serviceProvider.GetRequiredService<WaitDelay>(), new NextStep()]; return [serviceProvider.GetRequiredService<WaitDelay>(), new NextStep()];
} }
@ -136,7 +167,7 @@ internal static class WaitAtEnd
public ETaskResult Update() => ETaskResult.NextStep; public ETaskResult Update() => ETaskResult.NextStep;
public override string ToString() => "Next Step"; public override string ToString() => "NextStep";
} }
internal sealed class EndAutomation : ILastTask internal sealed class EndAutomation : ILastTask
@ -145,6 +176,6 @@ internal static class WaitAtEnd
public ETaskResult Update() => ETaskResult.End; public ETaskResult Update() => ETaskResult.End;
public override string ToString() => "End automation"; public override string ToString() => "EndAutomation";
} }
} }

View File

@ -32,7 +32,10 @@ internal sealed class MountTask(
} }
if (gameFunctions.HasStatusPreventingSprintOrMount()) if (gameFunctions.HasStatusPreventingSprintOrMount())
{
logger.LogInformation("Can't mount due to status preventing sprint or mount");
return false; return false;
}
logger.LogInformation("Step wants a mount, trying to mount in territory {Id}...", _territoryId); logger.LogInformation("Step wants a mount, trying to mount in territory {Id}...", _territoryId);
if (!condition[ConditionFlag.InCombat]) if (!condition[ConditionFlag.InCombat])
@ -48,6 +51,12 @@ internal sealed class MountTask(
{ {
if (!_mountTriggered) if (!_mountTriggered)
{ {
if (gameFunctions.HasStatusPreventingSprintOrMount())
{
logger.LogInformation("Can't mount due to status preventing sprint or mount");
return ETaskResult.TaskComplete;
}
_mountTriggered = gameFunctions.Mount(); _mountTriggered = gameFunctions.Mount();
return ETaskResult.StillRunning; return ETaskResult.StillRunning;
} }

View File

@ -24,7 +24,7 @@ internal static class Combat
ArgumentNullException.ThrowIfNull(step.DataId); ArgumentNullException.ThrowIfNull(step.DataId);
var task = serviceProvider.GetRequiredService<Interact.DoInteract>() var task = serviceProvider.GetRequiredService<Interact.DoInteract>()
.With(step.DataId.Value); .With(step.DataId.Value, true);
return [unmount, task]; return [unmount, task];
} }
else if (step.EnemySpawnType == EEnemySpawnType.AfterItemUse) else if (step.EnemySpawnType == EEnemySpawnType.AfterItemUse)

View File

@ -21,21 +21,25 @@ internal static class Interact
ArgumentNullException.ThrowIfNull(step.DataId); ArgumentNullException.ThrowIfNull(step.DataId);
return serviceProvider.GetRequiredService<DoInteract>().With(step.DataId.Value); return serviceProvider.GetRequiredService<DoInteract>()
.With(step.DataId.Value, step.TargetTerritoryId != null);
} }
} }
internal sealed class DoInteract(GameFunctions gameFunctions, ICondition condition, ILogger<DoInteract> logger) internal sealed class DoInteract(GameFunctions gameFunctions, ICondition condition, ILogger<DoInteract> logger)
: ITask : ITask
{ {
private bool _needsUnmount;
private bool _interacted; private bool _interacted;
private DateTime _continueAt = DateTime.MinValue; private DateTime _continueAt = DateTime.MinValue;
private uint DataId { get; set; } private uint DataId { get; set; }
private bool SkipMarkerCheck { get; set; }
public ITask With(uint dataId) public ITask With(uint dataId, bool skipMarkerCheck)
{ {
DataId = dataId; DataId = dataId;
SkipMarkerCheck = skipMarkerCheck;
return this; return this;
} }
@ -51,6 +55,7 @@ internal static class Interact
// this is only relevant for followers on quests // this is only relevant for followers on quests
if (!gameObject.IsTargetable && condition[ConditionFlag.Mounted]) if (!gameObject.IsTargetable && condition[ConditionFlag.Mounted])
{ {
_needsUnmount = true;
gameFunctions.Unmount(); gameFunctions.Unmount();
_continueAt = DateTime.Now.AddSeconds(0.5); _continueAt = DateTime.Now.AddSeconds(0.5);
return true; return true;
@ -71,6 +76,18 @@ internal static class Interact
if (DateTime.Now <= _continueAt) if (DateTime.Now <= _continueAt)
return ETaskResult.StillRunning; return ETaskResult.StillRunning;
if (_needsUnmount)
{
if (condition[ConditionFlag.Mounted])
{
gameFunctions.Unmount();
_continueAt = DateTime.Now.AddSeconds(0.5);
return ETaskResult.StillRunning;
}
else
_needsUnmount = false;
}
if (!_interacted) if (!_interacted)
{ {
GameObject? gameObject = gameFunctions.FindObjectByDataId(DataId); GameObject? gameObject = gameFunctions.FindObjectByDataId(DataId);
@ -87,7 +104,7 @@ internal static class Interact
private unsafe bool HasAnyMarker(GameObject gameObject) private unsafe bool HasAnyMarker(GameObject gameObject)
{ {
if (gameObject.ObjectKind != ObjectKind.EventNpc) if (SkipMarkerCheck || gameObject.ObjectKind != ObjectKind.EventNpc)
return true; return true;
var gameObjectStruct = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address; var gameObjectStruct = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address;

View File

@ -54,7 +54,7 @@ internal sealed class DalamudInitializer : IDisposable
} }
catch (MovementController.PathfindingFailedException) catch (MovementController.PathfindingFailedException)
{ {
_questController.Stop(); _questController.Stop("Pathfinding failed");
} }
} }

View File

@ -470,17 +470,18 @@ internal sealed unsafe class GameFunctions
public bool Unmount() public bool Unmount()
{ {
if (!_condition[ConditionFlag.Mounted]) if (!_condition[ConditionFlag.Mounted])
return false; return true;
if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23) == 0) if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23) == 0)
{ {
_logger.LogInformation("Unmounting..."); _logger.LogInformation("Unmounting...");
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23); return ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23);
} }
else else
{
_logger.LogWarning("Can't unmount right now?"); _logger.LogWarning("Can't unmount right now?");
return false;
return true; }
} }
public void OpenDutyFinder(uint contentFinderConditionId) public void OpenDutyFinder(uint contentFinderConditionId)

View File

@ -4,6 +4,7 @@ using System.Globalization;
using System.Numerics; using System.Numerics;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components; using Dalamud.Interface.Components;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Plugin; using Dalamud.Plugin;
@ -16,6 +17,7 @@ using ImGuiNET;
using LLib.ImGui; using LLib.ImGui;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Controller.Steps.BaseFactory;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.V1; using Questionable.Model.V1;
@ -85,6 +87,7 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig, IDisposab
ImGui.Separator(); ImGui.Separator();
DrawQuickAccessButtons(); DrawQuickAccessButtons();
DrawRemainingTasks();
} }
private unsafe void DrawQuest() private unsafe void DrawQuest()
@ -141,14 +144,25 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig, IDisposab
if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop)) if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop))
{ {
_movementController.Stop(); _movementController.Stop();
_questController.Stop(); _questController.Stop("Manual");
} }
QuestStep? currentStep = currentQuest.Quest
.FindSequence(currentQuest.Sequence)
?.FindStep(currentQuest.Step);
bool colored = currentStep != null && currentStep.InteractionType == EInteractionType.Instruction
&& _questController.HasCurrentTaskMatching<WaitAtEnd.WaitNextStepOrSequence>();
if (colored)
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.ArrowCircleRight, "Skip")) if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.ArrowCircleRight, "Skip"))
{ {
_questController.Stop(); _movementController.Stop();
_questController.Stop("Manual");
_questController.IncreaseStepCount(); _questController.IncreaseStepCount();
} }
if (colored)
ImGui.PopStyleColor();
} }
else else
ImGui.Text("No active quest"); ImGui.Text("No active quest");
@ -278,7 +292,7 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig, IDisposab
if (ImGui.Button("Stop Nav")) if (ImGui.Button("Stop Nav"))
{ {
_movementController.Stop(); _movementController.Stop();
_questController.Stop(); _questController.Stop("Manual");
} }
ImGui.EndDisabled(); ImGui.EndDisabled();
@ -289,7 +303,10 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig, IDisposab
_framework.RunOnTick(() => _gameUiController.HandleCurrentDialogueChoices(), _framework.RunOnTick(() => _gameUiController.HandleCurrentDialogueChoices(),
TimeSpan.FromMilliseconds(200)); TimeSpan.FromMilliseconds(200));
} }
}
private void DrawRemainingTasks()
{
var remainingTasks = _questController.GetRemainingTaskNames(); var remainingTasks = _questController.GetRemainingTaskNames();
if (remainingTasks.Count > 0) if (remainingTasks.Count > 0)
{ {