-/.idea
+obj/
+bin/
+/.idea
+/.vs
*.user
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <IsPackable>false</IsPackable>
+ <LangVersion>12</LangVersion>
+ <Nullable>enable</Nullable>
+ <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
+ <RootNamespace>Questionable.QuestPathGenerator</RootNamespace>
+ <DebugType>portable</DebugType>
+
+ <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
+ <IsRoslynComponent>true</IsRoslynComponent>
+
+ <PackageId>QuestPathGenerator</PackageId>
+ <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2"/>
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.2"/>
+ <PackageReference Include="System.Text.Json" Version="8.0.3" PrivateAssets="all"/>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj" />
+ </ItemGroup>
+
+ <Target Name="GetDependencyTargetPaths" AfterTargets="ResolvePackageDependenciesForBuild">
+ <ItemGroup>
+ <TargetPathWithTargetPlatformMoniker Include="..\Questionable.Model\$(OutputPath)\*.dll" IncludeRuntimeDependency="false" />
+ </ItemGroup>
+ </Target>
+</Project>
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Questionable.Model.V1;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+using static Questionable.QuestPathGenerator.RoslynShortcuts;
+
+namespace Questionable.QuestPathGenerator;
+
+/// <summary>
+/// A sample source generator that creates C# classes based on the text file (in this case, Domain Driven Design ubiquitous language registry).
+/// When using a simple text file as a baseline, we can create a non-incremental source generator.
+/// </summary>
+[Generator]
+public class QuestSourceGenerator : ISourceGenerator
+{
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ // No initialization required for this generator.
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ List<(ushort, QuestData)> quests = [];
+
+ // Go through all files marked as an Additional File in file properties.
+ foreach (var additionalFile in context.AdditionalFiles)
+ {
+ if (additionalFile == null)
+ continue;
+
+ if (Path.GetExtension(additionalFile.Path) != ".json")
+ continue;
+
+ string name = Path.GetFileName(additionalFile.Path);
+ ushort id = ushort.Parse(name.Substring(0, name.IndexOf('_')));
+
+ var text = additionalFile.GetText();
+ if (text == null)
+ continue;
+
+ var quest = JsonSerializer.Deserialize<QuestData>(text.ToString())!;
+ quests.Add((id, quest));
+ }
+
+ quests = quests.OrderBy(x => x.Item1).ToList();
+
+ var code =
+ CompilationUnit()
+ .WithUsings(
+ List(
+ new[]
+ {
+ UsingDirective(
+ IdentifierName("System")),
+ UsingDirective(
+ QualifiedName(
+ IdentifierName("System"),
+ IdentifierName("Numerics"))),
+ UsingDirective(
+ QualifiedName(
+ IdentifierName("System"),
+ IdentifierName("IO"))),
+ UsingDirective(
+ QualifiedName(
+ QualifiedName(
+ IdentifierName("System"), IdentifierName("Collections")),
+ IdentifierName("Generic"))),
+ UsingDirective(
+ QualifiedName(
+ QualifiedName(
+ IdentifierName("Questionable"),
+ IdentifierName("Model")),
+ IdentifierName("V1")))
+ }))
+ .WithMembers(
+ SingletonList<MemberDeclarationSyntax>(
+ FileScopedNamespaceDeclaration(
+ QualifiedName(
+ IdentifierName("Questionable"),
+ IdentifierName("QuestPaths")))
+ .WithMembers(
+ SingletonList<MemberDeclarationSyntax>(
+ ClassDeclaration("AssemblyQuestLoader")
+ .WithModifiers(
+ TokenList(
+ [
+ Token(SyntaxKind.PartialKeyword)
+ ]))
+ .WithMembers(
+ SingletonList<MemberDeclarationSyntax>(
+ FieldDeclaration(
+ VariableDeclaration(
+ GenericName(
+ Identifier("IReadOnlyDictionary"))
+ .WithTypeArgumentList(
+ TypeArgumentList(
+ SeparatedList<TypeSyntax>(
+ new SyntaxNodeOrToken[]
+ {
+ PredefinedType(
+ Token(SyntaxKind
+ .UShortKeyword)),
+ Token(SyntaxKind.CommaToken),
+ IdentifierName("QuestData")
+ }))))
+ .WithVariables(
+ SingletonSeparatedList(
+ VariableDeclarator(
+ Identifier("Quests"))
+ .WithInitializer(
+ EqualsValueClause(
+ ObjectCreationExpression(
+ GenericName(
+ Identifier(
+ "Dictionary"))
+ .WithTypeArgumentList(
+ TypeArgumentList(
+ SeparatedList<
+ TypeSyntax>(
+ new
+ SyntaxNodeOrToken
+ []
+ {
+ PredefinedType(
+ Token(
+ SyntaxKind
+ .UShortKeyword)),
+ Token(
+ SyntaxKind
+ .CommaToken),
+ IdentifierName(
+ "QuestData")
+ }))))
+ .WithArgumentList(
+ ArgumentList())
+ .WithInitializer(
+ InitializerExpression(
+ SyntaxKind
+ .CollectionInitializerExpression,
+ SeparatedList<
+ ExpressionSyntax>(
+ quests.SelectMany(x =>
+ CreateQuestInitializer(
+ x.Item1,
+ x.Item2)
+ .ToArray())))))))))
+ .WithModifiers(
+ TokenList(
+ [
+ Token(SyntaxKind.InternalKeyword),
+ Token(SyntaxKind.StaticKeyword)
+ ]))))))))
+ .NormalizeWhitespace();
+
+ // Add the source code to the compilation.
+ context.AddSource("AssemblyQuestLoader.g.cs", code.ToFullString());
+ }
+
+ private static IEnumerable<SyntaxNodeOrToken> CreateQuestInitializer(ushort questId, QuestData quest)
+ {
+ return new SyntaxNodeOrToken[]
+ {
+ InitializerExpression(
+ SyntaxKind.ComplexElementInitializerExpression,
+ SeparatedList<ExpressionSyntax>(
+ new SyntaxNodeOrToken[]
+ {
+ LiteralExpression(
+ SyntaxKind.NumericLiteralExpression,
+ Literal(questId)),
+ Token(SyntaxKind.CommaToken),
+ ObjectCreationExpression(
+ IdentifierName(nameof(QuestData)))
+ .WithInitializer(
+ InitializerExpression(
+ SyntaxKind.ObjectInitializerExpression,
+ SeparatedList<ExpressionSyntax>(
+ SyntaxNodeList(
+ Assignment(nameof(QuestData.Author), quest.Author, null)
+ .AsSyntaxNodeOrToken(),
+ AssignmentList(nameof(QuestData.Contributors), quest.Contributors)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestData.Comment), quest.Comment, null)
+ .AsSyntaxNodeOrToken(),
+ AssignmentList(nameof(QuestData.TerritoryBlacklist),
+ quest.TerritoryBlacklist).AsSyntaxNodeOrToken(),
+ AssignmentExpression(
+ SyntaxKind.SimpleAssignmentExpression,
+ IdentifierName(nameof(QuestData.QuestSequence)),
+ CreateQuestSequence(quest.QuestSequence))
+ ))))
+ })),
+ Token(SyntaxKind.CommaToken)
+ };
+ }
+
+ private static ExpressionSyntax CreateQuestSequence(List<QuestSequence> sequences)
+ {
+ return CollectionExpression(
+ SeparatedList<CollectionElementSyntax>(
+ sequences.SelectMany(sequence => new SyntaxNodeOrToken[]
+ {
+ ExpressionElement(
+ ObjectCreationExpression(
+ IdentifierName(nameof(QuestSequence)))
+ .WithInitializer(
+ InitializerExpression(
+ SyntaxKind.ObjectInitializerExpression,
+ SeparatedList<ExpressionSyntax>(
+ SyntaxNodeList(
+ Assignment<int?>(nameof(QuestSequence.Sequence), sequence.Sequence, null)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestSequence.Comment), sequence.Comment, null)
+ .AsSyntaxNodeOrToken(),
+ AssignmentExpression(
+ SyntaxKind.SimpleAssignmentExpression,
+ IdentifierName(nameof(QuestSequence.Steps)),
+ CreateQuestSteps(sequence.Steps))))))),
+ Token(SyntaxKind.CommaToken),
+ }.ToArray())));
+ }
+
+ private static ExpressionSyntax CreateQuestSteps(List<QuestStep> steps)
+ {
+ QuestStep emptyStep = new();
+ return CollectionExpression(
+ SeparatedList<CollectionElementSyntax>(
+ steps.SelectMany(step => new SyntaxNodeOrToken[]
+ {
+ ExpressionElement(
+ ObjectCreationExpression(
+ IdentifierName(nameof(QuestStep)))
+ .WithArgumentList(
+ ArgumentList(
+ SeparatedList<ArgumentSyntax>(
+ new SyntaxNodeOrToken[]
+ {
+ Argument(LiteralValue(step.InteractionType)),
+ Token(SyntaxKind.CommaToken),
+ Argument(LiteralValue(step.DataId)),
+ Token(SyntaxKind.CommaToken),
+ Argument(LiteralValue(step.Position)),
+ Token(SyntaxKind.CommaToken),
+ Argument(LiteralValue(step.TerritoryId))
+ })))
+ .WithInitializer(
+ InitializerExpression(
+ SyntaxKind.ObjectInitializerExpression,
+ SeparatedList<ExpressionSyntax>(
+ SyntaxNodeList(
+ Assignment(nameof(QuestStep.StopDistance), step.StopDistance,
+ emptyStep.StopDistance)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.TargetTerritoryId), step.TargetTerritoryId,
+ emptyStep.TargetTerritoryId)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.Disabled), step.Disabled, emptyStep.Disabled)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.DisableNavmesh), step.DisableNavmesh,
+ emptyStep.DisableNavmesh)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.Mount), step.Mount, emptyStep.Mount)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.Fly), step.Fly, emptyStep.Fly)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.Sprint), step.Sprint, emptyStep.Sprint)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.Comment), step.Comment, emptyStep.Comment)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.AetheryteShortcut), step.AetheryteShortcut,
+ emptyStep.AetheryteShortcut)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.AethernetShortcut), step.AethernetShortcut,
+ emptyStep.AethernetShortcut)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.AetherCurrentId), step.AetherCurrentId,
+ emptyStep.AetherCurrentId)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.ItemId), step.ItemId, emptyStep.ItemId)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.GroundTarget), step.GroundTarget,
+ emptyStep.GroundTarget)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.Emote), step.Emote, emptyStep.Emote)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.ChatMessage), step.ChatMessage,
+ emptyStep.ChatMessage)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.EnemySpawnType), step.EnemySpawnType,
+ emptyStep.EnemySpawnType)
+ .AsSyntaxNodeOrToken(),
+ AssignmentList(nameof(QuestStep.KillEnemyDataIds), step.KillEnemyDataIds)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.JumpDestination), step.JumpDestination,
+ emptyStep.JumpDestination)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.ContentFinderConditionId),
+ step.ContentFinderConditionId, emptyStep.ContentFinderConditionId)
+ .AsSyntaxNodeOrToken(),
+ AssignmentList(nameof(QuestStep.SkipIf), step.SkipIf)
+ .AsSyntaxNodeOrToken(),
+ AssignmentList(nameof(QuestStep.CompletionQuestVariablesFlags),
+ step.CompletionQuestVariablesFlags)
+ .AsSyntaxNodeOrToken(),
+ AssignmentList(nameof(QuestStep.DialogueChoices), step.DialogueChoices)
+ .AsSyntaxNodeOrToken()))))),
+ Token(SyntaxKind.CommaToken),
+ }.ToArray())));
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Questionable.Model.V1;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+
+namespace Questionable.QuestPathGenerator;
+
+public static class RoslynShortcuts
+{
+ public static IEnumerable<SyntaxNodeOrToken> SyntaxNodeList(params SyntaxNodeOrToken?[] nodes)
+ {
+ nodes = nodes.Where(x => x != null).ToArray();
+ if (nodes.Length == 0)
+ return [];
+
+ List<SyntaxNodeOrToken> list = new();
+ for (int i = 0; i < nodes.Length; ++i)
+ {
+ if (i > 0)
+ list.Add(Token(SyntaxKind.CommaToken));
+ list.Add(nodes[i].GetValueOrDefault());
+ }
+
+ return list;
+ }
+
+ public static ExpressionSyntax LiteralValue<T>(T? value)
+ {
+ if (value is string s)
+ return LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(s));
+ else if (value is bool b)
+ return LiteralExpression(b ? SyntaxKind.TrueLiteralExpression : SyntaxKind.FalseLiteralExpression);
+ else if (value is short i16)
+ return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(i16));
+ else if (value is int i32)
+ return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(i32));
+ else if (value is ushort u16)
+ return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(u16));
+ else if (value is uint u32)
+ return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(u32));
+ else if (value is float f)
+ return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(f));
+ else if (value != null && value.GetType().IsEnum)
+ return MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(value.GetType().Name),
+ IdentifierName(value.GetType().GetEnumName(value)!));
+ else if (value is Vector3 vector)
+ {
+ return ObjectCreationExpression(
+ IdentifierName(nameof(Vector3)))
+ .WithArgumentList(
+ ArgumentList(
+ SeparatedList<ArgumentSyntax>(
+ new SyntaxNodeOrToken[]
+ {
+ Argument(LiteralValue(vector.X)),
+ Token(SyntaxKind.CommaToken),
+ Argument(LiteralValue(vector.Y)),
+ Token(SyntaxKind.CommaToken),
+ Argument(LiteralValue(vector.Z))
+ })));
+ }
+ else if (value is AethernetShortcut aethernetShortcut)
+ {
+ return ObjectCreationExpression(
+ IdentifierName(nameof(AethernetShortcut)))
+ .WithInitializer(
+ InitializerExpression(
+ SyntaxKind.ObjectInitializerExpression,
+ SeparatedList<ExpressionSyntax>(
+ SyntaxNodeList(
+ Assignment<EAetheryteLocation?>(nameof(AethernetShortcut.From), aethernetShortcut.From,
+ null)
+ .AsSyntaxNodeOrToken(),
+ Assignment<EAetheryteLocation?>(nameof(AethernetShortcut.To), aethernetShortcut.To,
+ null)
+ .AsSyntaxNodeOrToken()))));
+ }
+ else if (value is ChatMessage chatMessage)
+ {
+ ChatMessage emptyMessage = new();
+ return ObjectCreationExpression(
+ IdentifierName(nameof(ChatMessage)))
+ .WithInitializer(
+ InitializerExpression(
+ SyntaxKind.ObjectInitializerExpression,
+ SeparatedList<ExpressionSyntax>(
+ SyntaxNodeList(
+ Assignment(nameof(ChatMessage.ExcelSheet), chatMessage.ExcelSheet,
+ emptyMessage.ExcelSheet)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(ChatMessage.Key), chatMessage.Key,
+ emptyMessage.Key)
+ .AsSyntaxNodeOrToken()))));
+ }
+ else if (value is DialogueChoice dialogueChoice)
+ {
+ DialogueChoice emptyChoice = new();
+ return ObjectCreationExpression(
+ IdentifierName(nameof(DialogueChoice)))
+ .WithInitializer(
+ InitializerExpression(
+ SyntaxKind.ObjectInitializerExpression,
+ SeparatedList<ExpressionSyntax>(
+ SyntaxNodeList(
+ Assignment<EDialogChoiceType?>(nameof(DialogueChoice.Type), dialogueChoice.Type, null)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(DialogueChoice.ExcelSheet), dialogueChoice.ExcelSheet,
+ emptyChoice.ExcelSheet)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(DialogueChoice.Prompt), dialogueChoice.Prompt, emptyChoice.Prompt)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(DialogueChoice.Yes), dialogueChoice.Yes, emptyChoice.Yes)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(DialogueChoice.Answer), dialogueChoice.Answer, emptyChoice.Answer)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(DialogueChoice.DataId), dialogueChoice.DataId, emptyChoice.DataId)
+ .AsSyntaxNodeOrToken()))));
+ }
+ else if (value is JumpDestination jumpDestination)
+ {
+ return ObjectCreationExpression(
+ IdentifierName(nameof(JumpDestination)))
+ .WithInitializer(
+ InitializerExpression(
+ SyntaxKind.ObjectInitializerExpression,
+ SeparatedList<ExpressionSyntax>(
+ SyntaxNodeList(
+ Assignment<Vector3?>(nameof(JumpDestination.Position), jumpDestination.Position, null)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(JumpDestination.StopDistance), jumpDestination.StopDistance, null)
+ .AsSyntaxNodeOrToken(),
+ Assignment(nameof(JumpDestination.DelaySeconds), jumpDestination.DelaySeconds, null)
+ .AsSyntaxNodeOrToken()))));
+ }
+ else if (value is ExcelRef excelRef)
+ {
+ if (excelRef.Type == ExcelRef.EType.Key)
+ {
+ return ObjectCreationExpression(
+ IdentifierName(nameof(ExcelRef)))
+ .WithArgumentList(
+ ArgumentList(
+ SingletonSeparatedList(
+ Argument(LiteralValue(excelRef.AsKey())))));
+ }
+ else if (excelRef.Type == ExcelRef.EType.RowId)
+ {
+ return ObjectCreationExpression(
+ IdentifierName(nameof(ExcelRef)))
+ .WithArgumentList(
+ ArgumentList(
+ SingletonSeparatedList(
+ Argument(LiteralValue(excelRef.AsRowId())))));
+ }
+ else
+ throw new Exception($"Unsupported ExcelRef type {excelRef.Type}");
+ }
+ else if (value is null)
+ return LiteralExpression(SyntaxKind.NullLiteralExpression);
+ else
+ throw new Exception($"Unsupported data type {value.GetType()} = {value}");
+ }
+
+ public static AssignmentExpressionSyntax? Assignment<T>(string name, T? value, T? defaultValue)
+ {
+ if (value == null && defaultValue == null)
+ return null;
+
+ if (value != null && defaultValue != null && value.Equals(defaultValue))
+ return null;
+
+ return AssignmentExpression(
+ SyntaxKind.SimpleAssignmentExpression,
+ IdentifierName(name),
+ LiteralValue(value));
+ }
+
+ public static AssignmentExpressionSyntax? AssignmentList<T>(string name, IEnumerable<T> value)
+ {
+ IEnumerable<T> list = value.ToList();
+ if (!list.Any())
+ return null;
+
+ return AssignmentExpression(
+ SyntaxKind.SimpleAssignmentExpression,
+ IdentifierName(name),
+ CollectionExpression(
+ SeparatedList<CollectionElementSyntax>(
+ SyntaxNodeList(list.Select(x => ExpressionElement(
+ LiteralValue(x)).AsSyntaxNodeOrToken()).ToArray())
+ )));
+ }
+
+ public static SyntaxNodeOrToken? AsSyntaxNodeOrToken(this SyntaxNode? node)
+ {
+ if (node == null)
+ return null;
+
+ return node;
+ }
+}
--- /dev/null
+{
+ "version": 1,
+ "dependencies": {
+ ".NETStandard,Version=v2.0": {
+ "Microsoft.CodeAnalysis.Analyzers": {
+ "type": "Direct",
+ "requested": "[3.3.4, )",
+ "resolved": "3.3.4",
+ "contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g=="
+ },
+ "Microsoft.CodeAnalysis.CSharp": {
+ "type": "Direct",
+ "requested": "[4.9.2, )",
+ "resolved": "4.9.2",
+ "contentHash": "HGIo7E9Mf3exAJbUdYpDFfLoYkSVaHDJXPyusWTYUTBaOPCowGw+Gap5McE1w+K+ryIXre72oiqL88sQHmHBmg==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.Common": "[4.9.2]"
+ }
+ },
+ "Microsoft.CodeAnalysis.CSharp.Workspaces": {
+ "type": "Direct",
+ "requested": "[4.9.2, )",
+ "resolved": "4.9.2",
+ "contentHash": "c74oxEil3fiZ3nXchnIgY6mXS4roHGiQBT6p3X6dMWokVqluHiqi3PNcXyxH8N/w28rQeXprF3mca83rPPNrMw==",
+ "dependencies": {
+ "Humanizer.Core": "2.14.1",
+ "Microsoft.CodeAnalysis.CSharp": "[4.9.2]",
+ "Microsoft.CodeAnalysis.Common": "[4.9.2]",
+ "Microsoft.CodeAnalysis.Workspaces.Common": "[4.9.2]"
+ }
+ },
+ "NETStandard.Library": {
+ "type": "Direct",
+ "requested": "[2.0.3, )",
+ "resolved": "2.0.3",
+ "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0"
+ }
+ },
+ "System.Text.Json": {
+ "type": "Direct",
+ "requested": "[8.0.3, )",
+ "resolved": "8.0.3",
+ "contentHash": "hpagS9joOwv6efWfrMmV9MjQXpiXZH72PgN067Ysfr6AWMSD1/1hEcvh/U5mUpPLezEWsOJSuVrmqDIVD958iA==",
+ "dependencies": {
+ "Microsoft.Bcl.AsyncInterfaces": "8.0.0",
+ "System.Buffers": "4.5.1",
+ "System.Memory": "4.5.5",
+ "System.Runtime.CompilerServices.Unsafe": "6.0.0",
+ "System.Text.Encodings.Web": "8.0.0",
+ "System.Threading.Tasks.Extensions": "4.5.4"
+ }
+ },
+ "Humanizer.Core": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
+ },
+ "Microsoft.Bcl.AsyncInterfaces": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==",
+ "dependencies": {
+ "System.Threading.Tasks.Extensions": "4.5.4"
+ }
+ },
+ "Microsoft.CodeAnalysis.Common": {
+ "type": "Transitive",
+ "resolved": "4.9.2",
+ "contentHash": "M5PThug7b2AdxL7xKmQs50KzAQTl9jENw5jMT3iUt16k+DAFlw1S87juU3UuPs3gvBm8trMBSOEvSFDr31c9Vw==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.Analyzers": "3.3.4",
+ "System.Collections.Immutable": "8.0.0",
+ "System.Memory": "4.5.5",
+ "System.Reflection.Metadata": "8.0.0",
+ "System.Runtime.CompilerServices.Unsafe": "6.0.0",
+ "System.Text.Encoding.CodePages": "8.0.0",
+ "System.Threading.Tasks.Extensions": "4.5.4"
+ }
+ },
+ "Microsoft.CodeAnalysis.Workspaces.Common": {
+ "type": "Transitive",
+ "resolved": "4.9.2",
+ "contentHash": "sgBlkBjKwUdpbtwM7SnBdOxvQxuaTtO9F8QgvKY5cH/OnlwDTZqmkK8hfDbhxv9wnN2wME10BL2vIv1fLJwFGA==",
+ "dependencies": {
+ "Humanizer.Core": "2.14.1",
+ "Microsoft.Bcl.AsyncInterfaces": "8.0.0",
+ "Microsoft.CodeAnalysis.Common": "[4.9.2]",
+ "System.Composition": "8.0.0",
+ "System.IO.Pipelines": "8.0.0",
+ "System.Threading.Channels": "8.0.0"
+ }
+ },
+ "Microsoft.NETCore.Platforms": {
+ "type": "Transitive",
+ "resolved": "1.1.0",
+ "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
+ },
+ "System.Buffers": {
+ "type": "Transitive",
+ "resolved": "4.5.1",
+ "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
+ },
+ "System.Collections.Immutable": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==",
+ "dependencies": {
+ "System.Memory": "4.5.5",
+ "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+ }
+ },
+ "System.Composition": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "E9oO9olNNxA39J8CxQwf7ceIPm+j/B/PhYpyK9M4LhN/OLLRw6u5fNInkhVqaWueMB9iXxYqnwqwgz+W91loIA==",
+ "dependencies": {
+ "System.Composition.AttributedModel": "8.0.0",
+ "System.Composition.Convention": "8.0.0",
+ "System.Composition.Hosting": "8.0.0",
+ "System.Composition.Runtime": "8.0.0",
+ "System.Composition.TypedParts": "8.0.0"
+ }
+ },
+ "System.Composition.AttributedModel": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "NyElSuvmBMYdn2iPG0n29i7Igu0bq99izOP3MAtEwskY3OP9jqsavvVmPn9lesVaj/KT/o/QkNjA43dOJTsDQw=="
+ },
+ "System.Composition.Convention": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "UuVkc1B3vQU/LzEbWLMZ1aYVssv4rpShzf8wPEyrUqoGNqdYKREmB8bXR73heOMKkwS6ZnPz3PjGODT2MenukQ==",
+ "dependencies": {
+ "System.Composition.AttributedModel": "8.0.0"
+ }
+ },
+ "System.Composition.Hosting": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "qwbONqoxlazxcbiohvb3t1JWZgKIKcRdXS5uEeLbo5wtuBupIbAvdC3PYTAeBCZrZeERvrtAbhYHuuS43Zr1bQ==",
+ "dependencies": {
+ "System.Composition.Runtime": "8.0.0"
+ }
+ },
+ "System.Composition.Runtime": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "G+kRyB5/6+3ucRRQz+DF4uSHGqpkK8Q4ilVdbt4zvxpmvLVZNmSkyFAQpJLcbOyVF85aomJx0m+TGMDVlwx7ZQ=="
+ },
+ "System.Composition.TypedParts": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "DsSklhuA+Dsgo3ZZrar8hjBFvq1wa1grrkNCTt+6SoX3vq0Vy+HXJnVXrU/nNH1BjlGH684A7h4hJQHZd/u5mA==",
+ "dependencies": {
+ "System.Composition.AttributedModel": "8.0.0",
+ "System.Composition.Hosting": "8.0.0",
+ "System.Composition.Runtime": "8.0.0"
+ }
+ },
+ "System.IO.Pipelines": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "FHNOatmUq0sqJOkTx+UF/9YK1f180cnW5FVqnQMvYUN0elp6wFzbtPSiqbo1/ru8ICp43JM1i7kKkk6GsNGHlA==",
+ "dependencies": {
+ "System.Buffers": "4.5.1",
+ "System.Memory": "4.5.5",
+ "System.Threading.Tasks.Extensions": "4.5.4"
+ }
+ },
+ "System.Memory": {
+ "type": "Transitive",
+ "resolved": "4.5.5",
+ "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
+ "dependencies": {
+ "System.Buffers": "4.5.1",
+ "System.Numerics.Vectors": "4.4.0",
+ "System.Runtime.CompilerServices.Unsafe": "4.5.3"
+ }
+ },
+ "System.Numerics.Vectors": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ=="
+ },
+ "System.Reflection.Metadata": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==",
+ "dependencies": {
+ "System.Collections.Immutable": "8.0.0",
+ "System.Memory": "4.5.5"
+ }
+ },
+ "System.Runtime.CompilerServices.Unsafe": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
+ },
+ "System.Text.Encoding.CodePages": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==",
+ "dependencies": {
+ "System.Memory": "4.5.5",
+ "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+ }
+ },
+ "System.Text.Encodings.Web": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==",
+ "dependencies": {
+ "System.Buffers": "4.5.1",
+ "System.Memory": "4.5.5",
+ "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+ }
+ },
+ "System.Threading.Channels": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA==",
+ "dependencies": {
+ "System.Threading.Tasks.Extensions": "4.5.4"
+ }
+ },
+ "System.Threading.Tasks.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.5.4",
+ "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
+ "dependencies": {
+ "System.Runtime.CompilerServices.Unsafe": "4.5.3"
+ }
+ },
+ "questionable.model": {
+ "type": "Project",
+ "dependencies": {
+ "System.Text.Json": "[8.0.3, )"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
-#if RELEASE
-using System;
-using System.IO;
-using System.IO.Compression;
+using System.Collections.Generic;
+using Questionable.Model.V1;
+#if RELEASE
namespace Questionable.QuestPaths;
-public static class AssemblyQuestLoader
+public static partial class AssemblyQuestLoader
{
- public static void LoadQuestsFromEmbeddedResources(Action<string, Stream> loadFunction)
- {
- foreach (string resourceName in typeof(AssemblyQuestLoader).Assembly.GetManifestResourceNames())
- {
- if (resourceName.EndsWith(".zip"))
- {
- using ZipArchive zipArchive =
- new ZipArchive(typeof(AssemblyQuestLoader).Assembly.GetManifestResourceStream(resourceName)!);
- foreach (ZipArchiveEntry entry in zipArchive.Entries)
- {
- if (entry.Name.EndsWith(".json"))
- {
- using Stream stream = entry.Open();
- loadFunction(entry.Name, stream);
- }
- }
- }
- }
- }
+ public static IReadOnlyDictionary<ushort, QuestData> GetQuests() => Quests;
}
#endif
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<DebugType>none</DebugType>
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
+ <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
- <Target Name="ZipARealmReborn" BeforeTargets="BeforeResGen" Condition="'$(Configuration)' == 'Release'">
- <ZipDirectory SourceDirectory="$(ProjectDir)\ARealmReborn"
- DestinationFile="$(TargetDir)\ARealmReborn.zip"
- Overwrite="true"/>
- </Target>
-
- <Target Name="ZipShadowbringers" BeforeTargets="BeforeResGen" Condition="'$(Configuration)' == 'Release'">
- <ZipDirectory SourceDirectory="$(ProjectDir)\Shadowbringers"
- DestinationFile="$(TargetDir)\Shadowbringers.zip"
- Overwrite="true"/>
- </Target>
-
- <Target Name="ZipEndwalker" BeforeTargets="BeforeResGen" Condition="'$(Configuration)' == 'Release'">
- <ZipDirectory SourceDirectory="$(ProjectDir)\Endwalker"
- DestinationFile="$(TargetDir)\Endwalker.zip"
- Overwrite="true"/>
- </Target>
+ <ItemGroup>
+ <ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj" />
+ <ProjectReference Include="..\QuestPathGenerator\QuestPathGenerator.csproj"
+ OutputItemType="Analyzer"
+ ReferenceOutputAssembly="false" />
+ </ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
- <EmbeddedResource Include="$(TargetDir)\ARealmReborn.zip"/>
- <EmbeddedResource Include="$(TargetDir)\Shadowbringers.zip"/>
- <EmbeddedResource Include="$(TargetDir)\Endwalker.zip"/>
+ <None Remove="ARealmReborn"/>
+ <None Remove="Shadowbringers"/>
+ <None Remove="Endwalker"/>
+ <AdditionalFiles Include="ARealmReborn\**\*.json" />
+ <AdditionalFiles Include="Shadowbringers\**\*.json" />
+ <AdditionalFiles Include="Endwalker\**\*.json" />
</ItemGroup>
</Project>
{
"version": 1,
"dependencies": {
- "net8.0-windows7.0": {}
+ "net8.0-windows7.0": {
+ "Dalamud.Extensions.MicrosoftLogging": {
+ "type": "Transitive",
+ "resolved": "4.0.1",
+ "contentHash": "fMEL2ajtF/30SBBku7vMyG0yye5eHN/A9fgT//1CEjUth/Wz2CYco5Ehye21T8KN1IuAPwoqJuu49rB71j+8ug==",
+ "dependencies": {
+ "Microsoft.Extensions.Logging": "8.0.0"
+ }
+ },
+ "DalamudPackager": {
+ "type": "Transitive",
+ "resolved": "2.1.12",
+ "contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
+ },
+ "JetBrains.Annotations": {
+ "type": "Transitive",
+ "resolved": "2023.3.0",
+ "contentHash": "PHfnvdBUdGaTVG9bR/GEfxgTwWM0Z97Y6X3710wiljELBISipSfF5okn/vz+C2gfO+ihoEyVPjaJwn8ZalVukA=="
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg=="
+ },
+ "Microsoft.Extensions.Logging": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection": "8.0.0",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Logging.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Options": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Primitives": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
+ },
+ "System.Text.Encodings.Web": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ=="
+ },
+ "System.Text.Json": {
+ "type": "Transitive",
+ "resolved": "8.0.3",
+ "contentHash": "hpagS9joOwv6efWfrMmV9MjQXpiXZH72PgN067Ysfr6AWMSD1/1hEcvh/U5mUpPLezEWsOJSuVrmqDIVD958iA==",
+ "dependencies": {
+ "System.Text.Encodings.Web": "8.0.0"
+ }
+ },
+ "llib": {
+ "type": "Project"
+ },
+ "questionable": {
+ "type": "Project",
+ "dependencies": {
+ "Dalamud.Extensions.MicrosoftLogging": "[4.0.1, )",
+ "DalamudPackager": "[2.1.12, )",
+ "JetBrains.Annotations": "[2023.3.0, )",
+ "LLib": "[1.0.0, )",
+ "Microsoft.Extensions.DependencyInjection": "[8.0.0, )",
+ "QuestPaths": "[1.0.0, )",
+ "Questionable.Model": "[1.0.0, )",
+ "System.Text.Json": "[8.0.3, )"
+ }
+ },
+ "questionable.model": {
+ "type": "Project",
+ "dependencies": {
+ "System.Text.Json": "[8.0.3, )"
+ }
+ }
+ }
}
}
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <LangVersion>12</LangVersion>
+ <Nullable>enable</Nullable>
+ <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
+ <PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
+ <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
+ <DebugType>portable</DebugType>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="System.Text.Json" Version="8.0.3"/>
+ </ItemGroup>
+</Project>
--- /dev/null
+using System.Text.Json.Serialization;
+using Questionable.Model.V1.Converter;
+
+namespace Questionable.Model.V1;
+
+[JsonConverter(typeof(AethernetShortcutConverter))]
+public sealed class AethernetShortcut
+{
+ public EAetheryteLocation From { get; set; }
+ public EAetheryteLocation To { get; set; }
+}
--- /dev/null
+namespace Questionable.Model.V1;
+
+public sealed class ChatMessage
+{
+ public string? ExcelSheet { get; set; }
+ public string Key { get; set; } = null!;
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Questionable.Model.V1.Converter;
+
+public sealed class AethernetShortcutConverter : JsonConverter<AethernetShortcut>
+{
+ private static readonly Dictionary<EAetheryteLocation, string> EnumToString = new()
+ {
+ { EAetheryteLocation.Gridania, "[Gridania] Aetheryte Plaza" },
+ { EAetheryteLocation.GridaniaArcher, "[Gridania] Archers' Guild" },
+ { EAetheryteLocation.GridaniaLeatherworker, "[Gridania] Leatherworkers' Guild & Shaded Bower" },
+ { EAetheryteLocation.GridaniaLancer, "[Gridania] Lancers' Guild" },
+ { EAetheryteLocation.GridaniaConjurer, "[Gridania] Conjurers' Guild" },
+ { EAetheryteLocation.GridaniaBotanist, "[Gridania] Botanists' Guild" },
+ { EAetheryteLocation.GridaniaAmphitheatre, "[Gridania] Mih Khetto's Amphitheatre" },
+ { EAetheryteLocation.GridaniaBlueBadgerGate, "[Gridania] Blue Badger Gate (Central Shroud)" },
+ { EAetheryteLocation.GridaniaYellowSerpentGate, "[Gridania] Yellow Serpent Gate (North Shroud)" },
+ { EAetheryteLocation.GridaniaWhiteWolfGate, "[Gridania] White Wolf Gate (Central Shroud)" },
+ { EAetheryteLocation.GridaniaAirship, "[Gridania] Airship Landing" },
+
+ { EAetheryteLocation.Uldah, "[Ul'dah] Aetheryte Plaza" },
+ { EAetheryteLocation.UldahAdventurers, "[Ul'dah] Adventurers' Guild" },
+ { EAetheryteLocation.UldahThaumaturge, "[Ul'dah] Thaumaturges' Guild" },
+ { EAetheryteLocation.UldahGladiator, "[Ul'dah] Gladiators' Guild" },
+ { EAetheryteLocation.UldahMiner, "[Ul'dah] Miners' Guild" },
+ { EAetheryteLocation.UldahWeaver, "[Ul'dah] Weavers' Guild" },
+ { EAetheryteLocation.UldahGoldsmith, "[Ul'dah] Goldsmiths' Guild" },
+ { EAetheryteLocation.UldahSapphireAvenue, "[Ul'dah] Sapphire Avenue Exchange" },
+ { EAetheryteLocation.UldahAlchemist, "[Ul'dah] Alchemists' Guild" },
+ { EAetheryteLocation.UldahChamberOfRule, "[Ul'dah] The Chamber of Rule" },
+ { EAetheryteLocation.UldahGateOfTheSultana, "[Ul'dah] Gate of the Sultana (Western Thanalan)" },
+ { EAetheryteLocation.UldahGateOfNald, "[Ul'dah] Gate of Nald (Central Thanalan)" },
+ { EAetheryteLocation.UldahGateOfThal, "[Ul'dah] Gate of Thal (Central Thanalan)" },
+ { EAetheryteLocation.UldahAirship, "[Ul'dah] Airship Landing" },
+
+ { EAetheryteLocation.Limsa, "[Limsa Lominsa] Aetheryte Plaza" },
+ { EAetheryteLocation.LimsaArcanist, "[Limsa Lominsa] Arcanists' Guild" },
+ { EAetheryteLocation.LimsaFisher, "[Limsa Lominsa] Fishermens' Guild" },
+ { EAetheryteLocation.LimsaHawkersAlley, "[Limsa Lominsa] Hawkers' Alley" },
+ { EAetheryteLocation.LimsaAftcastle, "[Limsa Lominsa] The Aftcastle" },
+ { EAetheryteLocation.LimsaCulinarian, "[Limsa Lominsa] Culinarians' Guild" },
+ { EAetheryteLocation.LimsaMarauder, "[Limsa Lominsa] Marauders' Guild" },
+ { EAetheryteLocation.LimsaZephyrGate, "[Limsa Lominsa] Zephyr Gate (Middle La Noscea)" },
+ { EAetheryteLocation.LimsaTempestGate, "[Limsa Lominsa] Tempest Gate (Lower La Noscea)" },
+ { EAetheryteLocation.LimsaAirship, "[Limsa Lominsa] Airship Landing" },
+
+ { EAetheryteLocation.Ishgard, "[Ishgard] Aetheryte Plaza" },
+ { EAetheryteLocation.IshgardForgottenKnight, "[Ishgard] The Forgotten Knight" },
+ { EAetheryteLocation.IshgardSkysteelManufactory, "[Ishgard] Skysteel Manufactory" },
+ { EAetheryteLocation.IshgardBrume, "[Ishgard] The Brume" },
+ { EAetheryteLocation.IshgardAthenaeumAstrologicum, "[Ishgard] Athenaeum Astrologicum" },
+ { EAetheryteLocation.IshgardJeweledCrozier, "[Ishgard] The Jeweled Crozier" },
+ { EAetheryteLocation.IshgardSaintReymanaudsCathedral, "[Ishgard] Saint Reymanaud's Cathedral" },
+ { EAetheryteLocation.IshgardTribunal, "[Ishgard] The Tribunal" },
+ { EAetheryteLocation.IshgardLastVigil, "[Ishgard] The Last Vigil" },
+ { EAetheryteLocation.IshgardGatesOfJudgement, "[Ishgard] The Gates of Judgement (Coerthas Central Highlands)" },
+
+ { EAetheryteLocation.Idyllshire, "[Idyllshire] Aetheryte Plaza" },
+ { EAetheryteLocation.IdyllshireWest, "[Idyllshire] West Idyllshire" },
+ { EAetheryteLocation.IdyllshirePrologueGate, "[Idyllshire] Prologue Gate" },
+ { EAetheryteLocation.IdyllshireEpilogueGate, "[Idyllshire] Epilogue Gate" },
+
+ { EAetheryteLocation.RhalgrsReach, "[Rhalgr's Reach] Aetheryte Plaza" },
+ { EAetheryteLocation.RhalgrsReachWest, "[Rhalgr's Reach] Western Rhalgr's Reach" },
+ { EAetheryteLocation.RhalgrsReachNorthEast, "[Rhalgr's Reach] Northeastern Rhalgr's Reach" },
+ { EAetheryteLocation.RhalgrsReachFringesGate, "[Rhalgr's Reach] Fringes Gate" },
+ { EAetheryteLocation.RhalgrsReachPeaksGate, "[Rhalgr's Reach] Peaks Gate" },
+
+ { EAetheryteLocation.Kugane, "[Kugane] Aetheryte Plaza" },
+ { EAetheryteLocation.KuganeShiokazeHostelry, "[Kugane] Shiokaze Hostelry" },
+ { EAetheryteLocation.KuganePier1, "[Kugane] Pier #1" },
+ { EAetheryteLocation.KuganeThavnairianConsulate, "[Kugane] Thavnairian Consulate" },
+ { EAetheryteLocation.KuganeMarkets, "[Kugane] Kogane Dori Markets" },
+ { EAetheryteLocation.KuganeBokairoInn, "[Kugane] Bokairo Inn" },
+ { EAetheryteLocation.KuganeRubyBazaar, "[Kugane] The Ruby Bazaar" },
+ { EAetheryteLocation.KuganeSekiseigumiBarracks, "[Kugane] Sekiseigumi Barracks" },
+ { EAetheryteLocation.KuganeRakuzaDistrict, "[Kugane] Rakuza District" },
+ { EAetheryteLocation.KuganeRubyPrice, "[Kugane] The Ruby Price" },
+ { EAetheryteLocation.KuganeAirship, "[Kugane] Airship Landing" },
+
+ { EAetheryteLocation.Crystarium, "[Crystarium] Aetheryte Plaza" },
+ { EAetheryteLocation.CrystariumMarkets, "[Crystarium] Musica Universalis Markets" },
+ { EAetheryteLocation.CrystariumThemenosRookery, "[Crystarium] Themenos Rookery" },
+ { EAetheryteLocation.CrystariumDossalGate, "[Crystarium] The Dossal Gate" },
+ { EAetheryteLocation.CrystariumPendants, "[Crystarium] The Pendants" },
+ { EAetheryteLocation.CrystariumAmaroLaunch, "[Crystarium] The Amaro Launch" },
+ { EAetheryteLocation.CrystariumCrystallineMean, "[Crystarium] The Crystalline Mean" },
+ { EAetheryteLocation.CrystariumCabinetOfCuriosity, "[Crystarium] The Cabinet of Curiosity" },
+ { EAetheryteLocation.CrystariumTessellation, "[Crystarium] Tessellation (Lakeland)" },
+
+ { EAetheryteLocation.Eulmore, "[Eulmore] Aetheryte Plaza" },
+ { EAetheryteLocation.EulmoreSoutheastDerelict, "[Eulmore] Southeast Derelicts" },
+ { EAetheryteLocation.EulmoreNightsoilPots, "[Eulmore] Nightsoil Pots" },
+ { EAetheryteLocation.EulmoreGloryGate, "[Eulmore] The Glory Gate" },
+ { EAetheryteLocation.EulmoreMainstay, "[Eulmore] The Mainstay" },
+ { EAetheryteLocation.EulmorePathToGlory, "[Eulmore] The Path to Glory (Kholusia)" },
+
+ { EAetheryteLocation.OldSharlayan, "[Old Sharlayan] Aetheryte Plaza" },
+ { EAetheryteLocation.OldSharlayanStudium, "[Old Sharlayan] The Studium" },
+ { EAetheryteLocation.OldSharlayanBaldesionAnnex, "[Old Sharlayan] The Baldesion Annex" },
+ { EAetheryteLocation.OldSharlayanRostra, "[Old Sharlayan] The Rostra" },
+ { EAetheryteLocation.OldSharlayanLeveilleurEstate, "[Old Sharlayan] The Leveilleur Estate" },
+ { EAetheryteLocation.OldSharlayanJourneysEnd, "[Old Sharlayan] Journey's End" },
+ { EAetheryteLocation.OldSharlayanScholarsHarbor, "[Old Sharlayan] Scholar's Harbor" },
+ { EAetheryteLocation.OldSharlayanHallOfArtifice, "[Old Sharlayan] The Hall of Artifice (Labyrinthos)" },
+
+ { EAetheryteLocation.RadzAtHan, "[Radz-at-Han] Aetheryte Plaza" },
+ { EAetheryteLocation.RadzAtHanMeghaduta, "[Radz-at-Han] Meghaduta" },
+ { EAetheryteLocation.RadzAtHanRuveydahFibers, "[Radz-at-Han] Ruveydah Fibers" },
+ { EAetheryteLocation.RadzAtHanAirship, "[Radz-at-Han] Airship Landing" },
+ { EAetheryteLocation.RadzAtHanAlzadaalsPeace, "[Radz-at-Han] Alzadaal's Peace" },
+ { EAetheryteLocation.RadzAtHanHallOfTheRadiantHost, "[Radz-at-Han] Hall of the Radiant Host" },
+ { EAetheryteLocation.RadzAtHanMehrydesMeyhane, "[Radz-at-Han] Mehryde's Meyhane" },
+ { EAetheryteLocation.RadzAtHanKama, "[Radz-at-Han] Kama" },
+ { EAetheryteLocation.RadzAtHanHighCrucible, "[Radz-at-Han] The High Crucible of Al-Kimiya" },
+ { EAetheryteLocation.RadzAtHanGateOfFirstSight, "[Radz-at-Han] The Gate of First Sight (Thavnair)" },
+ };
+
+ private static readonly Dictionary<string, EAetheryteLocation> StringToEnum =
+ EnumToString.ToDictionary(x => x.Value, x => x.Key);
+
+ public override AethernetShortcut Read(ref Utf8JsonReader reader, Type typeToConvert,
+ JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.StartArray)
+ throw new JsonException();
+
+ if (!reader.Read() || reader.TokenType != JsonTokenType.String)
+ throw new JsonException();
+
+ string from = reader.GetString() ?? throw new JsonException();
+
+ if (!reader.Read() || reader.TokenType != JsonTokenType.String)
+ throw new JsonException();
+
+ string to = reader.GetString() ?? throw new JsonException();
+
+ if (!reader.Read() || reader.TokenType != JsonTokenType.EndArray)
+ throw new JsonException();
+
+ return new AethernetShortcut
+ {
+ From = StringToEnum.TryGetValue(from, out var fromEnum) ? fromEnum : throw new JsonException(),
+ To = StringToEnum.TryGetValue(to, out var toEnum) ? toEnum : throw new JsonException()
+ };
+ }
+
+ public override void Write(Utf8JsonWriter writer, AethernetShortcut value, JsonSerializerOptions options)
+ {
+ writer.WriteStartArray();
+ writer.WriteStringValue(EnumToString[value.From]);
+ writer.WriteStringValue(EnumToString[value.To]);
+ writer.WriteEndArray();
+ }
+}
--- /dev/null
+using System.Collections.Generic;
+
+namespace Questionable.Model.V1.Converter;
+
+public sealed class AetheryteConverter() : EnumConverter<EAetheryteLocation>(Values)
+{
+ private static readonly Dictionary<EAetheryteLocation, string> Values = new()
+ {
+ { EAetheryteLocation.Gridania, "Gridania" },
+
+ { EAetheryteLocation.CentralShroudBentbranchMeadows, "Central Shroud - Bentbranch Meadows" },
+ { EAetheryteLocation.EastShroudHawthorneHut, "East Shroud - Hawthorne Hut" },
+ { EAetheryteLocation.SouthShroudQuarrymill, "South Shroud - Quarrymill" },
+ { EAetheryteLocation.SouthShroudCampTranquil, "South Shroud - Camp Tranquil" },
+ { EAetheryteLocation.NorthShroudFallgourdFloat, "North Shroud - Fallgourd Float" },
+
+ { EAetheryteLocation.Uldah, "Ul'dah" },
+ { EAetheryteLocation.WesternThanalanHorizon, "Western Thanalan - Horizon" },
+ { EAetheryteLocation.CentralThanalanBlackBrushStation, "Central Thanalan - Black Brush Station" },
+ { EAetheryteLocation.EasternThanalanCampDrybone, "Eastern Thanalan - Camp Drybone" },
+ { EAetheryteLocation.SouthernThanalanLittleAlaMhigo, "Southern Thanalan - Little Ala Mhigo" },
+ { EAetheryteLocation.SouthernThanalanForgottenSprings, "Southern Thanalan - Forgotten Springs" },
+ { EAetheryteLocation.NorthernThanalanCampBluefog, "Northern Thanalan - Camp Bluefog" },
+ { EAetheryteLocation.NorthernThanalanCeruleumProcessingPlant, "Northern Thanalan - Ceruleum Processing Plant" },
+
+ { EAetheryteLocation.Limsa, "Limsa Lominsa" },
+ { EAetheryteLocation.MiddleLaNosceaSummerfordFarms, "Middle La Noscea - Summerford Farms" },
+ { EAetheryteLocation.LowerLaNosceaMorabyDrydocks, "Lower La Noscea - Moraby Drydocks" },
+ { EAetheryteLocation.EasternLaNosceaCostaDelSol, "Eastern La Noscea - Costa Del Sol" },
+ { EAetheryteLocation.EasternLaNosceaWineport, "Eastern La Noscea - Wineport" },
+ { EAetheryteLocation.WesternLaNosceaSwiftperch, "Western La Noscea - Swiftperch" },
+ { EAetheryteLocation.WesternLaNosceaAleport, "Western La Noscea - Aleport" },
+ { EAetheryteLocation.UpperLaNosceaCampBronzeLake, "Upper La Noscea - Camp Bronze Lake" },
+ { EAetheryteLocation.OuterLaNosceaCampOverlook, "Outer La Noscea - Camp Overlook" },
+
+ { EAetheryteLocation.CoerthasCentralHighlandsCampDragonhead, "Coerthas Central Highlands - Camp Dragonhead" },
+ { EAetheryteLocation.MorDhona, "Mor Dhona" },
+ { EAetheryteLocation.GoldSaucer, "Gold Saucer" },
+ { EAetheryteLocation.WolvesDenPier, "Wolves' Den Pier" },
+
+ { EAetheryteLocation.Ishgard, "Ishgard" },
+ { EAetheryteLocation.Idyllshire, "Idyllshire" },
+ { EAetheryteLocation.CoerthasWesternHighlandsFalconsNest, "Coerthas Western Highlands - Falcon's Nest" },
+ { EAetheryteLocation.SeaOfCloudsCampCloudtop, "The Sea of Clouds - Camp Cloudtop" },
+ { EAetheryteLocation.SeaOfCloudsOkZundu, "The Sea of Clouds - Ok' Zundu" },
+ { EAetheryteLocation.AzysLlaHelix, "Azys Lla - Helix" },
+ { EAetheryteLocation.DravanianForelandsTailfeather, "The Dravanian Forelands - Tailfeather" },
+ { EAetheryteLocation.DravanianForelandsAnyxTrine, "The Dravanian Forelands - Anyx Trine" },
+ { EAetheryteLocation.ChurningMistsMoghome, "The Churning Mists - Moghome" },
+ { EAetheryteLocation.ChurningMistsZenith, "The Churning Mists - Zenith" },
+
+ { EAetheryteLocation.RhalgrsReach, "Rhalgr's Reach" },
+ { EAetheryteLocation.FringesCastrumOriens, "Fringes - Castrum Oriens" },
+ { EAetheryteLocation.FringesPeeringStones, "Fringes - Peering Stones" },
+ { EAetheryteLocation.PeaksAlaGannha, "Peaks - Ala Gannha" },
+ { EAetheryteLocation.PeaksAlaGhiri, "Peaks - Ala Ghiri" },
+ { EAetheryteLocation.LochsPortaPraetoria, "Lochs - Porta Praetoria" },
+ { EAetheryteLocation.LochsAlaMhiganQuarter, "Lochs - Ala Mhigan Quarter" },
+ { EAetheryteLocation.Kugane, "Kugane" },
+ { EAetheryteLocation.RubySeaTamamizu, "Ruby Sea - Tamamizu" },
+ { EAetheryteLocation.RubySeaOnokoro, "Ruby Sea - Onokoro" },
+ { EAetheryteLocation.YanxiaNamai, "Yanxia - Namai" },
+ { EAetheryteLocation.YanxiaHouseOfTheFierce, "Yanxia - House of the Fierce" },
+ { EAetheryteLocation.AzimSteppeReunion, "Azim Steppe - Reunion" },
+ { EAetheryteLocation.AzimSteppeDawnThrone, "Azim Steppe - Dawn Throne" },
+ { EAetheryteLocation.AzimSteppeDhoroIloh, "Azim Steppe - Dhoro Iloh" },
+ { EAetheryteLocation.DomanEnclave, "Doman Enclave" },
+
+ { EAetheryteLocation.Crystarium, "Crystarium" },
+ { EAetheryteLocation.Eulmore, "Eulmore" },
+ { EAetheryteLocation.LakelandFortJobb, "Lakeland - Fort Jobb" },
+ { EAetheryteLocation.LakelandOstallImperative, "Lakeland - Ostall Imperative" },
+ { EAetheryteLocation.KholusiaStilltide, "Kholusia - Stilltide" },
+ { EAetheryteLocation.KholusiaWright, "Kholusia - Wright" },
+ { EAetheryteLocation.KholusiaTomra, "Kholusia - Tomra" },
+ { EAetheryteLocation.AmhAraengMordSouq, "Amh Araeng - Mord Souq" },
+ { EAetheryteLocation.AmhAraengInnAtJourneysHead, "Amh Araeng - Inn at Journey's Head" },
+ { EAetheryteLocation.AmhAraengTwine, "Amh Araeng - Twine" },
+ { EAetheryteLocation.RaktikaSlitherbough, "Rak'tika - Slitherbough" },
+ { EAetheryteLocation.RaktikaFanow, "Rak'tika - Fanow" },
+ { EAetheryteLocation.IlMhegLydhaLran, "Il Mheg - Lydha Lran" },
+ { EAetheryteLocation.IlMhegPiaEnni, "Il Mheg - Pia Enni" },
+ { EAetheryteLocation.IlMhegWolekdorf, "Il Mheg - Wolekdorf" },
+ { EAetheryteLocation.TempestOndoCups, "Tempest - Ondo Cups" },
+ { EAetheryteLocation.TempestMacarensesAngle, "Tempest - Macarenses Angle" },
+
+ { EAetheryteLocation.OldSharlayan, "Old Sharlayan" },
+ { EAetheryteLocation.RadzAtHan, "Radz-at-Han" },
+ { EAetheryteLocation.LabyrinthosArcheion, "Labyrinthos - Archeion" },
+ { EAetheryteLocation.LabyrinthosSharlayanHamlet, "Labyrinthos - Sharlayan Hamlet" },
+ { EAetheryteLocation.LabyrinthosAporia, "Labyrinthos - Aporia" },
+ { EAetheryteLocation.ThavnairYedlihmad, "Thavnair - Yedlihmad" },
+ { EAetheryteLocation.ThavnairGreatWork, "Thavnair - Great Work" },
+ { EAetheryteLocation.ThavnairPalakasStand, "Thavnair - Palaka's Stand" },
+ { EAetheryteLocation.GarlemaldCampBrokenGlass, "Garlemald - Camp Broken Glass" },
+ { EAetheryteLocation.GarlemaldTertium, "Garlemald - Tertium" },
+ { EAetheryteLocation.MareLamentorumSinusLacrimarum, "Mare Lamentorum - Sinus Lacrimarum" },
+ { EAetheryteLocation.MareLamentorumBestwaysBurrow, "Mare Lamentorum - Bestways Burrow" },
+ { EAetheryteLocation.ElpisAnagnorisis, "Elpis - Anagnorisis" },
+ { EAetheryteLocation.ElpisTwelveWonders, "Elpis - Twelve Wonders" },
+ { EAetheryteLocation.ElpisPoietenOikos, "Elpis - Poieten Oikos" },
+ { EAetheryteLocation.UltimaThuleReahTahra, "Ultima Thule - Reah Tahra" },
+ { EAetheryteLocation.UltimaThuleAbodeOfTheEa, "Ultima Thule - Abode of the Ea" },
+ { EAetheryteLocation.UltimaThuleBaseOmicron, "Ultima Thule - Base Omicron" }
+ };
+
+ public static bool IsLargeAetheryte(EAetheryteLocation aetheryte) => Values.ContainsKey(aetheryte);
+}
--- /dev/null
+using System.Collections.Generic;
+
+namespace Questionable.Model.V1.Converter;
+
+public sealed class DialogueChoiceTypeConverter() : EnumConverter<EDialogChoiceType>(Values)
+{
+ private static readonly Dictionary<EDialogChoiceType, string> Values = new()
+ {
+ { EDialogChoiceType.YesNo, "YesNo" },
+ { EDialogChoiceType.List, "List" },
+ };
+}
--- /dev/null
+using System.Collections.Generic;
+
+namespace Questionable.Model.V1.Converter;
+
+public sealed class EmoteConverter() : EnumConverter<EEmote>(Values)
+{
+ private static readonly Dictionary<EEmote, string> Values = new()
+ {
+ { EEmote.Stretch, "stretch" },
+ { EEmote.Wave, "wave" },
+ { EEmote.Rally, "rally" },
+ { EEmote.Deny, "deny" },
+ { EEmote.Pray, "pray" },
+ { EEmote.Slap, "slap" },
+ { EEmote.Doubt, "doubt" },
+ { EEmote.Psych, "psych" },
+ };
+}
--- /dev/null
+using System.Collections.Generic;
+
+namespace Questionable.Model.V1.Converter;
+
+public sealed class EnemySpawnTypeConverter() : EnumConverter<EEnemySpawnType>(Values)
+{
+ private static readonly Dictionary<EEnemySpawnType, string> Values = new()
+ {
+ { EEnemySpawnType.AfterInteraction, "AfterInteraction" },
+ { EEnemySpawnType.AfterItemUse, "AfterItemUse" },
+ { EEnemySpawnType.AutoOnEnterArea, "AutoOnEnterArea" },
+ { EEnemySpawnType.OverworldEnemies, "OverworldEnemies" },
+ };
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Questionable.Model.V1.Converter;
+
+public abstract class EnumConverter<T> : JsonConverter<T>
+ where T : Enum
+{
+ private readonly ReadOnlyDictionary<T, string> _enumToString;
+ private readonly ReadOnlyDictionary<string, T> _stringToEnum;
+
+ protected EnumConverter(IReadOnlyDictionary<T, string> values)
+ {
+ _enumToString = values is IDictionary<T, string> dict
+ ? new ReadOnlyDictionary<T, string>(dict)
+ : new ReadOnlyDictionary<T, string>(values.ToDictionary(x => x.Key, x => x.Value));
+ _stringToEnum = new ReadOnlyDictionary<string, T>(_enumToString.ToDictionary(x => x.Value, x => x.Key));
+ }
+
+ public override T? Read(ref Utf8JsonReader reader, Type typeToConvert,
+ JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.String)
+ throw new JsonException();
+
+ string? str = reader.GetString();
+ if (str == null)
+ throw new JsonException();
+
+ return _stringToEnum.TryGetValue(str, out T? value) ? value : throw new JsonException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(_enumToString[value]);
+ }
+}
--- /dev/null
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Questionable.Model.V1.Converter;
+
+public sealed class ExcelRefConverter : JsonConverter<ExcelRef>
+{
+ public override ExcelRef? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.String)
+ return new ExcelRef(reader.GetString()!);
+ else if (reader.TokenType == JsonTokenType.Number)
+ return new ExcelRef(reader.GetUInt32());
+ else
+ return null;
+ }
+
+ public override void Write(Utf8JsonWriter writer, ExcelRef? value, JsonSerializerOptions options)
+ {
+ if (value == null)
+ writer.WriteNullValue();
+ else if (value.Type == ExcelRef.EType.Key)
+ writer.WriteStringValue(value.AsKey());
+ else if (value.Type == ExcelRef.EType.RowId)
+ writer.WriteNumberValue(value.AsRowId());
+ else
+ throw new JsonException();
+ }
+}
--- /dev/null
+using System.Collections.Generic;
+
+namespace Questionable.Model.V1.Converter;
+
+public sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>(Values)
+{
+ private static readonly Dictionary<EInteractionType, string> Values = new()
+ {
+ { EInteractionType.Interact, "Interact" },
+ { EInteractionType.WalkTo, "WalkTo" },
+ { EInteractionType.AttuneAethernetShard, "AttuneAethernetShard" },
+ { EInteractionType.AttuneAetheryte, "AttuneAetheryte" },
+ { EInteractionType.AttuneAetherCurrent, "AttuneAetherCurrent" },
+ { EInteractionType.Combat, "Combat" },
+ { EInteractionType.UseItem, "UseItem" },
+ { EInteractionType.EquipItem, "EquipItem" },
+ { EInteractionType.Say, "Say" },
+ { EInteractionType.Emote, "Emote" },
+ { EInteractionType.WaitForObjectAtPosition, "WaitForNpcAtPosition" },
+ { EInteractionType.WaitForManualProgress, "WaitForManualProgress" },
+ { EInteractionType.Duty, "Duty" },
+ { EInteractionType.SinglePlayerDuty, "SinglePlayerDuty" },
+ { EInteractionType.Jump, "Jump" },
+ { EInteractionType.ShouldBeAJump, "ShouldBeAJump" },
+ { EInteractionType.Instruction, "Instruction" },
+ };
+}
--- /dev/null
+using System.Collections.Generic;
+
+namespace Questionable.Model.V1.Converter;
+
+public sealed class SkipConditionConverter() : EnumConverter<ESkipCondition>(Values)
+{
+ private static readonly Dictionary<ESkipCondition, string> Values = new()
+ {
+ { ESkipCondition.Never, "Never" },
+ { ESkipCondition.FlyingLocked, "FlyingLocked" },
+ { ESkipCondition.FlyingUnlocked, "FlyingUnlocked" },
+ };
+}
--- /dev/null
+using System;
+using System.Numerics;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Questionable.Model.V1.Converter;
+
+public sealed class VectorConverter : JsonConverter<Vector3>
+{
+ public override Vector3 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.StartObject)
+ throw new JsonException();
+
+ Vector3 vec = new Vector3();
+ while (reader.Read())
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.PropertyName:
+ string? propertyName = reader.GetString();
+ if (propertyName == null || !reader.Read())
+ throw new JsonException();
+
+ switch (propertyName)
+ {
+ case nameof(Vector3.X):
+ vec.X = reader.GetSingle();
+ break;
+
+ case nameof(Vector3.Y):
+ vec.Y = reader.GetSingle();
+ break;
+
+ case nameof(Vector3.Z):
+ vec.Z = reader.GetSingle();
+ break;
+
+ default:
+ throw new JsonException();
+ }
+
+ break;
+
+ case JsonTokenType.EndObject:
+ return vec;
+
+ default:
+ throw new JsonException();
+ }
+ }
+
+ throw new JsonException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, Vector3 value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteNumber(nameof(Vector3.X), value.X);
+ writer.WriteNumber(nameof(Vector3.Y), value.X);
+ writer.WriteNumber(nameof(Vector3.Z), value.X);
+ writer.WriteEndObject();
+ }
+}
--- /dev/null
+using System.Text.Json.Serialization;
+using Questionable.Model.V1.Converter;
+
+namespace Questionable.Model.V1;
+
+public sealed class DialogueChoice
+{
+ [JsonConverter(typeof(DialogueChoiceTypeConverter))]
+ public EDialogChoiceType Type { get; set; }
+ public string? ExcelSheet { get; set; }
+
+ [JsonConverter(typeof(ExcelRefConverter))]
+ public ExcelRef? Prompt { get; set; }
+
+ public bool Yes { get; set; } = true;
+
+ [JsonConverter(typeof(ExcelRefConverter))]
+ public ExcelRef? Answer { get; set; }
+
+ /// <summary>
+ /// If set, only applies when focusing the given target id.
+ /// </summary>
+ public uint? DataId { get; set; }
+}
--- /dev/null
+using System.Text.Json.Serialization;
+using Questionable.Model.V1.Converter;
+
+namespace Questionable.Model.V1;
+
+[JsonConverter(typeof(AetheryteConverter))]
+public enum EAetheryteLocation
+{
+ None = 0,
+
+ Gridania = 2,
+ GridaniaArcher = 25,
+ GridaniaLeatherworker = 26,
+ GridaniaLancer = 27,
+ GridaniaConjurer = 28,
+ GridaniaBotanist = 29,
+ GridaniaAmphitheatre = 30,
+ GridaniaBlueBadgerGate = 31,
+ GridaniaYellowSerpentGate = 32,
+ GridaniaWhiteWolfGate = 54,
+ GridaniaAirship = 94,
+
+ CentralShroudBentbranchMeadows = 3,
+ EastShroudHawthorneHut = 4,
+ SouthShroudQuarrymill = 5,
+ SouthShroudCampTranquil = 6,
+ NorthShroudFallgourdFloat = 7,
+
+ Uldah = 9,
+ UldahAdventurers = 33,
+ UldahThaumaturge = 34,
+ UldahGladiator = 35,
+ UldahMiner = 36,
+ UldahAlchemist = 37,
+ UldahWeaver = 47,
+ UldahGoldsmith = 50,
+ UldahChamberOfRule = 51,
+ UldahAirship = 95,
+ UldahGateOfTheSultana = 38,
+ UldahGateOfNald = 39,
+ UldahGateOfThal = 40,
+ UldahSapphireAvenue = 125,
+
+ WesternThanalanHorizon = 17,
+ EasternThanalanCampDrybone = 18,
+ SouthernThanalanLittleAlaMhigo = 19,
+ SouthernThanalanForgottenSprings = 20,
+ NorthernThanalanCampBluefog = 21,
+ NorthernThanalanCeruleumProcessingPlant = 22,
+ CentralThanalanBlackBrushStation = 53,
+
+ Limsa = 8,
+ LimsaAftcastle = 41,
+ LimsaCulinarian = 42,
+ LimsaArcanist = 43,
+ LimsaFisher = 44,
+ LimsaMarauder = 48,
+ LimsaHawkersAlley = 49,
+ LimsaZephyrGate = 45,
+ LimsaTempestGate = 46,
+ LimsaAirship = 93,
+
+ LowerLaNosceaMorabyDrydocks = 10,
+ EasternLaNosceaCostaDelSol = 11,
+ EasternLaNosceaWineport = 12,
+ WesternLaNosceaSwiftperch = 13,
+ WesternLaNosceaAleport = 14,
+ UpperLaNosceaCampBronzeLake = 15,
+ OuterLaNosceaCampOverlook = 16,
+ MiddleLaNosceaSummerfordFarms = 52,
+
+ CoerthasCentralHighlandsCampDragonhead = 23,
+ MorDhona = 24,
+ GoldSaucer = 62,
+ WolvesDenPier = 55,
+
+ Ishgard = 70,
+ IshgardForgottenKnight = 80,
+ IshgardSkysteelManufactory = 81,
+ IshgardBrume = 82,
+ IshgardAthenaeumAstrologicum = 83,
+ IshgardJeweledCrozier = 84,
+ IshgardSaintReymanaudsCathedral = 85,
+ IshgardTribunal = 86,
+ IshgardLastVigil = 87,
+ IshgardGatesOfJudgement = 88,
+
+ Idyllshire = 75,
+ IdyllshireWest = 90,
+ IdyllshirePrologueGate = 91,
+ IdyllshireEpilogueGate = 92,
+
+ CoerthasWesternHighlandsFalconsNest = 71,
+ SeaOfCloudsCampCloudtop = 72,
+ SeaOfCloudsOkZundu = 73,
+ AzysLlaHelix = 74,
+ DravanianForelandsTailfeather = 76,
+ DravanianForelandsAnyxTrine = 77,
+ ChurningMistsMoghome = 78,
+ ChurningMistsZenith = 79,
+
+ RhalgrsReach = 104,
+ RhalgrsReachWest = 121,
+ RhalgrsReachNorthEast = 122,
+ RhalgrsReachFringesGate = 123,
+ RhalgrsReachPeaksGate = 124,
+
+ Kugane = 111,
+ KuganeShiokazeHostelry = 112,
+ KuganePier1 = 113,
+ KuganeThavnairianConsulate = 114,
+ KuganeMarkets = 115,
+ KuganeBokairoInn = 116,
+ KuganeRubyBazaar = 117,
+ KuganeSekiseigumiBarracks = 118,
+ KuganeRakuzaDistrict = 119,
+ KuganeRubyPrice = 120,
+ KuganeAirship = 126,
+
+ FringesCastrumOriens = 98,
+ FringesPeeringStones = 99,
+ PeaksAlaGannha = 100,
+ PeaksAlaGhiri = 101,
+ LochsPortaPraetoria = 102,
+ LochsAlaMhiganQuarter = 103,
+ RubySeaTamamizu = 105,
+ RubySeaOnokoro = 106,
+ YanxiaNamai = 107,
+ YanxiaHouseOfTheFierce = 108,
+ AzimSteppeReunion = 109,
+ AzimSteppeDawnThrone = 110,
+ AzimSteppeDhoroIloh = 128,
+
+ DomanEnclave = 127,
+ DomanEnclaveNorthern = 129,
+ DomanEnclaveSouthern = 130,
+ DomanEnclaveOneRiver = 131,
+ DomanEnclaveDocks = 162,
+
+ Crystarium = 133,
+ CrystariumMarkets = 149,
+ CrystariumThemenosRookery = 150,
+ CrystariumDossalGate = 151,
+ CrystariumPendants = 152,
+ CrystariumAmaroLaunch = 153,
+ CrystariumCrystallineMean = 154,
+ CrystariumCabinetOfCuriosity = 155,
+ CrystariumTessellation = 156,
+
+ Eulmore = 134,
+ EulmoreMainstay = 157,
+ EulmoreNightsoilPots = 158,
+ EulmoreGloryGate = 159,
+ EulmoreSoutheastDerelict = 135,
+ EulmorePathToGlory = 160,
+
+ LakelandFortJobb = 132,
+ LakelandOstallImperative = 136,
+ KholusiaStilltide = 137,
+ KholusiaWright = 138,
+ KholusiaTomra = 139,
+ AmhAraengMordSouq = 140,
+ AmhAraengInnAtJourneysHead = 161,
+ AmhAraengTwine = 141,
+ RaktikaSlitherbough = 142,
+ RaktikaFanow = 143,
+ IlMhegLydhaLran = 144,
+ IlMhegPiaEnni = 145,
+ IlMhegWolekdorf = 146,
+ TempestOndoCups = 147,
+ TempestMacarensesAngle = 148,
+
+ OldSharlayan = 182,
+ OldSharlayanStudium = 184,
+ OldSharlayanBaldesionAnnex = 185,
+ OldSharlayanRostra = 186,
+ OldSharlayanLeveilleurEstate = 187,
+ OldSharlayanJourneysEnd = 188,
+ OldSharlayanScholarsHarbor = 189,
+ OldSharlayanHallOfArtifice = 190,
+
+ RadzAtHan = 183,
+ RadzAtHanMeghaduta = 191,
+ RadzAtHanRuveydahFibers = 192,
+ RadzAtHanAirship = 193,
+ RadzAtHanAlzadaalsPeace = 194,
+ RadzAtHanHallOfTheRadiantHost = 195,
+ RadzAtHanMehrydesMeyhane = 196,
+ RadzAtHanKama = 198,
+ RadzAtHanHighCrucible = 199,
+ RadzAtHanGateOfFirstSight = 197,
+
+ LabyrinthosArcheion = 166,
+ LabyrinthosSharlayanHamlet = 167,
+ LabyrinthosAporia = 168,
+ ThavnairYedlihmad = 169,
+ ThavnairGreatWork = 170,
+ ThavnairPalakasStand = 171,
+ GarlemaldCampBrokenGlass = 172,
+ GarlemaldTertium = 173,
+ MareLamentorumSinusLacrimarum = 174,
+ MareLamentorumBestwaysBurrow = 175,
+ ElpisAnagnorisis = 176,
+ ElpisTwelveWonders = 177,
+ ElpisPoietenOikos = 178,
+ UltimaThuleReahTahra = 179,
+ UltimaThuleAbodeOfTheEa = 180,
+ UltimaThuleBaseOmicron = 181
+}
--- /dev/null
+namespace Questionable.Model.V1;
+
+public enum EDialogChoiceType
+{
+ None,
+ YesNo,
+ List,
+}
--- /dev/null
+using System.Text.Json.Serialization;
+using Questionable.Model.V1.Converter;
+
+namespace Questionable.Model.V1;
+
+[JsonConverter(typeof(EmoteConverter))]
+public enum EEmote
+{
+ None = 0,
+
+ Stretch = 37,
+ Wave = 16,
+ Rally = 34,
+ Deny = 25,
+ Pray = 58,
+ Slap = 111,
+ Doubt = 12,
+ Psych = 30,
+}
--- /dev/null
+using System.Text.Json.Serialization;
+using Questionable.Model.V1.Converter;
+
+namespace Questionable.Model.V1;
+
+[JsonConverter(typeof(EnemySpawnTypeConverter))]
+public enum EEnemySpawnType
+{
+ None = 0,
+ AfterInteraction,
+ AfterItemUse,
+ AutoOnEnterArea,
+ OverworldEnemies,
+}
--- /dev/null
+using System.Text.Json.Serialization;
+using Questionable.Model.V1.Converter;
+
+namespace Questionable.Model.V1;
+
+[JsonConverter(typeof(InteractionTypeConverter))]
+public enum EInteractionType
+{
+ Interact,
+ WalkTo,
+ AttuneAethernetShard,
+ AttuneAetheryte,
+ AttuneAetherCurrent,
+ Combat,
+ UseItem,
+ EquipItem,
+ Say,
+ Emote,
+ WaitForObjectAtPosition,
+ WaitForManualProgress,
+ Duty,
+ SinglePlayerDuty,
+ Jump,
+
+ /// <summary>
+ /// Needs to be adjusted for coords etc. in the quest data.
+ /// </summary>
+ ShouldBeAJump,
+
+ /// <summary>
+ /// Needs to be manually continued.
+ /// </summary>
+ Instruction,
+}
--- /dev/null
+using System.Text.Json.Serialization;
+using Questionable.Model.V1.Converter;
+
+namespace Questionable.Model.V1;
+
+[JsonConverter(typeof(SkipConditionConverter))]
+public enum ESkipCondition
+{
+ None,
+ Never,
+ FlyingLocked,
+ FlyingUnlocked,
+}
--- /dev/null
+using System;
+
+namespace Questionable.Model.V1;
+
+public class ExcelRef
+{
+ private readonly string? _stringValue;
+ private readonly uint? _rowIdValue;
+
+ public ExcelRef(string value)
+ {
+ _stringValue = value;
+ _rowIdValue = null;
+ Type = EType.Key;
+ }
+
+ public ExcelRef(uint value)
+ {
+ _stringValue = null;
+ _rowIdValue = value;
+ Type = EType.RowId;
+ }
+
+ public EType Type { get; }
+
+ public string AsKey()
+ {
+ if (Type != EType.Key)
+ throw new InvalidOperationException();
+
+ return _stringValue!;
+ }
+
+ public uint AsRowId()
+ {
+ if (Type != EType.RowId)
+ throw new InvalidOperationException();
+
+ return _rowIdValue!.Value;
+ }
+
+ public enum EType
+ {
+ None,
+ Key,
+ RowId,
+ }
+}
--- /dev/null
+using System.Numerics;
+using System.Text.Json.Serialization;
+using Questionable.Model.V1.Converter;
+
+namespace Questionable.Model.V1;
+
+public sealed class JumpDestination
+{
+ [JsonConverter(typeof(VectorConverter))]
+ public Vector3 Position { get; set; }
+
+ public float? StopDistance { get; set; }
+ public float? DelaySeconds { get; set; }
+}
--- /dev/null
+using System.Collections.Generic;
+
+namespace Questionable.Model.V1;
+
+public sealed class QuestData
+{
+ public string Author { get; set; } = null!;
+ public List<string> Contributors { get; set; } = new();
+ public string? Comment { get; set; }
+ public List<ushort> TerritoryBlacklist { get; set; } = new();
+ public List<QuestSequence> QuestSequence { get; set; } = new();
+}
--- /dev/null
+using System.Collections.Generic;
+
+namespace Questionable.Model.V1;
+
+public sealed class QuestSequence
+{
+ public int Sequence { get; set; }
+ public string? Comment { get; set; }
+ public List<QuestStep> Steps { get; set; } = new();
+
+ public QuestStep? FindStep(int step)
+ {
+ if (step < 0 || step >= Steps.Count)
+ return null;
+
+ return Steps[step];
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+using System.Text.Json.Serialization;
+using Questionable.Model.V1.Converter;
+
+namespace Questionable.Model.V1;
+
+public sealed class QuestStep
+{
+ public EInteractionType InteractionType { get; set; }
+
+ public uint? DataId { get; set; }
+
+ [JsonConverter(typeof(VectorConverter))]
+ public Vector3? Position { get; set; }
+
+ public float? StopDistance { get; set; }
+ public ushort TerritoryId { get; set; }
+ public ushort? TargetTerritoryId { get; set; }
+
+ public bool Disabled { get; set; }
+ public bool DisableNavmesh { get; set; }
+ public bool? Mount { get; set; }
+ public bool? Fly { get; set; }
+ public bool? Sprint { get; set; }
+ public string? Comment { get; set; }
+
+ public EAetheryteLocation? AetheryteShortcut { get; set; }
+
+ public AethernetShortcut? AethernetShortcut { get; set; }
+ public uint? AetherCurrentId { get; set; }
+
+ public uint? ItemId { get; set; }
+ public bool? GroundTarget { get; set; }
+
+ public EEmote? Emote { get; set; }
+ public ChatMessage? ChatMessage { get; set; }
+
+ public EEnemySpawnType? EnemySpawnType { get; set; }
+ public IList<uint> KillEnemyDataIds { get; set; } = new List<uint>();
+
+ public JumpDestination? JumpDestination { get; set; }
+ public uint? ContentFinderConditionId { get; set; }
+
+ public IList<ESkipCondition> SkipIf { get; set; } = new List<ESkipCondition>();
+ public IList<short?> CompletionQuestVariablesFlags { get; set; } = new List<short?>();
+ public IList<DialogueChoice> DialogueChoices { get; set; } = new List<DialogueChoice>();
+
+ [JsonConstructor]
+ public QuestStep()
+ {
+ }
+
+ public QuestStep(EInteractionType interactionType, uint? dataId, Vector3? position, ushort territoryId)
+ {
+ InteractionType = interactionType;
+ DataId = dataId;
+ Position = position;
+ TerritoryId = territoryId;
+ }
+}
--- /dev/null
+{
+ "version": 1,
+ "dependencies": {
+ ".NETStandard,Version=v2.0": {
+ "NETStandard.Library": {
+ "type": "Direct",
+ "requested": "[2.0.3, )",
+ "resolved": "2.0.3",
+ "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0"
+ }
+ },
+ "System.Text.Json": {
+ "type": "Direct",
+ "requested": "[8.0.3, )",
+ "resolved": "8.0.3",
+ "contentHash": "hpagS9joOwv6efWfrMmV9MjQXpiXZH72PgN067Ysfr6AWMSD1/1hEcvh/U5mUpPLezEWsOJSuVrmqDIVD958iA==",
+ "dependencies": {
+ "Microsoft.Bcl.AsyncInterfaces": "8.0.0",
+ "System.Buffers": "4.5.1",
+ "System.Memory": "4.5.5",
+ "System.Runtime.CompilerServices.Unsafe": "6.0.0",
+ "System.Text.Encodings.Web": "8.0.0",
+ "System.Threading.Tasks.Extensions": "4.5.4"
+ }
+ },
+ "Microsoft.Bcl.AsyncInterfaces": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==",
+ "dependencies": {
+ "System.Threading.Tasks.Extensions": "4.5.4"
+ }
+ },
+ "Microsoft.NETCore.Platforms": {
+ "type": "Transitive",
+ "resolved": "1.1.0",
+ "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
+ },
+ "System.Buffers": {
+ "type": "Transitive",
+ "resolved": "4.5.1",
+ "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
+ },
+ "System.Memory": {
+ "type": "Transitive",
+ "resolved": "4.5.5",
+ "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
+ "dependencies": {
+ "System.Buffers": "4.5.1",
+ "System.Numerics.Vectors": "4.4.0",
+ "System.Runtime.CompilerServices.Unsafe": "4.5.3"
+ }
+ },
+ "System.Numerics.Vectors": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ=="
+ },
+ "System.Runtime.CompilerServices.Unsafe": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
+ },
+ "System.Text.Encodings.Web": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==",
+ "dependencies": {
+ "System.Buffers": "4.5.1",
+ "System.Memory": "4.5.5",
+ "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+ }
+ },
+ "System.Threading.Tasks.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.5.4",
+ "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
+ "dependencies": {
+ "System.Runtime.CompilerServices.Unsafe": "4.5.3"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPaths", "QuestPaths\QuestPaths.csproj", "{7A136F28-8D5C-478D-B993-0F39F1451A47}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPaths", "QuestPathGenerator\QuestPathGenerator.csproj", "{DFFD56A8-FA89-4585-A47B-C6AB27B65F0F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Questionable.Model", "Questionable.Model\Questionable.Model.csproj", "{E15144A5-AFF5-4D86-9561-AFF7DF7F505D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
{7A136F28-8D5C-478D-B993-0F39F1451A47}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7A136F28-8D5C-478D-B993-0F39F1451A47}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7A136F28-8D5C-478D-B993-0F39F1451A47}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DFFD56A8-FA89-4585-A47B-C6AB27B65F0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DFFD56A8-FA89-4585-A47B-C6AB27B65F0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DFFD56A8-FA89-4585-A47B-C6AB27B65F0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DFFD56A8-FA89-4585-A47B-C6AB27B65F0F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E15144A5-AFF5-4D86-9561-AFF7DF7F505D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E15144A5-AFF5-4D86-9561-AFF7DF7F505D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E15144A5-AFF5-4D86-9561-AFF7DF7F505D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E15144A5-AFF5-4D86-9561-AFF7DF7F505D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
#if RELEASE
_logger.LogInformation("Loading quests from assembly");
- QuestPaths.AssemblyQuestLoader.LoadQuestsFromEmbeddedResources(LoadQuestFromStream);
+
+ foreach ((ushort questId, QuestData questData) in QuestPaths.AssemblyQuestLoader.GetQuests())
+ {
+ Quest quest = new()
+ {
+ QuestId = questId,
+ Name = string.Empty,
+ Data = questData,
+ };
+ _quests[questId] = quest;
+ }
#else
DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation?.Directory?.Parent?.Parent;
if (solutionDirectory != null)
}
}
#endif
+
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests")));
foreach (var (questId, quest) in _quests)
quest.Name = questData.Name.ToString();
quest.Level = questData.ClassJobLevel0;
}
+
+ _logger.LogInformation("Loaded {Count} quests", _quests.Count);
}
+++ /dev/null
-using System.Text.Json.Serialization;
-using Questionable.Model.V1.Converter;
-
-namespace Questionable.Model.V1;
-
-[JsonConverter(typeof(AethernetShortcutConverter))]
-internal sealed class AethernetShortcut
-{
- public EAetheryteLocation From { get; set; }
- public EAetheryteLocation To { get; set; }
-}
+++ /dev/null
-using JetBrains.Annotations;
-
-namespace Questionable.Model.V1;
-
-[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)]
-internal sealed class ChatMessage
-{
- public string? ExcelSheet { get; set; }
- public string Key { get; set; } = null!;
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace Questionable.Model.V1.Converter;
-
-internal sealed class AethernetShortcutConverter : JsonConverter<AethernetShortcut>
-{
- private static readonly Dictionary<EAetheryteLocation, string> EnumToString = new()
- {
- { EAetheryteLocation.Gridania, "[Gridania] Aetheryte Plaza" },
- { EAetheryteLocation.GridaniaArcher, "[Gridania] Archers' Guild" },
- { EAetheryteLocation.GridaniaLeatherworker, "[Gridania] Leatherworkers' Guild & Shaded Bower" },
- { EAetheryteLocation.GridaniaLancer, "[Gridania] Lancers' Guild" },
- { EAetheryteLocation.GridaniaConjurer, "[Gridania] Conjurers' Guild" },
- { EAetheryteLocation.GridaniaBotanist, "[Gridania] Botanists' Guild" },
- { EAetheryteLocation.GridaniaAmphitheatre, "[Gridania] Mih Khetto's Amphitheatre" },
- { EAetheryteLocation.GridaniaBlueBadgerGate, "[Gridania] Blue Badger Gate (Central Shroud)" },
- { EAetheryteLocation.GridaniaYellowSerpentGate, "[Gridania] Yellow Serpent Gate (North Shroud)" },
- { EAetheryteLocation.GridaniaWhiteWolfGate, "[Gridania] White Wolf Gate (Central Shroud)" },
- { EAetheryteLocation.GridaniaAirship, "[Gridania] Airship Landing" },
-
- { EAetheryteLocation.Uldah, "[Ul'dah] Aetheryte Plaza" },
- { EAetheryteLocation.UldahAdventurers, "[Ul'dah] Adventurers' Guild" },
- { EAetheryteLocation.UldahThaumaturge, "[Ul'dah] Thaumaturges' Guild" },
- { EAetheryteLocation.UldahGladiator, "[Ul'dah] Gladiators' Guild" },
- { EAetheryteLocation.UldahMiner, "[Ul'dah] Miners' Guild" },
- { EAetheryteLocation.UldahWeaver, "[Ul'dah] Weavers' Guild" },
- { EAetheryteLocation.UldahGoldsmith, "[Ul'dah] Goldsmiths' Guild" },
- { EAetheryteLocation.UldahSapphireAvenue, "[Ul'dah] Sapphire Avenue Exchange" },
- { EAetheryteLocation.UldahAlchemist, "[Ul'dah] Alchemists' Guild" },
- { EAetheryteLocation.UldahChamberOfRule, "[Ul'dah] The Chamber of Rule" },
- { EAetheryteLocation.UldahGateOfTheSultana, "[Ul'dah] Gate of the Sultana (Western Thanalan)" },
- { EAetheryteLocation.UldahGateOfNald, "[Ul'dah] Gate of Nald (Central Thanalan)" },
- { EAetheryteLocation.UldahGateOfThal, "[Ul'dah] Gate of Thal (Central Thanalan)" },
- { EAetheryteLocation.UldahAirship, "[Ul'dah] Airship Landing" },
-
- { EAetheryteLocation.Limsa, "[Limsa Lominsa] Aetheryte Plaza" },
- { EAetheryteLocation.LimsaArcanist, "[Limsa Lominsa] Arcanists' Guild" },
- { EAetheryteLocation.LimsaFisher, "[Limsa Lominsa] Fishermens' Guild" },
- { EAetheryteLocation.LimsaHawkersAlley, "[Limsa Lominsa] Hawkers' Alley" },
- { EAetheryteLocation.LimsaAftcastle, "[Limsa Lominsa] The Aftcastle" },
- { EAetheryteLocation.LimsaCulinarian, "[Limsa Lominsa] Culinarians' Guild" },
- { EAetheryteLocation.LimsaMarauder, "[Limsa Lominsa] Marauders' Guild" },
- { EAetheryteLocation.LimsaZephyrGate, "[Limsa Lominsa] Zephyr Gate (Middle La Noscea)" },
- { EAetheryteLocation.LimsaTempestGate, "[Limsa Lominsa] Tempest Gate (Lower La Noscea)" },
- { EAetheryteLocation.LimsaAirship, "[Limsa Lominsa] Airship Landing" },
-
- { EAetheryteLocation.Ishgard, "[Ishgard] Aetheryte Plaza" },
- { EAetheryteLocation.IshgardForgottenKnight, "[Ishgard] The Forgotten Knight" },
- { EAetheryteLocation.IshgardSkysteelManufactory, "[Ishgard] Skysteel Manufactory" },
- { EAetheryteLocation.IshgardBrume, "[Ishgard] The Brume" },
- { EAetheryteLocation.IshgardAthenaeumAstrologicum, "[Ishgard] Athenaeum Astrologicum" },
- { EAetheryteLocation.IshgardJeweledCrozier, "[Ishgard] The Jeweled Crozier" },
- { EAetheryteLocation.IshgardSaintReymanaudsCathedral, "[Ishgard] Saint Reymanaud's Cathedral" },
- { EAetheryteLocation.IshgardTribunal, "[Ishgard] The Tribunal" },
- { EAetheryteLocation.IshgardLastVigil, "[Ishgard] The Last Vigil" },
- { EAetheryteLocation.IshgardGatesOfJudgement, "[Ishgard] The Gates of Judgement (Coerthas Central Highlands)" },
-
- { EAetheryteLocation.Idyllshire, "[Idyllshire] Aetheryte Plaza" },
- { EAetheryteLocation.IdyllshireWest, "[Idyllshire] West Idyllshire" },
- { EAetheryteLocation.IdyllshirePrologueGate, "[Idyllshire] Prologue Gate" },
- { EAetheryteLocation.IdyllshireEpilogueGate, "[Idyllshire] Epilogue Gate" },
-
- { EAetheryteLocation.RhalgrsReach, "[Rhalgr's Reach] Aetheryte Plaza" },
- { EAetheryteLocation.RhalgrsReachWest, "[Rhalgr's Reach] Western Rhalgr's Reach" },
- { EAetheryteLocation.RhalgrsReachNorthEast, "[Rhalgr's Reach] Northeastern Rhalgr's Reach" },
- { EAetheryteLocation.RhalgrsReachFringesGate, "[Rhalgr's Reach] Fringes Gate" },
- { EAetheryteLocation.RhalgrsReachPeaksGate, "[Rhalgr's Reach] Peaks Gate" },
-
- { EAetheryteLocation.Kugane, "[Kugane] Aetheryte Plaza" },
- { EAetheryteLocation.KuganeShiokazeHostelry, "[Kugane] Shiokaze Hostelry" },
- { EAetheryteLocation.KuganePier1, "[Kugane] Pier #1" },
- { EAetheryteLocation.KuganeThavnairianConsulate, "[Kugane] Thavnairian Consulate" },
- { EAetheryteLocation.KuganeMarkets, "[Kugane] Kogane Dori Markets" },
- { EAetheryteLocation.KuganeBokairoInn, "[Kugane] Bokairo Inn" },
- { EAetheryteLocation.KuganeRubyBazaar, "[Kugane] The Ruby Bazaar" },
- { EAetheryteLocation.KuganeSekiseigumiBarracks, "[Kugane] Sekiseigumi Barracks" },
- { EAetheryteLocation.KuganeRakuzaDistrict, "[Kugane] Rakuza District" },
- { EAetheryteLocation.KuganeRubyPrice, "[Kugane] The Ruby Price" },
- { EAetheryteLocation.KuganeAirship, "[Kugane] Airship Landing" },
-
- { EAetheryteLocation.Crystarium, "[Crystarium] Aetheryte Plaza" },
- { EAetheryteLocation.CrystariumMarkets, "[Crystarium] Musica Universalis Markets" },
- { EAetheryteLocation.CrystariumThemenosRookery, "[Crystarium] Themenos Rookery" },
- { EAetheryteLocation.CrystariumDossalGate, "[Crystarium] The Dossal Gate" },
- { EAetheryteLocation.CrystariumPendants, "[Crystarium] The Pendants" },
- { EAetheryteLocation.CrystariumAmaroLaunch, "[Crystarium] The Amaro Launch" },
- { EAetheryteLocation.CrystariumCrystallineMean, "[Crystarium] The Crystalline Mean" },
- { EAetheryteLocation.CrystariumCabinetOfCuriosity, "[Crystarium] The Cabinet of Curiosity" },
- { EAetheryteLocation.CrystariumTessellation, "[Crystarium] Tessellation (Lakeland)" },
-
- { EAetheryteLocation.Eulmore, "[Eulmore] Aetheryte Plaza" },
- { EAetheryteLocation.EulmoreSoutheastDerelict, "[Eulmore] Southeast Derelicts" },
- { EAetheryteLocation.EulmoreNightsoilPots, "[Eulmore] Nightsoil Pots" },
- { EAetheryteLocation.EulmoreGloryGate, "[Eulmore] The Glory Gate" },
- { EAetheryteLocation.EulmoreMainstay, "[Eulmore] The Mainstay" },
- { EAetheryteLocation.EulmorePathToGlory, "[Eulmore] The Path to Glory (Kholusia)" },
-
- { EAetheryteLocation.OldSharlayan, "[Old Sharlayan] Aetheryte Plaza" },
- { EAetheryteLocation.OldSharlayanStudium, "[Old Sharlayan] The Studium" },
- { EAetheryteLocation.OldSharlayanBaldesionAnnex, "[Old Sharlayan] The Baldesion Annex" },
- { EAetheryteLocation.OldSharlayanRostra, "[Old Sharlayan] The Rostra" },
- { EAetheryteLocation.OldSharlayanLeveilleurEstate, "[Old Sharlayan] The Leveilleur Estate" },
- { EAetheryteLocation.OldSharlayanJourneysEnd, "[Old Sharlayan] Journey's End" },
- { EAetheryteLocation.OldSharlayanScholarsHarbor, "[Old Sharlayan] Scholar's Harbor" },
- { EAetheryteLocation.OldSharlayanHallOfArtifice, "[Old Sharlayan] The Hall of Artifice (Labyrinthos)" },
-
- { EAetheryteLocation.RadzAtHan, "[Radz-at-Han] Aetheryte Plaza" },
- { EAetheryteLocation.RadzAtHanMeghaduta, "[Radz-at-Han] Meghaduta" },
- { EAetheryteLocation.RadzAtHanRuveydahFibers, "[Radz-at-Han] Ruveydah Fibers" },
- { EAetheryteLocation.RadzAtHanAirship, "[Radz-at-Han] Airship Landing" },
- { EAetheryteLocation.RadzAtHanAlzadaalsPeace, "[Radz-at-Han] Alzadaal's Peace" },
- { EAetheryteLocation.RadzAtHanHallOfTheRadiantHost, "[Radz-at-Han] Hall of the Radiant Host" },
- { EAetheryteLocation.RadzAtHanMehrydesMeyhane, "[Radz-at-Han] Mehryde's Meyhane" },
- { EAetheryteLocation.RadzAtHanKama, "[Radz-at-Han] Kama" },
- { EAetheryteLocation.RadzAtHanHighCrucible, "[Radz-at-Han] The High Crucible of Al-Kimiya" },
- { EAetheryteLocation.RadzAtHanGateOfFirstSight, "[Radz-at-Han] The Gate of First Sight (Thavnair)" },
- };
-
- private static readonly Dictionary<string, EAetheryteLocation> StringToEnum =
- EnumToString.ToDictionary(x => x.Value, x => x.Key);
-
- public override AethernetShortcut Read(ref Utf8JsonReader reader, Type typeToConvert,
- JsonSerializerOptions options)
- {
- if (reader.TokenType != JsonTokenType.StartArray)
- throw new JsonException();
-
- if (!reader.Read() || reader.TokenType != JsonTokenType.String)
- throw new JsonException();
-
- string from = reader.GetString() ?? throw new JsonException();
-
- if (!reader.Read() || reader.TokenType != JsonTokenType.String)
- throw new JsonException();
-
- string to = reader.GetString() ?? throw new JsonException();
-
- if (!reader.Read() || reader.TokenType != JsonTokenType.EndArray)
- throw new JsonException();
-
- return new AethernetShortcut
- {
- From = StringToEnum.TryGetValue(from, out var fromEnum) ? fromEnum : throw new JsonException(),
- To = StringToEnum.TryGetValue(to, out var toEnum) ? toEnum : throw new JsonException()
- };
- }
-
- public override void Write(Utf8JsonWriter writer, AethernetShortcut value, JsonSerializerOptions options)
- {
- ArgumentNullException.ThrowIfNull(writer);
- ArgumentNullException.ThrowIfNull(value);
-
- writer.WriteStartArray();
- writer.WriteStringValue(EnumToString[value.From]);
- writer.WriteStringValue(EnumToString[value.To]);
- writer.WriteEndArray();
- }
-}
+++ /dev/null
-using System.Collections.Generic;
-
-namespace Questionable.Model.V1.Converter;
-
-internal sealed class AetheryteConverter() : EnumConverter<EAetheryteLocation>(Values)
-{
- private static readonly Dictionary<EAetheryteLocation, string> Values = new()
- {
- { EAetheryteLocation.Gridania, "Gridania" },
-
- { EAetheryteLocation.CentralShroudBentbranchMeadows, "Central Shroud - Bentbranch Meadows" },
- { EAetheryteLocation.EastShroudHawthorneHut, "East Shroud - Hawthorne Hut" },
- { EAetheryteLocation.SouthShroudQuarrymill, "South Shroud - Quarrymill" },
- { EAetheryteLocation.SouthShroudCampTranquil, "South Shroud - Camp Tranquil" },
- { EAetheryteLocation.NorthShroudFallgourdFloat, "North Shroud - Fallgourd Float" },
-
- { EAetheryteLocation.Uldah, "Ul'dah" },
- { EAetheryteLocation.WesternThanalanHorizon, "Western Thanalan - Horizon" },
- { EAetheryteLocation.CentralThanalanBlackBrushStation, "Central Thanalan - Black Brush Station" },
- { EAetheryteLocation.EasternThanalanCampDrybone, "Eastern Thanalan - Camp Drybone" },
- { EAetheryteLocation.SouthernThanalanLittleAlaMhigo, "Southern Thanalan - Little Ala Mhigo" },
- { EAetheryteLocation.SouthernThanalanForgottenSprings, "Southern Thanalan - Forgotten Springs" },
- { EAetheryteLocation.NorthernThanalanCampBluefog, "Northern Thanalan - Camp Bluefog" },
- { EAetheryteLocation.NorthernThanalanCeruleumProcessingPlant, "Northern Thanalan - Ceruleum Processing Plant" },
-
- { EAetheryteLocation.Limsa, "Limsa Lominsa" },
- { EAetheryteLocation.MiddleLaNosceaSummerfordFarms, "Middle La Noscea - Summerford Farms" },
- { EAetheryteLocation.LowerLaNosceaMorabyDrydocks, "Lower La Noscea - Moraby Drydocks" },
- { EAetheryteLocation.EasternLaNosceaCostaDelSol, "Eastern La Noscea - Costa Del Sol" },
- { EAetheryteLocation.EasternLaNosceaWineport, "Eastern La Noscea - Wineport" },
- { EAetheryteLocation.WesternLaNosceaSwiftperch, "Western La Noscea - Swiftperch" },
- { EAetheryteLocation.WesternLaNosceaAleport, "Western La Noscea - Aleport" },
- { EAetheryteLocation.UpperLaNosceaCampBronzeLake, "Upper La Noscea - Camp Bronze Lake" },
- { EAetheryteLocation.OuterLaNosceaCampOverlook, "Outer La Noscea - Camp Overlook" },
-
- { EAetheryteLocation.CoerthasCentralHighlandsCampDragonhead, "Coerthas Central Highlands - Camp Dragonhead" },
- { EAetheryteLocation.MorDhona, "Mor Dhona" },
- { EAetheryteLocation.GoldSaucer, "Gold Saucer" },
- { EAetheryteLocation.WolvesDenPier, "Wolves' Den Pier" },
-
- { EAetheryteLocation.Ishgard, "Ishgard" },
- { EAetheryteLocation.Idyllshire, "Idyllshire" },
- { EAetheryteLocation.CoerthasWesternHighlandsFalconsNest, "Coerthas Western Highlands - Falcon's Nest" },
- { EAetheryteLocation.SeaOfCloudsCampCloudtop, "The Sea of Clouds - Camp Cloudtop" },
- { EAetheryteLocation.SeaOfCloudsOkZundu, "The Sea of Clouds - Ok' Zundu" },
- { EAetheryteLocation.AzysLlaHelix, "Azys Lla - Helix" },
- { EAetheryteLocation.DravanianForelandsTailfeather, "The Dravanian Forelands - Tailfeather" },
- { EAetheryteLocation.DravanianForelandsAnyxTrine, "The Dravanian Forelands - Anyx Trine" },
- { EAetheryteLocation.ChurningMistsMoghome, "The Churning Mists - Moghome" },
- { EAetheryteLocation.ChurningMistsZenith, "The Churning Mists - Zenith" },
-
- { EAetheryteLocation.RhalgrsReach, "Rhalgr's Reach" },
- { EAetheryteLocation.FringesCastrumOriens, "Fringes - Castrum Oriens" },
- { EAetheryteLocation.FringesPeeringStones, "Fringes - Peering Stones" },
- { EAetheryteLocation.PeaksAlaGannha, "Peaks - Ala Gannha" },
- { EAetheryteLocation.PeaksAlaGhiri, "Peaks - Ala Ghiri" },
- { EAetheryteLocation.LochsPortaPraetoria, "Lochs - Porta Praetoria" },
- { EAetheryteLocation.LochsAlaMhiganQuarter, "Lochs - Ala Mhigan Quarter" },
- { EAetheryteLocation.Kugane, "Kugane" },
- { EAetheryteLocation.RubySeaTamamizu, "Ruby Sea - Tamamizu" },
- { EAetheryteLocation.RubySeaOnokoro, "Ruby Sea - Onokoro" },
- { EAetheryteLocation.YanxiaNamai, "Yanxia - Namai" },
- { EAetheryteLocation.YanxiaHouseOfTheFierce, "Yanxia - House of the Fierce" },
- { EAetheryteLocation.AzimSteppeReunion, "Azim Steppe - Reunion" },
- { EAetheryteLocation.AzimSteppeDawnThrone, "Azim Steppe - Dawn Throne" },
- { EAetheryteLocation.AzimSteppeDhoroIloh, "Azim Steppe - Dhoro Iloh" },
- { EAetheryteLocation.DomanEnclave, "Doman Enclave" },
-
- { EAetheryteLocation.Crystarium, "Crystarium" },
- { EAetheryteLocation.Eulmore, "Eulmore" },
- { EAetheryteLocation.LakelandFortJobb, "Lakeland - Fort Jobb" },
- { EAetheryteLocation.LakelandOstallImperative, "Lakeland - Ostall Imperative" },
- { EAetheryteLocation.KholusiaStilltide, "Kholusia - Stilltide" },
- { EAetheryteLocation.KholusiaWright, "Kholusia - Wright" },
- { EAetheryteLocation.KholusiaTomra, "Kholusia - Tomra" },
- { EAetheryteLocation.AmhAraengMordSouq, "Amh Araeng - Mord Souq" },
- { EAetheryteLocation.AmhAraengInnAtJourneysHead, "Amh Araeng - Inn at Journey's Head" },
- { EAetheryteLocation.AmhAraengTwine, "Amh Araeng - Twine" },
- { EAetheryteLocation.RaktikaSlitherbough, "Rak'tika - Slitherbough" },
- { EAetheryteLocation.RaktikaFanow, "Rak'tika - Fanow" },
- { EAetheryteLocation.IlMhegLydhaLran, "Il Mheg - Lydha Lran" },
- { EAetheryteLocation.IlMhegPiaEnni, "Il Mheg - Pia Enni" },
- { EAetheryteLocation.IlMhegWolekdorf, "Il Mheg - Wolekdorf" },
- { EAetheryteLocation.TempestOndoCups, "Tempest - Ondo Cups" },
- { EAetheryteLocation.TempestMacarensesAngle, "Tempest - Macarenses Angle" },
-
- { EAetheryteLocation.OldSharlayan, "Old Sharlayan" },
- { EAetheryteLocation.RadzAtHan, "Radz-at-Han" },
- { EAetheryteLocation.LabyrinthosArcheion, "Labyrinthos - Archeion" },
- { EAetheryteLocation.LabyrinthosSharlayanHamlet, "Labyrinthos - Sharlayan Hamlet" },
- { EAetheryteLocation.LabyrinthosAporia, "Labyrinthos - Aporia" },
- { EAetheryteLocation.ThavnairYedlihmad, "Thavnair - Yedlihmad" },
- { EAetheryteLocation.ThavnairGreatWork, "Thavnair - Great Work" },
- { EAetheryteLocation.ThavnairPalakasStand, "Thavnair - Palaka's Stand" },
- { EAetheryteLocation.GarlemaldCampBrokenGlass, "Garlemald - Camp Broken Glass" },
- { EAetheryteLocation.GarlemaldTertium, "Garlemald - Tertium" },
- { EAetheryteLocation.MareLamentorumSinusLacrimarum, "Mare Lamentorum - Sinus Lacrimarum" },
- { EAetheryteLocation.MareLamentorumBestwaysBurrow, "Mare Lamentorum - Bestways Burrow" },
- { EAetheryteLocation.ElpisAnagnorisis, "Elpis - Anagnorisis" },
- { EAetheryteLocation.ElpisTwelveWonders, "Elpis - Twelve Wonders" },
- { EAetheryteLocation.ElpisPoietenOikos, "Elpis - Poieten Oikos" },
- { EAetheryteLocation.UltimaThuleReahTahra, "Ultima Thule - Reah Tahra" },
- { EAetheryteLocation.UltimaThuleAbodeOfTheEa, "Ultima Thule - Abode of the Ea" },
- { EAetheryteLocation.UltimaThuleBaseOmicron, "Ultima Thule - Base Omicron" }
- };
-
- public static bool IsLargeAetheryte(EAetheryteLocation aetheryte) => Values.ContainsKey(aetheryte);
-}
+++ /dev/null
-using System.Collections.Generic;
-
-namespace Questionable.Model.V1.Converter;
-
-internal sealed class DialogueChoiceTypeConverter() : EnumConverter<EDialogChoiceType>(Values)
-{
- private static readonly Dictionary<EDialogChoiceType, string> Values = new()
- {
- { EDialogChoiceType.YesNo, "YesNo" },
- { EDialogChoiceType.List, "List" },
- };
-}
+++ /dev/null
-using System.Collections.Generic;
-
-namespace Questionable.Model.V1.Converter;
-
-internal sealed class EmoteConverter() : EnumConverter<EEmote>(Values)
-{
- private static readonly Dictionary<EEmote, string> Values = new()
- {
- { EEmote.Stretch, "stretch" },
- { EEmote.Wave, "wave" },
- { EEmote.Rally, "rally" },
- { EEmote.Deny, "deny" },
- { EEmote.Pray, "pray" },
- { EEmote.Slap, "slap" },
- { EEmote.Doubt, "doubt" },
- { EEmote.Psych, "psych" },
- };
-}
+++ /dev/null
-using System.Collections.Generic;
-
-namespace Questionable.Model.V1.Converter;
-
-internal sealed class EnemySpawnTypeConverter() : EnumConverter<EEnemySpawnType>(Values)
-{
- private static readonly Dictionary<EEnemySpawnType, string> Values = new()
- {
- { EEnemySpawnType.AfterInteraction, "AfterInteraction" },
- { EEnemySpawnType.AfterItemUse, "AfterItemUse" },
- { EEnemySpawnType.AutoOnEnterArea, "AutoOnEnterArea" },
- { EEnemySpawnType.OverworldEnemies, "OverworldEnemies" },
- };
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace Questionable.Model.V1.Converter;
-
-internal abstract class EnumConverter<T> : JsonConverter<T>
- where T : Enum
-{
- private readonly ReadOnlyDictionary<T, string> _enumToString;
- private readonly ReadOnlyDictionary<string, T> _stringToEnum;
-
- protected EnumConverter(IReadOnlyDictionary<T, string> values)
- {
- _enumToString = values is IDictionary<T, string> dict
- ? new ReadOnlyDictionary<T, string>(dict)
- : values.ToDictionary(x => x.Key, x => x.Value).AsReadOnly();
- _stringToEnum = _enumToString.ToDictionary(x => x.Value, x => x.Key)
- .AsReadOnly();
- }
-
- public override T? Read(ref Utf8JsonReader reader, Type typeToConvert,
- JsonSerializerOptions options)
- {
- if (reader.TokenType != JsonTokenType.String)
- throw new JsonException();
-
- string? str = reader.GetString();
- if (str == null)
- throw new JsonException();
-
- return _stringToEnum.TryGetValue(str, out T? value) ? value : throw new JsonException();
- }
-
- public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
- {
- ArgumentNullException.ThrowIfNull(writer);
- writer.WriteStringValue(_enumToString[value]);
- }
-}
+++ /dev/null
-using System;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace Questionable.Model.V1.Converter;
-
-internal sealed class ExcelRefConverter : JsonConverter<ExcelRef>
-{
- public override ExcelRef? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- if (reader.TokenType == JsonTokenType.String)
- return new ExcelRef(reader.GetString()!);
- else if (reader.TokenType == JsonTokenType.Number)
- return new ExcelRef(reader.GetUInt32());
- else
- return null;
- }
-
- public override void Write(Utf8JsonWriter writer, ExcelRef? value, JsonSerializerOptions options)
- {
- if (value == null)
- writer.WriteNullValue();
- else if (value.Type == ExcelRef.EType.Key)
- writer.WriteStringValue(value.AsKey());
- else if (value.Type == ExcelRef.EType.RowId)
- writer.WriteNumberValue(value.AsRowId());
- else
- throw new JsonException();
- }
-}
+++ /dev/null
-using System.Collections.Generic;
-
-namespace Questionable.Model.V1.Converter;
-
-internal sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>(Values)
-{
- private static readonly Dictionary<EInteractionType, string> Values = new()
- {
- { EInteractionType.Interact, "Interact" },
- { EInteractionType.WalkTo, "WalkTo" },
- { EInteractionType.AttuneAethernetShard, "AttuneAethernetShard" },
- { EInteractionType.AttuneAetheryte, "AttuneAetheryte" },
- { EInteractionType.AttuneAetherCurrent, "AttuneAetherCurrent" },
- { EInteractionType.Combat, "Combat" },
- { EInteractionType.UseItem, "UseItem" },
- { EInteractionType.EquipItem, "EquipItem" },
- { EInteractionType.Say, "Say" },
- { EInteractionType.Emote, "Emote" },
- { EInteractionType.WaitForObjectAtPosition, "WaitForNpcAtPosition" },
- { EInteractionType.WaitForManualProgress, "WaitForManualProgress" },
- { EInteractionType.Duty, "Duty" },
- { EInteractionType.SinglePlayerDuty, "SinglePlayerDuty" },
- { EInteractionType.Jump, "Jump" },
- { EInteractionType.ShouldBeAJump, "ShouldBeAJump" },
- { EInteractionType.Instruction, "Instruction" },
- };
-}
+++ /dev/null
-using System.Collections.Generic;
-
-namespace Questionable.Model.V1.Converter;
-
-internal sealed class SkipConditionConverter() : EnumConverter<ESkipCondition>(Values)
-{
- private static readonly Dictionary<ESkipCondition, string> Values = new()
- {
- { ESkipCondition.Never, "Never" },
- { ESkipCondition.FlyingLocked, "FlyingLocked" },
- { ESkipCondition.FlyingUnlocked, "FlyingUnlocked" },
- };
-}
+++ /dev/null
-using System;
-using System.Numerics;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace Questionable.Model.V1.Converter;
-
-internal sealed class VectorConverter : JsonConverter<Vector3>
-{
- public override Vector3 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- if (reader.TokenType != JsonTokenType.StartObject)
- throw new JsonException();
-
- Vector3 vec = new Vector3();
- while (reader.Read())
- {
- switch (reader.TokenType)
- {
- case JsonTokenType.PropertyName:
- string? propertyName = reader.GetString();
- if (propertyName == null || !reader.Read())
- throw new JsonException();
-
- switch (propertyName)
- {
- case nameof(Vector3.X):
- vec.X = reader.GetSingle();
- break;
-
- case nameof(Vector3.Y):
- vec.Y = reader.GetSingle();
- break;
-
- case nameof(Vector3.Z):
- vec.Z = reader.GetSingle();
- break;
-
- default:
- throw new JsonException();
- }
-
- break;
-
- case JsonTokenType.EndObject:
- return vec;
-
- default:
- throw new JsonException();
- }
- }
-
- throw new JsonException();
- }
-
- public override void Write(Utf8JsonWriter writer, Vector3 value, JsonSerializerOptions options)
- {
- ArgumentNullException.ThrowIfNull(writer);
-
- writer.WriteStartObject();
- writer.WriteNumber(nameof(Vector3.X), value.X);
- writer.WriteNumber(nameof(Vector3.Y), value.X);
- writer.WriteNumber(nameof(Vector3.Z), value.X);
- writer.WriteEndObject();
- }
-}
+++ /dev/null
-using System.Text.Json.Serialization;
-using JetBrains.Annotations;
-using Questionable.Model.V1.Converter;
-
-namespace Questionable.Model.V1;
-
-[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)]
-internal sealed class DialogueChoice
-{
- [JsonConverter(typeof(DialogueChoiceTypeConverter))]
- public EDialogChoiceType Type { get; set; }
- public string? ExcelSheet { get; set; }
-
- [JsonConverter(typeof(ExcelRefConverter))]
- public ExcelRef? Prompt { get; set; }
-
- public bool Yes { get; set; } = true;
-
- [JsonConverter(typeof(ExcelRefConverter))]
- public ExcelRef? Answer { get; set; }
-
- /// <summary>
- /// If set, only applies when focusing the given target id.
- /// </summary>
- public uint? DataId { get; set; }
-}
+++ /dev/null
-using System.Text.Json.Serialization;
-using Questionable.Model.V1.Converter;
-
-namespace Questionable.Model.V1;
-
-[JsonConverter(typeof(AetheryteConverter))]
-internal enum EAetheryteLocation
-{
- None = 0,
-
- Gridania = 2,
- GridaniaArcher = 25,
- GridaniaLeatherworker = 26,
- GridaniaLancer = 27,
- GridaniaConjurer = 28,
- GridaniaBotanist = 29,
- GridaniaAmphitheatre = 30,
- GridaniaBlueBadgerGate = 31,
- GridaniaYellowSerpentGate = 32,
- GridaniaWhiteWolfGate = 54,
- GridaniaAirship = 94,
-
- CentralShroudBentbranchMeadows = 3,
- EastShroudHawthorneHut = 4,
- SouthShroudQuarrymill = 5,
- SouthShroudCampTranquil = 6,
- NorthShroudFallgourdFloat = 7,
-
- Uldah = 9,
- UldahAdventurers = 33,
- UldahThaumaturge = 34,
- UldahGladiator = 35,
- UldahMiner = 36,
- UldahAlchemist = 37,
- UldahWeaver = 47,
- UldahGoldsmith = 50,
- UldahChamberOfRule = 51,
- UldahAirship = 95,
- UldahGateOfTheSultana = 38,
- UldahGateOfNald = 39,
- UldahGateOfThal = 40,
- UldahSapphireAvenue = 125,
-
- WesternThanalanHorizon = 17,
- EasternThanalanCampDrybone = 18,
- SouthernThanalanLittleAlaMhigo = 19,
- SouthernThanalanForgottenSprings = 20,
- NorthernThanalanCampBluefog = 21,
- NorthernThanalanCeruleumProcessingPlant = 22,
- CentralThanalanBlackBrushStation = 53,
-
- Limsa = 8,
- LimsaAftcastle = 41,
- LimsaCulinarian = 42,
- LimsaArcanist = 43,
- LimsaFisher = 44,
- LimsaMarauder = 48,
- LimsaHawkersAlley = 49,
- LimsaZephyrGate = 45,
- LimsaTempestGate = 46,
- LimsaAirship = 93,
-
- LowerLaNosceaMorabyDrydocks = 10,
- EasternLaNosceaCostaDelSol = 11,
- EasternLaNosceaWineport = 12,
- WesternLaNosceaSwiftperch = 13,
- WesternLaNosceaAleport = 14,
- UpperLaNosceaCampBronzeLake = 15,
- OuterLaNosceaCampOverlook = 16,
- MiddleLaNosceaSummerfordFarms = 52,
-
- CoerthasCentralHighlandsCampDragonhead = 23,
- MorDhona = 24,
- GoldSaucer = 62,
- WolvesDenPier = 55,
-
- Ishgard = 70,
- IshgardForgottenKnight = 80,
- IshgardSkysteelManufactory = 81,
- IshgardBrume = 82,
- IshgardAthenaeumAstrologicum = 83,
- IshgardJeweledCrozier = 84,
- IshgardSaintReymanaudsCathedral = 85,
- IshgardTribunal = 86,
- IshgardLastVigil = 87,
- IshgardGatesOfJudgement = 88,
-
- Idyllshire = 75,
- IdyllshireWest = 90,
- IdyllshirePrologueGate = 91,
- IdyllshireEpilogueGate = 92,
-
- CoerthasWesternHighlandsFalconsNest = 71,
- SeaOfCloudsCampCloudtop = 72,
- SeaOfCloudsOkZundu = 73,
- AzysLlaHelix = 74,
- DravanianForelandsTailfeather = 76,
- DravanianForelandsAnyxTrine = 77,
- ChurningMistsMoghome = 78,
- ChurningMistsZenith = 79,
-
- RhalgrsReach = 104,
- RhalgrsReachWest = 121,
- RhalgrsReachNorthEast = 122,
- RhalgrsReachFringesGate = 123,
- RhalgrsReachPeaksGate = 124,
-
- Kugane = 111,
- KuganeShiokazeHostelry = 112,
- KuganePier1 = 113,
- KuganeThavnairianConsulate = 114,
- KuganeMarkets = 115,
- KuganeBokairoInn = 116,
- KuganeRubyBazaar = 117,
- KuganeSekiseigumiBarracks = 118,
- KuganeRakuzaDistrict = 119,
- KuganeRubyPrice = 120,
- KuganeAirship = 126,
-
- FringesCastrumOriens = 98,
- FringesPeeringStones = 99,
- PeaksAlaGannha = 100,
- PeaksAlaGhiri = 101,
- LochsPortaPraetoria = 102,
- LochsAlaMhiganQuarter = 103,
- RubySeaTamamizu = 105,
- RubySeaOnokoro = 106,
- YanxiaNamai = 107,
- YanxiaHouseOfTheFierce = 108,
- AzimSteppeReunion = 109,
- AzimSteppeDawnThrone = 110,
- AzimSteppeDhoroIloh = 128,
-
- DomanEnclave = 127,
- DomanEnclaveNorthern = 129,
- DomanEnclaveSouthern = 130,
- DomanEnclaveOneRiver = 131,
- DomanEnclaveDocks = 162,
-
- Crystarium = 133,
- CrystariumMarkets = 149,
- CrystariumThemenosRookery = 150,
- CrystariumDossalGate = 151,
- CrystariumPendants = 152,
- CrystariumAmaroLaunch = 153,
- CrystariumCrystallineMean = 154,
- CrystariumCabinetOfCuriosity = 155,
- CrystariumTessellation = 156,
-
- Eulmore = 134,
- EulmoreMainstay = 157,
- EulmoreNightsoilPots = 158,
- EulmoreGloryGate = 159,
- EulmoreSoutheastDerelict = 135,
- EulmorePathToGlory = 160,
-
- LakelandFortJobb = 132,
- LakelandOstallImperative = 136,
- KholusiaStilltide = 137,
- KholusiaWright = 138,
- KholusiaTomra = 139,
- AmhAraengMordSouq = 140,
- AmhAraengInnAtJourneysHead = 161,
- AmhAraengTwine = 141,
- RaktikaSlitherbough = 142,
- RaktikaFanow = 143,
- IlMhegLydhaLran = 144,
- IlMhegPiaEnni = 145,
- IlMhegWolekdorf = 146,
- TempestOndoCups = 147,
- TempestMacarensesAngle = 148,
-
- OldSharlayan = 182,
- OldSharlayanStudium = 184,
- OldSharlayanBaldesionAnnex = 185,
- OldSharlayanRostra = 186,
- OldSharlayanLeveilleurEstate = 187,
- OldSharlayanJourneysEnd = 188,
- OldSharlayanScholarsHarbor = 189,
- OldSharlayanHallOfArtifice = 190,
-
- RadzAtHan = 183,
- RadzAtHanMeghaduta = 191,
- RadzAtHanRuveydahFibers = 192,
- RadzAtHanAirship = 193,
- RadzAtHanAlzadaalsPeace = 194,
- RadzAtHanHallOfTheRadiantHost = 195,
- RadzAtHanMehrydesMeyhane = 196,
- RadzAtHanKama = 198,
- RadzAtHanHighCrucible = 199,
- RadzAtHanGateOfFirstSight = 197,
-
- LabyrinthosArcheion = 166,
- LabyrinthosSharlayanHamlet = 167,
- LabyrinthosAporia = 168,
- ThavnairYedlihmad = 169,
- ThavnairGreatWork = 170,
- ThavnairPalakasStand = 171,
- GarlemaldCampBrokenGlass = 172,
- GarlemaldTertium = 173,
- MareLamentorumSinusLacrimarum = 174,
- MareLamentorumBestwaysBurrow = 175,
- ElpisAnagnorisis = 176,
- ElpisTwelveWonders = 177,
- ElpisPoietenOikos = 178,
- UltimaThuleReahTahra = 179,
- UltimaThuleAbodeOfTheEa = 180,
- UltimaThuleBaseOmicron = 181
-}
+++ /dev/null
-namespace Questionable.Model.V1;
-
-internal enum EDialogChoiceType
-{
- None,
- YesNo,
- List,
-}
+++ /dev/null
-using System.Text.Json.Serialization;
-using Questionable.Model.V1.Converter;
-
-namespace Questionable.Model.V1;
-
-[JsonConverter(typeof(EmoteConverter))]
-internal enum EEmote
-{
- None = 0,
-
- Stretch = 37,
- Wave = 16,
- Rally = 34,
- Deny = 25,
- Pray = 58,
- Slap = 111,
- Doubt = 12,
- Psych = 30,
-}
+++ /dev/null
-using System.Text.Json.Serialization;
-using Questionable.Model.V1.Converter;
-
-namespace Questionable.Model.V1;
-
-[JsonConverter(typeof(EnemySpawnTypeConverter))]
-internal enum EEnemySpawnType
-{
- None = 0,
- AfterInteraction,
- AfterItemUse,
- AutoOnEnterArea,
- OverworldEnemies,
-}
+++ /dev/null
-using System.Text.Json.Serialization;
-using Questionable.Model.V1.Converter;
-
-namespace Questionable.Model.V1;
-
-[JsonConverter(typeof(InteractionTypeConverter))]
-internal enum EInteractionType
-{
- Interact,
- WalkTo,
- AttuneAethernetShard,
- AttuneAetheryte,
- AttuneAetherCurrent,
- Combat,
- UseItem,
- EquipItem,
- Say,
- Emote,
- WaitForObjectAtPosition,
- WaitForManualProgress,
- Duty,
- SinglePlayerDuty,
- Jump,
-
- /// <summary>
- /// Needs to be adjusted for coords etc. in the quest data.
- /// </summary>
- ShouldBeAJump,
-
- /// <summary>
- /// Needs to be manually continued.
- /// </summary>
- Instruction,
-}
+++ /dev/null
-using System.Text.Json.Serialization;
-using Questionable.Model.V1.Converter;
-
-namespace Questionable.Model.V1;
-
-[JsonConverter(typeof(SkipConditionConverter))]
-internal enum ESkipCondition
-{
- None,
- Never,
- FlyingLocked,
- FlyingUnlocked,
-}
+++ /dev/null
-using System;
-
-namespace Questionable.Model.V1;
-
-public class ExcelRef
-{
- private readonly string? _stringValue;
- private readonly uint? _rowIdValue;
-
- public ExcelRef(string value)
- {
- _stringValue = value;
- _rowIdValue = null;
- Type = EType.Key;
- }
-
- public ExcelRef(uint value)
- {
- _stringValue = null;
- _rowIdValue = value;
- Type = EType.RowId;
- }
-
- public EType Type { get; }
-
- public string AsKey()
- {
- if (Type != EType.Key)
- throw new InvalidOperationException();
-
- return _stringValue!;
- }
-
- public uint AsRowId()
- {
- if (Type != EType.RowId)
- throw new InvalidOperationException();
-
- return _rowIdValue!.Value;
- }
-
- public enum EType
- {
- None,
- Key,
- RowId,
- }
-}
+++ /dev/null
-using System.Numerics;
-using System.Text.Json.Serialization;
-using JetBrains.Annotations;
-using Questionable.Model.V1.Converter;
-
-namespace Questionable.Model.V1;
-
-[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)]
-internal sealed class JumpDestination
-{
- [JsonConverter(typeof(VectorConverter))]
- public Vector3 Position { get; set; }
-
- public float? StopDistance { get; set; }
- public float? DelaySeconds { get; set; }
-}
+++ /dev/null
-using System.Collections.Generic;
-using JetBrains.Annotations;
-
-namespace Questionable.Model.V1;
-
-[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)]
-internal sealed class QuestData
-{
- public required string Author { get; set; }
- public List<string> Contributors { get; set; } = new();
- public string? Comment { get; set; }
- public List<ushort> TerritoryBlacklist { get; set; } = new();
- public required List<QuestSequence> QuestSequence { get; set; } = new();
-}
+++ /dev/null
-using System.Collections.Generic;
-using JetBrains.Annotations;
-
-namespace Questionable.Model.V1;
-
-[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)]
-internal sealed class QuestSequence
-{
- public required int Sequence { get; set; }
- public string? Comment { get; set; }
- public List<QuestStep> Steps { get; set; } = new();
-
- public QuestStep? FindStep(int step)
- {
- if (step < 0 || step >= Steps.Count)
- return null;
-
- return Steps[step];
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Numerics;
-using System.Text.Json.Serialization;
-using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
-using JetBrains.Annotations;
-using Questionable.Model.V1.Converter;
-
-namespace Questionable.Model.V1;
-
-[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)]
-internal sealed class QuestStep
-{
- public EInteractionType InteractionType { get; set; }
-
- public uint? DataId { get; set; }
-
- [JsonConverter(typeof(VectorConverter))]
- public Vector3? Position { get; set; }
-
- public float? StopDistance { get; set; }
- public ushort TerritoryId { get; set; }
- public ushort? TargetTerritoryId { get; set; }
-
- public bool Disabled { get; set; }
- public bool DisableNavmesh { get; set; }
- public bool? Mount { get; set; }
- public bool? Fly { get; set; }
- public bool? Sprint { get; set; }
- public string? Comment { get; set; }
-
- public EAetheryteLocation? AetheryteShortcut { get; set; }
-
- public AethernetShortcut? AethernetShortcut { get; set; }
- public uint? AetherCurrentId { get; set; }
-
- public uint? ItemId { get; set; }
- public bool? GroundTarget { get; set; }
-
- public EEmote? Emote { get; set; }
- public ChatMessage? ChatMessage { get; set; }
-
- public EEnemySpawnType? EnemySpawnType { get; set; }
-
- public IList<uint> KillEnemyDataIds { get; set; } = new List<uint>();
- public JumpDestination? JumpDestination { get; set; }
- public uint? ContentFinderConditionId { get; set; }
-
- public IList<ESkipCondition> SkipIf { get; set; } = new List<ESkipCondition>();
- public IList<short?> CompletionQuestVariablesFlags { get; set; } = new List<short?>();
- public IList<DialogueChoice> DialogueChoices { get; set; } = new List<DialogueChoice>();
-
- /// <summary>
- /// Positive values: Must be set to this value; will wait for the step to have these set.
- /// Negative values: Will skip if set to this value, won't wait for this to be set.
- /// </summary>
- public unsafe bool MatchesQuestVariables(QuestWork questWork, bool forSkip)
- {
- if (CompletionQuestVariablesFlags.Count != 6)
- return false;
-
- for (int i = 0; i < 6; ++i)
- {
- short? check = CompletionQuestVariablesFlags[i];
- if (check == null)
- continue;
-
- byte actualValue = questWork.Variables[i];
- byte checkByte = check > 0 ? (byte)check : (byte)-check;
- if (forSkip)
- {
- byte expectedValue = (byte)Math.Abs(check.Value);
- if ((actualValue & checkByte) != expectedValue)
- return false;
- }
- else if (!forSkip && check > 0)
- {
- byte expectedValue = check > 0 ? (byte)check : (byte)0;
- if ((actualValue & checkByte) != expectedValue)
- return false;
- }
- }
-
- return true;
- }
-}
--- /dev/null
+using System;
+using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
+
+namespace Questionable.Model.V1;
+
+internal static class QuestStepExtensions
+{
+ /// <summary>
+ /// Positive values: Must be set to this value; will wait for the step to have these set.
+ /// Negative values: Will skip if set to this value, won't wait for this to be set.
+ /// </summary>
+ public static unsafe bool MatchesQuestVariables(this QuestStep step, QuestWork questWork, bool forSkip)
+ {
+ if (step.CompletionQuestVariablesFlags.Count != 6)
+ return false;
+
+ for (int i = 0; i < 6; ++i)
+ {
+ short? check = step.CompletionQuestVariablesFlags[i];
+ if (check == null)
+ continue;
+
+ byte actualValue = questWork.Variables[i];
+ byte checkByte = check > 0 ? (byte)check : (byte)-check;
+ if (forSkip)
+ {
+ byte expectedValue = (byte)Math.Abs(check.Value);
+ if ((actualValue & checkByte) != expectedValue)
+ return false;
+ }
+ else if (!forSkip && check > 0)
+ {
+ byte expectedValue = check > 0 ? (byte)check : (byte)0;
+ if ((actualValue & checkByte) != expectedValue)
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
<ItemGroup>
<ProjectReference Include="..\LLib\LLib.csproj" />
- <ProjectReference Include="..\QuestPaths\QuestPaths.csproj" />
+ <ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj" />
+ <ProjectReference Include="..\QuestPaths\QuestPaths.csproj" />
</ItemGroup>
</Project>
"llib": {
"type": "Project"
},
+ "questionable.model": {
+ "type": "Project",
+ "dependencies": {
+ "System.Text.Json": "[8.0.3, )"
+ }
+ },
"questpaths": {
- "type": "Project"
+ "type": "Project",
+ "dependencies": {
+ "Questionable.Model": "[1.0.0, )"
+ }
}
}
}