using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text.Json; using Json.Schema; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Questionable.Model.Gathering; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static Questionable.QuestPathGenerator.RoslynShortcuts; namespace Questionable.QuestPathGenerator; [Generator] [SuppressMessage("MicrosoftCodeAnalysisReleaseTracking", "RS2008")] public class GatheringSourceGenerator : ISourceGenerator { private static readonly DiagnosticDescriptor InvalidJson = new("GPG0001", "Invalid JSON", "Invalid gathering file: {0}", nameof(GatheringSourceGenerator), DiagnosticSeverity.Error, true); public void Initialize(GeneratorInitializationContext context) { // No initialization required for this generator. } public void Execute(GeneratorExecutionContext context) { // Find schema definition AdditionalText? gatheringSchema = context.AdditionalFiles.SingleOrDefault(x => Path.GetFileName(x.Path) == "gatheringlocation-v1.json"); if (gatheringSchema != null) GenerateGatheringSource(context, gatheringSchema); } private void GenerateGatheringSource(GeneratorExecutionContext context, AdditionalText jsonSchemaFile) { var gatheringSchema = JsonSchema.FromText(jsonSchemaFile.GetText()!.ToString()); var jsonSchemaFiles = Utils.RegisterSchemas(context); List<(ushort, GatheringRoot)> gatheringLocations = []; foreach (var (id, node) in Utils.GetAdditionalFiles(context, jsonSchemaFiles, gatheringSchema, InvalidJson, ushort.Parse)) { var gatheringLocation = node.Deserialize<GatheringRoot>()!; gatheringLocations.Add((id, gatheringLocation)); } if (gatheringLocations.Count == 0) return; var partitionedLocations = gatheringLocations .OrderBy(x => x.Item1) .GroupBy(x => $"LoadLocation{x.Item1 / 100}") .ToList(); var methods = Utils.CreateMethods("LoadLocations", partitionedLocations, CreateInitializer); 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("Gathering"))), UsingDirective( QualifiedName( QualifiedName( IdentifierName("Questionable"), IdentifierName("Model")), IdentifierName("Questing"))), UsingDirective( QualifiedName( QualifiedName( IdentifierName("Questionable"), IdentifierName("Model")), IdentifierName("Common"))) })) .WithMembers( SingletonList<MemberDeclarationSyntax>( FileScopedNamespaceDeclaration( QualifiedName( IdentifierName("Questionable"), IdentifierName("GatheringPaths"))) .WithMembers( SingletonList<MemberDeclarationSyntax>( ClassDeclaration("AssemblyGatheringLocationLoader") .WithModifiers( TokenList(Token(SyntaxKind.PartialKeyword))) .WithMembers(List<MemberDeclarationSyntax>(methods)))))) .NormalizeWhitespace(); // Add the source code to the compilation. context.AddSource("AssemblyGatheringLocationLoader.g.cs", code.ToFullString()); } private static StatementSyntax[] CreateInitializer(List<(ushort QuestId, GatheringRoot Root)> quests) { List<StatementSyntax> statements = []; foreach (var quest in quests) { statements.Add( ExpressionStatement( InvocationExpression( IdentifierName("AddLocation")) .WithArgumentList( ArgumentList( SeparatedList<ArgumentSyntax>( new SyntaxNodeOrToken[] { Argument( LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(quest.QuestId))), Token(SyntaxKind.CommaToken), Argument(CreateGatheringRootExpression(quest.QuestId, quest.Root)) }))))); } return statements.ToArray(); } private static ObjectCreationExpressionSyntax CreateGatheringRootExpression(ushort locationId, GatheringRoot root) { try { var emptyRoot = new GatheringRoot(); return ObjectCreationExpression( IdentifierName(nameof(GatheringRoot))) .WithInitializer( InitializerExpression( SyntaxKind.ObjectInitializerExpression, SeparatedList<ExpressionSyntax>( SyntaxNodeList( AssignmentList(nameof(GatheringRoot.Author), root.Author).AsSyntaxNodeOrToken(), AssignmentList(nameof(GatheringRoot.Steps), root.Steps) .AsSyntaxNodeOrToken(), Assignment(nameof(GatheringRoot.FlyBetweenNodes), root.FlyBetweenNodes, emptyRoot.FlyBetweenNodes) .AsSyntaxNodeOrToken(), AssignmentList(nameof(GatheringRoot.Groups), root.Groups).AsSyntaxNodeOrToken())))); } catch (Exception e) { throw new Exception($"GatheringGen[{locationId}]: {e.Message}", e); } } }