--- /dev/null
+{
+ "$schema": "https://carvel.li/questionable/quest-1.0",
+ "Author": "liza",
+ "QuestSequence": [
+ {
+ "Sequence": 0,
+ "Steps": [
+ {
+ "DataId": 1049786,
+ "Position": {
+ "X": 340.13867,
+ "Y": 50.75,
+ "Z": 231.37244
+ },
+ "TerritoryId": 1186,
+ "InteractionType": "AcceptQuest"
+ }
+ ]
+ },
+ {
+ "Sequence": 1,
+ "Steps": [
+ {
+ "DataId": 1049787,
+ "Position": {
+ "X": 364.3396,
+ "Y": 60.125,
+ "Z": 357.1068
+ },
+ "TerritoryId": 1186,
+ "InteractionType": "Interact",
+ "DialogueChoices": [
+ {
+ "Type": "YesNo",
+ "Prompt": "TEXT_KINGRA101_04960_SYSTEM_100_030",
+ "Yes": true
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "Sequence": 255,
+ "Steps": [
+ {
+ "DataId": 1049788,
+ "Position": {
+ "X": 1.6021729,
+ "Y": 0,
+ "Z": -6.088379
+ },
+ "StopDistance": 5,
+ "TerritoryId": 1224,
+ "InteractionType": "CompleteQuest",
+ "NextQuestId": 4961
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$schema": "https://carvel.li/questionable/quest-1.0",
+ "Author": "liza",
+ "TerritoryBlacklist": [
+ 1225
+ ],
+ "QuestSequence": [
+ {
+ "Sequence": 0,
+ "Steps": [
+ {
+ "DataId": 1049788,
+ "Position": {
+ "X": 1.6021729,
+ "Y": 0,
+ "Z": -6.088379
+ },
+ "StopDistance": 5,
+ "TerritoryId": 1224,
+ "InteractionType": "AcceptQuest"
+ }
+ ]
+ },
+ {
+ "Sequence": 1,
+ "Steps": [
+ {
+ "TerritoryId": 1224,
+ "InteractionType": "Duty",
+ "ContentFinderConditionId": 985
+ }
+ ]
+ },
+ {
+ "Sequence": 255,
+ "Steps": [
+ {
+ "DataId": 1050476,
+ "Position": {
+ "X": 0.1373291,
+ "Y": -3.3667622E-13,
+ "Z": -9.658997
+ },
+ "StopDistance": 7,
+ "TerritoryId": 1224,
+ "InteractionType": "CompleteQuest",
+ "NextQuestId": 4962
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$schema": "https://carvel.li/questionable/quest-1.0",
+ "Author": "liza",
+ "TerritoryBlacklist": [
+ 1227
+ ],
+ "QuestSequence": [
+ {
+ "Sequence": 0,
+ "Steps": [
+ {
+ "DataId": 1050476,
+ "Position": {
+ "X": 0.1373291,
+ "Y": -3.3667622E-13,
+ "Z": -9.658997
+ },
+ "StopDistance": 7,
+ "TerritoryId": 1224,
+ "InteractionType": "AcceptQuest"
+ }
+ ]
+ },
+ {
+ "Sequence": 1,
+ "Steps": [
+ {
+ "TerritoryId": 1224,
+ "InteractionType": "Duty",
+ "ContentFinderConditionId": 987
+ }
+ ]
+ },
+ {
+ "Sequence": 2,
+ "Steps": [
+ {
+ "DataId": 1050476,
+ "Position": {
+ "X": 0.1373291,
+ "Y": -3.3667622E-13,
+ "Z": -9.658997
+ },
+ "StopDistance": 7,
+ "TerritoryId": 1224,
+ "InteractionType": "Interact"
+ }
+ ]
+ },
+ {
+ "Sequence": 255,
+ "Steps": [
+ {
+ "DataId": 1050477,
+ "Position": {
+ "X": 1.663208,
+ "Y": -1.9688797E-12,
+ "Z": -10.727112
+ },
+ "StopDistance": 7,
+ "TerritoryId": 1224,
+ "InteractionType": "CompleteQuest",
+ "NextQuestId": 4963
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$schema": "https://carvel.li/questionable/quest-1.0",
+ "Author": "liza",
+ "QuestSequence": [
+ {
+ "Sequence": 0,
+ "Steps": [
+ {
+ "DataId": 1050477,
+ "Position": {
+ "X": 1.663208,
+ "Y": -1.9688797E-12,
+ "Z": -10.727112
+ },
+ "StopDistance": 7,
+ "TerritoryId": 1224,
+ "InteractionType": "AcceptQuest"
+ }
+ ]
+ },
+ {
+ "Sequence": 1,
+ "Steps": [
+ {
+ "DataId": 1049790,
+ "Position": {
+ "X": 494.7433,
+ "Y": 59.55,
+ "Z": 125.10864
+ },
+ "StopDistance": 5,
+ "TerritoryId": 1186,
+ "InteractionType": "Interact"
+ }
+ ]
+ },
+ {
+ "Sequence": 255,
+ "Steps": [
+ {
+ "DataId": 1049789,
+ "Position": {
+ "X": 2.3651123,
+ "Y": -4.334177E-12,
+ "Z": -14.206177
+ },
+ "StopDistance": 7,
+ "TerritoryId": 1224,
+ "InteractionType": "CompleteQuest",
+ "NextQuestId": 4964
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$schema": "https://carvel.li/questionable/quest-1.0",
+ "Author": "liza",
+ "TerritoryBlacklist": [
+ 1229
+ ],
+ "QuestSequence": [
+ {
+ "Sequence": 0,
+ "Steps": [
+ {
+ "DataId": 1049789,
+ "Position": {
+ "X": 2.3651123,
+ "Y": -4.334177E-12,
+ "Z": -14.206177
+ },
+ "StopDistance": 7,
+ "TerritoryId": 1224,
+ "InteractionType": "AcceptQuest"
+ }
+ ]
+ },
+ {
+ "Sequence": 1,
+ "Steps": [
+ {
+ "TerritoryId": 1224,
+ "InteractionType": "Duty",
+ "ContentFinderConditionId": 989
+ }
+ ]
+ },
+ {
+ "Sequence": 2,
+ "Steps": [
+ {
+ "DataId": 1050477,
+ "Position": {
+ "X": 1.663208,
+ "Y": -1.9688797E-12,
+ "Z": -10.727112
+ },
+ "StopDistance": 7,
+ "TerritoryId": 1224,
+ "InteractionType": "Interact"
+ }
+ ]
+ },
+ {
+ "Sequence": 255,
+ "Steps": [
+ {
+ "DataId": 1050477,
+ "Position": {
+ "X": 1.663208,
+ "Y": -1.9688797E-12,
+ "Z": -10.727112
+ },
+ "StopDistance": 7,
+ "TerritoryId": 1224,
+ "InteractionType": "CompleteQuest",
+ "NextQuestId": 4965
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$schema": "https://carvel.li/questionable/quest-1.0",
+ "Author": "liza",
+ "TerritoryBlacklist": [
+ 1231
+ ],
+ "QuestSequence": [
+ {
+ "Sequence": 0,
+ "Steps": [
+ {
+ "DataId": 1049792,
+ "Position": {
+ "X": 1.663208,
+ "Y": -1.9688797E-12,
+ "Z": -10.727112
+ },
+ "StopDistance": 7,
+ "TerritoryId": 1224,
+ "InteractionType": "AcceptQuest"
+ }
+ ]
+ },
+ {
+ "Sequence": 1,
+ "Steps": [
+ {
+ "TerritoryId": 1224,
+ "InteractionType": "Duty",
+ "ContentFinderConditionId": 991
+ }
+ ]
+ },
+ {
+ "Sequence": 2,
+ "Steps": [
+ {
+ "DataId": 1050477,
+ "Position": {
+ "X": 1.663208,
+ "Y": -1.9688797E-12,
+ "Z": -10.727112
+ },
+ "StopDistance": 7,
+ "TerritoryId": 1224,
+ "InteractionType": "Interact"
+ }
+ ]
+ },
+ {
+ "Sequence": 3,
+ "Steps": [
+ {
+ "DataId": 2013722,
+ "Position": {
+ "X": -0.07635498,
+ "Y": 1.0527954,
+ "Z": 8.102478
+ },
+ "TerritoryId": 1224,
+ "InteractionType": "Interact",
+ "TargetTerritoryId": 1186
+ },
+ {
+ "DataId": 1049790,
+ "Position": {
+ "X": 494.7433,
+ "Y": 59.55,
+ "Z": 125.10864
+ },
+ "TerritoryId": 1186,
+ "InteractionType": "Interact",
+ "AethernetShortcut": [
+ "[Solution Nine] The Arcadion",
+ "[Solution Nine] True Vue"
+ ]
+ }
+ ]
+ },
+ {
+ "Sequence": 255,
+ "Steps": [
+ {
+ "DataId": 1049790,
+ "Position": {
+ "X": 494.7433,
+ "Y": 59.55,
+ "Z": 125.10864
+ },
+ "TerritoryId": 1186,
+ "InteractionType": "CompleteQuest"
+ }
+ ]
+ }
+ ]
+}
public IEnumerable<Quest> AllQuests => _quests.Values;
public int Count => _quests.Count;
public int ValidationIssueCount => _questValidator.IssueCount;
+ public int ValidationErrorCount => _questValidator.ErrorCount;
public void Reload()
{
-using System.Linq;
+using System.Collections.Generic;
+using System.Linq;
using Questionable.Model.V1;
namespace Questionable.Model;
public QuestSequence? FindSequence(byte currentSequence)
=> Root.QuestSequence.SingleOrDefault(seq => seq.Sequence == currentSequence);
+
+ public IEnumerable<QuestSequence> AllSequences() => Root.QuestSequence;
+
+ public IEnumerable<(QuestSequence Sequence, int StepId, QuestStep Step)> AllSteps()
+ {
+ foreach (var sequence in Root.QuestSequence)
+ {
+ for (int i = 0; i < sequence.Steps.Count; ++i)
+ {
+ var step = sequence.Steps[i];
+ yield return (sequence, i, step);
+ }
+ }
+ }
}
serviceCollection.AddSingleton<IQuestValidator, QuestDisabledValidator>();
serviceCollection.AddSingleton<IQuestValidator, BasicSequenceValidator>();
serviceCollection.AddSingleton<IQuestValidator, UniqueStartStopValidator>();
+ serviceCollection.AddSingleton<IQuestValidator, NextQuestValidator>();
serviceCollection.AddSingleton<IQuestValidator, CompletionFlagsValidator>();
serviceCollection.AddSingleton<CommandHandler>();
public IReadOnlyList<ValidationIssue> Issues => _validationIssues;
public int IssueCount => _validationIssues.Count;
+ public int ErrorCount => _validationIssues.Count(x => x.Severity == EIssueSeverity.Error);
public void ClearIssues() => _validationIssues.Clear();
{
public IEnumerable<ValidationIssue> Validate(Quest quest)
{
- foreach (var sequence in quest.Root.QuestSequence)
+ foreach (var sequence in quest.AllSequences())
{
var mappedCompletionFlags = sequence.Steps
.Select(x =>
--- /dev/null
+using System.Collections.Generic;
+using System.Linq;
+using Questionable.Model;
+
+namespace Questionable.Validation.Validators;
+
+internal sealed class NextQuestValidator : IQuestValidator
+{
+ public IEnumerable<ValidationIssue> Validate(Quest quest)
+ {
+ foreach (var invalidNextQuest in quest.AllSteps().Where(x => x.Step.NextQuestId == quest.QuestId))
+ {
+ yield return new ValidationIssue
+ {
+ QuestId = quest.QuestId,
+ Sequence = (byte)invalidNextQuest.Sequence.Sequence,
+ Step = invalidNextQuest.StepId,
+ Severity = EIssueSeverity.Error,
+ Description = "Next quest should not reference itself",
+ };
+ }
+ }
+}
.ToList();
foreach (var accept in questAccepts)
{
- if (accept.SequenceId != 0 || accept.StepId != quest.FindSequence(0)!.Steps.Count - 1)
+ if (accept.Sequence.Sequence != 0 || accept.StepId != quest.FindSequence(0)!.Steps.Count - 1)
{
yield return new ValidationIssue
{
QuestId = quest.QuestId,
- Sequence = (byte)accept.SequenceId,
+ Sequence = (byte)accept.Sequence.Sequence,
Step = accept.StepId,
Severity = EIssueSeverity.Error,
Description = "Unexpected AcceptQuest step",
.ToList();
foreach (var complete in questCompletes)
{
- if (complete.SequenceId != 255 || complete.StepId != quest.FindSequence(255)!.Steps.Count - 1)
+ if (complete.Sequence.Sequence != 255 || complete.StepId != quest.FindSequence(255)!.Steps.Count - 1)
{
yield return new ValidationIssue
{
QuestId = quest.QuestId,
- Sequence = (byte)complete.SequenceId,
+ Sequence = (byte)complete.Sequence.Sequence,
Step = complete.StepId,
Severity = EIssueSeverity.Error,
Description = "Unexpected CompleteQuest step",
}
}
- private static IEnumerable<(int SequenceId, int StepId, QuestStep Step)> FindQuestStepsWithInteractionType(Quest quest, EInteractionType interactionType)
- {
- foreach (var sequence in quest.Root.QuestSequence)
- {
- for (int i = 0; i < sequence.Steps.Count; ++i)
- {
- var step = sequence.Steps[i];
- if (step.InteractionType == interactionType)
- yield return (sequence.Sequence, i, step);
- }
- }
- }
+ private static IEnumerable<(QuestSequence Sequence, int StepId, QuestStep Step)> FindQuestStepsWithInteractionType(
+ Quest quest, EInteractionType interactionType)
+ => quest.AllSteps().Where(x => x.Step.InteractionType == interactionType);
}
using System.Globalization;
using System.Linq;
using System.Numerics;
+using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services;
private readonly QuestRegistry _questRegistry;
private readonly IGameGui _gameGui;
private readonly IClientState _clientState;
+ private readonly ICondition _condition;
private readonly Configuration _configuration;
public DebugOverlay(QuestController questController, QuestRegistry questRegistry, IGameGui gameGui,
- IClientState clientState, Configuration configuration)
+ IClientState clientState, ICondition condition, Configuration configuration)
: base("Questionable Debug Overlay###QuestionableDebugOverlay",
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoBackground |
ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoSavedSettings, true)
_questRegistry = questRegistry;
_gameGui = gameGui;
_clientState = clientState;
+ _condition = condition;
_configuration = configuration;
Position = Vector2.Zero;
public override bool DrawConditions()
{
return _configuration.Advanced.DebugOverlay && _clientState is
- { IsLoggedIn: true, LocalPlayer: not null, IsPvPExcludingDen: false };
+ { IsLoggedIn: true, LocalPlayer: not null, IsPvPExcludingDen: false } &&
+ !_condition[ConditionFlag.OccupiedInCutSceneEvent];
}
public override void PreDraw()
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Utility.Raii;
+using Dalamud.Plugin;
using FFXIVClientStructs.FFXIV.Common.Math;
using ImGuiNET;
using LLib.ImGui;
{
private readonly QuestValidator _questValidator;
private readonly QuestData _questData;
+ private readonly IDalamudPluginInterface _pluginInterface;
- public QuestValidationWindow(QuestValidator questValidator, QuestData questData) : base("Quest Validation###QuestionableValidator")
+ public QuestValidationWindow(QuestValidator questValidator, QuestData questData, IDalamudPluginInterface pluginInterface) : base("Quest Validation###QuestionableValidator")
{
_questValidator = questValidator;
_questData = questData;
+ _pluginInterface = pluginInterface;
Size = new Vector2(600, 200);
SizeCondition = ImGuiCond.Once;
ImGui.TableSetupColumn("Quest", ImGuiTableColumnFlags.WidthFixed, 50);
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, 200);
- ImGui.TableSetupColumn("Sq", ImGuiTableColumnFlags.WidthFixed, 30);
- ImGui.TableSetupColumn("Sp", ImGuiTableColumnFlags.WidthFixed, 30);
+ ImGui.TableSetupColumn("Seq", ImGuiTableColumnFlags.WidthFixed, 30);
+ ImGui.TableSetupColumn("Step", ImGuiTableColumnFlags.WidthFixed, 30);
ImGui.TableSetupColumn("Issue", ImGuiTableColumnFlags.None, 200);
ImGui.TableHeadersRow();
ImGui.TextUnformatted(validationIssue.Step?.ToString(CultureInfo.InvariantCulture) ?? string.Empty);
if (ImGui.TableNextColumn())
+ {
+ // ReSharper disable once UnusedVariable
+ using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
+ {
+ if (validationIssue.Severity == EIssueSeverity.Error)
+ {
+ using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
+ ImGui.TextUnformatted(FontAwesomeIcon.TimesCircle.ToIconString());
+ }
+ else
+ {
+ using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedBlue);
+ ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString());
+ }
+ }
+ ImGui.SameLine();
ImGui.TextUnformatted(validationIssue.Description);
+ }
}
}
}
{
ImGui.SameLine();
- using var textColor = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
+ bool colored = _questRegistry.ValidationErrorCount > 0;
+ if (colored)
+ ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
+
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Flag,
$"{_questRegistry.ValidationIssueCount}"))
_questValidationWindow.IsOpen = true;
+
+ if (colored)
+ ImGui.PopStyleColor();
}
}