Validation: Sum up disabled tribe quests instead of showing each one individually
authorLiza Carvelli <liza@carvel.li>
Fri, 26 Jul 2024 23:51:21 +0000 (01:51 +0200)
committerLiza Carvelli <liza@carvel.li>
Fri, 26 Jul 2024 23:51:45 +0000 (01:51 +0200)
13 files changed:
Questionable/Model/EBeastTribe.cs [new file with mode: 0644]
Questionable/Model/QuestInfo.cs
Questionable/Questionable.csproj
Questionable/Validation/EIssueType.cs [new file with mode: 0644]
Questionable/Validation/QuestValidator.cs
Questionable/Validation/ValidationIssue.cs
Questionable/Validation/Validators/BasicSequenceValidator.cs
Questionable/Validation/Validators/CompletionFlagsValidator.cs
Questionable/Validation/Validators/JsonSchemaValidator.cs
Questionable/Validation/Validators/NextQuestValidator.cs
Questionable/Validation/Validators/QuestDisabledValidator.cs
Questionable/Validation/Validators/UniqueStartStopValidator.cs
Questionable/Windows/QuestValidationWindow.cs

diff --git a/Questionable/Model/EBeastTribe.cs b/Questionable/Model/EBeastTribe.cs
new file mode 100644 (file)
index 0000000..e26fb67
--- /dev/null
@@ -0,0 +1,28 @@
+using System.Diagnostics.CodeAnalysis;
+using JetBrains.Annotations;
+
+namespace Questionable.Model;
+
+[SuppressMessage("Design", "CA1028", Justification = "Game type")]
+[UsedImplicitly(ImplicitUseTargetFlags.Members)]
+public enum EBeastTribe : byte
+{
+    None = 0,
+    Amaljaa = 1,
+    Sylphs = 2,
+    Kobolds = 3,
+    Sahagin = 4,
+    Ixal = 5,
+    VanuVanu = 6,
+    Vath = 7,
+    Moogles = 8,
+    Kojin = 9,
+    Ananta = 10,
+    Namazu = 11,
+    Pixies = 12,
+    Qitari = 13,
+    Dwarves = 14,
+    Arkasodara = 15,
+    Omicrons = 16,
+    Loporrits = 17,
+}
index db4ded12c94e4829d9363c907e43b7f4db94d75c..9caaef123015e0009d24925dffaba1e84f849e25 100644 (file)
@@ -27,6 +27,7 @@ internal sealed class QuestInfo
         PreviousInstanceContent = quest.InstanceContent.Select(x => (ushort)x.Row).Where(x => x != 0).ToList();
         PreviousInstanceContentJoin = (QuestJoin)quest.InstanceContentJoin;
         GrandCompany = (GrandCompany)quest.GrandCompany.Row;
+        BeastTribe = (EBeastTribe)quest.BeastTribe.Row;
     }
 
 
@@ -44,6 +45,7 @@ internal sealed class QuestInfo
     public bool IsMainScenarioQuest { get; }
     public bool CompletesInstantly { get; }
     public GrandCompany GrandCompany { get; }
+    public EBeastTribe BeastTribe { get; }
 
     public string SimplifiedName => Name
         .TrimStart(SeIconChar.QuestSync.ToIconChar(), SeIconChar.QuestRepeatable.ToIconChar(), ' ');
index e25f2e49e22344630780ecddb53a9cf50187d277..d3dcfe2dbd2b2b9144e2bcb30913d44c6f8dbf8a 100644 (file)
@@ -1,6 +1,6 @@
 <Project Sdk="Dalamud.NET.Sdk/9.0.2">
     <PropertyGroup>
-        <Version>1.18</Version>
+        <Version>1.19</Version>
         <OutputPath>dist</OutputPath>
         <PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
         <Platforms>x64</Platforms>
diff --git a/Questionable/Validation/EIssueType.cs b/Questionable/Validation/EIssueType.cs
new file mode 100644 (file)
index 0000000..a6ff28b
--- /dev/null
@@ -0,0 +1,18 @@
+namespace Questionable.Validation;
+
+public enum EIssueType
+{
+    None,
+    InvalidJsonSchema,
+    MissingSequence0,
+    MissingSequence,
+    DuplicateSequence,
+    MissingQuestAccept,
+    MissingQuestComplete,
+    InstantQuestWithMultipleSteps,
+    DuplicateCompletionFlags,
+    InvalidNextQuestId,
+    QuestDisabled,
+    UnexpectedAcceptQuestStep,
+    UnexpectedCompleteQuestStep,
+}
index d53a5b52c601434d208abfb703acfcb90decaa36..794324f9e787a0382c885672419907356468199e 100644 (file)
@@ -41,6 +41,7 @@ internal sealed class QuestValidator
         {
             try
             {
+                Dictionary<EBeastTribe, int> disabledTribeQuests = new();
                 foreach (var quest in quests)
                 {
                     foreach (var validator in _validators)
@@ -53,7 +54,13 @@ internal sealed class QuestValidator
                             _logger.Log(level,
                                 "Validation failed: {QuestId} ({QuestName}) / {QuestSequence} / {QuestStep} - {Description}",
                                 issue.QuestId, quest.Info.Name, issue.Sequence, issue.Step, issue.Description);
-                            _validationIssues.Add(issue);
+                            if (issue.Type == EIssueType.QuestDisabled && quest.Info.BeastTribe != EBeastTribe.None)
+                            {
+                                disabledTribeQuests.TryAdd(quest.Info.BeastTribe, 0);
+                                disabledTribeQuests[quest.Info.BeastTribe]++;
+                            }
+                            else
+                                _validationIssues.Add(issue);
                         }
                     }
                 }
@@ -62,6 +69,7 @@ internal sealed class QuestValidator
                     .ThenBy(x => x.Sequence)
                     .ThenBy(x => x.Step)
                     .ThenBy(x => x.Description)
+                    .Concat(DisabledTribesAsIssues(disabledTribeQuests))
                     .ToList();
             }
             catch (Exception e)
@@ -70,4 +78,20 @@ internal sealed class QuestValidator
             }
         }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
     }
+
+    private static IEnumerable<ValidationIssue> DisabledTribesAsIssues(Dictionary<EBeastTribe, int> disabledTribeQuests)
+    {
+        return disabledTribeQuests
+            .OrderBy(x => x.Key)
+            .Select(x => new ValidationIssue
+            {
+                QuestId = null,
+                Sequence = null,
+                Step = null,
+                BeastTribe = x.Key,
+                Type = EIssueType.QuestDisabled,
+                Severity = EIssueSeverity.None,
+                Description = $"{x.Value} disabled quest(s)",
+            });
+    }
 }
index 6e8427602eb818cd44a26021adbadc433aa9c2a0..5988fd5ba3f903ffbc31c727b5af7bb11da7b64e 100644 (file)
@@ -1,10 +1,14 @@
-namespace Questionable.Validation;
+using Questionable.Model;
+
+namespace Questionable.Validation;
 
 internal sealed record ValidationIssue
 {
-    public required ushort QuestId { get; init; }
+    public required ushort? QuestId { get; init; }
     public required byte? Sequence { get; init; }
     public required int? Step { get; init; }
+    public EBeastTribe BeastTribe { get; init; } = EBeastTribe.None;
+    public required EIssueType Type { get; init; }
     public required EIssueSeverity Severity { get; init; }
     public required string Description { get; init; }
 }
index c8d40a36988ec03108f80df18d97ff01f300e3d3..0c10e032808426aaf0fe188d83da0002151b6e27 100644 (file)
@@ -21,6 +21,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
                 QuestId = quest.QuestId,
                 Sequence = 0,
                 Step = null,
+                Type = EIssueType.MissingSequence0,
                 Severity = EIssueSeverity.Error,
                 Description = "Missing quest start",
             };
@@ -39,6 +40,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
                     QuestId = quest.QuestId,
                     Sequence = (byte)sequence.Sequence,
                     Step = null,
+                    Type = EIssueType.InstantQuestWithMultipleSteps,
                     Severity = EIssueSeverity.Error,
                     Description = "Instant quest should not have any sequences after the start",
                 };
@@ -74,6 +76,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
                 QuestId = quest.QuestId,
                 Sequence = (byte)sequenceNo,
                 Step = null,
+                Type = EIssueType.MissingSequence,
                 Severity = EIssueSeverity.Error,
                 Description = "Missing sequence",
             };
@@ -85,6 +88,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
                 QuestId = quest.QuestId,
                 Sequence = (byte)sequenceNo,
                 Step = null,
+                Type = EIssueType.DuplicateSequence,
                 Severity = EIssueSeverity.Error,
                 Description = "Duplicate sequence",
             };
index 0cf97f1d55669172c67280d5106b68ec81cde1c7..462e928957d762ba6c32ecd0fe0710e4d58b41fa 100644 (file)
@@ -44,6 +44,7 @@ internal sealed class CompletionFlagsValidator : IQuestValidator
                         QuestId = quest.QuestId,
                         Sequence = (byte)sequence.Sequence,
                         Step = i,
+                        Type = EIssueType.DuplicateCompletionFlags,
                         Severity = EIssueSeverity.Error,
                         Description = $"Duplicate completion flags: {string.Join(", ", sequence.Steps[i].CompletionQuestVariablesFlags)}",
                     };
index 04abc0127ba960ca80894f46455ad247522fbc15..b3ed6f4ec8f3d822bd5698a64f9043a5ad9fe986 100644 (file)
@@ -30,6 +30,7 @@ internal sealed class JsonSchemaValidator : IQuestValidator
                     QuestId = quest.QuestId,
                     Sequence = null,
                     Step = null,
+                    Type = EIssueType.InvalidJsonSchema,
                     Severity = EIssueSeverity.Error,
                     Description = "JSON Validation failed"
                 };
index 891c38645efddae0037babb0ad2b3e96d4a491ad..325d8fbfa7198466653c36aacda1be34e9e22458 100644 (file)
@@ -15,6 +15,7 @@ internal sealed class NextQuestValidator : IQuestValidator
                 QuestId = quest.QuestId,
                 Sequence = (byte)invalidNextQuest.Sequence.Sequence,
                 Step = invalidNextQuest.StepId,
+                Type = EIssueType.InvalidNextQuestId,
                 Severity = EIssueSeverity.Error,
                 Description = "Next quest should not reference itself",
             };
index 096fd37574fa54a430162e8e57f669d4b1194394..298b34239201b0a85f35dcb5dfd7fbc0809968dc 100644 (file)
@@ -14,6 +14,7 @@ internal sealed class QuestDisabledValidator : IQuestValidator
                 QuestId = quest.QuestId,
                 Sequence = null,
                 Step = null,
+                Type = EIssueType.QuestDisabled,
                 Severity = EIssueSeverity.None,
                 Description = "Quest is disabled",
             };
index 678e3500fc2c152c07ef0229d13106b6e1881949..b95522f1f85a7dd9ace112960f9ec3972a7ab947 100644 (file)
@@ -21,6 +21,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
                     QuestId = quest.QuestId,
                     Sequence = (byte)accept.Sequence.Sequence,
                     Step = accept.StepId,
+                    Type = EIssueType.UnexpectedAcceptQuestStep,
                     Severity = EIssueSeverity.Error,
                     Description = "Unexpected AcceptQuest step",
                 };
@@ -34,6 +35,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
                 QuestId = quest.QuestId,
                 Sequence = 0,
                 Step = null,
+                Type = EIssueType.MissingQuestAccept,
                 Severity = EIssueSeverity.Error,
                 Description = "No AcceptQuest step",
             };
@@ -51,6 +53,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
                     QuestId = quest.QuestId,
                     Sequence = (byte)complete.Sequence.Sequence,
                     Step = complete.StepId,
+                    Type = EIssueType.UnexpectedCompleteQuestStep,
                     Severity = EIssueSeverity.Error,
                     Description = "Unexpected CompleteQuest step",
                 };
@@ -64,6 +67,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
                 QuestId = quest.QuestId,
                 Sequence = 255,
                 Step = null,
+                Type = EIssueType.MissingQuestComplete,
                 Severity = EIssueSeverity.Error,
                 Description = "No CompleteQuest step",
             };
index 83e6d2ecf471e71f1b009005ca5bac29bc183ea4..be931d6c7661f66bc67c080d8950f23f2f97690c 100644 (file)
@@ -19,7 +19,9 @@ internal sealed class QuestValidationWindow : LWindow
     private readonly QuestData _questData;
     private readonly IDalamudPluginInterface _pluginInterface;
 
-    public QuestValidationWindow(QuestValidator questValidator, QuestData questData, IDalamudPluginInterface pluginInterface) : base("Quest Validation###QuestionableValidator")
+    public QuestValidationWindow(QuestValidator questValidator, QuestData questData,
+        IDalamudPluginInterface pluginInterface)
+        : base("Quest Validation###QuestionableValidator")
     {
         _questValidator = questValidator;
         _questData = questData;
@@ -54,10 +56,12 @@ internal sealed class QuestValidationWindow : LWindow
             ImGui.TableNextRow();
 
             if (ImGui.TableNextColumn())
-                ImGui.TextUnformatted(validationIssue.QuestId.ToString(CultureInfo.InvariantCulture));
+                ImGui.TextUnformatted(validationIssue.QuestId?.ToString(CultureInfo.InvariantCulture) ?? string.Empty);
 
             if (ImGui.TableNextColumn())
-                ImGui.TextUnformatted(_questData.GetQuestInfo(validationIssue.QuestId).Name);
+                ImGui.TextUnformatted(validationIssue.QuestId != null
+                    ? _questData.GetQuestInfo(validationIssue.QuestId.Value).Name
+                    : validationIssue.BeastTribe.ToString());
 
             if (ImGui.TableNextColumn())
                 ImGui.TextUnformatted(validationIssue.Sequence?.ToString(CultureInfo.InvariantCulture) ?? string.Empty);
@@ -81,6 +85,7 @@ internal sealed class QuestValidationWindow : LWindow
                         ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString());
                     }
                 }
+
                 ImGui.SameLine();
                 ImGui.TextUnformatted(validationIssue.Description);
             }