From 527fa3440e2f728c19d013074929005d1d0dfb5b Mon Sep 17 00:00:00 2001
From: Liza Carvelli <liza@carvel.li>
Date: Sun, 21 Jul 2024 23:10:16 +0200
Subject: [PATCH] Update source gen

---
 .../QuestGeneratorTest.cs                     |  26 ++
 .../QuestPathGenerator.Tests.csproj           |  22 +
 QuestPathGenerator/QuestSourceGenerator.cs    |  87 ++--
 QuestPathGenerator/RoslynShortcuts.cs         | 416 ++++++++++--------
 QuestPaths/packages.lock.json                 |   6 +-
 Questionable.Model/V1/ComplexCombatData.cs    |   2 +-
 Questionable.sln                              |   6 +
 7 files changed, 331 insertions(+), 234 deletions(-)
 create mode 100644 QuestPathGenerator.Tests/QuestGeneratorTest.cs
 create mode 100644 QuestPathGenerator.Tests/QuestPathGenerator.Tests.csproj

diff --git a/QuestPathGenerator.Tests/QuestGeneratorTest.cs b/QuestPathGenerator.Tests/QuestGeneratorTest.cs
new file mode 100644
index 00000000..55d4cd64
--- /dev/null
+++ b/QuestPathGenerator.Tests/QuestGeneratorTest.cs
@@ -0,0 +1,26 @@
+using Questionable.Model.V1;
+using Questionable.QuestPathGenerator;
+using Xunit;
+
+namespace QuestPathGenerator.Tests;
+
+public sealed class QuestGeneratorTest
+{
+    [Fact]
+    public void SyntaxNodeListWithNullValues()
+    {
+        var complexCombatData = new ComplexCombatData
+        {
+            DataId = 47,
+            IgnoreQuestMarker = true,
+            MinimumKillCount = 1,
+        };
+
+        var list =
+            RoslynShortcuts.SyntaxNodeList(
+                RoslynShortcuts.AssignmentList(nameof(ComplexCombatData.CompletionQuestVariablesFlags),
+                    complexCombatData.CompletionQuestVariablesFlags)).ToList();
+
+        Assert.Empty(list);
+    }
+}
diff --git a/QuestPathGenerator.Tests/QuestPathGenerator.Tests.csproj b/QuestPathGenerator.Tests/QuestPathGenerator.Tests.csproj
new file mode 100644
index 00000000..98a27c97
--- /dev/null
+++ b/QuestPathGenerator.Tests/QuestPathGenerator.Tests.csproj
@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+        <LangVersion>12</LangVersion>
+
+        <IsPackable>false</IsPackable>
+        <IsTestProject>true</IsTestProject>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="coverlet.collector" Version="6.0.0"/>
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
+        <PackageReference Include="xunit" Version="2.5.3"/>
+        <PackageReference Include="xunit.runner.visualstudio" Version="2.5.3"/>
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\QuestPathGenerator\QuestPathGenerator.csproj" />
+    </ItemGroup>
+</Project>
diff --git a/QuestPathGenerator/QuestSourceGenerator.cs b/QuestPathGenerator/QuestSourceGenerator.cs
index 7184ebb0..02fc2715 100644
--- a/QuestPathGenerator/QuestSourceGenerator.cs
+++ b/QuestPathGenerator/QuestSourceGenerator.cs
@@ -208,38 +208,45 @@ public class QuestSourceGenerator : ISourceGenerator
 
     private static IEnumerable<SyntaxNodeOrToken> CreateQuestInitializer(ushort questId, QuestRoot quest)
     {
-        return new SyntaxNodeOrToken[]
+        try
         {
-            InitializerExpression(
-                SyntaxKind.ComplexElementInitializerExpression,
-                SeparatedList<ExpressionSyntax>(
-                    new SyntaxNodeOrToken[]
-                    {
-                        LiteralExpression(
-                            SyntaxKind.NumericLiteralExpression,
-                            Literal(questId)),
-                        Token(SyntaxKind.CommaToken),
-                        ObjectCreationExpression(
-                                IdentifierName(nameof(QuestRoot)))
-                            .WithInitializer(
-                                InitializerExpression(
-                                    SyntaxKind.ObjectInitializerExpression,
-                                    SeparatedList<ExpressionSyntax>(
-                                        SyntaxNodeList(
-                                            AssignmentList(nameof(QuestRoot.Author), quest.Author)
-                                                .AsSyntaxNodeOrToken(),
-                                            Assignment(nameof(QuestRoot.Comment), quest.Comment, null)
-                                                .AsSyntaxNodeOrToken(),
-                                            AssignmentList(nameof(QuestRoot.TerritoryBlacklist),
-                                                quest.TerritoryBlacklist).AsSyntaxNodeOrToken(),
-                                            AssignmentExpression(
-                                                SyntaxKind.SimpleAssignmentExpression,
-                                                IdentifierName(nameof(QuestRoot.QuestSequence)),
-                                                CreateQuestSequence(quest.QuestSequence))
-                                        ))))
-                    })),
-            Token(SyntaxKind.CommaToken)
-        };
+            return new SyntaxNodeOrToken[]
+            {
+                InitializerExpression(
+                    SyntaxKind.ComplexElementInitializerExpression,
+                    SeparatedList<ExpressionSyntax>(
+                        new SyntaxNodeOrToken[]
+                        {
+                            LiteralExpression(
+                                SyntaxKind.NumericLiteralExpression,
+                                Literal(questId)),
+                            Token(SyntaxKind.CommaToken),
+                            ObjectCreationExpression(
+                                    IdentifierName(nameof(QuestRoot)))
+                                .WithInitializer(
+                                    InitializerExpression(
+                                        SyntaxKind.ObjectInitializerExpression,
+                                        SeparatedList<ExpressionSyntax>(
+                                            SyntaxNodeList(
+                                                AssignmentList(nameof(QuestRoot.Author), quest.Author)
+                                                    .AsSyntaxNodeOrToken(),
+                                                Assignment(nameof(QuestRoot.Comment), quest.Comment, null)
+                                                    .AsSyntaxNodeOrToken(),
+                                                AssignmentList(nameof(QuestRoot.TerritoryBlacklist),
+                                                    quest.TerritoryBlacklist).AsSyntaxNodeOrToken(),
+                                                AssignmentExpression(
+                                                    SyntaxKind.SimpleAssignmentExpression,
+                                                    IdentifierName(nameof(QuestRoot.QuestSequence)),
+                                                    CreateQuestSequence(quest.QuestSequence))
+                                            ))))
+                        })),
+                Token(SyntaxKind.CommaToken)
+            };
+        }
+        catch (Exception e)
+        {
+            throw new Exception($"QuestGen[{questId}]: {e.Message}", e);
+        }
     }
 
     private static ExpressionSyntax CreateQuestSequence(List<QuestSequence> sequences)
@@ -321,13 +328,15 @@ public class QuestSourceGenerator : ISourceGenerator
                                                 .AsSyntaxNodeOrToken(),
                                             Assignment(nameof(QuestStep.Sprint), step.Sprint, emptyStep.Sprint)
                                                 .AsSyntaxNodeOrToken(),
-                                            Assignment(nameof(QuestStep.IgnoreDistanceToObject), step.IgnoreDistanceToObject, emptyStep.IgnoreDistanceToObject)
+                                            Assignment(nameof(QuestStep.IgnoreDistanceToObject),
+                                                    step.IgnoreDistanceToObject, emptyStep.IgnoreDistanceToObject)
                                                 .AsSyntaxNodeOrToken(),
                                             Assignment(nameof(QuestStep.Comment), step.Comment, emptyStep.Comment)
                                                 .AsSyntaxNodeOrToken(),
                                             Assignment(nameof(QuestStep.Aetheryte), step.Aetheryte, emptyStep.Aetheryte)
                                                 .AsSyntaxNodeOrToken(),
-                                            Assignment(nameof(QuestStep.AethernetShard), step.AethernetShard, emptyStep.AethernetShard)
+                                            Assignment(nameof(QuestStep.AethernetShard), step.AethernetShard,
+                                                    emptyStep.AethernetShard)
                                                 .AsSyntaxNodeOrToken(),
                                             Assignment(nameof(QuestStep.AetheryteShortcut), step.AetheryteShortcut,
                                                     emptyStep.AetheryteShortcut)
@@ -357,7 +366,8 @@ public class QuestSourceGenerator : ISourceGenerator
                                                 .AsSyntaxNodeOrToken(),
                                             AssignmentList(nameof(QuestStep.ComplexCombatData), step.ComplexCombatData)
                                                 .AsSyntaxNodeOrToken(),
-                                            Assignment(nameof(QuestStep.CombatDelaySecondsAtStart), step.CombatDelaySecondsAtStart,
+                                            Assignment(nameof(QuestStep.CombatDelaySecondsAtStart),
+                                                    step.CombatDelaySecondsAtStart,
                                                     emptyStep.CombatDelaySecondsAtStart)
                                                 .AsSyntaxNodeOrToken(),
                                             Assignment(nameof(QuestStep.JumpDestination), step.JumpDestination,
@@ -378,11 +388,14 @@ public class QuestSourceGenerator : ISourceGenerator
                                                 .AsSyntaxNodeOrToken(),
                                             AssignmentList(nameof(QuestStep.PointMenuChoices), step.PointMenuChoices)
                                                 .AsSyntaxNodeOrToken(),
-                                            Assignment(nameof(QuestStep.PickUpQuestId), step.PickUpQuestId, emptyStep.PickUpQuestId)
+                                            Assignment(nameof(QuestStep.PickUpQuestId), step.PickUpQuestId,
+                                                    emptyStep.PickUpQuestId)
                                                 .AsSyntaxNodeOrToken(),
-                                            Assignment(nameof(QuestStep.TurnInQuestId), step.TurnInQuestId, emptyStep.TurnInQuestId)
+                                            Assignment(nameof(QuestStep.TurnInQuestId), step.TurnInQuestId,
+                                                    emptyStep.TurnInQuestId)
                                                 .AsSyntaxNodeOrToken(),
-                                            Assignment(nameof(QuestStep.NextQuestId), step.NextQuestId, emptyStep.NextQuestId)
+                                            Assignment(nameof(QuestStep.NextQuestId), step.NextQuestId,
+                                                    emptyStep.NextQuestId)
                                                 .AsSyntaxNodeOrToken()))))),
                     Token(SyntaxKind.CommaToken),
                 }.ToArray())));
diff --git a/QuestPathGenerator/RoslynShortcuts.cs b/QuestPathGenerator/RoslynShortcuts.cs
index ecc3dd51..2ebecd41 100644
--- a/QuestPathGenerator/RoslynShortcuts.cs
+++ b/QuestPathGenerator/RoslynShortcuts.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Numerics;
 using Microsoft.CodeAnalysis;
@@ -14,7 +15,7 @@ public static class RoslynShortcuts
 {
     public static IEnumerable<SyntaxNodeOrToken> SyntaxNodeList(params SyntaxNodeOrToken?[] nodes)
     {
-        nodes = nodes.Where(x => x != null).ToArray();
+        nodes = nodes.Where(x => x != null && x.Value.RawKind != 0).ToArray();
         if (nodes.Length == 0)
             return [];
 
@@ -31,222 +32,251 @@ public static class RoslynShortcuts
 
     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 byte u8)
-            return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(u8));
-        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)
+        try
         {
-            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)
+            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 byte u8)
+                return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(u8));
+            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(ExcelRef)))
+                        IdentifierName(nameof(Vector3)))
                     .WithArgumentList(
                         ArgumentList(
-                            SingletonSeparatedList(
-                                Argument(LiteralValue(excelRef.AsKey())))));
+                            SeparatedList<ArgumentSyntax>(
+                                new SyntaxNodeOrToken[]
+                                {
+                                    Argument(LiteralValue(vector.X)),
+                                    Token(SyntaxKind.CommaToken),
+                                    Argument(LiteralValue(vector.Y)),
+                                    Token(SyntaxKind.CommaToken),
+                                    Argument(LiteralValue(vector.Z))
+                                })));
             }
-            else if (excelRef.Type == ExcelRef.EType.RowId)
+            else if (value is AethernetShortcut aethernetShortcut)
             {
                 return ObjectCreationExpression(
-                        IdentifierName(nameof(ExcelRef)))
+                        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 ComplexCombatData complexCombatData)
+            {
+                var emptyData = new ComplexCombatData();
+                return ObjectCreationExpression(
+                        IdentifierName(nameof(ComplexCombatData)))
+                    .WithInitializer(
+                        InitializerExpression(
+                            SyntaxKind.ObjectInitializerExpression,
+                            SeparatedList<ExpressionSyntax>(
+                                SyntaxNodeList(
+                                    Assignment(nameof(ComplexCombatData.DataId), complexCombatData.DataId,
+                                            emptyData.DataId)
+                                        .AsSyntaxNodeOrToken(),
+                                    Assignment(nameof(ComplexCombatData.MinimumKillCount),
+                                            complexCombatData.MinimumKillCount, emptyData.MinimumKillCount)
+                                        .AsSyntaxNodeOrToken(),
+                                    Assignment(nameof(ComplexCombatData.RewardItemId), complexCombatData.RewardItemId,
+                                            emptyData.RewardItemId)
+                                        .AsSyntaxNodeOrToken(),
+                                    Assignment(nameof(ComplexCombatData.RewardItemCount),
+                                            complexCombatData.RewardItemCount,
+                                            emptyData.RewardItemCount)
+                                        .AsSyntaxNodeOrToken(),
+                                    AssignmentList(nameof(ComplexCombatData.CompletionQuestVariablesFlags),
+                                        complexCombatData.CompletionQuestVariablesFlags),
+                                    Assignment(nameof(ComplexCombatData.IgnoreQuestMarker),
+                                            complexCombatData.IgnoreQuestMarker,
+                                            emptyData.IgnoreQuestMarker)
+                                        .AsSyntaxNodeOrToken()))));
+            }
+            else if (value is QuestWorkValue qwv)
+            {
+                return ObjectCreationExpression(
+                        IdentifierName(nameof(QuestWorkValue)))
                     .WithArgumentList(
                         ArgumentList(
-                            SingletonSeparatedList(
-                                Argument(LiteralValue(excelRef.AsRowId())))));
+                            SeparatedList<ArgumentSyntax>(
+                                new SyntaxNodeOrToken[]
+                                {
+                                    Argument(LiteralValue(qwv.High)),
+                                    Token(SyntaxKind.CommaToken),
+                                    Argument(LiteralValue(qwv.Low))
+                                })));
             }
-            else
-                throw new Exception($"Unsupported ExcelRef type {excelRef.Type}");
+            else if (value is List<QuestWorkValue> list)
+            {
+                return CollectionExpression(
+                    SeparatedList<CollectionElementSyntax>(
+                        SyntaxNodeList(list.Select(x => ExpressionElement(
+                            LiteralValue(x)).AsSyntaxNodeOrToken()).ToArray())));
+            }
+            else if (value is null)
+                return LiteralExpression(SyntaxKind.NullLiteralExpression);
         }
-        else if (value is ComplexCombatData complexCombatData)
+        catch (Exception e)
         {
-            var emptyData = new ComplexCombatData();
-            return ObjectCreationExpression(
-                    IdentifierName(nameof(ComplexCombatData)))
-                .WithInitializer(
-                    InitializerExpression(
-                        SyntaxKind.ObjectInitializerExpression,
-                        SeparatedList<ExpressionSyntax>(
-                            SyntaxNodeList(
-                                Assignment(nameof(ComplexCombatData.DataId), complexCombatData.DataId, emptyData.DataId)
-                                    .AsSyntaxNodeOrToken(),
-                                Assignment(nameof(ComplexCombatData.MinimumKillCount),
-                                        complexCombatData.MinimumKillCount, emptyData.MinimumKillCount)
-                                    .AsSyntaxNodeOrToken(),
-                                Assignment(nameof(ComplexCombatData.RewardItemId), complexCombatData.RewardItemId,
-                                        emptyData.RewardItemId)
-                                    .AsSyntaxNodeOrToken(),
-                                Assignment(nameof(ComplexCombatData.RewardItemCount), complexCombatData.RewardItemCount,
-                                        emptyData.RewardItemCount)
-                                    .AsSyntaxNodeOrToken(),
-                                AssignmentList(nameof(ComplexCombatData.CompletionQuestVariablesFlags),
-                                    complexCombatData.CompletionQuestVariablesFlags),
-                                Assignment(nameof(ComplexCombatData.IgnoreQuestMarker),
-                                        complexCombatData.IgnoreQuestMarker,
-                                        emptyData.IgnoreQuestMarker)
-                                    .AsSyntaxNodeOrToken()))));
+            throw new Exception($"Unable to handle literal [{value}]: {e.StackTrace}", e);
         }
-        else if (value is QuestWorkValue qwv)
-        {
-            return ObjectCreationExpression(
-                    IdentifierName(nameof(QuestWorkValue)))
-                .WithArgumentList(
-                    ArgumentList(
-                        SeparatedList<ArgumentSyntax>(
-                            new SyntaxNodeOrToken[]
-                            {
-                                Argument(LiteralValue(qwv.High)),
-                                Token(SyntaxKind.CommaToken),
-                                Argument(LiteralValue(qwv.Low))
-                            })));
-        }
-        else if (value is List<QuestWorkValue> list)
-        {
-            return CollectionExpression(
-                SeparatedList<CollectionElementSyntax>(
-                    SyntaxNodeList(list.Select(x => ExpressionElement(
-                        LiteralValue(x)).AsSyntaxNodeOrToken()).ToArray())));
-        }
-        else if (value is null)
-            return LiteralExpression(SyntaxKind.NullLiteralExpression);
-        else
-            throw new Exception($"Unsupported data type {value.GetType()} = {value}");
+
+        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;
+        try
+        {
+            if (value == null && defaultValue == null)
+                return null;
 
-        if (value != null && defaultValue != null && value.Equals(defaultValue))
-            return null;
+            if (value != null && defaultValue != null && value.Equals(defaultValue))
+                return null;
 
-        return AssignmentExpression(
-            SyntaxKind.SimpleAssignmentExpression,
-            IdentifierName(name),
-            LiteralValue(value));
+            return AssignmentExpression(
+                SyntaxKind.SimpleAssignmentExpression,
+                IdentifierName(name),
+                LiteralValue(value));
+        }
+        catch (Exception e)
+        {
+            throw new Exception($"Unable to handle assignment [{name}]: {e.Message}", e);
+        }
     }
 
-    public static AssignmentExpressionSyntax? AssignmentList<T>(string name, IEnumerable<T> value)
+    public static AssignmentExpressionSyntax? AssignmentList<T>(string name, IEnumerable<T>? value)
     {
-        IEnumerable<T> list = value.ToList();
-        if (!list.Any())
-            return null;
+        try
+        {
+            if (value == null)
+                return null;
 
-        return AssignmentExpression(
-            SyntaxKind.SimpleAssignmentExpression,
-            IdentifierName(name),
-            CollectionExpression(
-                SeparatedList<CollectionElementSyntax>(
-                    SyntaxNodeList(list.Select(x => ExpressionElement(
-                        LiteralValue(x)).AsSyntaxNodeOrToken()).ToArray())
-                )));
+            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())
+                    )));
+        }
+        catch (Exception e)
+        {
+            throw new Exception($"Unable to handle list [{name}]: {e.StackTrace}", e);
+        }
     }
 
     public static SyntaxNodeOrToken? AsSyntaxNodeOrToken(this SyntaxNode? node)
diff --git a/QuestPaths/packages.lock.json b/QuestPaths/packages.lock.json
index e77d9c0c..408e267a 100644
--- a/QuestPaths/packages.lock.json
+++ b/QuestPaths/packages.lock.json
@@ -9,8 +9,8 @@
       },
       "System.Text.Json": {
         "type": "Transitive",
-        "resolved": "8.0.3",
-        "contentHash": "hpagS9joOwv6efWfrMmV9MjQXpiXZH72PgN067Ysfr6AWMSD1/1hEcvh/U5mUpPLezEWsOJSuVrmqDIVD958iA==",
+        "resolved": "8.0.4",
+        "contentHash": "bAkhgDJ88XTsqczoxEMliSrpijKZHhbJQldhAmObj/RbrN3sU5dcokuXmWJWsdQAhiMJ9bTayWsL1C9fbbCRhw==",
         "dependencies": {
           "System.Text.Encodings.Web": "8.0.0"
         }
@@ -18,7 +18,7 @@
       "questionable.model": {
         "type": "Project",
         "dependencies": {
-          "System.Text.Json": "[8.0.3, )"
+          "System.Text.Json": "[8.0.4, )"
         }
       }
     }
diff --git a/Questionable.Model/V1/ComplexCombatData.cs b/Questionable.Model/V1/ComplexCombatData.cs
index 93a0d9d4..f1e70fed 100644
--- a/Questionable.Model/V1/ComplexCombatData.cs
+++ b/Questionable.Model/V1/ComplexCombatData.cs
@@ -12,5 +12,5 @@ public sealed class ComplexCombatData
     public uint? RewardItemId { get; set; }
     public int? RewardItemCount { get; set; }
     public IList<short?> CompletionQuestVariablesFlags { get; set; } = new List<short?>();
-    public bool IgnoreQuestMarker { get; }
+    public bool IgnoreQuestMarker { get; set; }
 }
diff --git a/Questionable.sln b/Questionable.sln
index 5a420a9c..0e614b41 100644
--- a/Questionable.sln
+++ b/Questionable.sln
@@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPaths", "QuestPathGene
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Questionable.Model", "Questionable.Model\Questionable.Model.csproj", "{E15144A5-AFF5-4D86-9561-AFF7DF7F505D}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPathGenerator.Tests", "QuestPathGenerator.Tests\QuestPathGenerator.Tests.csproj", "{4FD6F346-8961-4BD5-BDA2-E5F426DE4FC7}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -36,5 +38,9 @@ Global
 		{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
+		{4FD6F346-8961-4BD5-BDA2-E5F426DE4FC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{4FD6F346-8961-4BD5-BDA2-E5F426DE4FC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4FD6F346-8961-4BD5-BDA2-E5F426DE4FC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4FD6F346-8961-4BD5-BDA2-E5F426DE4FC7}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 EndGlobal