1
0
forked from liza/Questionable

Update how quests are embedded

This commit is contained in:
Liza 2024-06-14 11:37:33 +02:00
parent 39e5a5a81d
commit 5a4a693d66
Signed by: liza
GPG Key ID: 7199F8D727D55F67
42 changed files with 1160 additions and 146 deletions

5
.gitignore vendored
View File

@ -1,2 +1,5 @@
/.idea obj/
bin/
/.idea
/.vs
*.user *.user

View File

@ -0,0 +1,37 @@
<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>

View File

@ -0,0 +1,316 @@
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())));
}
}

View File

@ -0,0 +1,208 @@
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;
}
}

View File

@ -0,0 +1,244 @@
{
"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, )"
}
}
}
}
}

View File

@ -1,30 +1,11 @@
#if RELEASE using System.Collections.Generic;
using System; using Questionable.Model.V1;
using System.IO;
using System.IO.Compression;
#if RELEASE
namespace Questionable.QuestPaths; namespace Questionable.QuestPaths;
public static class AssemblyQuestLoader public static partial class AssemblyQuestLoader
{ {
public static void LoadQuestsFromEmbeddedResources(Action<string, Stream> loadFunction) public static IReadOnlyDictionary<ushort, QuestData> GetQuests() => Quests;
{
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);
}
}
}
}
}
} }
#endif #endif

View File

@ -8,29 +8,22 @@
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile> <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<DebugType>none</DebugType> <DebugType>none</DebugType>
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap> <PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup> </PropertyGroup>
<Target Name="ZipARealmReborn" BeforeTargets="BeforeResGen" Condition="'$(Configuration)' == 'Release'"> <ItemGroup>
<ZipDirectory SourceDirectory="$(ProjectDir)\ARealmReborn" <ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj" />
DestinationFile="$(TargetDir)\ARealmReborn.zip" <ProjectReference Include="..\QuestPathGenerator\QuestPathGenerator.csproj"
Overwrite="true"/> OutputItemType="Analyzer"
</Target> ReferenceOutputAssembly="false" />
</ItemGroup>
<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 Condition="'$(Configuration)' == 'Release'"> <ItemGroup Condition="'$(Configuration)' == 'Release'">
<EmbeddedResource Include="$(TargetDir)\ARealmReborn.zip"/> <None Remove="ARealmReborn"/>
<EmbeddedResource Include="$(TargetDir)\Shadowbringers.zip"/> <None Remove="Shadowbringers"/>
<EmbeddedResource Include="$(TargetDir)\Endwalker.zip"/> <None Remove="Endwalker"/>
<AdditionalFiles Include="ARealmReborn\**\*.json" />
<AdditionalFiles Include="Shadowbringers\**\*.json" />
<AdditionalFiles Include="Endwalker\**\*.json" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,6 +1,105 @@
{ {
"version": 1, "version": 1,
"dependencies": { "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, )"
}
}
}
} }
} }

View File

@ -0,0 +1,15 @@
<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>

View File

@ -4,7 +4,7 @@ using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[JsonConverter(typeof(AethernetShortcutConverter))] [JsonConverter(typeof(AethernetShortcutConverter))]
internal sealed class AethernetShortcut public sealed class AethernetShortcut
{ {
public EAetheryteLocation From { get; set; } public EAetheryteLocation From { get; set; }
public EAetheryteLocation To { get; set; } public EAetheryteLocation To { get; set; }

View File

@ -0,0 +1,7 @@
namespace Questionable.Model.V1;
public sealed class ChatMessage
{
public string? ExcelSheet { get; set; }
public string Key { get; set; } = null!;
}

View File

@ -6,7 +6,7 @@ using System.Text.Json.Serialization;
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
internal sealed class AethernetShortcutConverter : JsonConverter<AethernetShortcut> public sealed class AethernetShortcutConverter : JsonConverter<AethernetShortcut>
{ {
private static readonly Dictionary<EAetheryteLocation, string> EnumToString = new() private static readonly Dictionary<EAetheryteLocation, string> EnumToString = new()
{ {
@ -151,9 +151,6 @@ internal sealed class AethernetShortcutConverter : JsonConverter<AethernetShortc
public override void Write(Utf8JsonWriter writer, AethernetShortcut value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, AethernetShortcut value, JsonSerializerOptions options)
{ {
ArgumentNullException.ThrowIfNull(writer);
ArgumentNullException.ThrowIfNull(value);
writer.WriteStartArray(); writer.WriteStartArray();
writer.WriteStringValue(EnumToString[value.From]); writer.WriteStringValue(EnumToString[value.From]);
writer.WriteStringValue(EnumToString[value.To]); writer.WriteStringValue(EnumToString[value.To]);

View File

@ -2,7 +2,7 @@
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
internal sealed class AetheryteConverter() : EnumConverter<EAetheryteLocation>(Values) public sealed class AetheryteConverter() : EnumConverter<EAetheryteLocation>(Values)
{ {
private static readonly Dictionary<EAetheryteLocation, string> Values = new() private static readonly Dictionary<EAetheryteLocation, string> Values = new()
{ {

View File

@ -2,7 +2,7 @@
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
internal sealed class DialogueChoiceTypeConverter() : EnumConverter<EDialogChoiceType>(Values) public sealed class DialogueChoiceTypeConverter() : EnumConverter<EDialogChoiceType>(Values)
{ {
private static readonly Dictionary<EDialogChoiceType, string> Values = new() private static readonly Dictionary<EDialogChoiceType, string> Values = new()
{ {

View File

@ -2,7 +2,7 @@
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
internal sealed class EmoteConverter() : EnumConverter<EEmote>(Values) public sealed class EmoteConverter() : EnumConverter<EEmote>(Values)
{ {
private static readonly Dictionary<EEmote, string> Values = new() private static readonly Dictionary<EEmote, string> Values = new()
{ {

View File

@ -2,7 +2,7 @@
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
internal sealed class EnemySpawnTypeConverter() : EnumConverter<EEnemySpawnType>(Values) public sealed class EnemySpawnTypeConverter() : EnumConverter<EEnemySpawnType>(Values)
{ {
private static readonly Dictionary<EEnemySpawnType, string> Values = new() private static readonly Dictionary<EEnemySpawnType, string> Values = new()
{ {

View File

@ -7,7 +7,7 @@ using System.Text.Json.Serialization;
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
internal abstract class EnumConverter<T> : JsonConverter<T> public abstract class EnumConverter<T> : JsonConverter<T>
where T : Enum where T : Enum
{ {
private readonly ReadOnlyDictionary<T, string> _enumToString; private readonly ReadOnlyDictionary<T, string> _enumToString;
@ -17,9 +17,8 @@ internal abstract class EnumConverter<T> : JsonConverter<T>
{ {
_enumToString = values is IDictionary<T, string> dict _enumToString = values is IDictionary<T, string> dict
? new ReadOnlyDictionary<T, string>(dict) ? new ReadOnlyDictionary<T, string>(dict)
: values.ToDictionary(x => x.Key, x => x.Value).AsReadOnly(); : new ReadOnlyDictionary<T, string>(values.ToDictionary(x => x.Key, x => x.Value));
_stringToEnum = _enumToString.ToDictionary(x => x.Value, x => x.Key) _stringToEnum = new ReadOnlyDictionary<string, T>(_enumToString.ToDictionary(x => x.Value, x => x.Key));
.AsReadOnly();
} }
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, public override T? Read(ref Utf8JsonReader reader, Type typeToConvert,
@ -37,7 +36,6 @@ internal abstract class EnumConverter<T> : JsonConverter<T>
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{ {
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStringValue(_enumToString[value]); writer.WriteStringValue(_enumToString[value]);
} }
} }

View File

@ -4,7 +4,7 @@ using System.Text.Json.Serialization;
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
internal sealed class ExcelRefConverter : JsonConverter<ExcelRef> public sealed class ExcelRefConverter : JsonConverter<ExcelRef>
{ {
public override ExcelRef? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override ExcelRef? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {

View File

@ -2,7 +2,7 @@
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
internal sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>(Values) public sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>(Values)
{ {
private static readonly Dictionary<EInteractionType, string> Values = new() private static readonly Dictionary<EInteractionType, string> Values = new()
{ {

View File

@ -2,7 +2,7 @@
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
internal sealed class SkipConditionConverter() : EnumConverter<ESkipCondition>(Values) public sealed class SkipConditionConverter() : EnumConverter<ESkipCondition>(Values)
{ {
private static readonly Dictionary<ESkipCondition, string> Values = new() private static readonly Dictionary<ESkipCondition, string> Values = new()
{ {

View File

@ -5,7 +5,7 @@ using System.Text.Json.Serialization;
namespace Questionable.Model.V1.Converter; namespace Questionable.Model.V1.Converter;
internal sealed class VectorConverter : JsonConverter<Vector3> public sealed class VectorConverter : JsonConverter<Vector3>
{ {
public override Vector3 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override Vector3 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
@ -55,8 +55,6 @@ internal sealed class VectorConverter : JsonConverter<Vector3>
public override void Write(Utf8JsonWriter writer, Vector3 value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, Vector3 value, JsonSerializerOptions options)
{ {
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStartObject(); writer.WriteStartObject();
writer.WriteNumber(nameof(Vector3.X), value.X); writer.WriteNumber(nameof(Vector3.X), value.X);
writer.WriteNumber(nameof(Vector3.Y), value.X); writer.WriteNumber(nameof(Vector3.Y), value.X);

View File

@ -1,11 +1,9 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using JetBrains.Annotations;
using Questionable.Model.V1.Converter; using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public sealed class DialogueChoice
internal sealed class DialogueChoice
{ {
[JsonConverter(typeof(DialogueChoiceTypeConverter))] [JsonConverter(typeof(DialogueChoiceTypeConverter))]
public EDialogChoiceType Type { get; set; } public EDialogChoiceType Type { get; set; }

View File

@ -4,7 +4,7 @@ using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[JsonConverter(typeof(AetheryteConverter))] [JsonConverter(typeof(AetheryteConverter))]
internal enum EAetheryteLocation public enum EAetheryteLocation
{ {
None = 0, None = 0,

View File

@ -1,6 +1,6 @@
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
internal enum EDialogChoiceType public enum EDialogChoiceType
{ {
None, None,
YesNo, YesNo,

View File

@ -4,7 +4,7 @@ using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[JsonConverter(typeof(EmoteConverter))] [JsonConverter(typeof(EmoteConverter))]
internal enum EEmote public enum EEmote
{ {
None = 0, None = 0,

View File

@ -4,7 +4,7 @@ using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[JsonConverter(typeof(EnemySpawnTypeConverter))] [JsonConverter(typeof(EnemySpawnTypeConverter))]
internal enum EEnemySpawnType public enum EEnemySpawnType
{ {
None = 0, None = 0,
AfterInteraction, AfterInteraction,

View File

@ -4,7 +4,7 @@ using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[JsonConverter(typeof(InteractionTypeConverter))] [JsonConverter(typeof(InteractionTypeConverter))]
internal enum EInteractionType public enum EInteractionType
{ {
Interact, Interact,
WalkTo, WalkTo,

View File

@ -4,7 +4,7 @@ using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[JsonConverter(typeof(SkipConditionConverter))] [JsonConverter(typeof(SkipConditionConverter))]
internal enum ESkipCondition public enum ESkipCondition
{ {
None, None,
Never, Never,

View File

@ -1,12 +1,10 @@
using System.Numerics; using System.Numerics;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using JetBrains.Annotations;
using Questionable.Model.V1.Converter; using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public sealed class JumpDestination
internal sealed class JumpDestination
{ {
[JsonConverter(typeof(VectorConverter))] [JsonConverter(typeof(VectorConverter))]
public Vector3 Position { get; set; } public Vector3 Position { get; set; }

View File

@ -0,0 +1,12 @@
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();
}

View File

@ -1,12 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using JetBrains.Annotations;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public sealed class QuestSequence
internal sealed class QuestSequence
{ {
public required int Sequence { get; set; } public int Sequence { get; set; }
public string? Comment { get; set; } public string? Comment { get; set; }
public List<QuestStep> Steps { get; set; } = new(); public List<QuestStep> Steps { get; set; } = new();

View File

@ -3,14 +3,11 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
using JetBrains.Annotations;
using Questionable.Model.V1.Converter; using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public sealed class QuestStep
internal sealed class QuestStep
{ {
public EInteractionType InteractionType { get; set; } public EInteractionType InteractionType { get; set; }
@ -42,8 +39,8 @@ internal sealed class QuestStep
public ChatMessage? ChatMessage { get; set; } public ChatMessage? ChatMessage { get; set; }
public EEnemySpawnType? EnemySpawnType { get; set; } public EEnemySpawnType? EnemySpawnType { get; set; }
public IList<uint> KillEnemyDataIds { get; set; } = new List<uint>(); public IList<uint> KillEnemyDataIds { get; set; } = new List<uint>();
public JumpDestination? JumpDestination { get; set; } public JumpDestination? JumpDestination { get; set; }
public uint? ContentFinderConditionId { get; set; } public uint? ContentFinderConditionId { get; set; }
@ -51,37 +48,16 @@ internal sealed class QuestStep
public IList<short?> CompletionQuestVariablesFlags { get; set; } = new List<short?>(); public IList<short?> CompletionQuestVariablesFlags { get; set; } = new List<short?>();
public IList<DialogueChoice> DialogueChoices { get; set; } = new List<DialogueChoice>(); public IList<DialogueChoice> DialogueChoices { get; set; } = new List<DialogueChoice>();
/// <summary> [JsonConstructor]
/// Positive values: Must be set to this value; will wait for the step to have these set. public QuestStep()
/// 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) public QuestStep(EInteractionType interactionType, uint? dataId, Vector3? position, ushort territoryId)
{ {
short? check = CompletionQuestVariablesFlags[i]; InteractionType = interactionType;
if (check == null) DataId = dataId;
continue; Position = position;
TerritoryId = territoryId;
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;
} }
} }

View File

@ -0,0 +1,86 @@
{
"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"
}
}
}
}
}

View File

@ -6,6 +6,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LLib", "LLib\LLib.csproj",
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPaths", "QuestPaths\QuestPaths.csproj", "{7A136F28-8D5C-478D-B993-0F39F1451A47}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPaths", "QuestPaths\QuestPaths.csproj", "{7A136F28-8D5C-478D-B993-0F39F1451A47}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -24,5 +28,13 @@ Global
{7A136F28-8D5C-478D-B993-0F39F1451A47}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{7A136F28-8D5C-478D-B993-0F39F1451A47}.Release|Any CPU.Build.0 = 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 EndGlobalSection
EndGlobal EndGlobal

View File

@ -1,3 +1 @@
/dist /dist
/obj
/bin

View File

@ -33,7 +33,17 @@ internal sealed class QuestRegistry
#if RELEASE #if RELEASE
_logger.LogInformation("Loading quests from assembly"); _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 #else
DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation?.Directory?.Parent?.Parent; DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation?.Directory?.Parent?.Parent;
if (solutionDirectory != null) if (solutionDirectory != null)
@ -48,6 +58,7 @@ internal sealed class QuestRegistry
} }
} }
#endif #endif
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests"))); LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests")));
foreach (var (questId, quest) in _quests) foreach (var (questId, quest) in _quests)
@ -60,6 +71,8 @@ internal sealed class QuestRegistry
quest.Name = questData.Name.ToString(); quest.Name = questData.Name.ToString();
quest.Level = questData.ClassJobLevel0; quest.Level = questData.ClassJobLevel0;
} }
_logger.LogInformation("Loaded {Count} quests", _quests.Count);
} }

View File

@ -1,10 +0,0 @@
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!;
}

View File

@ -1,14 +0,0 @@
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();
}

View File

@ -0,0 +1,41 @@
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;
}
}

View File

@ -59,6 +59,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\LLib\LLib.csproj" /> <ProjectReference Include="..\LLib\LLib.csproj" />
<ProjectReference Include="..\QuestPaths\QuestPaths.csproj" /> <ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj" />
<ProjectReference Include="..\QuestPaths\QuestPaths.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -86,8 +86,17 @@
"llib": { "llib": {
"type": "Project" "type": "Project"
}, },
"questionable.model": {
"type": "Project",
"dependencies": {
"System.Text.Json": "[8.0.3, )"
}
},
"questpaths": { "questpaths": {
"type": "Project" "type": "Project",
"dependencies": {
"Questionable.Model": "[1.0.0, )"
}
} }
} }
} }