From 36a0f72bd9f105597f7cc94f320e2f3a12ac0c97 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Sat, 3 Aug 2024 20:30:18 +0200 Subject: [PATCH] Migrate questIds to IId --- QuestPathGenerator/QuestSourceGenerator.cs | 4 +- QuestPathGenerator/RoslynShortcuts.cs | 19 ++- .../6.x - Endwalker/4807_DebugGathering.json | 27 ----- .../MIN, BTN/4159_The Culture of Love.json | 3 +- .../Questing/Converter/IdConverter.cs | 19 +++ Questionable.Model/Questing/IId.cs | 58 +++++++++ Questionable.Model/Questing/QuestStep.cs | 10 +- .../Questing/SkipStepConditions.cs | 4 +- Questionable/Controller/CombatController.cs | 6 +- Questionable/Controller/CommandHandler.cs | 15 +-- Questionable/Controller/GameUiController.cs | 6 +- Questionable/Controller/QuestController.cs | 11 +- Questionable/Controller/QuestRegistry.cs | 45 +++++-- .../Controller/Steps/Common/NextQuest.cs | 10 +- Questionable/Controller/Steps/ILastTask.cs | 6 +- .../Controller/Steps/Interactions/Combat.cs | 6 +- .../Controller/Steps/Interactions/UseItem.cs | 14 +-- .../Controller/Steps/Shared/SkipCondition.cs | 47 ++++---- .../Controller/Steps/Shared/WaitAtEnd.cs | 38 +++--- Questionable/Data/JournalData.cs | 9 +- Questionable/Data/QuestData.cs | 13 +- Questionable/GameFunctions.cs | 111 +++++++++++------- Questionable/Model/Quest.cs | 2 +- Questionable/Model/QuestInfo.cs | 21 ++-- Questionable/Validation/ValidationIssue.cs | 3 +- .../Validators/AethernetShortcutValidator.cs | 2 +- .../Validators/JsonSchemaValidator.cs | 5 +- Questionable/Windows/DebugOverlay.cs | 4 +- Questionable/Windows/JournalProgressWindow.cs | 3 +- .../QuestComponents/ARealmRebornComponent.cs | 11 +- .../QuestComponents/ActiveQuestComponent.cs | 7 +- Questionable/Windows/QuestSelectionWindow.cs | 4 +- Questionable/Windows/QuestValidationWindow.cs | 4 +- Questionable/Windows/UiUtils.cs | 3 +- 34 files changed, 349 insertions(+), 201 deletions(-) delete mode 100644 QuestPaths/6.x - Endwalker/4807_DebugGathering.json create mode 100644 Questionable.Model/Questing/Converter/IdConverter.cs create mode 100644 Questionable.Model/Questing/IId.cs diff --git a/QuestPathGenerator/QuestSourceGenerator.cs b/QuestPathGenerator/QuestSourceGenerator.cs index b6cccbaba..5060d3b44 100644 --- a/QuestPathGenerator/QuestSourceGenerator.cs +++ b/QuestPathGenerator/QuestSourceGenerator.cs @@ -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) diff --git a/QuestPathGenerator/RoslynShortcuts.cs b/QuestPathGenerator/RoslynShortcuts.cs index 07b829127..7e2054ee0 100644 --- a/QuestPathGenerator/RoslynShortcuts.cs +++ b/QuestPathGenerator/RoslynShortcuts.cs @@ -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 index 3d411f437..000000000 --- a/QuestPaths/6.x - Endwalker/4807_DebugGathering.json +++ /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 - } - ] - } - ] - } - ] -} diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4159_The Culture of Love.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4159_The Culture of Love.json index 850f04f54..a7dd1f4ad 100644 --- a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4159_The Culture of Love.json +++ b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4159_The Culture of Love.json @@ -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 index 000000000..188627d12 --- /dev/null +++ b/Questionable.Model/Questing/Converter/IdConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Questionable.Model.Questing.Converter; + +public class IdConverter : JsonConverter +{ + 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 index 000000000..eefb5eba6 --- /dev/null +++ b/Questionable.Model/Questing/IId.cs @@ -0,0 +1,58 @@ +using System; +using System.Globalization; + +namespace Questionable.Model.Questing +{ + public interface IId : IComparable + { + 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 + { + } +} diff --git a/Questionable.Model/Questing/QuestStep.cs b/Questionable.Model/Questing/QuestStep.cs index 4ec5f81dc..dec9d156c 100644 --- a/Questionable.Model/Questing/QuestStep.cs +++ b/Questionable.Model/Questing/QuestStep.cs @@ -72,10 +72,14 @@ public sealed class QuestStep public IList PointMenuChoices { get; set; } = new List(); // 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() diff --git a/Questionable.Model/Questing/SkipStepConditions.cs b/Questionable.Model/Questing/SkipStepConditions.cs index ee7e7dcf9..61d126f3e 100644 --- a/Questionable.Model/Questing/SkipStepConditions.cs +++ b/Questionable.Model/Questing/SkipStepConditions.cs @@ -13,8 +13,8 @@ public sealed class SkipStepConditions public List InTerritory { get; set; } = new(); public List NotInTerritory { get; set; } = new(); public SkipItemConditions? Item { get; set; } - public List QuestsAccepted { get; set; } = new(); - public List QuestsCompleted { get; set; } = new(); + public List QuestsAccepted { get; set; } = new(); + public List QuestsCompleted { get; set; } = new(); public EExtraSkipCondition? ExtraCondition { get; set; } public bool HasSkipConditions() diff --git a/Questionable/Controller/CombatController.cs b/Questionable/Controller/CombatController.cs index 1f95e9da1..4099da390 100644 --- a/Questionable/Controller/CombatController.cs +++ b/Questionable/Controller/CombatController.cs @@ -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 KillEnemyDataIds { get; init; } public required List ComplexCombatDatas { get; init; } diff --git a/Questionable/Controller/CommandHandler.cs b/Questionable/Controller/CommandHandler.cs index ed552f62f..a7fcf8d4a 100644 --- a/Questionable/Controller/CommandHandler.cs +++ b/Questionable/Controller/CommandHandler.cs @@ -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})."); diff --git a/Questionable/Controller/GameUiController.cs b/Questionable/Controller/GameUiController.cs index 61d31fbd5..fa55793d3 100644 --- a/Questionable/Controller/GameUiController.cs +++ b/Questionable/Controller/GameUiController.cs @@ -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 /// 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; diff --git a/Questionable/Controller/QuestController.cs b/Questionable/Controller/QuestController.cs index 9b7fe9a9d..569275b3d 100644 --- a/Questionable/Controller/QuestController.cs +++ b/Questionable/Controller/QuestController.cs @@ -209,8 +209,8 @@ internal sealed class QuestController : MiniTaskController } 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 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 } } - 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 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); diff --git a/Questionable/Controller/QuestRegistry.cs b/Questionable/Controller/QuestRegistry.cs index da557891e..426606b6b 100644 --- a/Questionable/Controller/QuestRegistry.cs +++ b/Questionable/Controller/QuestRegistry.cs @@ -28,7 +28,7 @@ internal sealed class QuestRegistry private readonly ILogger _logger; private readonly ICallGateProvider _reloadDataIpc; - private readonly Dictionary _quests = new(); + private readonly Dictionary _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()!, - 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 TryGetQuest(ushort questId, [NotNullWhen(true)] out Quest? quest) + 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(QuestId questId, [NotNullWhen(true)] out Quest? quest) => _quests.TryGetValue(questId, out quest); } diff --git a/Questionable/Controller/Steps/Common/NextQuest.cs b/Questionable/Controller/Steps/Common/NextQuest.cs index dddc8f9db..5bddb2aad 100644 --- a/Questionable/Controller/Steps/Common/NextQuest.cs +++ b/Questionable/Controller/Steps/Common/NextQuest.cs @@ -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() - .With(step.NextQuestId.Value, quest.QuestId); + .With(step.NextQuestId, quest.QuestId); } } internal sealed class SetQuest(QuestRegistry questRegistry, QuestController questController, GameFunctions gameFunctions, ILogger 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; diff --git a/Questionable/Controller/Steps/ILastTask.cs b/Questionable/Controller/Steps/ILastTask.cs index f0f99cc47..8b12de452 100644 --- a/Questionable/Controller/Steps/ILastTask.cs +++ b/Questionable/Controller/Steps/ILastTask.cs @@ -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; } } diff --git a/Questionable/Controller/Steps/Interactions/Combat.cs b/Questionable/Controller/Steps/Interactions/Combat.cs index 2d7de4022..58161886a 100644 --- a/Questionable/Controller/Steps/Interactions/Combat.cs +++ b/Questionable/Controller/Steps/Interactions/Combat.cs @@ -84,7 +84,7 @@ internal static class Combat private CombatController.CombatData _combatData = null!; private IList _completionQuestVariableFlags = null!; - public ITask With(ushort questId, bool isLastStep, EEnemySpawnType enemySpawnType, IList killEnemyDataIds, + public ITask With(IId questId, bool isLastStep, EEnemySpawnType enemySpawnType, IList killEnemyDataIds, IList completionQuestVariablesFlags, IList 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; diff --git a/Questionable/Controller/Steps/Interactions/UseItem.cs b/Questionable/Controller/Steps/Interactions/UseItem.cs index e17f005e4..d971b7829 100644 --- a/Questionable/Controller/Steps/Interactions/UseItem.cs +++ b/Questionable/Controller/Steps/Interactions/UseItem.cs @@ -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 CompletionQuestVariablesFlags { get; set; } = new List(); 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 completionQuestVariablesFlags) + public ITask With(IId? questId, uint dataId, uint itemId, IList 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 completionQuestVariablesFlags) + public ITask With(IId? questId, Vector3 position, uint itemId, IList 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 completionQuestVariablesFlags, + public ITask With(IId? questId, uint dataId, uint itemId, IList 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 completionQuestVariablesFlags) + public ITask With(IId? questId, uint itemId, IList completionQuestVariablesFlags) { QuestId = questId; ItemId = itemId; diff --git a/Questionable/Controller/Steps/Shared/SkipCondition.cs b/Questionable/Controller/Steps/Shared/SkipCondition.cs index 5d50ba183..0b24ecaa8 100644 --- a/Questionable/Controller/Steps/Shared/SkipCondition.cs +++ b/Questionable/Controller/Steps/Shared/SkipCondition.cs @@ -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; diff --git a/Questionable/Controller/Steps/Shared/WaitAtEnd.cs b/Questionable/Controller/Steps/Shared/WaitAtEnd.cs index 6d54623e0..55fede1b2 100644 --- a/Questionable/Controller/Steps/Shared/WaitAtEnd.cs +++ b/Questionable/Controller/Steps/Shared/WaitAtEnd.cs @@ -30,7 +30,7 @@ internal static class WaitAtEnd if (step.CompletionQuestVariablesFlags.Count == 6 && QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags)) { var task = serviceProvider.GetRequiredService() - .With(quest, step); + .With((QuestId)quest.QuestId, step); var delay = serviceProvider.GetRequiredService(); 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 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; diff --git a/Questionable/Data/JournalData.cs b/Questionable/Data/JournalData.cs index a7bf55e32..171dfe827 100644 --- a/Questionable/Data/JournalData.cs +++ b/Questionable/Data/JournalData.cs @@ -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) diff --git a/Questionable/Data/QuestData.cs b/Questionable/Data/QuestData.cs index 7b36aef5c..d30a5f2e6 100644 --- a/Questionable/Data/QuestData.cs +++ b/Questionable/Data/QuestData.cs @@ -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 _quests; + private readonly ImmutableDictionary _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)); } diff --git a/Questionable/GameFunctions.cs b/Questionable/GameFunctions.cs index cf824af6e..521d89e80 100644 --- a/Questionable/GameFunctions.cs +++ b/Questionable/GameFunctions.cs @@ -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((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(ushort questId, ushort? extraCompletedQuest = null) + public bool IsQuestLocked(IId id, IId? extraCompletedQuest = null) + { + if (id is QuestId questId) + return IsQuestLocked(questId, extraCompletedQuest); + return false; + } + + 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()!.GetRow((uint)currentQuest.QuestId + + _dataManager.GetExcelSheet()!.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(excelSheetName); diff --git a/Questionable/Model/Quest.cs b/Questionable/Model/Quest.cs index 5124de9dd..f778b137c 100644 --- a/Questionable/Model/Quest.cs +++ b/Questionable/Model/Quest.cs @@ -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; } diff --git a/Questionable/Model/QuestInfo.cs b/Questionable/Model/QuestInfo.cs index 7b62f14b7..59a3adad6 100644 --- a/Questionable/Model/QuestInfo.cs +++ b/Questionable/Model/QuestInfo.cs @@ -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 PreviousQuests { get; } + public ImmutableList PreviousQuests { get; } public QuestJoin PreviousQuestJoin { get; } - public ImmutableList QuestLocks { get; } + public ImmutableList QuestLocks { get; } public QuestJoin QuestLockJoin { get; } public List PreviousInstanceContent { get; } public QuestJoin PreviousInstanceContentJoin { get; } diff --git a/Questionable/Validation/ValidationIssue.cs b/Questionable/Validation/ValidationIssue.cs index 5988fd5ba..c0c1c7b6a 100644 --- a/Questionable/Validation/ValidationIssue.cs +++ b/Questionable/Validation/ValidationIssue.cs @@ -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; diff --git a/Questionable/Validation/Validators/AethernetShortcutValidator.cs b/Questionable/Validation/Validators/AethernetShortcutValidator.cs index e620d8a54..e094f83b2 100644 --- a/Questionable/Validation/Validators/AethernetShortcutValidator.cs +++ b/Questionable/Validation/Validators/AethernetShortcutValidator.cs @@ -23,7 +23,7 @@ internal sealed class AethernetShortcutValidator : IQuestValidator .Cast(); } - 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; diff --git a/Questionable/Validation/Validators/JsonSchemaValidator.cs b/Questionable/Validation/Validators/JsonSchemaValidator.cs index 7cacc520a..2efa6be8b 100644 --- a/Questionable/Validation/Validators/JsonSchemaValidator.cs +++ b/Questionable/Validation/Validators/JsonSchemaValidator.cs @@ -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 _questNodes = new(); + private readonly Dictionary _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(); } diff --git a/Questionable/Windows/DebugOverlay.cs b/Questionable/Windows/DebugOverlay.cs index e7cc34e79..ed68542c7 100644 --- a/Questionable/Windows/DebugOverlay.cs +++ b/Questionable/Windows/DebugOverlay.cs @@ -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) diff --git a/Questionable/Windows/JournalProgressWindow.cs b/Questionable/Windows/JournalProgressWindow.cs index 74556bdfc..c9dcd9fc1 100644 --- a/Questionable/Windows/JournalProgressWindow.cs +++ b/Questionable/Windows/JournalProgressWindow.cs @@ -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()) diff --git a/Questionable/Windows/QuestComponents/ARealmRebornComponent.cs b/Questionable/Windows/QuestComponents/ARealmRebornComponent.cs index a82329054..39e9ec97b 100644 --- a/Questionable/Windows/QuestComponents/ARealmRebornComponent.cs +++ b/Questionable/Windows/QuestComponents/ARealmRebornComponent.cs @@ -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; diff --git a/Questionable/Windows/QuestComponents/ActiveQuestComponent.cs b/Questionable/Windows/QuestComponents/ActiveQuestComponent.cs index 17ab37f4c..e566eaada 100644 --- a/Questionable/Windows/QuestComponents/ActiveQuestComponent.cs +++ b/Questionable/Windows/QuestComponents/ActiveQuestComponent.cs @@ -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; diff --git a/Questionable/Windows/QuestSelectionWindow.cs b/Questionable/Windows/QuestSelectionWindow.cs index 8da2c2d08..ab8803cb2 100644 --- a/Questionable/Windows/QuestSelectionWindow.cs +++ b/Questionable/Windows/QuestSelectionWindow.cs @@ -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()) diff --git a/Questionable/Windows/QuestValidationWindow.cs b/Questionable/Windows/QuestValidationWindow.cs index be931d6c7..15eff9812 100644 --- a/Questionable/Windows/QuestValidationWindow.cs +++ b/Questionable/Windows/QuestValidationWindow.cs @@ -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()) diff --git a/Questionable/Windows/UiUtils.cs b/Questionable/Windows/UiUtils.cs index ff51d9631..701e874bf 100644 --- a/Questionable/Windows/UiUtils.cs +++ b/Questionable/Windows/UiUtils.cs @@ -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");