diff --git a/QuestPaths/7.x - Dawntrail/Raid Quests/4960_A New Challenger Appears.json b/QuestPaths/7.x - Dawntrail/Raid Quests/4960_A New Challenger Appears.json new file mode 100644 index 000000000..997d35fda --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Raid Quests/4960_A New Challenger Appears.json @@ -0,0 +1,60 @@ +{ + "$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 + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/Raid Quests/4961_The Claw in the Dark.json b/QuestPaths/7.x - Dawntrail/Raid Quests/4961_The Claw in the Dark.json new file mode 100644 index 000000000..766588cea --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Raid Quests/4961_The Claw in the Dark.json @@ -0,0 +1,52 @@ +{ + "$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 + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/Raid Quests/4962_Sweet Poison.json b/QuestPaths/7.x - Dawntrail/Raid Quests/4962_Sweet Poison.json new file mode 100644 index 000000000..79026f6a1 --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Raid Quests/4962_Sweet Poison.json @@ -0,0 +1,68 @@ +{ + "$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 + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/Raid Quests/4963_Yaana's Yarn.json b/QuestPaths/7.x - Dawntrail/Raid Quests/4963_Yaana's Yarn.json new file mode 100644 index 000000000..cc897c5df --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Raid Quests/4963_Yaana's Yarn.json @@ -0,0 +1,55 @@ +{ + "$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 + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/Raid Quests/4964_Vile Heat.json b/QuestPaths/7.x - Dawntrail/Raid Quests/4964_Vile Heat.json new file mode 100644 index 000000000..7d7164a73 --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Raid Quests/4964_Vile Heat.json @@ -0,0 +1,68 @@ +{ + "$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 + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/Raid Quests/4965_The Neoteric Witch.json b/QuestPaths/7.x - Dawntrail/Raid Quests/4965_The Neoteric Witch.json new file mode 100644 index 000000000..1cd29ff41 --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Raid Quests/4965_The Neoteric Witch.json @@ -0,0 +1,96 @@ +{ + "$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" + } + ] + } + ] +} diff --git a/Questionable/Controller/QuestRegistry.cs b/Questionable/Controller/QuestRegistry.cs index b179b8588..93d83f9dc 100644 --- a/Questionable/Controller/QuestRegistry.cs +++ b/Questionable/Controller/QuestRegistry.cs @@ -36,6 +36,7 @@ internal sealed class QuestRegistry public IEnumerable AllQuests => _quests.Values; public int Count => _quests.Count; public int ValidationIssueCount => _questValidator.IssueCount; + public int ValidationErrorCount => _questValidator.ErrorCount; public void Reload() { diff --git a/Questionable/Model/Quest.cs b/Questionable/Model/Quest.cs index 9bdf6cb52..a33bcd181 100644 --- a/Questionable/Model/Quest.cs +++ b/Questionable/Model/Quest.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using Questionable.Model.V1; namespace Questionable.Model; @@ -12,4 +13,18 @@ internal sealed class Quest public QuestSequence? FindSequence(byte currentSequence) => Root.QuestSequence.SingleOrDefault(seq => seq.Sequence == currentSequence); + + public IEnumerable 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); + } + } + } } diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index 39c370d04..58f580e3d 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -136,6 +136,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Questionable/Validation/QuestValidator.cs b/Questionable/Validation/QuestValidator.cs index 931d8786d..694ff5f4e 100644 --- a/Questionable/Validation/QuestValidator.cs +++ b/Questionable/Validation/QuestValidator.cs @@ -24,6 +24,7 @@ internal sealed class QuestValidator public IReadOnlyList Issues => _validationIssues; public int IssueCount => _validationIssues.Count; + public int ErrorCount => _validationIssues.Count(x => x.Severity == EIssueSeverity.Error); public void ClearIssues() => _validationIssues.Clear(); diff --git a/Questionable/Validation/Validators/CompletionFlagsValidator.cs b/Questionable/Validation/Validators/CompletionFlagsValidator.cs index f1ea4c029..0cf97f1d5 100644 --- a/Questionable/Validation/Validators/CompletionFlagsValidator.cs +++ b/Questionable/Validation/Validators/CompletionFlagsValidator.cs @@ -10,7 +10,7 @@ internal sealed class CompletionFlagsValidator : IQuestValidator { public IEnumerable Validate(Quest quest) { - foreach (var sequence in quest.Root.QuestSequence) + foreach (var sequence in quest.AllSequences()) { var mappedCompletionFlags = sequence.Steps .Select(x => diff --git a/Questionable/Validation/Validators/NextQuestValidator.cs b/Questionable/Validation/Validators/NextQuestValidator.cs new file mode 100644 index 000000000..891c38645 --- /dev/null +++ b/Questionable/Validation/Validators/NextQuestValidator.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Linq; +using Questionable.Model; + +namespace Questionable.Validation.Validators; + +internal sealed class NextQuestValidator : IQuestValidator +{ + public IEnumerable 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", + }; + } + } +} diff --git a/Questionable/Validation/Validators/UniqueStartStopValidator.cs b/Questionable/Validation/Validators/UniqueStartStopValidator.cs index 83c350a5d..495e026e8 100644 --- a/Questionable/Validation/Validators/UniqueStartStopValidator.cs +++ b/Questionable/Validation/Validators/UniqueStartStopValidator.cs @@ -14,12 +14,12 @@ internal sealed class UniqueStartStopValidator : IQuestValidator .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", @@ -44,12 +44,12 @@ internal sealed class UniqueStartStopValidator : IQuestValidator .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", @@ -70,16 +70,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator } } - 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); } diff --git a/Questionable/Windows/DebugOverlay.cs b/Questionable/Windows/DebugOverlay.cs index e778372d1..0d2af7be3 100644 --- a/Questionable/Windows/DebugOverlay.cs +++ b/Questionable/Windows/DebugOverlay.cs @@ -2,6 +2,7 @@ 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; @@ -18,10 +19,11 @@ internal sealed class DebugOverlay : Window 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) @@ -30,6 +32,7 @@ internal sealed class DebugOverlay : Window _questRegistry = questRegistry; _gameGui = gameGui; _clientState = clientState; + _condition = condition; _configuration = configuration; Position = Vector2.Zero; @@ -44,7 +47,8 @@ internal sealed class DebugOverlay : Window 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() diff --git a/Questionable/Windows/QuestValidationWindow.cs b/Questionable/Windows/QuestValidationWindow.cs index 2cb3f5b22..83e6d2ecf 100644 --- a/Questionable/Windows/QuestValidationWindow.cs +++ b/Questionable/Windows/QuestValidationWindow.cs @@ -3,6 +3,7 @@ using Dalamud.Interface; 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; @@ -16,11 +17,13 @@ internal sealed class QuestValidationWindow : LWindow { 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; @@ -41,8 +44,8 @@ internal sealed class QuestValidationWindow : LWindow 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(); @@ -63,7 +66,24 @@ internal sealed class QuestValidationWindow : LWindow 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); + } } } } diff --git a/Questionable/Windows/QuestWindow.cs b/Questionable/Windows/QuestWindow.cs index 92d876813..9fd5c9ec2 100644 --- a/Questionable/Windows/QuestWindow.cs +++ b/Questionable/Windows/QuestWindow.cs @@ -581,10 +581,16 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig { 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(); } }