Migrate questIds to IId

This commit is contained in:
Liza 2024-08-03 20:30:18 +02:00
parent ee2b49f566
commit 36a0f72bd9
Signed by: liza
GPG Key ID: 7199F8D727D55F67
34 changed files with 349 additions and 201 deletions

View File

@ -162,6 +162,7 @@ public class QuestSourceGenerator : ISourceGenerator
SyntaxNodeList( SyntaxNodeList(
AssignmentList(nameof(QuestRoot.Author), quest.Author) AssignmentList(nameof(QuestRoot.Author), quest.Author)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(QuestRoot.Disabled), quest.Disabled, false).AsSyntaxNodeOrToken(),
Assignment(nameof(QuestRoot.Comment), quest.Comment, null) Assignment(nameof(QuestRoot.Comment), quest.Comment, null)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
AssignmentList(nameof(QuestRoot.TerritoryBlacklist), AssignmentList(nameof(QuestRoot.TerritoryBlacklist),
@ -304,7 +305,8 @@ public class QuestSourceGenerator : ISourceGenerator
Assignment(nameof(QuestStep.ContentFinderConditionId), Assignment(nameof(QuestStep.ContentFinderConditionId),
step.ContentFinderConditionId, emptyStep.ContentFinderConditionId) step.ContentFinderConditionId, emptyStep.ContentFinderConditionId)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.SkipConditions), step.SkipConditions, emptyStep.SkipConditions) Assignment(nameof(QuestStep.SkipConditions), step.SkipConditions,
emptyStep.SkipConditions)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
AssignmentList(nameof(QuestStep.RequiredQuestVariables), AssignmentList(nameof(QuestStep.RequiredQuestVariables),
step.RequiredQuestVariables) step.RequiredQuestVariables)

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
@ -57,6 +56,24 @@ public static class RoslynShortcuts
SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(value.GetType().Name), IdentifierName(value.GetType().Name),
IdentifierName(value.GetType().GetEnumName(value)!)); 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) else if (value is Vector3 vector)
{ {
return ObjectCreationExpression( return ObjectCreationExpression(

View File

@ -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
}
]
}
]
}
]
}

View File

@ -39,8 +39,7 @@
"ItemId": 35848, "ItemId": 35848,
"ItemCount": 1 "ItemCount": 1
} }
], ]
"NextQuestId": 4159
} }
] ]
}, },

View File

@ -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();
}
}

View File

@ -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
{
}
}

View File

@ -72,10 +72,14 @@ public sealed class QuestStep
public IList<uint> PointMenuChoices { get; set; } = new List<uint>(); public IList<uint> PointMenuChoices { get; set; } = new List<uint>();
// TODO: Not implemented // TODO: Not implemented
public ushort? PickUpQuestId { get; set; } [JsonConverter(typeof(IdConverter))]
public IId? PickUpQuestId { get; set; }
public ushort? TurnInQuestId { get; set; } [JsonConverter(typeof(IdConverter))]
public ushort? NextQuestId { get; set; } public IId? TurnInQuestId { get; set; }
[JsonConverter(typeof(IdConverter))]
public IId? NextQuestId { get; set; }
[JsonConstructor] [JsonConstructor]
public QuestStep() public QuestStep()

View File

@ -13,8 +13,8 @@ public sealed class SkipStepConditions
public List<ushort> InTerritory { get; set; } = new(); public List<ushort> InTerritory { get; set; } = new();
public List<ushort> NotInTerritory { get; set; } = new(); public List<ushort> NotInTerritory { get; set; } = new();
public SkipItemConditions? Item { get; set; } public SkipItemConditions? Item { get; set; }
public List<ushort> QuestsAccepted { get; set; } = new(); public List<IId> QuestsAccepted { get; set; } = new();
public List<ushort> QuestsCompleted { get; set; } = new(); public List<IId> QuestsCompleted { get; set; } = new();
public EExtraSkipCondition? ExtraCondition { get; set; } public EExtraSkipCondition? ExtraCondition { get; set; }
public bool HasSkipConditions() public bool HasSkipConditions()

View 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, if (questWork != null && QuestWorkUtils.MatchesQuestWork(condition.CompletionQuestVariablesFlags,
questWork.Value)) questWork.Value))
{ {
@ -303,7 +303,7 @@ internal sealed class CombatController : IDisposable
public sealed class CombatData public sealed class CombatData
{ {
public required ushort QuestId { get; init; } public required IId QuestId { get; init; }
public required EEnemySpawnType SpawnType { get; init; } public required EEnemySpawnType SpawnType { get; init; }
public required List<uint> KillEnemyDataIds { get; init; } public required List<uint> KillEnemyDataIds { get; init; }
public required List<ComplexCombatData> ComplexCombatDatas { get; init; } public required List<ComplexCombatData> ComplexCombatDatas { get; init; }

View File

@ -4,6 +4,7 @@ using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Command; using Dalamud.Game.Command;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing;
using Questionable.Windows; using Questionable.Windows;
using Questionable.Windows.QuestComponents; using Questionable.Windows.QuestComponents;
@ -127,11 +128,11 @@ internal sealed class CommandHandler : IDisposable
return; 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})."); _chatGui.Print($"[Questionable] Set highlighted quest to {questId} ({quest.Info.Name}).");
} }
else else
@ -146,11 +147,11 @@ internal sealed class CommandHandler : IDisposable
private void SetNextQuest(string[] arguments) 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."); _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); _questController.SetNextQuest(quest);
_chatGui.Print($"[Questionable] Set next quest to {questId} ({quest.Info.Name})."); _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 (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); _questController.SimulateQuest(quest);
_chatGui.Print($"[Questionable] Simulating quest {questId} ({quest.Info.Name})."); _chatGui.Print($"[Questionable] Simulating quest {questId} ({quest.Info.Name}).");

View File

@ -600,7 +600,7 @@ internal sealed class GameUiController : IDisposable
private unsafe void UnendingCodexPostSetup(AddonEvent type, AddonArgs args) 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"); _logger.LogInformation("Closing Unending Codex");
AtkUnitBase* addon = (AtkUnitBase*)args.Addon; AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
@ -610,7 +610,7 @@ internal sealed class GameUiController : IDisposable
private unsafe void ContentsTutorialPostSetup(AddonEvent type, AddonArgs args) 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"); _logger.LogInformation("Closing ContentsTutorial");
AtkUnitBase* addon = (AtkUnitBase*)args.Addon; AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
@ -623,7 +623,7 @@ internal sealed class GameUiController : IDisposable
/// </summary> /// </summary>
private unsafe void MultipleHelpWindowPostSetup(AddonEvent type, AddonArgs args) 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"); _logger.LogInformation("Closing MultipleHelpWindow");
AtkUnitBase* addon = (AtkUnitBase*)args.Addon; AtkUnitBase* addon = (AtkUnitBase*)args.Addon;

View File

@ -209,8 +209,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
} }
else else
{ {
(ushort currentQuestId, currentSequence) = _gameFunctions.GetCurrentQuest(); (IId? currentQuestId, currentSequence) = _gameFunctions.GetCurrentQuest();
if (currentQuestId == 0) if (currentQuestId == null || currentQuestId.Value == 0)
{ {
if (_startedQuest != null) if (_startedQuest != null)
{ {
@ -330,7 +330,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
return (seq, seq.Steps[CurrentQuest.Step]); 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) 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) lock (_progressLock)
{ {
@ -609,8 +609,9 @@ internal sealed class QuestController : MiniTaskController<QuestController>
1158, // Titan (Hard) 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)) if (_gameFunctions.IsReadyToAcceptQuest(questId) && _questRegistry.TryGetQuest(questId, out var quest))
{ {
SetNextQuest(quest); SetNextQuest(quest);

View File

@ -28,7 +28,7 @@ internal sealed class QuestRegistry
private readonly ILogger<QuestRegistry> _logger; private readonly ILogger<QuestRegistry> _logger;
private readonly ICallGateProvider<object> _reloadDataIpc; 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, public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData,
QuestValidator questValidator, JsonSchemaValidator jsonSchemaValidator, QuestValidator questValidator, JsonSchemaValidator jsonSchemaValidator,
@ -91,12 +91,12 @@ internal sealed class QuestRegistry
{ {
Quest quest = new() Quest quest = new()
{ {
QuestId = questId, QuestId = new QuestId(questId),
Root = questRoot, Root = questRoot,
Info = _questData.GetQuestInfo(questId), Info = _questData.GetQuestInfo(new QuestId(questId)),
ReadOnly = true, ReadOnly = true,
}; };
_quests[questId] = quest; _quests[quest.QuestId] = quest;
} }
_logger.LogInformation("Loaded {Count} quests from assembly", _quests.Count); _logger.LogInformation("Loaded {Count} quests from assembly", _quests.Count);
@ -136,21 +136,21 @@ internal sealed class QuestRegistry
private void LoadQuestFromStream(string fileName, Stream stream) private void LoadQuestFromStream(string fileName, Stream stream)
{ {
_logger.LogTrace("Loading quest from '{FileName}'", fileName); _logger.LogTrace("Loading quest from '{FileName}'", fileName);
ushort? questId = ExtractQuestIdFromName(fileName); IId? questId = ExtractQuestIdFromName(fileName);
if (questId == null) if (questId == null)
return; return;
var questNode = JsonNode.Parse(stream)!; var questNode = JsonNode.Parse(stream)!;
_jsonSchemaValidator.Enqueue(questId.Value, questNode); _jsonSchemaValidator.Enqueue(questId, questNode);
Quest quest = new Quest Quest quest = new Quest
{ {
QuestId = questId.Value, QuestId = questId,
Root = questNode.Deserialize<QuestRoot>()!, Root = questNode.Deserialize<QuestRoot>()!,
Info = _questData.GetQuestInfo(questId.Value), Info = _questData.GetQuestInfo(questId),
ReadOnly = false, ReadOnly = false,
}; };
_quests[questId.Value] = quest; _quests[quest.QuestId] = quest;
} }
private void LoadFromDirectory(DirectoryInfo directory, LogLevel logLevel = LogLevel.Information) private void LoadFromDirectory(DirectoryInfo directory, LogLevel logLevel = LogLevel.Information)
@ -179,7 +179,7 @@ internal sealed class QuestRegistry
LoadFromDirectory(childDirectory, logLevel); LoadFromDirectory(childDirectory, logLevel);
} }
private static ushort? ExtractQuestIdFromName(string resourceName) private static IId? ExtractQuestIdFromName(string resourceName)
{ {
string name = resourceName.Substring(0, resourceName.Length - ".json".Length); string name = resourceName.Substring(0, resourceName.Length - ".json".Length);
name = name.Substring(name.LastIndexOf('.') + 1); name = name.Substring(name.LastIndexOf('.') + 1);
@ -188,11 +188,30 @@ internal sealed class QuestRegistry
return null; return null;
string[] parts = name.Split('_', 2); 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); => _quests.TryGetValue(questId, out quest);
} }

View File

@ -18,20 +18,20 @@ internal static class NextQuest
if (step.NextQuestId == null) if (step.NextQuestId == null)
return null; return null;
if (step.NextQuestId.Value == quest.QuestId) if (step.NextQuestId == quest.QuestId)
return null; return null;
return serviceProvider.GetRequiredService<SetQuest>() 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 internal sealed class SetQuest(QuestRegistry questRegistry, QuestController questController, GameFunctions gameFunctions, ILogger<SetQuest> logger) : ITask
{ {
public ushort NextQuestId { get; set; } public IId NextQuestId { get; set; } = null!;
public ushort CurrentQuestId { get; set; } public IId CurrentQuestId { get; set; } = null!;
public ITask With(ushort nextQuestId, ushort currentQuestId) public ITask With(IId nextQuestId, IId currentQuestId)
{ {
NextQuestId = nextQuestId; NextQuestId = nextQuestId;
CurrentQuestId = currentQuestId; CurrentQuestId = currentQuestId;

View File

@ -1,7 +1,9 @@
namespace Questionable.Controller.Steps; using Questionable.Model.Questing;
namespace Questionable.Controller.Steps;
internal interface ILastTask : ITask internal interface ILastTask : ITask
{ {
public ushort QuestId { get; } public IId QuestId { get; }
public int Sequence { get; } public int Sequence { get; }
} }

View File

@ -84,7 +84,7 @@ internal static class Combat
private CombatController.CombatData _combatData = null!; private CombatController.CombatData _combatData = null!;
private IList<QuestWorkValue?> _completionQuestVariableFlags = 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) IList<QuestWorkValue?> completionQuestVariablesFlags, IList<ComplexCombatData> complexCombatData)
{ {
_isLastStep = isLastStep; _isLastStep = isLastStep;
@ -107,9 +107,9 @@ internal static class Combat
return ETaskResult.StillRunning; return ETaskResult.StillRunning;
// if our quest step has any completion flags, we need to check if they are set // 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) if (questWork == null)
return ETaskResult.StillRunning; return ETaskResult.StillRunning;

View File

@ -118,7 +118,7 @@ internal static class UseItem
private DateTime _continueAt; private DateTime _continueAt;
private int _itemCount; private int _itemCount;
public ushort? QuestId { get; set; } public IId? QuestId { get; set; }
public uint ItemId { get; set; } public uint ItemId { get; set; }
public IList<QuestWorkValue?> CompletionQuestVariablesFlags { get; set; } = new List<QuestWorkValue?>(); public IList<QuestWorkValue?> CompletionQuestVariablesFlags { get; set; } = new List<QuestWorkValue?>();
public bool StartingCombat { get; set; } public bool StartingCombat { get; set; }
@ -142,9 +142,9 @@ internal static class UseItem
public unsafe ETaskResult Update() 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 && if (questWork != null &&
QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork.Value)) QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork.Value))
return ETaskResult.TaskComplete; return ETaskResult.TaskComplete;
@ -203,7 +203,7 @@ internal static class UseItem
public uint DataId { get; set; } 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; QuestId = questId;
DataId = dataId; DataId = dataId;
@ -227,7 +227,7 @@ internal static class UseItem
public Vector3 Position { get; set; } 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; QuestId = questId;
Position = position; Position = position;
@ -249,7 +249,7 @@ internal static class UseItem
public uint DataId { get; set; } 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) bool startingCombat = false)
{ {
QuestId = questId; QuestId = questId;
@ -270,7 +270,7 @@ internal static class UseItem
{ {
private readonly GameFunctions _gameFunctions = gameFunctions; 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; QuestId = questId;
ItemId = itemId; ItemId = itemId;

View File

@ -45,9 +45,9 @@ internal static class SkipCondition
{ {
public QuestStep Step { get; set; } = null!; public QuestStep Step { get; set; } = null!;
public SkipStepConditions SkipConditions { 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; Step = step;
SkipConditions = skipConditions; SkipConditions = skipConditions;
@ -156,32 +156,35 @@ internal static class SkipCondition
return true; return true;
} }
QuestWork? questWork = gameFunctions.GetQuestEx(QuestId); if (QuestId is QuestId questId)
if (QuestWorkUtils.HasCompletionFlags(Step.CompletionQuestVariablesFlags) && questWork != null)
{ {
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)"); if (QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value))
return true; {
logger.LogInformation("Skipping step, as quest variables match (step is complete)");
return true;
}
} }
}
if (Step is { SkipConditions.StepIf: { } conditions } && questWork != null) if (Step is { SkipConditions.StepIf: { } conditions } && questWork != null)
{
if (QuestWorkUtils.MatchesQuestWork(conditions.CompletionQuestVariablesFlags, questWork.Value))
{ {
logger.LogInformation("Skipping step, as quest variables match (step can be skipped)"); if (QuestWorkUtils.MatchesQuestWork(conditions.CompletionQuestVariablesFlags, questWork.Value))
return true; {
logger.LogInformation("Skipping step, as quest variables match (step can be skipped)");
return true;
}
} }
}
if (Step is { RequiredQuestVariables: { } requiredQuestVariables } && questWork != null) if (Step is { RequiredQuestVariables: { } requiredQuestVariables } && questWork != null)
{
if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork.Value,
logger))
{ {
logger.LogInformation("Skipping step, as required variables do not match"); if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork.Value,
return true; 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"); logger.LogInformation("Skipping step, as we have already picked up the relevant quest");
return true; 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"); logger.LogInformation("Skipping step, as we have already completed the relevant quest");
return true; return true;

View File

@ -30,7 +30,7 @@ internal static class WaitAtEnd
if (step.CompletionQuestVariablesFlags.Count == 6 && QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags)) if (step.CompletionQuestVariablesFlags.Count == 6 && QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
{ {
var task = serviceProvider.GetRequiredService<WaitForCompletionFlags>() var task = serviceProvider.GetRequiredService<WaitForCompletionFlags>()
.With(quest, step); .With((QuestId)quest.QuestId, step);
var delay = serviceProvider.GetRequiredService<WaitDelay>(); var delay = serviceProvider.GetRequiredService<WaitDelay>();
return [task, delay, Next(quest, sequence)]; return [task, delay, Next(quest, sequence)];
} }
@ -162,11 +162,11 @@ internal static class WaitAtEnd
internal sealed class WaitForCompletionFlags(GameFunctions gameFunctions) : ITask 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 QuestStep Step { get; set; } = null!;
public IList<QuestWorkValue?> Flags { 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; Quest = quest;
Step = step; Step = step;
@ -178,7 +178,7 @@ internal static class WaitAtEnd
public ETaskResult Update() public ETaskResult Update()
{ {
QuestWork? questWork = gameFunctions.GetQuestEx(Quest.QuestId); QuestWork? questWork = gameFunctions.GetQuestEx(Quest);
return questWork != null && return questWork != null &&
QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value) QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value)
? ETaskResult.TaskComplete ? ETaskResult.TaskComplete
@ -214,11 +214,11 @@ internal static class WaitAtEnd
$"WaitObj({DataId} at {Destination.ToString("G", CultureInfo.InvariantCulture)} < {Distance})"; $"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; QuestId = questId;
return this; return this;
@ -228,23 +228,19 @@ internal static class WaitAtEnd
public ETaskResult Update() public ETaskResult Update()
{ {
unsafe return gameFunctions.IsQuestAccepted(QuestId)
{ ? ETaskResult.TaskComplete
var questManager = QuestManager.Instance(); : ETaskResult.StillRunning;
return questManager != null && questManager->IsQuestAccepted(QuestId)
? ETaskResult.TaskComplete
: ETaskResult.StillRunning;
}
} }
public override string ToString() => $"WaitQuestAccepted({QuestId})"; 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; QuestId = questId;
return this; return this;
@ -254,15 +250,15 @@ internal static class WaitAtEnd
public ETaskResult Update() 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})"; 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 int Sequence { get; } = sequence;
public bool Start() => true; public bool Start() => true;
@ -274,7 +270,7 @@ internal static class WaitAtEnd
internal sealed class EndAutomation : ILastTask internal sealed class EndAutomation : ILastTask
{ {
public ushort QuestId => throw new InvalidOperationException(); public IId QuestId => throw new InvalidOperationException();
public int Sequence => throw new InvalidOperationException(); public int Sequence => throw new InvalidOperationException();
public bool Start() => true; public bool Start() => true;

View File

@ -3,6 +3,7 @@ using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Data; namespace Questionable.Data;
@ -21,15 +22,17 @@ internal sealed class JournalData
var genreLimsa = new Genre(uint.MaxValue - 3, "Starting in Limsa Lominsa", 1, var genreLimsa = new Genre(uint.MaxValue - 3, "Starting in Limsa Lominsa", 1,
new uint[] { 108, 109 }.Concat(limsaStart.Quest.Select(x => x.Row)) new uint[] { 108, 109 }.Concat(limsaStart.Quest.Select(x => x.Row))
.Where(x => x != 0) .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, var genreGridania = new Genre(uint.MaxValue - 2, "Starting in Gridania", 1,
new uint[] { 85, 123, 124 }.Concat(gridaniaStart.Quest.Select(x => x.Row)) new uint[] { 85, 123, 124 }.Concat(gridaniaStart.Quest.Select(x => x.Row))
.Where(x => x != 0) .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, var genreUldah = new Genre(uint.MaxValue - 1, "Starting in Ul'dah", 1,
new uint[] { 568, 569, 570 }.Concat(uldahStart.Quest.Select(x => x.Row)) new uint[] { 568, 569, 570 }.Concat(uldahStart.Quest.Select(x => x.Row))
.Where(x => x != 0) .Where(x => x != 0)
.Select(x => questData.GetQuestInfo((ushort)(x & 0xFFFF))) .Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
.ToList()); .ToList());
genres.InsertRange(0, [genreLimsa, genreGridania, genreUldah]); genres.InsertRange(0, [genreLimsa, genreGridania, genreUldah]);
genres.Single(x => x.Id == 1) genres.Single(x => x.Id == 1)

View File

@ -4,13 +4,14 @@ using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing;
using Quest = Lumina.Excel.GeneratedSheets.Quest; using Quest = Lumina.Excel.GeneratedSheets.Quest;
namespace Questionable.Data; namespace Questionable.Data;
internal sealed class QuestData internal sealed class QuestData
{ {
private readonly ImmutableDictionary<ushort, QuestInfo> _quests; private readonly ImmutableDictionary<QuestId, QuestInfo> _quests;
public QuestData(IDataManager dataManager) public QuestData(IDataManager dataManager)
{ {
@ -22,7 +23,15 @@ internal sealed class QuestData
.ToImmutableDictionary(x => x.QuestId, x => x); .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)); return _quests[questId] ?? throw new ArgumentOutOfRangeException(nameof(questId));
} }

View File

@ -89,37 +89,36 @@ internal sealed unsafe class GameFunctions
public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue; public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue;
public (ushort CurrentQuest, byte Sequence) GetCurrentQuest() public (IId? CurrentQuest, byte Sequence) GetCurrentQuest()
{ {
var (currentQuest, sequence) = GetCurrentQuestInternal(); var (currentQuest, sequence) = GetCurrentQuestInternal();
QuestManager* questManager = QuestManager.Instance();
PlayerState* playerState = PlayerState.Instance(); PlayerState* playerState = PlayerState.Instance();
if (currentQuest == 0) if (currentQuest == null || currentQuest.Value == 0)
{ {
if (_clientState.TerritoryType == 181) // Starting in Limsa if (_clientState.TerritoryType == 181) // Starting in Limsa
return (107, 0); return (new QuestId(107), 0);
if (_clientState.TerritoryType == 182) // Starting in Ul'dah if (_clientState.TerritoryType == 182) // Starting in Ul'dah
return (594, 0); return (new QuestId(594), 0);
if (_clientState.TerritoryType == 183) // Starting in Gridania if (_clientState.TerritoryType == 183) // Starting in Gridania
return (39, 0); return (new QuestId(39), 0);
return default; 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 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); return (currentQuest, sequence);
// The company you keep... // The company you keep...
return _configuration.General.GrandCompany switch return _configuration.General.GrandCompany switch
{ {
GrandCompany.TwinAdder => (680, 0), GrandCompany.TwinAdder => (new QuestId(680), 0),
GrandCompany.Maelstrom => (681, 0), GrandCompany.Maelstrom => (new QuestId(681), 0),
_ => default _ => 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 ushort chocoboQuest = (GrandCompany)playerState->GrandCompany switch
{ {
@ -129,20 +128,20 @@ internal sealed unsafe class GameFunctions
}; };
if (chocoboQuest != 0 && !QuestManager.IsQuestComplete(chocoboQuest)) 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 // skeletons in her closet, finish 'broadening horizons' to unlock the white wolf gate
ushort broadeningHorizons = 802; QuestId broadeningHorizons = new QuestId(802);
if (questManager->IsQuestAccepted(broadeningHorizons)) if (IsQuestAccepted(broadeningHorizons))
return (broadeningHorizons, QuestManager.GetQuestSequence(broadeningHorizons)); return (broadeningHorizons, QuestManager.GetQuestSequence(broadeningHorizons.Value));
} }
return (currentQuest, sequence); return (currentQuest, sequence);
} }
public (ushort CurrentQuest, byte Sequence) GetCurrentQuestInternal() public (IId? CurrentQuest, byte Sequence) GetCurrentQuestInternal()
{ {
var questManager = QuestManager.Instance(); var questManager = QuestManager.Instance();
if (questManager != null) 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 // 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. // side quests until the end of time.
var msqQuest = GetMainScenarioQuest(questManager); 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; 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, // 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. // 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) for (int i = questManager->TrackedQuests.Length - 1; i >= 0; --i)
{ {
ushort currentQuest; IId currentQuest;
var trackedQuest = questManager->TrackedQuests[i]; var trackedQuest = questManager->TrackedQuests[i];
switch (trackedQuest.QuestType) switch (trackedQuest.QuestType)
{ {
@ -167,12 +166,12 @@ internal sealed unsafe class GameFunctions
continue; continue;
case 1: // normal quest case 1: // normal quest
currentQuest = questManager->NormalQuests[trackedQuest.Index].QuestId; currentQuest = new QuestId(questManager->NormalQuests[trackedQuest.Index].QuestId);
break; break;
} }
if (_questRegistry.IsKnownQuest(currentQuest)) 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 // 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; return default;
} }
private (ushort CurrentQuest, byte Sequence) GetMainScenarioQuest(QuestManager* questManager) private (QuestId? CurrentQuest, byte Sequence) GetMainScenarioQuest(QuestManager* questManager)
{ {
if (QuestManager.IsQuestComplete(3759)) // Memories Rekindled if (QuestManager.IsQuestComplete(3759)) // Memories Rekindled
{ {
@ -202,7 +201,7 @@ internal sealed unsafe class GameFunctions
// redoHud+44 is chapter // redoHud+44 is chapter
// redoHud+46 is quest // redoHud+46 is quest
ushort questId = MemoryHelper.Read<ushort>((nint)questRedoHud + 46); 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) if (scenarioTree->Data == null)
return default; return default;
ushort currentQuest = scenarioTree->Data->CurrentScenarioQuest; QuestId currentQuest = new QuestId(scenarioTree->Data->CurrentScenarioQuest);
if (currentQuest == 0) if (currentQuest.Value == 0)
return default; return default;
// if the MSQ is hidden, we generally ignore it // 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; return default;
// it can sometimes happen (although this isn't reliably reproducible) that the quest returned here // 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) && quest.Info.Level > currentLevel)
return default; 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; 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); _questRegistry.TryGetQuest(questId, out var quest);
if (quest is { Info.IsRepeatable: true }) if (quest is { Info.IsRepeatable: true })
@ -268,29 +274,50 @@ internal sealed unsafe class GameFunctions
return true; return true;
} }
public bool IsQuestAcceptedOrComplete(ushort questId) public bool IsQuestAcceptedOrComplete(IId questId)
{ {
return IsQuestComplete(questId) || IsQuestAccepted(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(); 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")] [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); var questInfo = _questData.GetQuestInfo(questId);
if (questInfo.QuestLocks.Count > 0) 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) if (questInfo.QuestLockJoin == QuestInfo.QuestJoin.All && questInfo.QuestLocks.Count == completedQuests)
return true; return true;
else if (questInfo.QuestLockJoin == QuestInfo.QuestJoin.AtLeastOne && completedQuests > 0) else if (questInfo.QuestLockJoin == QuestInfo.QuestJoin.AtLeastOne && completedQuests > 0)
@ -303,12 +330,12 @@ internal sealed unsafe class GameFunctions
return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo); 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) if (questInfo.PreviousQuests.Count == 0)
return true; 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 && if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.All &&
questInfo.PreviousQuests.Count == completedQuests) questInfo.PreviousQuests.Count == completedQuests)
return true; return true;
@ -388,7 +415,7 @@ internal sealed unsafe class GameFunctions
if (_configuration.Advanced.NeverFly) if (_configuration.Advanced.NeverFly)
return false; return false;
if (IsQuestAccepted(3304) && _condition[ConditionFlag.Mounted]) if (IsQuestAccepted(new QuestId(3304)) && _condition[ConditionFlag.Mounted])
{ {
BattleChara* battleChara = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0); BattleChara* battleChara = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0);
if (battleChara != null && battleChara->Mount.MountId == 198) // special quest amaro, not the normal one 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) if (excelSheetName == null)
{ {
var questRow = var questRow =
_dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.QuestId + _dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.QuestId.Value +
0x10000); 0x10000);
if (questRow == null) if (questRow == null)
{ {
@ -688,7 +715,7 @@ internal sealed unsafe class GameFunctions
return null; 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); var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName);

View File

@ -6,7 +6,7 @@ namespace Questionable.Model;
internal sealed class Quest internal sealed class Quest
{ {
public required ushort QuestId { get; init; } public required IId QuestId { get; init; }
public required QuestRoot Root { get; init; } public required QuestRoot Root { get; init; }
public required QuestInfo Info { get; init; } public required QuestInfo Info { get; init; }
public required bool ReadOnly { get; init; } public required bool ReadOnly { get; init; }

View File

@ -5,6 +5,7 @@ using System.Linq;
using Dalamud.Game.Text; using Dalamud.Game.Text;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using JetBrains.Annotations; using JetBrains.Annotations;
using Questionable.Model.Questing;
using ExcelQuest = Lumina.Excel.GeneratedSheets.Quest; using ExcelQuest = Lumina.Excel.GeneratedSheets.Quest;
namespace Questionable.Model; namespace Questionable.Model;
@ -13,9 +14,9 @@ internal sealed class QuestInfo
{ {
public QuestInfo(ExcelQuest quest) 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)", 85 => " (Lancer)",
108 => " (Marauder)", 108 => " (Marauder)",
@ -34,9 +35,15 @@ internal sealed class QuestInfo
Level = quest.ClassJobLevel0; Level = quest.ClassJobLevel0;
IssuerDataId = quest.IssuerStart; IssuerDataId = quest.IssuerStart;
IsRepeatable = quest.IsRepeatable; 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; 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; QuestLockJoin = (QuestJoin)quest.QuestLockJoin;
JournalGenre = quest.JournalGenre?.Row; JournalGenre = quest.JournalGenre?.Row;
SortKey = quest.SortKey; SortKey = quest.SortKey;
@ -49,14 +56,14 @@ internal sealed class QuestInfo
} }
public ushort QuestId { get; } public QuestId QuestId { get; }
public string Name { get; } public string Name { get; }
public ushort Level { get; } public ushort Level { get; }
public uint IssuerDataId { get; } public uint IssuerDataId { get; }
public bool IsRepeatable { get; } public bool IsRepeatable { get; }
public ImmutableList<ushort> PreviousQuests { get; } public ImmutableList<QuestId> PreviousQuests { get; }
public QuestJoin PreviousQuestJoin { get; } public QuestJoin PreviousQuestJoin { get; }
public ImmutableList<ushort> QuestLocks { get; } public ImmutableList<QuestId> QuestLocks { get; }
public QuestJoin QuestLockJoin { get; } public QuestJoin QuestLockJoin { get; }
public List<ushort> PreviousInstanceContent { get; } public List<ushort> PreviousInstanceContent { get; }
public QuestJoin PreviousInstanceContentJoin { get; } public QuestJoin PreviousInstanceContentJoin { get; }

View File

@ -1,10 +1,11 @@
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Validation; namespace Questionable.Validation;
internal sealed record ValidationIssue internal sealed record ValidationIssue
{ {
public required ushort? QuestId { get; init; } public required IId? QuestId { get; init; }
public required byte? Sequence { get; init; } public required byte? Sequence { get; init; }
public required int? Step { get; init; } public required int? Step { get; init; }
public EBeastTribe BeastTribe { get; init; } = EBeastTribe.None; public EBeastTribe BeastTribe { get; init; } = EBeastTribe.None;

View File

@ -23,7 +23,7 @@ internal sealed class AethernetShortcutValidator : IQuestValidator
.Cast<ValidationIssue>(); .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) if (aethernetShortcut == null)
return null; return null;

View File

@ -4,13 +4,14 @@ using System.Globalization;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Json.Schema; using Json.Schema;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing;
using Questionable.QuestPaths; using Questionable.QuestPaths;
namespace Questionable.Validation.Validators; namespace Questionable.Validation.Validators;
internal sealed class JsonSchemaValidator : IQuestValidator internal sealed class JsonSchemaValidator : IQuestValidator
{ {
private readonly Dictionary<ushort, JsonNode> _questNodes = new(); private readonly Dictionary<IId, JsonNode> _questNodes = new();
private JsonSchema? _questSchema; private JsonSchema? _questSchema;
public JsonSchemaValidator() 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(); public void Reset() => _questNodes.Clear();
} }

View File

@ -46,7 +46,7 @@ internal sealed class DebugOverlay : Window
IsOpen = true; IsOpen = true;
} }
public ushort? HighlightedQuest { get; set; } public IId? HighlightedQuest { get; set; }
public override bool DrawConditions() => _configuration.Advanced.DebugOverlay; public override bool DrawConditions() => _configuration.Advanced.DebugOverlay;
@ -93,7 +93,7 @@ internal sealed class DebugOverlay : Window
private void DrawHighlightedQuest() private void DrawHighlightedQuest()
{ {
if (HighlightedQuest == null || !_questRegistry.TryGetQuest(HighlightedQuest.Value, out var quest)) if (HighlightedQuest == null || !_questRegistry.TryGetQuest(HighlightedQuest, out var quest))
return; return;
foreach (var sequence in quest.Root.QuestSequence) foreach (var sequence in quest.Root.QuestSequence)

View File

@ -201,8 +201,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
if (ImGui.IsItemClicked() && _commandManager.Commands.TryGetValue("/questinfo", out var commandInfo)) if (ImGui.IsItemClicked() && _commandManager.Commands.TryGetValue("/questinfo", out var commandInfo))
{ {
_commandManager.DispatchCommand("/questinfo", _commandManager.DispatchCommand("/questinfo", questInfo.QuestId.ToString(), commandInfo);
questInfo.QuestId.ToString(CultureInfo.InvariantCulture), commandInfo);
} }
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())

View File

@ -4,16 +4,19 @@ using Dalamud.Interface.Utility.Raii;
using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Common.Math; using FFXIVClientStructs.FFXIV.Common.Math;
using Questionable.Data; using Questionable.Data;
using Questionable.Model.Questing;
namespace Questionable.Windows.QuestComponents; namespace Questionable.Windows.QuestComponents;
internal sealed class ARealmRebornComponent internal sealed class ARealmRebornComponent
{ {
private const ushort ATimeForEveryPurpose = 425; private static readonly QuestId ATimeForEveryPurpose = new(425);
private const ushort TheUltimateWeapon = 524; private static readonly QuestId TheUltimateWeapon = new(524);
private const ushort GoodIntentions = 363; private static readonly QuestId GoodIntentions = new(363);
private static readonly ushort[] RequiredPrimalInstances = [20004, 20006, 20005]; 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 GameFunctions _gameFunctions;
private readonly QuestData _questData; private readonly QuestData _questData;

View File

@ -151,7 +151,10 @@ internal sealed class ActiveQuestComponent
private QuestWork? DrawQuestWork(QuestController.QuestProgress currentQuest) 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) if (questWork != null)
{ {
Vector4 color; Vector4 color;
@ -271,7 +274,7 @@ internal sealed class ActiveQuestComponent
ImGui.SameLine(); ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Atlas)) if (ImGuiComponents.IconButton(FontAwesomeIcon.Atlas))
_commandManager.DispatchCommand("/questinfo", _commandManager.DispatchCommand("/questinfo",
currentQuest.Quest.QuestId.ToString(CultureInfo.InvariantCulture), commandInfo); currentQuest.Quest.QuestId.ToString() ?? string.Empty, commandInfo);
} }
bool autoAcceptNextQuest = _configuration.General.AutoAcceptNextQuest; bool autoAcceptNextQuest = _configuration.General.AutoAcceptNextQuest;

View File

@ -110,7 +110,7 @@ internal sealed class QuestSelectionWindow : LWindow
foreach (var unacceptedQuest in Map.Instance()->UnacceptedQuestMarkers) 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)) if (_quests.All(q => q.QuestId != questId))
_quests.Add(_questData.GetQuestInfo(questId)); _quests.Add(_questData.GetQuestInfo(questId));
} }
@ -161,7 +161,7 @@ internal sealed class QuestSelectionWindow : LWindow
{ {
ImGui.TableNextRow(); ImGui.TableNextRow();
string questId = quest.QuestId.ToString(CultureInfo.InvariantCulture); string questId = quest.QuestId.ToString();
bool isKnownQuest = _questRegistry.TryGetQuest(quest.QuestId, out var knownQuest); bool isKnownQuest = _questRegistry.TryGetQuest(quest.QuestId, out var knownQuest);
if (ImGui.TableNextColumn()) if (ImGui.TableNextColumn())

View File

@ -56,11 +56,11 @@ internal sealed class QuestValidationWindow : LWindow
ImGui.TableNextRow(); ImGui.TableNextRow();
if (ImGui.TableNextColumn()) if (ImGui.TableNextColumn())
ImGui.TextUnformatted(validationIssue.QuestId?.ToString(CultureInfo.InvariantCulture) ?? string.Empty); ImGui.TextUnformatted(validationIssue.QuestId?.ToString() ?? string.Empty);
if (ImGui.TableNextColumn()) if (ImGui.TableNextColumn())
ImGui.TextUnformatted(validationIssue.QuestId != null ImGui.TextUnformatted(validationIssue.QuestId != null
? _questData.GetQuestInfo(validationIssue.QuestId.Value).Name ? _questData.GetQuestInfo(validationIssue.QuestId).Name
: validationIssue.BeastTribe.ToString()); : validationIssue.BeastTribe.ToString());
if (ImGui.TableNextColumn()) if (ImGui.TableNextColumn())

View File

@ -4,6 +4,7 @@ using Dalamud.Interface.Colors;
using Dalamud.Plugin; using Dalamud.Plugin;
using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.Game.UI;
using ImGuiNET; using ImGuiNET;
using Questionable.Model.Questing;
namespace Questionable.Windows; namespace Questionable.Windows;
@ -18,7 +19,7 @@ internal sealed class UiUtils
_pluginInterface = pluginInterface; _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)) if (_gameFunctions.IsQuestAccepted(questId))
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Active"); return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Active");