Schema update
authorLiza Carvelli <liza@carvel.li>
Fri, 2 Aug 2024 18:04:45 +0000 (20:04 +0200)
committerLiza Carvelli <liza@carvel.li>
Fri, 2 Aug 2024 18:04:59 +0000 (20:04 +0200)
20 files changed:
.gitmodules
GatheringPathRenderer/GatheringPathRenderer.csproj
GatheringPathRenderer/GatheringPathRenderer.json [new file with mode: 0644]
GatheringPathRenderer/RendererPlugin.cs
GatheringPathRenderer/packages.lock.json
GatheringPaths/6.x - Endwalker/Thavnair/820_Pewter Ore.json
GatheringPaths/AssemblyGatheringLocationLoader.cs
GatheringPaths/GatheringPaths.csproj
GatheringPaths/gatheringlocation-v1.json
QuestPathGenerator/GatheringSourceGenerator.cs
QuestPathGenerator/RoslynShortcuts.cs
QuestPathGenerator/Utils.cs
Questionable.Model/Gathering/GatheringLocation.cs [new file with mode: 0644]
Questionable.Model/Gathering/GatheringNode.cs [new file with mode: 0644]
Questionable.Model/Gathering/GatheringNodeGroup.cs [new file with mode: 0644]
Questionable.Model/Gathering/GatheringNodeLocation.cs [deleted file]
Questionable.Model/Gathering/GatheringRoot.cs
Questionable.sln
Questionable/Controller/QuestRegistry.cs
vendor/ECommons [new submodule]

index 4ac68e07e564ed954feaa198f00c8d38cb08586c..0bc08a361743a42d7aa7a0d391142562aa3e1405 100644 (file)
@@ -1,3 +1,6 @@
 [submodule "LLib"]
        path = LLib
        url = https://git.carvel.li/liza/LLib.git
+[submodule "vendor/ECommons"]
+       path = vendor/ECommons
+       url = https://github.com/NightmareXIV/ECommons.git
index 5d22cd3fbc5ca126561748fa60aa04691c0657e7..1d3aeae5c9c2acf1b7fa537f0ea68f0529903778 100644 (file)
@@ -1,3 +1,8 @@
 <Project Sdk="Dalamud.NET.Sdk/10.0.0">
+    <ItemGroup>
+      <ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj" />
+      <ProjectReference Include="..\vendor\ECommons\ECommons\ECommons.csproj" />
+    </ItemGroup>
+
     <Import Project="..\LLib\LLib.targets"/>
 </Project>
diff --git a/GatheringPathRenderer/GatheringPathRenderer.json b/GatheringPathRenderer/GatheringPathRenderer.json
new file mode 100644 (file)
index 0000000..8d68d1d
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "Name": "GatheringPathRenderer",
+  "Author": "Liza Carvelli",
+  "Punchline": "dev only plugin: Renders gathering location.",
+  "Description": "dev only plugin: Renders gathering location (without ECommons polluting the entire normal project)."
+}
index d33eae96100934cc66909bc12ce062908e76da04..9c9a50d2c5e1633f141c943b2aa474fbbed09ec3 100644 (file)
-using Dalamud.Plugin;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using Dalamud.Plugin;
+using Dalamud.Plugin.Services;
+using ECommons;
+using ECommons.Schedulers;
+using ECommons.SplatoonAPI;
+using Questionable.Model.Gathering;
 
 namespace GatheringPathRenderer;
 
 public sealed class RendererPlugin : IDalamudPlugin
 {
+    private const long OnTerritoryChange = -2;
+    private readonly IDalamudPluginInterface _pluginInterface;
+    private readonly IClientState _clientState;
+    private readonly IPluginLog _pluginLog;
+    private readonly List<(ushort Id, GatheringRoot Root)> _gatheringLocations = [];
+
+    public RendererPlugin(IDalamudPluginInterface pluginInterface, IClientState clientState, IPluginLog pluginLog)
+    {
+        _pluginInterface = pluginInterface;
+        _clientState = clientState;
+        _pluginLog = pluginLog;
+
+        _pluginInterface.GetIpcSubscriber<object>("Questionable.ReloadData")
+            .Subscribe(Reload);
+
+        ECommonsMain.Init(pluginInterface, this, Module.SplatoonAPI);
+        LoadGatheringLocationsFromDirectory();
+
+        _clientState.TerritoryChanged += TerritoryChanged;
+        if (_clientState.IsLoggedIn)
+            TerritoryChanged(_clientState.TerritoryType);
+    }
+
+    private void Reload()
+    {
+        LoadGatheringLocationsFromDirectory();
+        TerritoryChanged(_clientState.TerritoryType);
+    }
+
+    private void LoadGatheringLocationsFromDirectory()
+    {
+        _gatheringLocations.Clear();
+
+        DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent?.Parent;
+        if (solutionDirectory != null)
+        {
+            DirectoryInfo pathProjectDirectory =
+                new DirectoryInfo(Path.Combine(solutionDirectory.FullName, "GatheringPaths"));
+            if (pathProjectDirectory.Exists)
+            {
+                try
+                {
+                    LoadFromDirectory(
+                        new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "2.x - A Realm Reborn")));
+                    LoadFromDirectory(
+                        new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "3.x - Heavensward")));
+                    LoadFromDirectory(
+                        new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "4.x - Stormblood")));
+                    LoadFromDirectory(
+                        new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "5.x - Shadowbringers")));
+                    LoadFromDirectory(
+                        new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "6.x - Endwalker")));
+                    LoadFromDirectory(
+                        new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "7.x - Dawntrail")));
+
+                    _pluginLog.Information(
+                        $"Loaded {_gatheringLocations.Count} gathering root locations from project directory");
+                }
+                catch (Exception e)
+                {
+                    _pluginLog.Error(e, "Failed to load quests from project directory");
+                }
+            }
+            else
+                _pluginLog.Warning($"Project directory {pathProjectDirectory} does not exist");
+        }
+        else
+            _pluginLog.Warning($"Solution directory {solutionDirectory} does not exist");
+    }
+
+    private void LoadFromDirectory(DirectoryInfo directory)
+    {
+        if (!directory.Exists)
+            return;
+
+        _pluginLog.Information($"Loading locations from {directory}");
+        foreach (FileInfo fileInfo in directory.GetFiles("*.json"))
+        {
+            try
+            {
+                using FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
+                LoadLocationFromStream(fileInfo.Name, stream);
+            }
+            catch (Exception e)
+            {
+                throw new InvalidDataException($"Unable to load file {fileInfo.FullName}", e);
+            }
+        }
+
+        foreach (DirectoryInfo childDirectory in directory.GetDirectories())
+            LoadFromDirectory(childDirectory);
+    }
+
+    private void LoadLocationFromStream(string fileName, Stream stream)
+    {
+        var locationNode = JsonNode.Parse(stream)!;
+        GatheringRoot root = locationNode.Deserialize<GatheringRoot>()!;
+        _gatheringLocations.Add((ushort.Parse(fileName.Split('_')[0]), root));
+    }
+
+    private void TerritoryChanged(ushort territoryId)
+    {
+        Splatoon.RemoveDynamicElements("GatheringPathRenderer");
+
+        var elements = _gatheringLocations
+            .Where(x => x.Root.TerritoryId == territoryId)
+            .SelectMany(v =>
+                v.Root.Groups.SelectMany(group =>
+                    group.Nodes.SelectMany(node => node.Locations
+                        .SelectMany(x =>
+                            new List<Element>
+                            {
+                                new Element(x.IsCone()
+                                    ? ElementType.ConeAtFixedCoordinates
+                                    : ElementType.CircleAtFixedCoordinates)
+                                {
+                                    refX = x.Position.X,
+                                    refY = x.Position.Z,
+                                    refZ = x.Position.Y,
+                                    Filled = true,
+                                    radius = x.MinimumDistance,
+                                    Donut = x.MaximumDistance - x.MinimumDistance,
+                                    color = 0x2020FF80,
+                                    Enabled = true,
+                                    coneAngleMin = x.IsCone() ? (int)x.MinimumAngle.GetValueOrDefault() : 0,
+                                    coneAngleMax = x.IsCone() ? (int)x.MaximumAngle.GetValueOrDefault() : 0
+                                },
+                                new Element(ElementType.CircleAtFixedCoordinates)
+                                {
+                                    refX = x.Position.X,
+                                    refY = x.Position.Z,
+                                    refZ = x.Position.Y,
+                                    color = 0x00000000,
+                                    Enabled = true,
+                                    overlayText = $"{v.Id} // {node.DataId} / {node.Locations.IndexOf(x)}"
+                                }
+                            }))))
+            .ToList();
+
+        if (elements.Count == 0)
+        {
+            _pluginLog.Information("No new elements to render.");
+            return;
+        }
+
+        _ = new TickScheduler(delegate
+        {
+            try
+            {
+                Splatoon.AddDynamicElements("GatheringPathRenderer",
+                    elements.ToArray(),
+                    new[] { OnTerritoryChange });
+                _pluginLog.Information($"Created {elements.Count} splatoon elements.");
+            }
+            catch (Exception e)
+            {
+                _pluginLog.Error(e, "Unable to create splatoon layer");
+            }
+        });
+    }
+
     public void Dispose()
     {
+        _clientState.TerritoryChanged -= TerritoryChanged;
+
+        Splatoon.RemoveDynamicElements("GatheringPathRenderer");
+        ECommonsMain.Dispose();
 
+        _pluginInterface.GetIpcSubscriber<object>("Questionable.ReloadData")
+            .Unsubscribe(Reload);
     }
 }
index 0c669ebfdb526f155dc44a2f88fc439aabed4a15..c7a267af8b14e896060e790f880a2d0244a8cc5d 100644 (file)
           "Microsoft.Build.Tasks.Git": "1.1.1",
           "Microsoft.SourceLink.Common": "1.1.1"
         }
+      },
+      "System.Text.Encodings.Web": {
+        "type": "Transitive",
+        "resolved": "8.0.0",
+        "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ=="
+      },
+      "System.Text.Json": {
+        "type": "Transitive",
+        "resolved": "8.0.4",
+        "contentHash": "bAkhgDJ88XTsqczoxEMliSrpijKZHhbJQldhAmObj/RbrN3sU5dcokuXmWJWsdQAhiMJ9bTayWsL1C9fbbCRhw==",
+        "dependencies": {
+          "System.Text.Encodings.Web": "8.0.0"
+        }
+      },
+      "ecommons": {
+        "type": "Project"
+      },
+      "gatheringpaths": {
+        "type": "Project",
+        "dependencies": {
+          "Questionable.Model": "[1.0.0, )"
+        }
+      },
+      "questionable.model": {
+        "type": "Project",
+        "dependencies": {
+          "System.Text.Json": "[8.0.4, )"
+        }
       }
     }
   }
index 425d77eb9bafde3f584b85fe1143da3c128b4a4f..de17572581e8492ac91c71bf6614ecf5e8efe76b 100644 (file)
   "Author": "liza",
   "TerritoryId": 957,
   "AetheryteShortcut": "Thavnair - Great Work",
-  "Nodes": [
+  "Groups": [
     {
-      "DataId": 33918,
-      "Position": {
-        "X": -582.5132,
-        "Y": 40.54578,
-        "Z": -426.0171
-      }
+      "Nodes": [
+        {
+          "DataId": 33918,
+          "Locations": [
+            {
+              "Position": {
+                "X": -582.5132,
+                "Y": 40.54578,
+                "Z": -426.0171
+              },
+              "MinimumAngle": -50,
+              "MaximumAngle": 90
+            }
+          ]
+        },
+        {
+          "DataId": 33919,
+          "Locations": [
+            {
+              "Position": {
+                "X": -578.2101,
+                "Y": 41.27147,
+                "Z": -447.6376
+              },
+              "MinimumAngle": 130,
+              "MaximumAngle": 220
+            },
+            {
+              "Position": {
+                "X": -546.2882,
+                "Y": 44.52267,
+                "Z": -435.8184
+              },
+              "MinimumAngle": 200,
+              "MaximumAngle": 360
+            }
+          ]
+        }
+      ]
     },
     {
-      "DataId": 33919,
-      "Position": {
-        "X": -578.2101,
-        "Y": 41.27147,
-        "Z": -447.6376
-      }
+      "Nodes": [
+        {
+          "DataId": 33920,
+          "Locations": [
+            {
+              "Position": {
+                "X": -488.2276,
+                "Y": 34.71221,
+                "Z": -359.6945
+              },
+              "MinimumAngle": 20,
+              "MaximumAngle": 128,
+              "MinimumDistance": 1.3
+            }
+          ]
+        },
+        {
+          "DataId": 33921,
+          "Locations": [
+            {
+              "Position": {
+                "X": -498.8687,
+                "Y": 31.08014,
+                "Z": -351.9397
+              },
+              "MinimumAngle": 40,
+              "MaximumAngle": 190
+            },
+            {
+              "Position": {
+                "X": -490.7759,
+                "Y": 28.70215,
+                "Z": -344.4114
+              },
+              "MinimumAngle": -110,
+              "MaximumAngle": 60
+            },
+            {
+              "Position": {
+                "X": -494.1286,
+                "Y": 32.89971,
+                "Z": -355.0208
+              },
+              "MinimumAngle": 80,
+              "MaximumAngle": 230
+            }
+          ]
+        }
+      ]
     },
     {
-      "DataId": 33920,
-      "Position": {
-        "X": -488.2276,
-        "Y": 34.71221,
-        "Z": -359.6945
-      }
-    },
-    {
-      "DataId": 33921,
-      "Position": {
-        "X": -498.8687,
-        "Y": 31.08014,
-        "Z": -351.9397
-      }
-    },
-    {
-      "DataId": 33922,
-      "Position": {
-        "X": -304.0609,
-        "Y": 68.76999,
-        "Z": -479.1875
-      }
-    },
-    {
-      "DataId": 33923,
-      "Position": {
-        "X": -293.6989,
-        "Y": 68.77935,
-        "Z": -484.2256
-      }
+      "Nodes": [
+        {
+          "DataId": 33922,
+          "Locations": [
+            {
+              "Position": {
+                "X": -304.0609,
+                "Y": 68.76999,
+                "Z": -479.1875
+              },
+              "MinimumAngle": -110,
+              "MaximumAngle": 70
+            }
+          ]
+        },
+        {
+          "DataId": 33923,
+          "Locations": [
+            {
+              "Position": {
+                "X": -293.6989,
+                "Y": 68.77935,
+                "Z": -484.2256
+              },
+              "MinimumAngle": -30,
+              "MaximumAngle": 110
+            },
+            {
+              "Position": {
+                "X": -295.0806,
+                "Y": 69.12621,
+                "Z": -498.1898
+              },
+              "MinimumAngle": 10,
+              "MaximumAngle": 200
+            },
+            {
+              "Position": {
+                "X": -281.4858,
+                "Y": 67.64153,
+                "Z": -477.6673
+              },
+              "MinimumAngle": -90,
+              "MaximumAngle": 60
+            }
+          ]
+        }
+      ]
     }
   ]
 }
index 041764106ce38ca09294815f0f27b7be4bdf6ebb..e74e59f8021f783351f12b1fea6aa0ca35ccd9ee 100644 (file)
@@ -16,12 +16,10 @@ public static partial class AssemblyGatheringLocationLoader
         if (_locations == null)
         {
             _locations = [];
-#if RELEASE
             LoadLocations();
-#endif
         }
 
-        return _locations ?? throw new InvalidOperationException("quest data is not initialized");
+        return _locations ?? throw new InvalidOperationException("location data is not initialized");
     }
 
     public static Stream QuestSchema =>
index f9e9725f272d12fab9ca9cafbe1359c0a5ab9bd7..8a5f329198b598371259fedd1e28c5c62478145a 100644 (file)
@@ -26,7 +26,7 @@
         <AdditionalFiles Include="..\Questionable.Model\common-schema.json" />
     </ItemGroup>
 
-    <ItemGroup Condition="'$(Configuration)' == 'Release'">
+    <ItemGroup>
         <None Remove="2.x - A Realm Reborn" />
         <None Remove="3.x - Heavensward" />
         <None Remove="4.x - Stormblood" />
index 53b34f741493b96be8c278734c66c7ea9d05bf2c..cba558a64607a4e416e7a93b703b046c2a0979a3 100644 (file)
     "AetheryteShortcut": {
       "$ref": "https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-schema.json#/$defs/Aetheryte"
     },
-    "Nodes": {
+    "Groups": {
       "type": "array",
       "items": {
         "type": "object",
         "properties": {
-          "DataId": {
-            "type": "number",
-            "minimum": 30000,
-            "maximum": 50000
-          },
-          "Position": {
-            "$ref": "#/$defs/Vector3"
-          },
-          "MinimumAngle": {
-            "type": "number",
-            "minimum": -360,
-            "maximum": 360
-          },
-          "MaximumAngle": {
-            "type": "number",
-            "minimum": -360,
-            "maximum": 360
-          },
-          "MinimumDistance": {
-            "type": "number",
-            "minimum": 0
-          },
-          "MaximumDistance": {
-            "type": "number",
-            "exclusiveMinimum": 0
+          "Nodes": {
+            "type": "array",
+            "items": {
+              "type": "object",
+              "properties": {
+                "DataId": {
+                  "type": "number",
+                  "minimum": 30000,
+                  "maximum": 50000
+                },
+                "Locations": {
+                  "type": "array",
+                  "items": {
+                    "type": "object",
+                    "properties": {
+                      "Position": {
+                        "$ref": "#/$defs/Vector3"
+                      },
+                      "MinimumAngle": {
+                        "type": "number",
+                        "minimum": -360,
+                        "maximum": 360
+                      },
+                      "MaximumAngle": {
+                        "type": "number",
+                        "minimum": -360,
+                        "maximum": 360
+                      },
+                      "MinimumDistance": {
+                        "type": "number",
+                        "minimum": 0
+                      },
+                      "MaximumDistance": {
+                        "type": "number",
+                        "exclusiveMinimum": 0
+                      }
+                    },
+                    "required": [
+                      "Position"
+                    ],
+                    "additionalProperties": false
+                  }
+                }
+              },
+              "required": [
+                "DataId"
+              ],
+              "additionalProperties": false
+            }
           }
         },
         "required": [
-          "DataId",
-          "Position"
+          "Nodes"
         ],
         "additionalProperties": false
       }
index a10acd0e299417621afdad28024d0d998201f3e2..4995c3e4bd6995e0ca3dbeea723635eb50bbc7a0 100644 (file)
@@ -153,7 +153,7 @@ public class GatheringSourceGenerator : ISourceGenerator
                                 Assignment(nameof(GatheringRoot.TerritoryId), root.TerritoryId, default)
                                     .AsSyntaxNodeOrToken(),
                                 Assignment(nameof(GatheringRoot.AetheryteShortcut), root.AetheryteShortcut, null),
-                                AssignmentList(nameof(GatheringRoot.Nodes), root.Nodes).AsSyntaxNodeOrToken()))));
+                                AssignmentList(nameof(GatheringRoot.Groups), root.Groups).AsSyntaxNodeOrToken()))));
         }
         catch (Exception e)
         {
index 4f5a8fb008d42653c8233dea4212d580829bff03..07b8291276d9d9bad5bdf36ea692f00f2fd2e0c4 100644 (file)
@@ -314,30 +314,55 @@ public static class RoslynShortcuts
                                     Assignment(nameof(SkipAetheryteCondition.InSameTerritory),
                                         skipAetheryteCondition.InSameTerritory, emptyAetheryte.InSameTerritory)))));
             }
-            else if (value is GatheringNodeLocation nodeLocation)
+            else if (value is GatheringNodeGroup nodeGroup)
             {
-                var emptyLocation = new GatheringNodeLocation();
                 return ObjectCreationExpression(
-                        IdentifierName(nameof(GatheringNodeLocation)))
+                        IdentifierName(nameof(GatheringNodeGroup)))
                     .WithInitializer(
                         InitializerExpression(
                             SyntaxKind.ObjectInitializerExpression,
                             SeparatedList<ExpressionSyntax>(
                                 SyntaxNodeList(
-                                    Assignment(nameof(GatheringNodeLocation.DataId), nodeLocation.DataId,
+                                    AssignmentList(nameof(GatheringNodeGroup.Nodes), nodeGroup.Nodes)
+                                        .AsSyntaxNodeOrToken()))));
+            }
+            else if (value is GatheringNode nodeLocation)
+            {
+                var emptyLocation = new GatheringNode();
+                return ObjectCreationExpression(
+                        IdentifierName(nameof(GatheringNode)))
+                    .WithInitializer(
+                        InitializerExpression(
+                            SyntaxKind.ObjectInitializerExpression,
+                            SeparatedList<ExpressionSyntax>(
+                                SyntaxNodeList(
+                                    Assignment(nameof(GatheringNode.DataId), nodeLocation.DataId,
                                             emptyLocation.DataId)
                                         .AsSyntaxNodeOrToken(),
-                                    Assignment(nameof(GatheringNodeLocation.Position), nodeLocation.Position,
+                                    AssignmentList(nameof(GatheringNode.Locations), nodeLocation.Locations)
+                                        .AsSyntaxNodeOrToken()))));
+            }
+            else if (value is GatheringLocation location)
+            {
+                var emptyLocation = new GatheringLocation();
+                return ObjectCreationExpression(
+                        IdentifierName(nameof(GatheringLocation)))
+                    .WithInitializer(
+                        InitializerExpression(
+                            SyntaxKind.ObjectInitializerExpression,
+                            SeparatedList<ExpressionSyntax>(
+                                SyntaxNodeList(
+                                    Assignment(nameof(GatheringLocation.Position), location.Position,
                                         emptyLocation.Position).AsSyntaxNodeOrToken(),
-                                    Assignment(nameof(GatheringNodeLocation.MinimumAngle), nodeLocation.MinimumAngle,
+                                    Assignment(nameof(GatheringLocation.MinimumAngle), location.MinimumAngle,
                                         emptyLocation.MinimumAngle).AsSyntaxNodeOrToken(),
-                                    Assignment(nameof(GatheringNodeLocation.MaximumAngle), nodeLocation.MaximumAngle,
+                                    Assignment(nameof(GatheringLocation.MaximumAngle), location.MaximumAngle,
                                         emptyLocation.MaximumAngle).AsSyntaxNodeOrToken(),
-                                    Assignment(nameof(GatheringNodeLocation.MinimumDistance),
-                                            nodeLocation.MinimumDistance, emptyLocation.MinimumDistance)
+                                    Assignment(nameof(GatheringLocation.MinimumDistance),
+                                            location.MinimumDistance, emptyLocation.MinimumDistance)
                                         .AsSyntaxNodeOrToken(),
-                                    Assignment(nameof(GatheringNodeLocation.MaximumDistance),
-                                            nodeLocation.MaximumDistance, emptyLocation.MaximumDistance)
+                                    Assignment(nameof(GatheringLocation.MaximumDistance),
+                                            location.MaximumDistance, emptyLocation.MaximumDistance)
                                         .AsSyntaxNodeOrToken()))));
             }
             else if (value is null)
index 12873a718de796b9c4525d86726a924b4c66e4a3..5e1baac022f43ae18f6658173986134e65d0fe24 100644 (file)
@@ -55,12 +55,13 @@ public static class Utils
                 Culture = CultureInfo.InvariantCulture,
                 OutputFormat = OutputFormat.List,
             });
-            if (!evaluationResult.IsValid)
+            if (evaluationResult.HasErrors)
             {
                 var error = Diagnostic.Create(invalidJson,
                     null,
                     Path.GetFileName(additionalFile.Path));
                 context.ReportDiagnostic(error);
+                continue;
             }
 
             yield return (id, node);
diff --git a/Questionable.Model/Gathering/GatheringLocation.cs b/Questionable.Model/Gathering/GatheringLocation.cs
new file mode 100644 (file)
index 0000000..5d14a55
--- /dev/null
@@ -0,0 +1,21 @@
+using System.Numerics;
+using System.Text.Json.Serialization;
+using Questionable.Model.Common.Converter;
+
+namespace Questionable.Model.Gathering;
+
+public sealed class GatheringLocation
+{
+    [JsonConverter(typeof(VectorConverter))]
+    public Vector3 Position { get; set; }
+
+    public float? MinimumAngle { get; set; }
+    public float? MaximumAngle { get; set; }
+    public float MinimumDistance { get; set; } = 1f;
+    public float MaximumDistance { get; set; } = 3f;
+
+    public bool IsCone()
+    {
+        return MinimumAngle != null && MaximumAngle != null;
+    }
+}
diff --git a/Questionable.Model/Gathering/GatheringNode.cs b/Questionable.Model/Gathering/GatheringNode.cs
new file mode 100644 (file)
index 0000000..a88f53e
--- /dev/null
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace Questionable.Model.Gathering;
+
+public sealed class GatheringNode
+{
+    public uint DataId { get; set; }
+
+    public List<GatheringLocation> Locations { get; set; } = [];
+}
diff --git a/Questionable.Model/Gathering/GatheringNodeGroup.cs b/Questionable.Model/Gathering/GatheringNodeGroup.cs
new file mode 100644 (file)
index 0000000..7922d62
--- /dev/null
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace Questionable.Model.Gathering;
+
+public sealed class GatheringNodeGroup
+{
+    public List<GatheringNode> Nodes { get; set; } = [];
+}
diff --git a/Questionable.Model/Gathering/GatheringNodeLocation.cs b/Questionable.Model/Gathering/GatheringNodeLocation.cs
deleted file mode 100644 (file)
index 49a5e64..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Numerics;
-
-namespace Questionable.Model.Gathering;
-
-public sealed class GatheringNodeLocation
-{
-    public uint DataId { get; set; }
-    public Vector3 Position { get; set; }
-    public float? MinimumAngle { get; set; }
-    public float? MaximumAngle { get; set; }
-    public float? MinimumDistance { get; set; } = 0.5f;
-    public float? MaximumDistance { get; set; } = 3f;
-}
index c572faab69b4a0578a77ebd114b17cd627e075f3..5670482ec48f10f20bbef492f77064f8b2f69bdf 100644 (file)
@@ -14,5 +14,5 @@ public sealed class GatheringRoot
     [JsonConverter(typeof(AetheryteConverter))]
     public EAetheryteLocation? AetheryteShortcut { get; set; }
 
-    public List<GatheringNodeLocation> Nodes { get; set; } = [];
+    public List<GatheringNodeGroup> Groups { get; set; } = [];
 }
index b531428724f61a534e2a31ab8426aaa311f45c45..7ca82fc89de3dd1c1a9621cab6d68515a50d0fa0 100644 (file)
@@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GatheringPaths", "Gathering
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GatheringPathRenderer", "GatheringPathRenderer\GatheringPathRenderer.csproj", "{F514DA95-9867-4F3F-8062-ACE0C62E8740}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ECommons", "vendor\ECommons\ECommons\ECommons.csproj", "{A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|x64 = Debug|x64
@@ -57,6 +59,10 @@ Global
                {F514DA95-9867-4F3F-8062-ACE0C62E8740}.Debug|x64.Build.0 = Debug|Any CPU
                {F514DA95-9867-4F3F-8062-ACE0C62E8740}.Release|x64.ActiveCfg = Release|Any CPU
                {F514DA95-9867-4F3F-8062-ACE0C62E8740}.Release|x64.Build.0 = Release|Any CPU
+               {A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}.Debug|x64.ActiveCfg = Debug|x64
+               {A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}.Debug|x64.Build.0 = Debug|x64
+               {A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}.Release|x64.ActiveCfg = Release|x64
+               {A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}.Release|x64.Build.0 = Release|x64
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
index f472c115e4cd22c146bd589b80cff838a3855190..038fe1293c9db7c0b677f8de53d57e380a537c33 100644 (file)
@@ -8,6 +8,7 @@ using System.Linq;
 using System.Text.Json;
 using System.Text.Json.Nodes;
 using Dalamud.Plugin;
+using Dalamud.Plugin.Ipc;
 using Microsoft.Extensions.Logging;
 using Questionable.Data;
 using Questionable.Model;
@@ -23,8 +24,9 @@ internal sealed class QuestRegistry
     private readonly IDalamudPluginInterface _pluginInterface;
     private readonly QuestData _questData;
     private readonly QuestValidator _questValidator;
-    private readonly ILogger<QuestRegistry> _logger;
     private readonly JsonSchemaValidator _jsonSchemaValidator;
+    private readonly ILogger<QuestRegistry> _logger;
+    private readonly ICallGateProvider<object> _reloadDataIpc;
 
     private readonly Dictionary<ushort, Quest> _quests = new();
 
@@ -37,6 +39,7 @@ internal sealed class QuestRegistry
         _questValidator = questValidator;
         _jsonSchemaValidator = jsonSchemaValidator;
         _logger = logger;
+        _reloadDataIpc = _pluginInterface.GetIpcProvider<object>("Questionable.ReloadData");
     }
 
     public IEnumerable<Quest> AllQuests => _quests.Values;
@@ -66,6 +69,7 @@ internal sealed class QuestRegistry
 
         ValidateQuests();
         Reloaded?.Invoke(this, EventArgs.Empty);
+        _reloadDataIpc.SendMessage();
         _logger.LogInformation("Loaded {Count} quests in total", _quests.Count);
     }
 
diff --git a/vendor/ECommons b/vendor/ECommons
new file mode 160000 (submodule)
index 0000000..9e90d00
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit 9e90d0032f0efd4c9e65d9c5a8e8bd0e99557d68