Migrate questIds to IId
authorLiza Carvelli <liza@carvel.li>
Sat, 3 Aug 2024 18:30:18 +0000 (20:30 +0200)
committerLiza Carvelli <liza@carvel.li>
Sat, 3 Aug 2024 18:30:18 +0000 (20:30 +0200)
34 files changed:
QuestPathGenerator/QuestSourceGenerator.cs
QuestPathGenerator/RoslynShortcuts.cs
QuestPaths/6.x - Endwalker/4807_DebugGathering.json [deleted file]
QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4159_The Culture of Love.json
Questionable.Model/Questing/Converter/IdConverter.cs [new file with mode: 0644]
Questionable.Model/Questing/IId.cs [new file with mode: 0644]
Questionable.Model/Questing/QuestStep.cs
Questionable.Model/Questing/SkipStepConditions.cs
Questionable/Controller/CombatController.cs
Questionable/Controller/CommandHandler.cs
Questionable/Controller/GameUiController.cs
Questionable/Controller/QuestController.cs
Questionable/Controller/QuestRegistry.cs
Questionable/Controller/Steps/Common/NextQuest.cs
Questionable/Controller/Steps/ILastTask.cs
Questionable/Controller/Steps/Interactions/Combat.cs
Questionable/Controller/Steps/Interactions/UseItem.cs
Questionable/Controller/Steps/Shared/SkipCondition.cs
Questionable/Controller/Steps/Shared/WaitAtEnd.cs
Questionable/Data/JournalData.cs
Questionable/Data/QuestData.cs
Questionable/GameFunctions.cs
Questionable/Model/Quest.cs
Questionable/Model/QuestInfo.cs
Questionable/Validation/ValidationIssue.cs
Questionable/Validation/Validators/AethernetShortcutValidator.cs
Questionable/Validation/Validators/JsonSchemaValidator.cs
Questionable/Windows/DebugOverlay.cs
Questionable/Windows/JournalProgressWindow.cs
Questionable/Windows/QuestComponents/ARealmRebornComponent.cs
Questionable/Windows/QuestComponents/ActiveQuestComponent.cs
Questionable/Windows/QuestSelectionWindow.cs
Questionable/Windows/QuestValidationWindow.cs
Questionable/Windows/UiUtils.cs

index b6cccbaba0b4cd23e74bdda5112ff7f38b8ae530..5060d3b44333e538f3cefa6922d18849b124a531 100644 (file)
@@ -162,6 +162,7 @@ public class QuestSourceGenerator : ISourceGenerator
                             SyntaxNodeList(
                                 AssignmentList(nameof(QuestRoot.Author), quest.Author)
                                     .AsSyntaxNodeOrToken(),
+                                Assignment(nameof(QuestRoot.Disabled), quest.Disabled, false).AsSyntaxNodeOrToken(),
                                 Assignment(nameof(QuestRoot.Comment), quest.Comment, null)
                                     .AsSyntaxNodeOrToken(),
                                 AssignmentList(nameof(QuestRoot.TerritoryBlacklist),
@@ -304,7 +305,8 @@ public class QuestSourceGenerator : ISourceGenerator
                                             Assignment(nameof(QuestStep.ContentFinderConditionId),
                                                     step.ContentFinderConditionId, emptyStep.ContentFinderConditionId)
                                                 .AsSyntaxNodeOrToken(),
-                                            Assignment(nameof(QuestStep.SkipConditions), step.SkipConditions, emptyStep.SkipConditions)
+                                            Assignment(nameof(QuestStep.SkipConditions), step.SkipConditions,
+                                                    emptyStep.SkipConditions)
                                                 .AsSyntaxNodeOrToken(),
                                             AssignmentList(nameof(QuestStep.RequiredQuestVariables),
                                                     step.RequiredQuestVariables)
index 07b8291276d9d9bad5bdf36ea692f00f2fd2e0c4..7e2054ee083f74f79b3918e1c2eabbcc1e2ba912 100644 (file)
@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Linq;
 using System.Numerics;
 using Microsoft.CodeAnalysis;
@@ -57,6 +56,24 @@ public static class RoslynShortcuts
                     SyntaxKind.SimpleMemberAccessExpression,
                     IdentifierName(value.GetType().Name),
                     IdentifierName(value.GetType().GetEnumName(value)!));
+            else if (value is QuestId questId)
+            {
+                return ObjectCreationExpression(
+                        IdentifierName(nameof(QuestId)))
+                    .WithArgumentList(
+                        ArgumentList(
+                            SingletonSeparatedList(
+                                Argument(LiteralValue(questId.Value)))));
+            }
+            else if (value is LeveId leveId)
+            {
+                return ObjectCreationExpression(
+                        IdentifierName(nameof(LeveId)))
+                    .WithArgumentList(
+                        ArgumentList(
+                            SingletonSeparatedList(
+                                Argument(LiteralValue(leveId.Value)))));
+            }
             else if (value is Vector3 vector)
             {
                 return ObjectCreationExpression(
diff --git a/QuestPaths/6.x - Endwalker/4807_DebugGathering.json b/QuestPaths/6.x - Endwalker/4807_DebugGathering.json
deleted file mode 100644 (file)
index 3d411f4..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-{
-  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
-  "Author": "liza",
-  "QuestSequence": [
-    {
-      "Sequence": 255,
-      "Steps": [
-        {
-          "Position": {
-            "X": -435.39066,
-            "Y": -9.809827,
-            "Z": -594.5472
-          },
-          "TerritoryId": 1187,
-          "InteractionType": "WalkTo",
-          "Fly": true,
-          "RequiredGatheredItems": [
-            {
-              "ItemId": 43992,
-              "ItemCount": 1234
-            }
-          ]
-        }
-      ]
-    }
-  ]
-}
index 850f04f5492785e45b8eb2584e710dedec9b8e9d..a7dd1f4adbd913264a30d171973d2aea737de897 100644 (file)
@@ -39,8 +39,7 @@
               "ItemId": 35848,
               "ItemCount": 1
             }
-          ],
-          "NextQuestId": 4159
+          ]
         }
       ]
     },
diff --git a/Questionable.Model/Questing/Converter/IdConverter.cs b/Questionable.Model/Questing/Converter/IdConverter.cs
new file mode 100644 (file)
index 0000000..188627d
--- /dev/null
@@ -0,0 +1,19 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Questionable.Model.Questing.Converter;
+
+public class IdConverter : JsonConverter<IId>
+{
+    public override IId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+    {
+        uint value = reader.GetUInt32();
+        return Id.From(value);
+    }
+
+    public override void Write(Utf8JsonWriter writer, IId value, JsonSerializerOptions options)
+    {
+        throw new NotImplementedException();
+    }
+}
diff --git a/Questionable.Model/Questing/IId.cs b/Questionable.Model/Questing/IId.cs
new file mode 100644 (file)
index 0000000..eefb5eb
--- /dev/null
@@ -0,0 +1,58 @@
+using System;
+using System.Globalization;
+
+namespace Questionable.Model.Questing
+{
+    public interface IId : IComparable<IId>
+    {
+        public ushort Value { get; }
+    }
+
+    public static class Id
+    {
+        public static IId From(uint value)
+        {
+            if (value >= 100_000 && value < 200_000)
+                return new LeveId((ushort)(value - 100_000));
+            else
+                return new QuestId((ushort)value);
+        }
+    }
+
+    public sealed record QuestId(ushort Value) : IId
+    {
+        public override string ToString()
+        {
+            return "Q" + Value.ToString(CultureInfo.InvariantCulture);
+        }
+
+        public int CompareTo(IId? other)
+        {
+            if (ReferenceEquals(this, other)) return 0;
+            if (ReferenceEquals(null, other)) return 1;
+            return Value.CompareTo(other.Value);
+        }
+    }
+
+    public sealed record LeveId(ushort Value) : IId
+    {
+        public override string ToString()
+        {
+            return "L" + Value.ToString(CultureInfo.InvariantCulture);
+        }
+
+        public int CompareTo(IId? other)
+        {
+            if (ReferenceEquals(this, other)) return 0;
+            if (ReferenceEquals(null, other)) return 1;
+            return Value.CompareTo(other.Value);
+        }
+    }
+}
+
+namespace System.Runtime.CompilerServices
+{
+    internal static class IsExternalInit
+    {
+    }
+}
index 4ec5f81dcb9bf5b468f70015cab3e498694310bf..dec9d156cb53f989552cf3b9aac6ce2d77088bdb 100644 (file)
@@ -72,10 +72,14 @@ public sealed class QuestStep
     public IList<uint> PointMenuChoices { get; set; } = new List<uint>();
 
     // TODO: Not implemented
-    public ushort? PickUpQuestId { get; set; }
+    [JsonConverter(typeof(IdConverter))]
+    public IId? PickUpQuestId { get; set; }
 
-    public ushort? TurnInQuestId { get; set; }
-    public ushort? NextQuestId { get; set; }
+    [JsonConverter(typeof(IdConverter))]
+    public IId? TurnInQuestId { get; set; }
+
+    [JsonConverter(typeof(IdConverter))]
+    public IId? NextQuestId { get; set; }
 
     [JsonConstructor]
     public QuestStep()
index ee7e7dcf9e19e4be9597bd3311a1b6edcb1dd02c..61d126f3eaa7184071dc1a6e0c70db77a358dda7 100644 (file)
@@ -13,8 +13,8 @@ public sealed class SkipStepConditions
     public List<ushort> InTerritory { get; set; } = new();
     public List<ushort> NotInTerritory { get; set; } = new();
     public SkipItemConditions? Item { get; set; }
-    public List<ushort> QuestsAccepted { get; set; } = new();
-    public List<ushort> QuestsCompleted { get; set; } = new();
+    public List<IId> QuestsAccepted { get; set; } = new();
+    public List<IId> QuestsCompleted { get; set; } = new();
     public EExtraSkipCondition? ExtraCondition { get; set; }
 
     public bool HasSkipConditions()
index 1f95e9da128ed36f3ecb407ddd6cea6b4b7b0651..4099da390d4c579f8ed823d36d74a1fa5c73331b 100644 (file)
@@ -168,9 +168,9 @@ internal sealed class CombatController : IDisposable
                     }
                 }
 
-                if (QuestWorkUtils.HasCompletionFlags(condition.CompletionQuestVariablesFlags))
+                if (QuestWorkUtils.HasCompletionFlags(condition.CompletionQuestVariablesFlags) && _currentFight.Data.QuestId is QuestId questId)
                 {
-                    var questWork = _gameFunctions.GetQuestEx(_currentFight.Data.QuestId);
+                    var questWork = _gameFunctions.GetQuestEx(questId);
                     if (questWork != null && QuestWorkUtils.MatchesQuestWork(condition.CompletionQuestVariablesFlags,
                             questWork.Value))
                     {
@@ -303,7 +303,7 @@ internal sealed class CombatController : IDisposable
 
     public sealed class CombatData
     {
-        public required ushort QuestId { get; init; }
+        public required IId QuestId { get; init; }
         public required EEnemySpawnType SpawnType { get; init; }
         public required List<uint> KillEnemyDataIds { get; init; }
         public required List<ComplexCombatData> ComplexCombatDatas { get; init; }
index ed552f62f3718b85c058b0288c579901fa153f83..a7fcf8d4a91400ea18571c40b4ec6574a4310326 100644 (file)
@@ -4,6 +4,7 @@ using Dalamud.Game.ClientState.Objects;
 using Dalamud.Game.Command;
 using Dalamud.Plugin.Services;
 using Questionable.Model;
+using Questionable.Model.Questing;
 using Questionable.Windows;
 using Questionable.Windows.QuestComponents;
 
@@ -127,11 +128,11 @@ internal sealed class CommandHandler : IDisposable
             return;
         }
 
-        if (arguments.Length >= 1 && ushort.TryParse(arguments[0], out ushort questId))
+        if (arguments.Length >= 1 && uint.TryParse(arguments[0], out uint questId))
         {
-            if (_questRegistry.TryGetQuest(questId, out Quest? quest))
+            if (_questRegistry.TryGetQuest(Id.From(questId), out Quest? quest))
             {
-                _debugOverlay.HighlightedQuest = questId;
+                _debugOverlay.HighlightedQuest = quest.QuestId;
                 _chatGui.Print($"[Questionable] Set highlighted quest to {questId} ({quest.Info.Name}).");
             }
             else
@@ -146,11 +147,11 @@ internal sealed class CommandHandler : IDisposable
 
     private void SetNextQuest(string[] arguments)
     {
-        if (arguments.Length >= 1 && ushort.TryParse(arguments[0], out ushort questId))
+        if (arguments.Length >= 1 && uint.TryParse(arguments[0], out uint questId))
         {
-            if (_gameFunctions.IsQuestLocked(questId, 0))
+            if (_gameFunctions.IsQuestLocked(Id.From(questId)))
                 _chatGui.PrintError($"[Questionable] Quest {questId} is locked.");
-            else if (_questRegistry.TryGetQuest(questId, out Quest? quest))
+            else if (_questRegistry.TryGetQuest(Id.From(questId), out Quest? quest))
             {
                 _questController.SetNextQuest(quest);
                 _chatGui.Print($"[Questionable] Set next quest to {questId} ({quest.Info.Name}).");
@@ -171,7 +172,7 @@ internal sealed class CommandHandler : IDisposable
     {
         if (arguments.Length >= 1 && ushort.TryParse(arguments[0], out ushort questId))
         {
-            if (_questRegistry.TryGetQuest(questId, out Quest? quest))
+            if (_questRegistry.TryGetQuest(Id.From(questId), out Quest? quest))
             {
                 _questController.SimulateQuest(quest);
                 _chatGui.Print($"[Questionable] Simulating quest {questId} ({quest.Info.Name}).");
index 61d31fbd5153e62ae5e8819d50f76adf53ad9f24..fa55793d3b2a5fe5ffe2ddaa0126b49b231a3120 100644 (file)
@@ -600,7 +600,7 @@ internal sealed class GameUiController : IDisposable
 
     private unsafe void UnendingCodexPostSetup(AddonEvent type, AddonArgs args)
     {
-        if (_questController.StartedQuest?.Quest.QuestId == 4526)
+        if (_questController.StartedQuest?.Quest.QuestId.Value == 4526)
         {
             _logger.LogInformation("Closing Unending Codex");
             AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
@@ -610,7 +610,7 @@ internal sealed class GameUiController : IDisposable
 
     private unsafe void ContentsTutorialPostSetup(AddonEvent type, AddonArgs args)
     {
-        if (_questController.StartedQuest?.Quest.QuestId == 245)
+        if (_questController.StartedQuest?.Quest.QuestId.Value == 245)
         {
             _logger.LogInformation("Closing ContentsTutorial");
             AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
@@ -623,7 +623,7 @@ internal sealed class GameUiController : IDisposable
     /// </summary>
     private unsafe void MultipleHelpWindowPostSetup(AddonEvent type, AddonArgs args)
     {
-        if (_questController.StartedQuest?.Quest.QuestId == 245)
+        if (_questController.StartedQuest?.Quest.QuestId.Value == 245)
         {
             _logger.LogInformation("Closing MultipleHelpWindow");
             AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
index 9b7fe9a9def34abdfb755af64d3d01e256a2fcc3..569275b3d73e6ca91282406e86f573c4e0c8509d 100644 (file)
@@ -209,8 +209,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
             }
             else
             {
-                (ushort currentQuestId, currentSequence) = _gameFunctions.GetCurrentQuest();
-                if (currentQuestId == 0)
+                (IId? currentQuestId, currentSequence) = _gameFunctions.GetCurrentQuest();
+                if (currentQuestId == null || currentQuestId.Value == 0)
                 {
                     if (_startedQuest != null)
                     {
@@ -330,7 +330,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
         return (seq, seq.Steps[CurrentQuest.Step]);
     }
 
-    public void IncreaseStepCount(ushort? questId, int? sequence, bool shouldContinue = false)
+    public void IncreaseStepCount(IId? questId, int? sequence, bool shouldContinue = false)
     {
         lock (_progressLock)
         {
@@ -545,7 +545,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
         }
     }
 
-    public void Skip(ushort questQuestId, byte currentQuestSequence)
+    public void Skip(IId questQuestId, byte currentQuestSequence)
     {
         lock (_progressLock)
         {
@@ -609,8 +609,9 @@ internal sealed class QuestController : MiniTaskController<QuestController>
             1158, // Titan (Hard)
         ];
 
-        foreach (var questId in priorityQuests)
+        foreach (var id in priorityQuests)
         {
+            var questId = new QuestId(id);
             if (_gameFunctions.IsReadyToAcceptQuest(questId) && _questRegistry.TryGetQuest(questId, out var quest))
             {
                 SetNextQuest(quest);
index da557891e065979b7649aa1c7ec1322756f2efa4..426606b6b4e00b350017936cb633c07f03cec323 100644 (file)
@@ -28,7 +28,7 @@ internal sealed class QuestRegistry
     private readonly ILogger<QuestRegistry> _logger;
     private readonly ICallGateProvider<object> _reloadDataIpc;
 
-    private readonly Dictionary<ushort, Quest> _quests = new();
+    private readonly Dictionary<IId, Quest> _quests = new();
 
     public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData,
         QuestValidator questValidator, JsonSchemaValidator jsonSchemaValidator,
@@ -91,12 +91,12 @@ internal sealed class QuestRegistry
         {
             Quest quest = new()
             {
-                QuestId = questId,
+                QuestId = new QuestId(questId),
                 Root = questRoot,
-                Info = _questData.GetQuestInfo(questId),
+                Info = _questData.GetQuestInfo(new QuestId(questId)),
                 ReadOnly = true,
             };
-            _quests[questId] = quest;
+            _quests[quest.QuestId] = quest;
         }
 
         _logger.LogInformation("Loaded {Count} quests from assembly", _quests.Count);
@@ -136,21 +136,21 @@ internal sealed class QuestRegistry
     private void LoadQuestFromStream(string fileName, Stream stream)
     {
         _logger.LogTrace("Loading quest from '{FileName}'", fileName);
-        ushort? questId = ExtractQuestIdFromName(fileName);
+        IId? questId = ExtractQuestIdFromName(fileName);
         if (questId == null)
             return;
 
         var questNode = JsonNode.Parse(stream)!;
-        _jsonSchemaValidator.Enqueue(questId.Value, questNode);
+        _jsonSchemaValidator.Enqueue(questId, questNode);
 
         Quest quest = new Quest
         {
-            QuestId = questId.Value,
+            QuestId = questId,
             Root = questNode.Deserialize<QuestRoot>()!,
-            Info = _questData.GetQuestInfo(questId.Value),
+            Info = _questData.GetQuestInfo(questId),
             ReadOnly = false,
         };
-        _quests[questId.Value] = quest;
+        _quests[quest.QuestId] = quest;
     }
 
     private void LoadFromDirectory(DirectoryInfo directory, LogLevel logLevel = LogLevel.Information)
@@ -179,7 +179,7 @@ internal sealed class QuestRegistry
             LoadFromDirectory(childDirectory, logLevel);
     }
 
-    private static ushort? ExtractQuestIdFromName(string resourceName)
+    private static IId? ExtractQuestIdFromName(string resourceName)
     {
         string name = resourceName.Substring(0, resourceName.Length - ".json".Length);
         name = name.Substring(name.LastIndexOf('.') + 1);
@@ -188,11 +188,30 @@ internal sealed class QuestRegistry
             return null;
 
         string[] parts = name.Split('_', 2);
-        return ushort.Parse(parts[0], CultureInfo.InvariantCulture);
+        return Id.From(uint.Parse(parts[0], CultureInfo.InvariantCulture));
     }
 
-    public bool IsKnownQuest(ushort questId) => _quests.ContainsKey(questId);
+    public bool IsKnownQuest(IId id)
+    {
+        if (id is QuestId questId)
+            return IsKnownQuest(questId);
+        else
+            return false;
+    }
+
+    public bool IsKnownQuest(QuestId questId) => _quests.ContainsKey(questId);
+
+    public bool TryGetQuest(IId id, [NotNullWhen(true)] out Quest? quest)
+    {
+        if (id is QuestId questId)
+            return TryGetQuest(questId, out quest);
+        else
+        {
+            quest = null;
+            return false;
+        }
+    }
 
-    public bool TryGetQuest(ushort questId, [NotNullWhen(true)] out Quest? quest)
+    public bool TryGetQuest(QuestId questId, [NotNullWhen(true)] out Quest? quest)
         => _quests.TryGetValue(questId, out quest);
 }
index dddc8f9db6f345d3c8ba4fdfd6d409ffa8d0f63e..5bddb2aad3e6373b8a280a5b9d8b03313a547b42 100644 (file)
@@ -18,20 +18,20 @@ internal static class NextQuest
             if (step.NextQuestId == null)
                 return null;
 
-            if (step.NextQuestId.Value == quest.QuestId)
+            if (step.NextQuestId == quest.QuestId)
                 return null;
 
             return serviceProvider.GetRequiredService<SetQuest>()
-                .With(step.NextQuestId.Value, quest.QuestId);
+                .With(step.NextQuestId, quest.QuestId);
         }
     }
 
     internal sealed class SetQuest(QuestRegistry questRegistry, QuestController questController, GameFunctions gameFunctions, ILogger<SetQuest> logger) : ITask
     {
-        public ushort NextQuestId { get; set; }
-        public ushort CurrentQuestId { get; set; }
+        public IId NextQuestId { get; set; } = null!;
+        public IId CurrentQuestId { get; set; } = null!;
 
-        public ITask With(ushort nextQuestId, ushort currentQuestId)
+        public ITask With(IId nextQuestId, IId currentQuestId)
         {
             NextQuestId = nextQuestId;
             CurrentQuestId = currentQuestId;
index f0f99cc47d5652bf5159a1f304d9adc510aadb3b..8b12de4520c5427815d12dabf82c9bb7545b432d 100644 (file)
@@ -1,7 +1,9 @@
-namespace Questionable.Controller.Steps;
+using Questionable.Model.Questing;
+
+namespace Questionable.Controller.Steps;
 
 internal interface ILastTask : ITask
 {
-    public ushort QuestId { get; }
+    public IId QuestId { get; }
     public int Sequence { get; }
 }
index 2d7de4022fd5e75488abb81d827d128b321eb947..58161886a286118a797ce7b6d202823bfaf1890b 100644 (file)
@@ -84,7 +84,7 @@ internal static class Combat
         private CombatController.CombatData _combatData = null!;
         private IList<QuestWorkValue?> _completionQuestVariableFlags = null!;
 
-        public ITask With(ushort questId, bool isLastStep, EEnemySpawnType enemySpawnType, IList<uint> killEnemyDataIds,
+        public ITask With(IId questId, bool isLastStep, EEnemySpawnType enemySpawnType, IList<uint> killEnemyDataIds,
             IList<QuestWorkValue?> completionQuestVariablesFlags, IList<ComplexCombatData> complexCombatData)
         {
             _isLastStep = isLastStep;
@@ -107,9 +107,9 @@ internal static class Combat
                 return ETaskResult.StillRunning;
 
             // if our quest step has any completion flags, we need to check if they are set
-            if (QuestWorkUtils.HasCompletionFlags(_completionQuestVariableFlags))
+            if (QuestWorkUtils.HasCompletionFlags(_completionQuestVariableFlags) && _combatData.QuestId is QuestId questId)
             {
-                var questWork = gameFunctions.GetQuestEx(_combatData.QuestId);
+                var questWork = gameFunctions.GetQuestEx(questId);
                 if (questWork == null)
                     return ETaskResult.StillRunning;
 
index e17f005e4a90e39cf1b2c18a742470b67ed2213a..d971b782967227bd71600a733a74fe5fbd194a04 100644 (file)
@@ -118,7 +118,7 @@ internal static class UseItem
         private DateTime _continueAt;
         private int _itemCount;
 
-        public ushort? QuestId { get; set; }
+        public IId? QuestId { get; set; }
         public uint ItemId { get; set; }
         public IList<QuestWorkValue?> CompletionQuestVariablesFlags { get; set; } = new List<QuestWorkValue?>();
         public bool StartingCombat { get; set; }
@@ -142,9 +142,9 @@ internal static class UseItem
 
         public unsafe ETaskResult Update()
         {
-            if (QuestId.HasValue && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
+            if (QuestId is QuestId questId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
             {
-                QuestWork? questWork = gameFunctions.GetQuestEx(QuestId.Value);
+                QuestWork? questWork = gameFunctions.GetQuestEx(questId);
                 if (questWork != null &&
                     QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork.Value))
                     return ETaskResult.TaskComplete;
@@ -203,7 +203,7 @@ internal static class UseItem
 
         public uint DataId { get; set; }
 
-        public ITask With(ushort? questId, uint dataId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
+        public ITask With(IId? questId, uint dataId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
         {
             QuestId = questId;
             DataId = dataId;
@@ -227,7 +227,7 @@ internal static class UseItem
 
         public Vector3 Position { get; set; }
 
-        public ITask With(ushort? questId, Vector3 position, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
+        public ITask With(IId? questId, Vector3 position, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
         {
             QuestId = questId;
             Position = position;
@@ -249,7 +249,7 @@ internal static class UseItem
 
         public uint DataId { get; set; }
 
-        public ITask With(ushort? questId, uint dataId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags,
+        public ITask With(IId? questId, uint dataId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags,
             bool startingCombat = false)
         {
             QuestId = questId;
@@ -270,7 +270,7 @@ internal static class UseItem
     {
         private readonly GameFunctions _gameFunctions = gameFunctions;
 
-        public ITask With(ushort? questId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
+        public ITask With(IId? questId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
         {
             QuestId = questId;
             ItemId = itemId;
index 5d50ba183ee4167db57025f4523a101587ba46bc..0b24ecaa8b1d996178ba62bab9910feb3efb7500 100644 (file)
@@ -45,9 +45,9 @@ internal static class SkipCondition
     {
         public QuestStep Step { get; set; } = null!;
         public SkipStepConditions SkipConditions { get; set; } = null!;
-        public ushort QuestId { get; set; }
+        public IId QuestId { get; set; } = null!;
 
-        public ITask With(QuestStep step, SkipStepConditions skipConditions, ushort questId)
+        public ITask With(QuestStep step, SkipStepConditions skipConditions, IId questId)
         {
             Step = step;
             SkipConditions = skipConditions;
@@ -156,32 +156,35 @@ internal static class SkipCondition
                 return true;
             }
 
-            QuestWork? questWork = gameFunctions.GetQuestEx(QuestId);
-            if (QuestWorkUtils.HasCompletionFlags(Step.CompletionQuestVariablesFlags) && questWork != null)
+            if (QuestId is QuestId questId)
             {
-                if (QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value))
+                QuestWork? questWork = gameFunctions.GetQuestEx(questId);
+                if (QuestWorkUtils.HasCompletionFlags(Step.CompletionQuestVariablesFlags) && questWork != null)
                 {
-                    logger.LogInformation("Skipping step, as quest variables match (step is complete)");
-                    return true;
+                    if (QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value))
+                    {
+                        logger.LogInformation("Skipping step, as quest variables match (step is complete)");
+                        return true;
+                    }
                 }
-            }
 
-            if (Step is { SkipConditions.StepIf: { } conditions } && questWork != null)
-            {
-                if (QuestWorkUtils.MatchesQuestWork(conditions.CompletionQuestVariablesFlags, questWork.Value))
+                if (Step is { SkipConditions.StepIf: { } conditions } && questWork != null)
                 {
-                    logger.LogInformation("Skipping step, as quest variables match (step can be skipped)");
-                    return true;
+                    if (QuestWorkUtils.MatchesQuestWork(conditions.CompletionQuestVariablesFlags, questWork.Value))
+                    {
+                        logger.LogInformation("Skipping step, as quest variables match (step can be skipped)");
+                        return true;
+                    }
                 }
-            }
 
-            if (Step is { RequiredQuestVariables: { } requiredQuestVariables } && questWork != null)
-            {
-                if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork.Value,
-                        logger))
+                if (Step is { RequiredQuestVariables: { } requiredQuestVariables } && questWork != null)
                 {
-                    logger.LogInformation("Skipping step, as required variables do not match");
-                    return true;
+                    if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork.Value,
+                            logger))
+                    {
+                        logger.LogInformation("Skipping step, as required variables do not match");
+                        return true;
+                    }
                 }
             }
 
@@ -195,13 +198,13 @@ internal static class SkipCondition
                 }
             }
 
-            if (Step.PickUpQuestId != null && gameFunctions.IsQuestAcceptedOrComplete(Step.PickUpQuestId.Value))
+            if (Step.PickUpQuestId != null && gameFunctions.IsQuestAcceptedOrComplete(Step.PickUpQuestId))
             {
                 logger.LogInformation("Skipping step, as we have already picked up the relevant quest");
                 return true;
             }
 
-            if (Step.TurnInQuestId != null && gameFunctions.IsQuestComplete(Step.TurnInQuestId.Value))
+            if (Step.TurnInQuestId != null && gameFunctions.IsQuestComplete(Step.TurnInQuestId))
             {
                 logger.LogInformation("Skipping step, as we have already completed the relevant quest");
                 return true;
index 6d54623e0a31b1bebf4a61767533d1ce8ea4b76e..55fede1b24296c93ef4928ab942e416074ff4c48 100644 (file)
@@ -30,7 +30,7 @@ internal static class WaitAtEnd
             if (step.CompletionQuestVariablesFlags.Count == 6 && QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
             {
                 var task = serviceProvider.GetRequiredService<WaitForCompletionFlags>()
-                    .With(quest, step);
+                    .With((QuestId)quest.QuestId, step);
                 var delay = serviceProvider.GetRequiredService<WaitDelay>();
                 return [task, delay, Next(quest, sequence)];
             }
@@ -162,11 +162,11 @@ internal static class WaitAtEnd
 
     internal sealed class WaitForCompletionFlags(GameFunctions gameFunctions) : ITask
     {
-        public Quest Quest { get; set; } = null!;
+        public QuestId Quest { get; set; } = null!;
         public QuestStep Step { get; set; } = null!;
         public IList<QuestWorkValue?> Flags { get; set; } = null!;
 
-        public ITask With(Quest quest, QuestStep step)
+        public ITask With(QuestId quest, QuestStep step)
         {
             Quest = quest;
             Step = step;
@@ -178,7 +178,7 @@ internal static class WaitAtEnd
 
         public ETaskResult Update()
         {
-            QuestWork? questWork = gameFunctions.GetQuestEx(Quest.QuestId);
+            QuestWork? questWork = gameFunctions.GetQuestEx(Quest);
             return questWork != null &&
                    QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value)
                 ? ETaskResult.TaskComplete
@@ -214,11 +214,11 @@ internal static class WaitAtEnd
             $"WaitObj({DataId} at {Destination.ToString("G", CultureInfo.InvariantCulture)} < {Distance})";
     }
 
-    internal sealed class WaitQuestAccepted : ITask
+    internal sealed class WaitQuestAccepted(GameFunctions gameFunctions) : ITask
     {
-        public ushort QuestId { get; set; }
+        public IId QuestId { get; set; } = null!;
 
-        public ITask With(ushort questId)
+        public ITask With(IId questId)
         {
             QuestId = questId;
             return this;
@@ -228,23 +228,19 @@ internal static class WaitAtEnd
 
         public ETaskResult Update()
         {
-            unsafe
-            {
-                var questManager = QuestManager.Instance();
-                return questManager != null && questManager->IsQuestAccepted(QuestId)
-                    ? ETaskResult.TaskComplete
-                    : ETaskResult.StillRunning;
-            }
+            return gameFunctions.IsQuestAccepted(QuestId)
+                ? ETaskResult.TaskComplete
+                : ETaskResult.StillRunning;
         }
 
         public override string ToString() => $"WaitQuestAccepted({QuestId})";
     }
 
-    internal sealed class WaitQuestCompleted : ITask
+    internal sealed class WaitQuestCompleted(GameFunctions gameFunctions) : ITask
     {
-        public ushort QuestId { get; set; }
+        public IId QuestId { get; set; } = null!;
 
-        public ITask With(ushort questId)
+        public ITask With(IId questId)
         {
             QuestId = questId;
             return this;
@@ -254,15 +250,15 @@ internal static class WaitAtEnd
 
         public ETaskResult Update()
         {
-            return QuestManager.IsQuestComplete(QuestId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
+            return gameFunctions.IsQuestComplete(QuestId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
         }
 
         public override string ToString() => $"WaitQuestComplete({QuestId})";
     }
 
-    internal sealed class NextStep(ushort questId, int sequence) : ILastTask
+    internal sealed class NextStep(IId questId, int sequence) : ILastTask
     {
-        public ushort QuestId { get; } = questId;
+        public IId QuestId { get; } = questId;
         public int Sequence { get; } = sequence;
 
         public bool Start() => true;
@@ -274,7 +270,7 @@ internal static class WaitAtEnd
 
     internal sealed class EndAutomation : ILastTask
     {
-        public ushort QuestId => throw new InvalidOperationException();
+        public IId QuestId => throw new InvalidOperationException();
         public int Sequence => throw new InvalidOperationException();
 
         public bool Start() => true;
index a7bf55e3293bb440af09ae4d6759d323b6a553df..171dfe827e7f3371ba8f60a32d70f9f0a4878cf1 100644 (file)
@@ -3,6 +3,7 @@ using System.Linq;
 using Dalamud.Plugin.Services;
 using Lumina.Excel.GeneratedSheets;
 using Questionable.Model;
+using Questionable.Model.Questing;
 
 namespace Questionable.Data;
 
@@ -21,15 +22,17 @@ internal sealed class JournalData
         var genreLimsa = new Genre(uint.MaxValue - 3, "Starting in Limsa Lominsa", 1,
             new uint[] { 108, 109 }.Concat(limsaStart.Quest.Select(x => x.Row))
                 .Where(x => x != 0)
-                .Select(x => questData.GetQuestInfo((ushort)(x & 0xFFFF))).ToList());
+                .Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
+                .ToList());
         var genreGridania = new Genre(uint.MaxValue - 2, "Starting in Gridania", 1,
             new uint[] { 85, 123, 124 }.Concat(gridaniaStart.Quest.Select(x => x.Row))
                 .Where(x => x != 0)
-                .Select(x => questData.GetQuestInfo((ushort)(x & 0xFFFF))).ToList());
+                .Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
+                .ToList());
         var genreUldah = new Genre(uint.MaxValue - 1, "Starting in Ul'dah", 1,
             new uint[] { 568, 569, 570 }.Concat(uldahStart.Quest.Select(x => x.Row))
                 .Where(x => x != 0)
-                .Select(x => questData.GetQuestInfo((ushort)(x & 0xFFFF)))
+                .Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
                 .ToList());
         genres.InsertRange(0, [genreLimsa, genreGridania, genreUldah]);
         genres.Single(x => x.Id == 1)
index 7b36aef5c81a55c170ddedc537930b02793829e2..d30a5f2e6edbef294fafd1eaf5d7cc24af5470e8 100644 (file)
@@ -4,13 +4,14 @@ using System.Collections.Immutable;
 using System.Linq;
 using Dalamud.Plugin.Services;
 using Questionable.Model;
+using Questionable.Model.Questing;
 using Quest = Lumina.Excel.GeneratedSheets.Quest;
 
 namespace Questionable.Data;
 
 internal sealed class QuestData
 {
-    private readonly ImmutableDictionary<ushort, QuestInfo> _quests;
+    private readonly ImmutableDictionary<QuestId, QuestInfo> _quests;
 
     public QuestData(IDataManager dataManager)
     {
@@ -22,7 +23,15 @@ internal sealed class QuestData
             .ToImmutableDictionary(x => x.QuestId, x => x);
     }
 
-    public QuestInfo GetQuestInfo(ushort questId)
+    public QuestInfo GetQuestInfo(IId id)
+    {
+        if (id is QuestId questId)
+            return GetQuestInfo(questId);
+
+        throw new ArgumentException("Invalid id", nameof(id));
+    }
+
+    public QuestInfo GetQuestInfo(QuestId questId)
     {
         return _quests[questId] ?? throw new ArgumentOutOfRangeException(nameof(questId));
     }
index cf824af6ee7a9dc0a017c49ad8b51760baebbe51..521d89e804f3bcd2de170b982f9a5ba82a610bc4 100644 (file)
@@ -89,37 +89,36 @@ internal sealed unsafe class GameFunctions
 
     public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue;
 
-    public (ushort CurrentQuest, byte Sequence) GetCurrentQuest()
+    public (IId? CurrentQuest, byte Sequence) GetCurrentQuest()
     {
         var (currentQuest, sequence) = GetCurrentQuestInternal();
-        QuestManager* questManager = QuestManager.Instance();
         PlayerState* playerState = PlayerState.Instance();
 
-        if (currentQuest == 0)
+        if (currentQuest == null || currentQuest.Value == 0)
         {
             if (_clientState.TerritoryType == 181) // Starting in Limsa
-                return (107, 0);
+                return (new QuestId(107), 0);
             if (_clientState.TerritoryType == 182) // Starting in Ul'dah
-                return (594, 0);
+                return (new QuestId(594), 0);
             if (_clientState.TerritoryType == 183) // Starting in Gridania
-                return (39, 0);
+                return (new QuestId(39), 0);
             return default;
         }
-        else if (currentQuest == 681)
+        else if (currentQuest.Value == 681)
         {
             // if we have already picked up the GC quest, just return the progress for it
-            if (questManager->IsQuestAccepted(currentQuest) || QuestManager.IsQuestComplete(currentQuest))
+            if (IsQuestAccepted(currentQuest) || IsQuestComplete(currentQuest))
                 return (currentQuest, sequence);
 
             // The company you keep...
             return _configuration.General.GrandCompany switch
             {
-                GrandCompany.TwinAdder => (680, 0),
-                GrandCompany.Maelstrom => (681, 0),
+                GrandCompany.TwinAdder => (new QuestId(680), 0),
+                GrandCompany.Maelstrom => (new QuestId(681), 0),
                 _ => default
             };
         }
-        else if (currentQuest == 3856 && !playerState->IsMountUnlocked(1)) // we come in peace
+        else if (currentQuest.Value == 3856 && !playerState->IsMountUnlocked(1)) // we come in peace
         {
             ushort chocoboQuest = (GrandCompany)playerState->GrandCompany switch
             {
@@ -129,20 +128,20 @@ internal sealed unsafe class GameFunctions
             };
 
             if (chocoboQuest != 0 && !QuestManager.IsQuestComplete(chocoboQuest))
-                return (chocoboQuest, QuestManager.GetQuestSequence(chocoboQuest));
+                return (new QuestId(chocoboQuest), QuestManager.GetQuestSequence(chocoboQuest));
         }
-        else if (currentQuest == 801)
+        else if (currentQuest.Value == 801)
         {
             // skeletons in her closet, finish 'broadening horizons' to unlock the white wolf gate
-            ushort broadeningHorizons = 802;
-            if (questManager->IsQuestAccepted(broadeningHorizons))
-                return (broadeningHorizons, QuestManager.GetQuestSequence(broadeningHorizons));
+            QuestId broadeningHorizons = new QuestId(802);
+            if (IsQuestAccepted(broadeningHorizons))
+                return (broadeningHorizons, QuestManager.GetQuestSequence(broadeningHorizons.Value));
         }
 
         return (currentQuest, sequence);
     }
 
-    public (ushort CurrentQuest, byte Sequence) GetCurrentQuestInternal()
+    public (IId? CurrentQuest, byte Sequence) GetCurrentQuestInternal()
     {
         var questManager = QuestManager.Instance();
         if (questManager != null)
@@ -150,7 +149,7 @@ internal sealed unsafe class GameFunctions
             // always prioritize accepting MSQ quests, to make sure we don't turn in one MSQ quest and then go off to do
             // side quests until the end of time.
             var msqQuest = GetMainScenarioQuest(questManager);
-            if (msqQuest.CurrentQuest != 0 && _questRegistry.IsKnownQuest(msqQuest.CurrentQuest))
+            if (msqQuest.CurrentQuest is { Value: not 0 } && _questRegistry.IsKnownQuest(msqQuest.CurrentQuest))
                 return msqQuest;
 
             // Use the quests in the same order as they're shown in the to-do list, e.g. if the MSQ is the first item,
@@ -159,7 +158,7 @@ internal sealed unsafe class GameFunctions
             // If no quests are marked as 'priority', accepting a new quest adds it to the top of the list.
             for (int i = questManager->TrackedQuests.Length - 1; i >= 0; --i)
             {
-                ushort currentQuest;
+                IId currentQuest;
                 var trackedQuest = questManager->TrackedQuests[i];
                 switch (trackedQuest.QuestType)
                 {
@@ -167,12 +166,12 @@ internal sealed unsafe class GameFunctions
                         continue;
 
                     case 1: // normal quest
-                        currentQuest = questManager->NormalQuests[trackedQuest.Index].QuestId;
+                        currentQuest = new QuestId(questManager->NormalQuests[trackedQuest.Index].QuestId);
                         break;
                 }
 
                 if (_questRegistry.IsKnownQuest(currentQuest))
-                    return (currentQuest, QuestManager.GetQuestSequence(currentQuest));
+                    return (currentQuest, QuestManager.GetQuestSequence(currentQuest.Value));
             }
 
             // if we know no quest of those currently in the to-do list, just do MSQ
@@ -182,7 +181,7 @@ internal sealed unsafe class GameFunctions
         return default;
     }
 
-    private (ushort CurrentQuest, byte Sequence) GetMainScenarioQuest(QuestManager* questManager)
+    private (QuestId? CurrentQuest, byte Sequence) GetMainScenarioQuest(QuestManager* questManager)
     {
         if (QuestManager.IsQuestComplete(3759)) // Memories Rekindled
         {
@@ -202,7 +201,7 @@ internal sealed unsafe class GameFunctions
                     // redoHud+44 is chapter
                     // redoHud+46 is quest
                     ushort questId = MemoryHelper.Read<ushort>((nint)questRedoHud + 46);
-                    return (questId, QuestManager.GetQuestSequence(questId));
+                    return (new QuestId(questId), QuestManager.GetQuestSequence(questId));
                 }
             }
         }
@@ -214,12 +213,12 @@ internal sealed unsafe class GameFunctions
         if (scenarioTree->Data == null)
             return default;
 
-        ushort currentQuest = scenarioTree->Data->CurrentScenarioQuest;
-        if (currentQuest == 0)
+        QuestId currentQuest = new QuestId(scenarioTree->Data->CurrentScenarioQuest);
+        if (currentQuest.Value == 0)
             return default;
 
         // if the MSQ is hidden, we generally ignore it
-        if (questManager->IsQuestAccepted(currentQuest) && questManager->GetQuestById(currentQuest)->IsHidden)
+        if (IsQuestAccepted(currentQuest) && questManager->GetQuestById(currentQuest.Value)->IsHidden)
             return default;
 
         // it can sometimes happen (although this isn't reliably reproducible) that the quest returned here
@@ -234,16 +233,23 @@ internal sealed unsafe class GameFunctions
             && quest.Info.Level > currentLevel)
             return default;
 
-        return (currentQuest, QuestManager.GetQuestSequence(currentQuest));
+        return (currentQuest, QuestManager.GetQuestSequence(currentQuest.Value));
     }
 
-    public QuestWork? GetQuestEx(ushort questId)
+    public QuestWork? GetQuestEx(QuestId questId)
     {
-        QuestWork* questWork = QuestManager.Instance()->GetQuestById(questId);
+        QuestWork* questWork = QuestManager.Instance()->GetQuestById(questId.Value);
         return questWork != null ? *questWork : null;
     }
 
-    public bool IsReadyToAcceptQuest(ushort questId)
+    public bool IsReadyToAcceptQuest(IId id)
+    {
+        if (id is QuestId questId)
+            return IsReadyToAcceptQuest(questId);
+        return false;
+    }
+
+    public bool IsReadyToAcceptQuest(QuestId questId)
     {
         _questRegistry.TryGetQuest(questId, out var quest);
         if (quest is { Info.IsRepeatable: true })
@@ -268,29 +274,50 @@ internal sealed unsafe class GameFunctions
         return true;
     }
 
-    public bool IsQuestAcceptedOrComplete(ushort questId)
+    public bool IsQuestAcceptedOrComplete(IId questId)
     {
         return IsQuestComplete(questId) || IsQuestAccepted(questId);
     }
 
-    public bool IsQuestAccepted(ushort questId)
+    public bool IsQuestAccepted(IId id)
+    {
+        if (id is QuestId questId)
+            return IsQuestAccepted(questId);
+        return false;
+    }
+
+    public bool IsQuestAccepted(QuestId questId)
     {
         QuestManager* questManager = QuestManager.Instance();
-        return questManager->IsQuestAccepted(questId);
+        return questManager->IsQuestAccepted(questId.Value);
+    }
+
+    public bool IsQuestComplete(IId id)
+    {
+        if (id is QuestId questId)
+            return IsQuestComplete(questId);
+        return false;
     }
 
     [SuppressMessage("Performance", "CA1822")]
-    public bool IsQuestComplete(ushort questId)
+    public bool IsQuestComplete(QuestId questId)
     {
-        return QuestManager.IsQuestComplete(questId);
+        return QuestManager.IsQuestComplete(questId.Value);
+    }
+
+    public bool IsQuestLocked(IId id, IId? extraCompletedQuest = null)
+    {
+        if (id is QuestId questId)
+            return IsQuestLocked(questId, extraCompletedQuest);
+        return false;
     }
 
-    public bool IsQuestLocked(ushort questId, ushort? extraCompletedQuest = null)
+    public bool IsQuestLocked(QuestId questId, IId? extraCompletedQuest = null)
     {
         var questInfo = _questData.GetQuestInfo(questId);
         if (questInfo.QuestLocks.Count > 0)
         {
-            var completedQuests = questInfo.QuestLocks.Count(x => IsQuestComplete(x) || x == extraCompletedQuest);
+            var completedQuests = questInfo.QuestLocks.Count(x => IsQuestComplete(x) || x.Equals(extraCompletedQuest));
             if (questInfo.QuestLockJoin == QuestInfo.QuestJoin.All && questInfo.QuestLocks.Count == completedQuests)
                 return true;
             else if (questInfo.QuestLockJoin == QuestInfo.QuestJoin.AtLeastOne && completedQuests > 0)
@@ -303,12 +330,12 @@ internal sealed unsafe class GameFunctions
         return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo);
     }
 
-    private bool HasCompletedPreviousQuests(QuestInfo questInfo, ushort? extraCompletedQuest)
+    private bool HasCompletedPreviousQuests(QuestInfo questInfo, IId? extraCompletedQuest)
     {
         if (questInfo.PreviousQuests.Count == 0)
             return true;
 
-        var completedQuests = questInfo.PreviousQuests.Count(x => IsQuestComplete(x) || x == extraCompletedQuest);
+        var completedQuests = questInfo.PreviousQuests.Count(x => IsQuestComplete(x) || x.Equals(extraCompletedQuest));
         if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.All &&
             questInfo.PreviousQuests.Count == completedQuests)
             return true;
@@ -388,7 +415,7 @@ internal sealed unsafe class GameFunctions
         if (_configuration.Advanced.NeverFly)
             return false;
 
-        if (IsQuestAccepted(3304) && _condition[ConditionFlag.Mounted])
+        if (IsQuestAccepted(new QuestId(3304)) && _condition[ConditionFlag.Mounted])
         {
             BattleChara* battleChara = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0);
             if (battleChara != null && battleChara->Mount.MountId == 198) // special quest amaro, not the normal one
@@ -680,7 +707,7 @@ internal sealed unsafe class GameFunctions
         if (excelSheetName == null)
         {
             var questRow =
-                _dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.QuestId +
+                _dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.QuestId.Value +
                     0x10000);
             if (questRow == null)
             {
@@ -688,7 +715,7 @@ internal sealed unsafe class GameFunctions
                 return null;
             }
 
-            excelSheetName = $"quest/{(currentQuest.QuestId / 100):000}/{questRow.Id}";
+            excelSheetName = $"quest/{(currentQuest.QuestId.Value / 100):000}/{questRow.Id}";
         }
 
         var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName);
index 5124de9dd733eed4e4c92b78a384f4b36276ed96..f778b137c4bbe6d8be448446e8d9d9bd2d3a7abb 100644 (file)
@@ -6,7 +6,7 @@ namespace Questionable.Model;
 
 internal sealed class Quest
 {
-    public required ushort QuestId { get; init; }
+    public required IId QuestId { get; init; }
     public required QuestRoot Root { get; init; }
     public required QuestInfo Info { get; init; }
     public required bool ReadOnly { get; init; }
index 7b62f14b797b3df412b6a48b6940b8e20ff09880..59a3adad61b77dd5d8b54089597a0609b280974c 100644 (file)
@@ -5,6 +5,7 @@ using System.Linq;
 using Dalamud.Game.Text;
 using FFXIVClientStructs.FFXIV.Client.UI.Agent;
 using JetBrains.Annotations;
+using Questionable.Model.Questing;
 using ExcelQuest = Lumina.Excel.GeneratedSheets.Quest;
 
 namespace Questionable.Model;
@@ -13,9 +14,9 @@ internal sealed class QuestInfo
 {
     public QuestInfo(ExcelQuest quest)
     {
-        QuestId = (ushort)(quest.RowId & 0xFFFF);
+        QuestId = new QuestId((ushort)(quest.RowId & 0xFFFF));
 
-        string suffix = QuestId switch
+        string suffix = QuestId.Value switch
         {
             85 => " (Lancer)",
             108 => " (Marauder)",
@@ -34,9 +35,15 @@ internal sealed class QuestInfo
         Level = quest.ClassJobLevel0;
         IssuerDataId = quest.IssuerStart;
         IsRepeatable = quest.IsRepeatable;
-        PreviousQuests = quest.PreviousQuest.Select(x => (ushort)(x.Row & 0xFFFF)).Where(x => x != 0).ToImmutableList();
+        PreviousQuests = quest.PreviousQuest
+            .Select(x => new QuestId((ushort)(x.Row & 0xFFFF)))
+            .Where(x => x.Value != 0)
+            .ToImmutableList();
         PreviousQuestJoin = (QuestJoin)quest.PreviousQuestJoin;
-        QuestLocks = quest.QuestLock.Select(x => (ushort)(x.Row & 0xFFFFF)).Where(x => x != 0).ToImmutableList();
+        QuestLocks = quest.QuestLock
+            .Select(x => new QuestId((ushort)(x.Row & 0xFFFFF)))
+            .Where(x => x.Value != 0)
+            .ToImmutableList();
         QuestLockJoin = (QuestJoin)quest.QuestLockJoin;
         JournalGenre = quest.JournalGenre?.Row;
         SortKey = quest.SortKey;
@@ -49,14 +56,14 @@ internal sealed class QuestInfo
     }
 
 
-    public ushort QuestId { get; }
+    public QuestId QuestId { get; }
     public string Name { get; }
     public ushort Level { get; }
     public uint IssuerDataId { get; }
     public bool IsRepeatable { get; }
-    public ImmutableList<ushort> PreviousQuests { get; }
+    public ImmutableList<QuestId> PreviousQuests { get; }
     public QuestJoin PreviousQuestJoin { get; }
-    public ImmutableList<ushort> QuestLocks { get; }
+    public ImmutableList<QuestId> QuestLocks { get; }
     public QuestJoin QuestLockJoin { get; }
     public List<ushort> PreviousInstanceContent { get; }
     public QuestJoin PreviousInstanceContentJoin { get; }
index 5988fd5ba3f903ffbc31c727b5af7bb11da7b64e..c0c1c7b6a0e7cfb85c43d76216c8fa276c99a7af 100644 (file)
@@ -1,10 +1,11 @@
 using Questionable.Model;
+using Questionable.Model.Questing;
 
 namespace Questionable.Validation;
 
 internal sealed record ValidationIssue
 {
-    public required ushort? QuestId { get; init; }
+    public required IId? QuestId { get; init; }
     public required byte? Sequence { get; init; }
     public required int? Step { get; init; }
     public EBeastTribe BeastTribe { get; init; } = EBeastTribe.None;
index e620d8a54da39bf94ffb9e883f7a4a69ca744fbb..e094f83b231a9b6c5f26b7d620cfd70dc9f83b76 100644 (file)
@@ -23,7 +23,7 @@ internal sealed class AethernetShortcutValidator : IQuestValidator
             .Cast<ValidationIssue>();
     }
 
-    private ValidationIssue? Validate(ushort questId, int sequenceNo, int stepId, AethernetShortcut? aethernetShortcut)
+    private ValidationIssue? Validate(IId questId, int sequenceNo, int stepId, AethernetShortcut? aethernetShortcut)
     {
         if (aethernetShortcut == null)
             return null;
index 7cacc520acc839e9521978f28c410b61032e05e1..2efa6be8b6d35860d57e6c3fa0de023441d74443 100644 (file)
@@ -4,13 +4,14 @@ using System.Globalization;
 using System.Text.Json.Nodes;
 using Json.Schema;
 using Questionable.Model;
+using Questionable.Model.Questing;
 using Questionable.QuestPaths;
 
 namespace Questionable.Validation.Validators;
 
 internal sealed class JsonSchemaValidator : IQuestValidator
 {
-    private readonly Dictionary<ushort, JsonNode> _questNodes = new();
+    private readonly Dictionary<IId, JsonNode> _questNodes = new();
     private JsonSchema? _questSchema;
 
     public JsonSchemaValidator()
@@ -46,7 +47,7 @@ internal sealed class JsonSchemaValidator : IQuestValidator
         }
     }
 
-    public void Enqueue(ushort questId, JsonNode questNode) => _questNodes[questId] = questNode;
+    public void Enqueue(IId questId, JsonNode questNode) => _questNodes[questId] = questNode;
 
     public void Reset() => _questNodes.Clear();
 }
index e7cc34e791bde3f8257c9f71fffed873d5d98f00..ed68542c70766f4e11a6565d99fe7c6f6a9b13cb 100644 (file)
@@ -46,7 +46,7 @@ internal sealed class DebugOverlay : Window
         IsOpen = true;
     }
 
-    public ushort? HighlightedQuest { get; set; }
+    public IId? HighlightedQuest { get; set; }
 
     public override bool DrawConditions() => _configuration.Advanced.DebugOverlay;
 
@@ -93,7 +93,7 @@ internal sealed class DebugOverlay : Window
 
     private void DrawHighlightedQuest()
     {
-        if (HighlightedQuest == null || !_questRegistry.TryGetQuest(HighlightedQuest.Value, out var quest))
+        if (HighlightedQuest == null || !_questRegistry.TryGetQuest(HighlightedQuest, out var quest))
             return;
 
         foreach (var sequence in quest.Root.QuestSequence)
index 74556bdfcaf7bbf0a80a1a2095799d11c084c9a6..c9dcd9fc1e72be8a7e670ce6682e7661dde67330 100644 (file)
@@ -201,8 +201,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
 
         if (ImGui.IsItemClicked() && _commandManager.Commands.TryGetValue("/questinfo", out var commandInfo))
         {
-            _commandManager.DispatchCommand("/questinfo",
-                questInfo.QuestId.ToString(CultureInfo.InvariantCulture), commandInfo);
+            _commandManager.DispatchCommand("/questinfo", questInfo.QuestId.ToString(), commandInfo);
         }
 
         if (ImGui.IsItemHovered())
index a8232905455ede1ffabdd9a025d3b9d50e593b72..39e9ec97bed80f882fa051829d70bb097b68f07d 100644 (file)
@@ -4,16 +4,19 @@ using Dalamud.Interface.Utility.Raii;
 using FFXIVClientStructs.FFXIV.Client.Game.UI;
 using FFXIVClientStructs.FFXIV.Common.Math;
 using Questionable.Data;
+using Questionable.Model.Questing;
 
 namespace Questionable.Windows.QuestComponents;
 
 internal sealed class ARealmRebornComponent
 {
-    private const ushort ATimeForEveryPurpose = 425;
-    private const ushort TheUltimateWeapon = 524;
-    private const ushort GoodIntentions = 363;
+    private static readonly QuestId ATimeForEveryPurpose = new(425);
+    private static readonly QuestId TheUltimateWeapon = new(524);
+    private static readonly QuestId GoodIntentions = new(363);
     private static readonly ushort[] RequiredPrimalInstances = [20004, 20006, 20005];
-    private static readonly ushort[] RequiredAllianceRaidQuests = [1709, 1200, 1201, 1202, 1203, 1474, 494, 495];
+
+    private static readonly QuestId[] RequiredAllianceRaidQuests =
+        [new(1709), new(1200), new(1201), new(1202), new(1203), new(1474), new(494), new(495)];
 
     private readonly GameFunctions _gameFunctions;
     private readonly QuestData _questData;
index 17ab37f4c2fdb63c5bfbd8f1040f6eacf07ea27c..e566eaadad6c8e2d5456d9fc3891f5dfade5f4f6 100644 (file)
@@ -151,7 +151,10 @@ internal sealed class ActiveQuestComponent
 
     private QuestWork? DrawQuestWork(QuestController.QuestProgress currentQuest)
     {
-        var questWork = _gameFunctions.GetQuestEx(currentQuest.Quest.QuestId);
+        if (currentQuest.Quest.QuestId is not QuestId questId)
+            return null;
+
+        var questWork = _gameFunctions.GetQuestEx(questId);
         if (questWork != null)
         {
             Vector4 color;
@@ -271,7 +274,7 @@ internal sealed class ActiveQuestComponent
             ImGui.SameLine();
             if (ImGuiComponents.IconButton(FontAwesomeIcon.Atlas))
                 _commandManager.DispatchCommand("/questinfo",
-                    currentQuest.Quest.QuestId.ToString(CultureInfo.InvariantCulture), commandInfo);
+                    currentQuest.Quest.QuestId.ToString() ?? string.Empty, commandInfo);
         }
 
         bool autoAcceptNextQuest = _configuration.General.AutoAcceptNextQuest;
index 8da2c2d080c28afa938b50bda960359c25def86d..ab8803cb2294945390c32631b111c775c0d91712 100644 (file)
@@ -110,7 +110,7 @@ internal sealed class QuestSelectionWindow : LWindow
 
         foreach (var unacceptedQuest in Map.Instance()->UnacceptedQuestMarkers)
         {
-            ushort questId = (ushort)(unacceptedQuest.ObjectiveId & 0xFFFF);
+            QuestId questId = new QuestId((ushort)(unacceptedQuest.ObjectiveId & 0xFFFF));
             if (_quests.All(q => q.QuestId != questId))
                 _quests.Add(_questData.GetQuestInfo(questId));
         }
@@ -161,7 +161,7 @@ internal sealed class QuestSelectionWindow : LWindow
         {
             ImGui.TableNextRow();
 
-            string questId = quest.QuestId.ToString(CultureInfo.InvariantCulture);
+            string questId = quest.QuestId.ToString();
             bool isKnownQuest = _questRegistry.TryGetQuest(quest.QuestId, out var knownQuest);
 
             if (ImGui.TableNextColumn())
index be931d6c7661f66bc67c080d8950f23f2f97690c..15eff98122fc1f27303d534d2b288fb21d4a3774 100644 (file)
@@ -56,11 +56,11 @@ internal sealed class QuestValidationWindow : LWindow
             ImGui.TableNextRow();
 
             if (ImGui.TableNextColumn())
-                ImGui.TextUnformatted(validationIssue.QuestId?.ToString(CultureInfo.InvariantCulture) ?? string.Empty);
+                ImGui.TextUnformatted(validationIssue.QuestId?.ToString() ?? string.Empty);
 
             if (ImGui.TableNextColumn())
                 ImGui.TextUnformatted(validationIssue.QuestId != null
-                    ? _questData.GetQuestInfo(validationIssue.QuestId.Value).Name
+                    ? _questData.GetQuestInfo(validationIssue.QuestId).Name
                     : validationIssue.BeastTribe.ToString());
 
             if (ImGui.TableNextColumn())
index ff51d96310d3c8181d3ed1e09126a25d2b6aca6f..701e874bf108d737f02a104f8a0999f17f9abac6 100644 (file)
@@ -4,6 +4,7 @@ using Dalamud.Interface.Colors;
 using Dalamud.Plugin;
 using FFXIVClientStructs.FFXIV.Client.Game.UI;
 using ImGuiNET;
+using Questionable.Model.Questing;
 
 namespace Questionable.Windows;
 
@@ -18,7 +19,7 @@ internal sealed class UiUtils
         _pluginInterface = pluginInterface;
     }
 
-    public (Vector4 color, FontAwesomeIcon icon, string status) GetQuestStyle(ushort questId)
+    public (Vector4 color, FontAwesomeIcon icon, string status) GetQuestStyle(IId questId)
     {
         if (_gameFunctions.IsQuestAccepted(questId))
             return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Active");