Load gathering points dynamically, BTN 65-70
authorLiza Carvelli <liza@carvel.li>
Mon, 5 Aug 2024 18:00:02 +0000 (20:00 +0200)
committerLiza Carvelli <liza@carvel.li>
Mon, 5 Aug 2024 18:01:01 +0000 (20:01 +0200)
23 files changed:
GatheringPathRenderer/Windows/EditorWindow.cs
GatheringPaths/4.x - Stormblood/The Lochs/526_Abalathia's Skull_BTN.json [new file with mode: 0644]
GatheringPaths/4.x - Stormblood/The Ruby Sea/507_Onokoro_BTN.json
GatheringPaths/4.x - Stormblood/The Ruby Sea/510_East Othard Coastline_BTN.json [new file with mode: 0644]
GatheringPaths/4.x - Stormblood/The Ruby Sea/511_Rasen Kaikyo_BTN.json [new file with mode: 0644]
GatheringPaths/4.x - Stormblood/The Ruby Sea/529_Rasen Kaikyo_BTN.json [new file with mode: 0644]
GatheringPaths/AssemblyGatheringLocationLoader.cs
GatheringPaths/GatheringPaths.csproj
QuestPaths/4.x - Stormblood/Class Quests/BTN/2622_Walking for Walker's.json [new file with mode: 0644]
QuestPaths/4.x - Stormblood/Class Quests/BTN/2623_The White Death.json [new file with mode: 0644]
QuestPaths/4.x - Stormblood/Class Quests/BTN/2624_Edgyth's Winning Streak.json [new file with mode: 0644]
QuestPaths/4.x - Stormblood/Custom Deliveries/Adkiragh/3177_Between a Rock and the Hard Place.json [new file with mode: 0644]
QuestPaths/QuestPaths.csproj
Questionable.Model/Gathering/GatheringPointId.cs [new file with mode: 0644]
Questionable/Controller/ContextMenuController.cs
Questionable/Controller/GatheringController.cs
Questionable/Controller/GatheringPointRegistry.cs [new file with mode: 0644]
Questionable/Controller/NavigationOverrides/MovementOverrideController.cs
Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs
Questionable/Data/GatheringData.cs
Questionable/Data/QuestData.cs
Questionable/GameStructs/AgentSatisfactionSupply2.cs
Questionable/QuestionablePlugin.cs

index dd5fd09d692c59708eed153f3131bdce2126ab9c..678286f6b15709896979508ccf8e51567561d40f 100644 (file)
@@ -52,6 +52,13 @@ internal sealed class EditorWindow : Window
 
     public override void Update()
     {
+        if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
+        {
+            _target = null;
+            _targetLocation = null;
+            return;
+        }
+
         _target = _targetManager.Target;
         var gatheringLocations = _plugin.GetLocationsInTerritory(_clientState.TerritoryType);
         var location = gatheringLocations.SelectMany(context =>
@@ -63,7 +70,7 @@ internal sealed class EditorWindow : Window
                             if (_target != null)
                                 distance = Vector3.Distance(location.Position, _target.Position);
                             else
-                                distance = Vector3.Distance(location.Position, _clientState.LocalPlayer!.Position);
+                                distance = Vector3.Distance(location.Position, _clientState.LocalPlayer.Position);
 
                             return new { Context = context, Node = node, Location = location, Distance = distance };
                         })
@@ -86,7 +93,7 @@ internal sealed class EditorWindow : Window
             .Select(x => new
             {
                 Object = x,
-                Distance = Vector3.Distance(location.Location.Position, _clientState.LocalPlayer!.Position)
+                Distance = Vector3.Distance(location.Location.Position, _clientState.LocalPlayer.Position)
             })
             .Where(x => x.Distance < 3f)
             .OrderBy(x => x.Distance)
diff --git a/GatheringPaths/4.x - Stormblood/The Lochs/526_Abalathia's Skull_BTN.json b/GatheringPaths/4.x - Stormblood/The Lochs/526_Abalathia's Skull_BTN.json
new file mode 100644 (file)
index 0000000..f16197d
--- /dev/null
@@ -0,0 +1,38 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
+  "Author": "liza",
+  "TerritoryId": 621,
+  "AetheryteShortcut": "Lochs - Porta Praetoria",
+  "Groups": [
+    {
+      "Nodes": [
+        {
+          "DataId": 32274,
+          "Locations": [
+            {
+              "Position": {
+                "X": -737.3265,
+                "Y": 76.50858,
+                "Z": -591.2296
+              }
+            },
+            {
+              "Position": {
+                "X": -787.0406,
+                "Y": 78.22382,
+                "Z": -640.0532
+              }
+            },
+            {
+              "Position": {
+                "X": -670.0129,
+                "Y": 70.42876,
+                "Z": -645.2087
+              }
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
index cbe79c5478f3eb50cd6bffe6b57dab2c8ae9b8be..9ba00a291d77160ce9b041ae8b258a17433a0201 100644 (file)
@@ -2,6 +2,7 @@
   "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
   "Author": "liza",
   "TerritoryId": 613,
+  "AetheryteShortcut": "Ruby Sea - Onokoro",
   "Groups": [
     {
       "Nodes": [
diff --git a/GatheringPaths/4.x - Stormblood/The Ruby Sea/510_East Othard Coastline_BTN.json b/GatheringPaths/4.x - Stormblood/The Ruby Sea/510_East Othard Coastline_BTN.json
new file mode 100644 (file)
index 0000000..f3621a4
--- /dev/null
@@ -0,0 +1,133 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
+  "Author": "liza",
+  "TerritoryId": 613,
+  "AetheryteShortcut": "Ruby Sea - Onokoro",
+  "Groups": [
+    {
+      "Nodes": [
+        {
+          "DataId": 32223,
+          "Locations": [
+            {
+              "Position": {
+                "X": -741.6174,
+                "Y": 6.276045,
+                "Z": -639.2435
+              }
+            },
+            {
+              "Position": {
+                "X": -724.2017,
+                "Y": 2.514658,
+                "Z": -647.5651
+              }
+            }
+          ]
+        },
+        {
+          "DataId": 32222,
+          "Locations": [
+            {
+              "Position": {
+                "X": -719.345,
+                "Y": 2.520793,
+                "Z": -645.692
+              }
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "Nodes": [
+        {
+          "DataId": 32218,
+          "Locations": [
+            {
+              "Position": {
+                "X": -664.7355,
+                "Y": 15.23751,
+                "Z": -801.2688
+              },
+              "MinimumAngle": -60,
+              "MaximumAngle": 185
+            }
+          ]
+        },
+        {
+          "DataId": 32219,
+          "Locations": [
+            {
+              "Position": {
+                "X": -673.4941,
+                "Y": 16.74037,
+                "Z": -818.1156
+              }
+            },
+            {
+              "Position": {
+                "X": -677.8458,
+                "Y": 15.77772,
+                "Z": -809.6718
+              }
+            },
+            {
+              "Position": {
+                "X": -663.3188,
+                "Y": 14.57053,
+                "Z": -797.132
+              }
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "Nodes": [
+        {
+          "DataId": 32221,
+          "Locations": [
+            {
+              "Position": {
+                "X": -502.177,
+                "Y": 36.7399,
+                "Z": -724.8087
+              }
+            },
+            {
+              "Position": {
+                "X": -508.0492,
+                "Y": 33.89026,
+                "Z": -703.5477
+              }
+            },
+            {
+              "Position": {
+                "X": -490.3923,
+                "Y": 36.73062,
+                "Z": -685.7646
+              },
+              "MinimumAngle": -40,
+              "MaximumAngle": 150
+            }
+          ]
+        },
+        {
+          "DataId": 32220,
+          "Locations": [
+            {
+              "Position": {
+                "X": -493.5531,
+                "Y": 36.08752,
+                "Z": -687.7089
+              },
+              "MinimumAngle": -70,
+              "MaximumAngle": 160
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/GatheringPaths/4.x - Stormblood/The Ruby Sea/511_Rasen Kaikyo_BTN.json b/GatheringPaths/4.x - Stormblood/The Ruby Sea/511_Rasen Kaikyo_BTN.json
new file mode 100644 (file)
index 0000000..0972e66
--- /dev/null
@@ -0,0 +1,134 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
+  "Author": "liza",
+  "TerritoryId": 613,
+  "AetheryteShortcut": "Ruby Sea - Tamamizu",
+  "Groups": [
+    {
+      "Nodes": [
+        {
+          "DataId": 32225,
+          "Locations": [
+            {
+              "Position": {
+                "X": 283.2321,
+                "Y": -105.8964,
+                "Z": -89.553
+              }
+            },
+            {
+              "Position": {
+                "X": 251.241,
+                "Y": -112.7794,
+                "Z": -103.9756
+              }
+            },
+            {
+              "Position": {
+                "X": 338.0264,
+                "Y": -86.76475,
+                "Z": -30.31015
+              }
+            }
+          ]
+        },
+        {
+          "DataId": 32224,
+          "Locations": [
+            {
+              "Position": {
+                "X": 304.9474,
+                "Y": -98.91968,
+                "Z": -59.39315
+              }
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "Nodes": [
+        {
+          "DataId": 32228,
+          "Locations": [
+            {
+              "Position": {
+                "X": 260.0728,
+                "Y": -124.0964,
+                "Z": 68.48814
+              }
+            }
+          ]
+        },
+        {
+          "DataId": 32229,
+          "Locations": [
+            {
+              "Position": {
+                "X": 265.4464,
+                "Y": -128.0422,
+                "Z": 88.30737
+              }
+            },
+            {
+              "Position": {
+                "X": 256.5239,
+                "Y": -113.9164,
+                "Z": -3.19258
+              }
+            },
+            {
+              "Position": {
+                "X": 237.8169,
+                "Y": -122.1193,
+                "Z": -35.11102
+              }
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "Nodes": [
+        {
+          "DataId": 32226,
+          "Locations": [
+            {
+              "Position": {
+                "X": 318.7197,
+                "Y": -88.4566,
+                "Z": 91.66042
+              }
+            }
+          ]
+        },
+        {
+          "DataId": 32227,
+          "Locations": [
+            {
+              "Position": {
+                "X": 281.2504,
+                "Y": -113.1205,
+                "Z": 176.6557
+              }
+            },
+            {
+              "Position": {
+                "X": 309.1248,
+                "Y": -88.04077,
+                "Z": 109.5688
+              }
+            },
+            {
+              "Position": {
+                "X": 312.3843,
+                "Y": -95.51873,
+                "Z": 79.94166
+              }
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/GatheringPaths/4.x - Stormblood/The Ruby Sea/529_Rasen Kaikyo_BTN.json b/GatheringPaths/4.x - Stormblood/The Ruby Sea/529_Rasen Kaikyo_BTN.json
new file mode 100644 (file)
index 0000000..71f3029
--- /dev/null
@@ -0,0 +1,131 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
+  "Author": "liza",
+  "TerritoryId": 613,
+  "AetheryteShortcut": "Ruby Sea - Onokoro",
+  "Groups": [
+    {
+      "Nodes": [
+        {
+          "DataId": 32282,
+          "Locations": [
+            {
+              "Position": {
+                "X": -332.5556,
+                "Y": -139.5151,
+                "Z": -276.7727
+              }
+            },
+            {
+              "Position": {
+                "X": -398.5437,
+                "Y": -137.4753,
+                "Z": -264.6068
+              }
+            }
+          ]
+        },
+        {
+          "DataId": 32281,
+          "Locations": [
+            {
+              "Position": {
+                "X": -377.4165,
+                "Y": -151.0521,
+                "Z": -211.4394
+              }
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "Nodes": [
+        {
+          "DataId": 32277,
+          "Locations": [
+            {
+              "Position": {
+                "X": -509.283,
+                "Y": -107.1854,
+                "Z": -389.3152
+              }
+            }
+          ]
+        },
+        {
+          "DataId": 32278,
+          "Locations": [
+            {
+              "Position": {
+                "X": -470.5726,
+                "Y": -93.28533,
+                "Z": -430.0346
+              }
+            },
+            {
+              "Position": {
+                "X": -549.3453,
+                "Y": -103.2194,
+                "Z": -352.9945
+              }
+            },
+            {
+              "Position": {
+                "X": -548.5269,
+                "Y": -99.45416,
+                "Z": -459.6198
+              }
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "Nodes": [
+        {
+          "DataId": 32279,
+          "Locations": [
+            {
+              "Position": {
+                "X": -347.0872,
+                "Y": -72.09894,
+                "Z": -420.7435
+              },
+              "MinimumAngle": -80,
+              "MaximumAngle": 100
+            }
+          ]
+        },
+        {
+          "DataId": 32280,
+          "Locations": [
+            {
+              "Position": {
+                "X": -410.6145,
+                "Y": -86.76735,
+                "Z": -432.2196
+              }
+            },
+            {
+              "Position": {
+                "X": -407.9324,
+                "Y": -108.0931,
+                "Z": -384.4449
+              }
+            },
+            {
+              "Position": {
+                "X": -341.0429,
+                "Y": -89.68655,
+                "Z": -396.5815
+              },
+              "MinimumAngle": -45,
+              "MaximumAngle": 110
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
index e74e59f8021f783351f12b1fea6aa0ca35ccd9ee..0a292b8482e4eb10e30988a354ddee1176e36696 100644 (file)
@@ -16,13 +16,15 @@ public static partial class AssemblyGatheringLocationLoader
         if (_locations == null)
         {
             _locations = [];
+#if RELEASE
             LoadLocations();
+#endif
         }
 
         return _locations ?? throw new InvalidOperationException("location data is not initialized");
     }
 
-    public static Stream QuestSchema =>
+    public static Stream GatheringSchema =>
         typeof(AssemblyGatheringLocationLoader).Assembly.GetManifestResourceStream("Questionable.GatheringPaths.GatheringLocationSchema")!;
 
     [SuppressMessage("ReSharper", "UnusedMember.Local")]
index 8a5f329198b598371259fedd1e28c5c62478145a..f9e9725f272d12fab9ca9cafbe1359c0a5ab9bd7 100644 (file)
@@ -26,7 +26,7 @@
         <AdditionalFiles Include="..\Questionable.Model\common-schema.json" />
     </ItemGroup>
 
-    <ItemGroup>
+    <ItemGroup Condition="'$(Configuration)' == 'Release'">
         <None Remove="2.x - A Realm Reborn" />
         <None Remove="3.x - Heavensward" />
         <None Remove="4.x - Stormblood" />
diff --git a/QuestPaths/4.x - Stormblood/Class Quests/BTN/2622_Walking for Walker's.json b/QuestPaths/4.x - Stormblood/Class Quests/BTN/2622_Walking for Walker's.json
new file mode 100644 (file)
index 0000000..c59687c
--- /dev/null
@@ -0,0 +1,216 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1021349,
+          "Position": {
+            "X": -43.930786,
+            "Y": 209.23637,
+            "Z": -90.77594
+          },
+          "TerritoryId": 478,
+          "InteractionType": "AcceptQuest",
+          "AetheryteShortcut": "Idyllshire",
+          "AethernetShortcut": [
+            "[Idyllshire] Aetheryte Plaza",
+            "[Idyllshire] West Idyllshire"
+          ],
+          "SkipConditions": {
+            "AetheryteShortcutIf": {
+              "InSameTerritory": true
+            }
+          }
+        }
+      ]
+    },
+    {
+      "Sequence": 1,
+      "Steps": [
+        {
+          "DataId": 1000815,
+          "Position": {
+            "X": -233.9361,
+            "Y": 6.668152,
+            "Z": -171.03839
+          },
+          "TerritoryId": 133,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Gridania",
+          "AethernetShortcut": [
+            "[Gridania] Aetheryte Plaza",
+            "[Gridania] Botanists' Guild"
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 2,
+      "Steps": [
+        {
+          "DataId": 1002659,
+          "Position": {
+            "X": 532.8297,
+            "Y": 88.99998,
+            "Z": -72.09894
+          },
+          "TerritoryId": 135,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Limsa Lominsa",
+          "AethernetShortcut": [
+            "[Limsa Lominsa] Aetheryte Plaza",
+            "[Limsa Lominsa] Tempest Gate (Lower La Noscea)"
+          ],
+          "SkipConditions": {
+            "AetheryteShortcutIf": {
+              "InTerritory": [
+                135
+              ]
+            }
+          },
+          "Fly": true,
+          "CompletionQuestVariablesFlags": [
+            null,
+            null,
+            null,
+            null,
+            null,
+            64
+          ]
+        },
+        {
+          "DataId": 1002657,
+          "Position": {
+            "X": 540.52026,
+            "Y": 89,
+            "Z": -74.02161
+          },
+          "TerritoryId": 135,
+          "InteractionType": "Interact",
+          "CompletionQuestVariablesFlags": [
+            null,
+            null,
+            null,
+            null,
+            null,
+            128
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 3,
+      "Steps": [
+        {
+          "DataId": 1021353,
+          "Position": {
+            "X": 564.17163,
+            "Y": 84.45213,
+            "Z": -100.328125
+          },
+          "TerritoryId": 135,
+          "InteractionType": "Interact",
+          "Fly": true
+        }
+      ]
+    },
+    {
+      "Sequence": 4,
+      "Steps": [
+        {
+          "DataId": 1019064,
+          "Position": {
+            "X": 59.220215,
+            "Y": 4,
+            "Z": 67.70422
+          },
+          "TerritoryId": 628,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Kugane",
+          "AethernetShortcut": [
+            "[Kugane] Aetheryte Plaza",
+            "[Kugane] Kogane Dori Markets"
+          ],
+          "SkipConditions": {
+            "AetheryteShortcutIf": {
+              "InSameTerritory": true
+            }
+          },
+          "CompletionQuestVariablesFlags": [
+            null,
+            null,
+            null,
+            null,
+            null,
+            128
+          ]
+        },
+        {
+          "DataId": 1019065,
+          "Position": {
+            "X": 80.49133,
+            "Y": 4,
+            "Z": 56.443115
+          },
+          "TerritoryId": 628,
+          "InteractionType": "Interact",
+          "CompletionQuestVariablesFlags": [
+            null,
+            null,
+            null,
+            null,
+            null,
+            64
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 5,
+      "Steps": [
+        {
+          "DataId": 1019233,
+          "Position": {
+            "X": -734.15735,
+            "Y": 1.9602847,
+            "Z": -611.38324
+          },
+          "TerritoryId": 613,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Ruby Sea - Onokoro",
+          "Fly": true
+        }
+      ]
+    },
+    {
+      "Sequence": 255,
+      "Steps": [
+        {
+          "DataId": 1021349,
+          "Position": {
+            "X": -43.930786,
+            "Y": 209.23637,
+            "Z": -90.77594
+          },
+          "TerritoryId": 478,
+          "InteractionType": "CompleteQuest",
+          "AetheryteShortcut": "Idyllshire",
+          "AethernetShortcut": [
+            "[Idyllshire] Aetheryte Plaza",
+            "[Idyllshire] West Idyllshire"
+          ],
+          "RequiredGatheredItems": [
+            {
+              "ItemId": 17946,
+              "ItemCount": 20
+            }
+          ],
+          "NextQuestId": 2623
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/4.x - Stormblood/Class Quests/BTN/2623_The White Death.json b/QuestPaths/4.x - Stormblood/Class Quests/BTN/2623_The White Death.json
new file mode 100644 (file)
index 0000000..fcc2e5f
--- /dev/null
@@ -0,0 +1,160 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1021349,
+          "Position": {
+            "X": -43.930786,
+            "Y": 209.23637,
+            "Z": -90.77594
+          },
+          "TerritoryId": 478,
+          "InteractionType": "AcceptQuest",
+          "AetheryteShortcut": "Idyllshire",
+          "AethernetShortcut": [
+            "[Idyllshire] Aetheryte Plaza",
+            "[Idyllshire] West Idyllshire"
+          ],
+          "SkipConditions": {
+            "AetheryteShortcutIf": {
+              "InSameTerritory": true
+            }
+          }
+        }
+      ]
+    },
+    {
+      "Sequence": 1,
+      "Steps": [
+        {
+          "DataId": 1000815,
+          "Position": {
+            "X": -233.9361,
+            "Y": 6.668152,
+            "Z": -171.03839
+          },
+          "TerritoryId": 133,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Gridania",
+          "AethernetShortcut": [
+            "[Gridania] Aetheryte Plaza",
+            "[Gridania] Botanists' Guild"
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 2,
+      "Steps": [
+        {
+          "DataId": 2008181,
+          "Position": {
+            "X": -46.31122,
+            "Y": 209.49109,
+            "Z": -86.35089
+          },
+          "TerritoryId": 478,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Idyllshire",
+          "AethernetShortcut": [
+            "[Idyllshire] Aetheryte Plaza",
+            "[Idyllshire] West Idyllshire"
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 3,
+      "Steps": [
+        {
+          "DataId": 2008181,
+          "Position": {
+            "X": -46.31122,
+            "Y": 209.49109,
+            "Z": -86.35089
+          },
+          "TerritoryId": 478,
+          "InteractionType": "Interact"
+        }
+      ]
+    },
+    {
+      "Sequence": 4,
+      "Steps": [
+        {
+          "DataId": 1021349,
+          "Position": {
+            "X": -43.930786,
+            "Y": 209.23637,
+            "Z": -90.77594
+          },
+          "TerritoryId": 478,
+          "InteractionType": "Interact",
+          "DialogueChoices": [
+            {
+              "Type": "List",
+              "Prompt": "TEXT_CLSHRV680_02623_Q1_000_000",
+              "Answer": "TEXT_CLSHRV680_02623_A1_000_002"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 5,
+      "Steps": [
+        {
+          "DataId": 1017106,
+          "Position": {
+            "X": 73.99097,
+            "Y": 214.12,
+            "Z": -92.57648
+          },
+          "TerritoryId": 478,
+          "InteractionType": "Interact"
+        }
+      ]
+    },
+    {
+      "Sequence": 6,
+      "Steps": [
+        {
+          "DataId": 1017106,
+          "Position": {
+            "X": 73.99097,
+            "Y": 214.12,
+            "Z": -92.57648
+          },
+          "TerritoryId": 478,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Idyllshire",
+          "RequiredGatheredItems": [
+            {
+              "ItemId": 17947,
+              "ItemCount": 20
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 255,
+      "Steps": [
+        {
+          "DataId": 1021349,
+          "Position": {
+            "X": -43.930786,
+            "Y": 209.23637,
+            "Z": -90.77594
+          },
+          "TerritoryId": 478,
+          "InteractionType": "CompleteQuest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/4.x - Stormblood/Class Quests/BTN/2624_Edgyth's Winning Streak.json b/QuestPaths/4.x - Stormblood/Class Quests/BTN/2624_Edgyth's Winning Streak.json
new file mode 100644 (file)
index 0000000..b682928
--- /dev/null
@@ -0,0 +1,184 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1021349,
+          "Position": {
+            "X": -43.930786,
+            "Y": 209.23637,
+            "Z": -90.77594
+          },
+          "TerritoryId": 478,
+          "InteractionType": "AcceptQuest",
+          "AetheryteShortcut": "Idyllshire",
+          "AethernetShortcut": [
+            "[Idyllshire] Aetheryte Plaza",
+            "[Idyllshire] West Idyllshire"
+          ],
+          "SkipConditions": {
+            "AetheryteShortcutIf": {
+              "InSameTerritory": true
+            }
+          }
+        }
+      ]
+    },
+    {
+      "Sequence": 1,
+      "Steps": [
+        {
+          "DataId": 1000815,
+          "Position": {
+            "X": -233.9361,
+            "Y": 6.668152,
+            "Z": -171.03839
+          },
+          "TerritoryId": 133,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Gridania",
+          "AethernetShortcut": [
+            "[Gridania] Aetheryte Plaza",
+            "[Gridania] Botanists' Guild"
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 2,
+      "Steps": [
+        {
+          "DataId": 1021358,
+          "Position": {
+            "X": -599.54224,
+            "Y": 130,
+            "Z": -483.32953
+          },
+          "TerritoryId": 612,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Fringes - Castrum Oriens"
+        }
+      ]
+    },
+    {
+      "Sequence": 3,
+      "Steps": [
+        {
+          "DataId": 1021359,
+          "Position": {
+            "X": -262.25684,
+            "Y": 256.97177,
+            "Z": 695.52136
+          },
+          "TerritoryId": 620,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Peaks - Ala Ghiri"
+        }
+      ]
+    },
+    {
+      "Sequence": 4,
+      "Steps": [
+        {
+          "DataId": 1021591,
+          "Position": {
+            "X": -277.82104,
+            "Y": 258.90652,
+            "Z": 782.77246
+          },
+          "TerritoryId": 620,
+          "InteractionType": "Interact",
+          "CompletionQuestVariablesFlags": [
+            null,
+            null,
+            null,
+            null,
+            null,
+            64
+          ]
+        },
+        {
+          "DataId": 1021595,
+          "Position": {
+            "X": -232.50171,
+            "Y": 258.90652,
+            "Z": 783.505
+          },
+          "TerritoryId": 620,
+          "InteractionType": "Interact",
+          "CompletionQuestVariablesFlags": [
+            null,
+            null,
+            null,
+            null,
+            null,
+            32
+          ]
+        },
+        {
+          "DataId": 1020870,
+          "Position": {
+            "X": -218.61603,
+            "Y": 257.52652,
+            "Z": 737.1786
+          },
+          "TerritoryId": 620,
+          "InteractionType": "Interact",
+          "CompletionQuestVariablesFlags": [
+            null,
+            null,
+            null,
+            null,
+            null,
+            128
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 5,
+      "Steps": [
+        {
+          "DataId": 1022381,
+          "Position": {
+            "X": -243.12207,
+            "Y": 257.52652,
+            "Z": 744.0145
+          },
+          "TerritoryId": 620,
+          "InteractionType": "Interact"
+        }
+      ]
+    },
+    {
+      "Sequence": 255,
+      "Steps": [
+        {
+          "DataId": 1021349,
+          "Position": {
+            "X": -43.930786,
+            "Y": 209.23637,
+            "Z": -90.77594
+          },
+          "TerritoryId": 478,
+          "InteractionType": "CompleteQuest",
+          "AetheryteShortcut": "Idyllshire",
+          "AethernetShortcut": [
+            "[Idyllshire] Aetheryte Plaza",
+            "[Idyllshire] West Idyllshire"
+          ],
+          "Comment": "Eorzean Time: 4:00-5:59 AM/PM",
+          "RequiredGatheredItems": [
+            {
+              "ItemId": 17948,
+              "ItemCount": 5
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/4.x - Stormblood/Custom Deliveries/Adkiragh/3177_Between a Rock and the Hard Place.json b/QuestPaths/4.x - Stormblood/Custom Deliveries/Adkiragh/3177_Between a Rock and the Hard Place.json
new file mode 100644 (file)
index 0000000..8445dfd
--- /dev/null
@@ -0,0 +1,36 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1012299,
+          "Position": {
+            "X": -16.586609,
+            "Y": 206.49942,
+            "Z": 42.98462
+          },
+          "TerritoryId": 478,
+          "InteractionType": "AcceptQuest"
+        }
+      ]
+    },
+    {
+      "Sequence": 255,
+      "Steps": [
+        {
+          "DataId": 1018393,
+          "Position": {
+            "X": -60.380005,
+            "Y": 206.50021,
+            "Z": 26.16919
+          },
+          "TerritoryId": 478,
+          "InteractionType": "CompleteQuest"
+        }
+      ]
+    }
+  ]
+}
index 1e0389ef9d1fd32a33cacb6d2915c074e7d33caf..f0bd1b4281941c2e8a2da101007b3329dc5a7fe1 100644 (file)
@@ -22,6 +22,7 @@
         <EmbeddedResource Include="quest-v1.json">
             <LogicalName>Questionable.QuestPaths.QuestSchema</LogicalName>
         </EmbeddedResource>
+        <AdditionalFiles Include="4.x - Stormblood\Class Quests\BTN\2623_The White Death.json" />
         <AdditionalFiles Include="quest-v1.json" />
         <AdditionalFiles Include="..\Questionable.Model\common-schema.json" />
     </ItemGroup>
diff --git a/Questionable.Model/Gathering/GatheringPointId.cs b/Questionable.Model/Gathering/GatheringPointId.cs
new file mode 100644 (file)
index 0000000..641f255
--- /dev/null
@@ -0,0 +1,56 @@
+using System;
+using System.Globalization;
+
+namespace Questionable.Model.Gathering;
+
+public class GatheringPointId : IComparable<GatheringPointId>, IEquatable<GatheringPointId>
+{
+    public GatheringPointId(ushort value)
+    {
+        Value = value;
+    }
+
+    public ushort Value { get; }
+
+    public int CompareTo(GatheringPointId? other)
+    {
+        if (ReferenceEquals(this, other)) return 0;
+        if (ReferenceEquals(null, other)) return 1;
+        return Value.CompareTo(other.Value);
+    }
+
+    public bool Equals(GatheringPointId? other)
+    {
+        if (ReferenceEquals(null, other)) return false;
+        if (ReferenceEquals(this, other)) return true;
+        return Value == other.Value;
+    }
+
+    public override bool Equals(object? obj)
+    {
+        if (ReferenceEquals(null, obj)) return false;
+        if (ReferenceEquals(this, obj)) return true;
+        if (obj.GetType() != this.GetType()) return false;
+        return Equals((GatheringPointId)obj);
+    }
+
+    public override int GetHashCode()
+    {
+        return Value.GetHashCode();
+    }
+
+    public static bool operator ==(GatheringPointId? left, GatheringPointId? right)
+    {
+        return Equals(left, right);
+    }
+
+    public static bool operator !=(GatheringPointId? left, GatheringPointId? right)
+    {
+        return !Equals(left, right);
+    }
+
+    public static GatheringPointId FromString(string value)
+    {
+        return new GatheringPointId(ushort.Parse(value, CultureInfo.InvariantCulture));
+    }
+}
index 59f0d876e2fe1c3e75e53cdf4a306a91174b1df1..bd7aef2d1c655cca76d856765b0f39a10319afc6 100644 (file)
@@ -98,8 +98,9 @@ internal sealed class ContextMenuController : IDisposable
             var agentSatisfactionSupply = AgentSatisfactionSupply.Instance();
             if (agentSatisfactionSupply->IsAgentActive())
             {
+                int maxTurnIns = agentSatisfactionSupply->NpcInfo.SatisfactionRank == 1 ? 3 : 6;
                 quantityToGather = Math.Min(agentSatisfactionSupply->RemainingAllowances,
-                    ((AgentSatisfactionSupply2*)agentSatisfactionSupply)->TurnInsToNextRank);
+                    ((AgentSatisfactionSupply2*)agentSatisfactionSupply)->CalculateTurnInsToNextRank(maxTurnIns));
             }
         }
 
index 87a1eee12aac692733ff1b41cadb760b8c4be77d..52f251a8856bb55abae459638f56dc63c576e4be 100644 (file)
@@ -23,6 +23,7 @@ namespace Questionable.Controller;
 internal sealed unsafe class GatheringController : MiniTaskController<GatheringController>
 {
     private readonly MovementController _movementController;
+    private readonly GatheringPointRegistry _gatheringPointRegistry;
     private readonly GameFunctions _gameFunctions;
     private readonly NavmeshIpc _navmeshIpc;
     private readonly IObjectTable _objectTable;
@@ -33,6 +34,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
 
     public GatheringController(
         MovementController movementController,
+        GatheringPointRegistry gatheringPointRegistry,
         GameFunctions gameFunctions,
         NavmeshIpc navmeshIpc,
         IObjectTable objectTable,
@@ -43,6 +45,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
         : base(chatGui, logger)
     {
         _movementController = movementController;
+        _gatheringPointRegistry = gatheringPointRegistry;
         _gameFunctions = gameFunctions;
         _navmeshIpc = navmeshIpc;
         _objectTable = objectTable;
@@ -52,8 +55,8 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
 
     public bool Start(GatheringRequest gatheringRequest)
     {
-        if (!AssemblyGatheringLocationLoader.GetLocations()
-                .TryGetValue(gatheringRequest.GatheringPointId, out GatheringRoot? gatheringRoot))
+        if (!_gatheringPointRegistry.TryGetGatheringPoint(gatheringRequest.GatheringPointId,
+                out GatheringRoot? gatheringRoot))
         {
             _logger.LogError("Unable to resolve gathering point, no path found for {ItemId} / point {PointId}",
                 gatheringRequest.ItemId, gatheringRequest.GatheringPointId);
@@ -190,7 +193,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
     }
 
     public sealed record GatheringRequest(
-        ushort GatheringPointId,
+        GatheringPointId GatheringPointId,
         uint ItemId,
         int Quantity,
         ushort Collectability = 0);
diff --git a/Questionable/Controller/GatheringPointRegistry.cs b/Questionable/Controller/GatheringPointRegistry.cs
new file mode 100644 (file)
index 0000000..6b688e5
--- /dev/null
@@ -0,0 +1,150 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Text.Json;
+using Dalamud.Plugin;
+using Microsoft.Extensions.Logging;
+using Questionable.GatheringPaths;
+using Questionable.Model;
+using Questionable.Model.Gathering;
+
+namespace Questionable.Controller;
+
+internal sealed class GatheringPointRegistry : IDisposable
+{
+    private readonly IDalamudPluginInterface _pluginInterface;
+    private readonly QuestRegistry _questRegistry;
+    private readonly ILogger<QuestRegistry> _logger;
+
+    private readonly Dictionary<GatheringPointId, GatheringRoot> _gatheringPoints = new();
+
+    public GatheringPointRegistry(IDalamudPluginInterface pluginInterface, QuestRegistry questRegistry,
+        ILogger<QuestRegistry> logger)
+    {
+        _pluginInterface = pluginInterface;
+        _questRegistry = questRegistry;
+        _logger = logger;
+
+        _questRegistry.Reloaded += OnReloaded;
+    }
+
+    private void OnReloaded(object? sender, EventArgs e) => Reload();
+
+    public void Reload()
+    {
+        _gatheringPoints.Clear();
+
+        LoadGatheringPointsFromAssembly();
+        LoadGatheringPointsFromProjectDirectory();
+
+        try
+        {
+            LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "GatheringPoints")));
+        }
+        catch (Exception e)
+        {
+            _logger.LogError(e,
+                "Failed to load gathering points from user directory (some may have been successfully loaded)");
+        }
+
+        _logger.LogInformation("Loaded {Count} gathering points in total", _gatheringPoints.Count);
+    }
+
+    [Conditional("RELEASE")]
+    private void LoadGatheringPointsFromAssembly()
+    {
+        _logger.LogInformation("Loading gathering points from assembly");
+
+        foreach ((ushort gatheringPointId, GatheringRoot gatheringRoot) in
+                 AssemblyGatheringLocationLoader.GetLocations())
+        {
+            _gatheringPoints[new GatheringPointId(gatheringPointId)] = gatheringRoot;
+        }
+
+        _logger.LogInformation("Loaded {Count} gathering points from assembly", _gatheringPoints.Count);
+    }
+
+    [Conditional("DEBUG")]
+    private void LoadGatheringPointsFromProjectDirectory()
+    {
+        DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent;
+        if (solutionDirectory != null)
+        {
+            DirectoryInfo pathProjectDirectory =
+                new DirectoryInfo(Path.Combine(solutionDirectory.FullName, "GatheringPaths"));
+            if (pathProjectDirectory.Exists)
+            {
+                try
+                {
+                    foreach (var expansionFolder in ExpansionData.ExpansionFolders.Values)
+                        LoadFromDirectory(
+                            new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, expansionFolder)),
+                            LogLevel.Trace);
+                }
+                catch (Exception e)
+                {
+                    _gatheringPoints.Clear();
+                    _logger.LogError(e, "Failed to load gathering points from project directory");
+                }
+            }
+        }
+    }
+
+    private void LoadGatheringPointFromStream(string fileName, Stream stream)
+    {
+        _logger.LogTrace("Loading gathering point from '{FileName}'", fileName);
+        GatheringPointId? gatheringPointId = ExtractGatheringPointIdFromName(fileName);
+        if (gatheringPointId == null)
+            return;
+
+        _gatheringPoints[gatheringPointId] = JsonSerializer.Deserialize<GatheringRoot>(stream)!;
+    }
+
+    private void LoadFromDirectory(DirectoryInfo directory, LogLevel logLevel = LogLevel.Information)
+    {
+        if (!directory.Exists)
+        {
+            _logger.LogInformation("Not loading gathering points from {DirectoryName} (doesn't exist)", directory);
+            return;
+        }
+
+        _logger.Log(logLevel, "Loading gathering points from {DirectoryName}", directory);
+        foreach (FileInfo fileInfo in directory.GetFiles("*.json"))
+        {
+            try
+            {
+                using FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
+                LoadGatheringPointFromStream(fileInfo.Name, stream);
+            }
+            catch (Exception e)
+            {
+                throw new InvalidDataException($"Unable to load file {fileInfo.FullName}", e);
+            }
+        }
+
+        foreach (DirectoryInfo childDirectory in directory.GetDirectories())
+            LoadFromDirectory(childDirectory, logLevel);
+    }
+
+    private static GatheringPointId? ExtractGatheringPointIdFromName(string resourceName)
+    {
+        string name = resourceName.Substring(0, resourceName.Length - ".json".Length);
+        name = name.Substring(name.LastIndexOf('.') + 1);
+
+        if (!name.Contains('_', StringComparison.Ordinal))
+            return null;
+
+        string[] parts = name.Split('_', 2);
+        return GatheringPointId.FromString(parts[0]);
+    }
+
+    public bool TryGetGatheringPoint(GatheringPointId gatheringPointId, [NotNullWhen(true)] out GatheringRoot? gatheringRoot)
+        => _gatheringPoints.TryGetValue(gatheringPointId, out gatheringRoot);
+
+    public void Dispose()
+    {
+        _questRegistry.Reloaded -= OnReloaded;
+    }
+}
index 3896aa2125165595f30b6ed834fcac634d65cbe3..f94d0b0fe7cad55761a18b8b0defb2416b953fe2 100644 (file)
@@ -13,7 +13,7 @@ internal sealed class MovementOverrideController
     [
         new BlacklistedArea(1191, new(-223.0412f, 31.937134f, -584.03906f), 5f, 7.75f),
 
-        // limsa, aftcastle to baderon
+        // limsa, aftcastle to Baderon
         new BlacklistedPoint(128, new(2f, 40.25f, 36.5f), new(0.25f, 40.25f, 36.5f)),
 
         // New Gridania, Carline Canopy stairs
@@ -28,7 +28,7 @@ internal sealed class MovementOverrideController
         new BlacklistedPoint(132, new(45.5f, -8f, 101f), new(50.53978f, -8.046954f, 101.06045f)),
         new BlacklistedPoint(132, new(48.5f, -8f, 98.25f), new(50.53978f, -8.046954f, 101.06045f)),
 
-        // ul'dah lamp near adventuer's guild
+        // ul'dah lamp near adventurers' guild
         new BlacklistedPoint(130, new(59.5f, 4.25f, -118f), new(60.551353f, 4f, -119.76446f)),
 
         // eastern thanalan
@@ -37,7 +37,7 @@ internal sealed class MovementOverrideController
         // southern thanalan
         new BlacklistedPoint(146, new(-201.75f, 10.5f, -265.5f), new(-203.75235f, 10.130764f, -265.15314f)),
 
-        // lower la noscea - moraby drydocks aetheryte
+        // lower la noscea - Moraby Drydocks aetheryte
         new BlacklistedArea(135, new(156.11499f, 15.518433f, 673.21277f), 0.5f, 5f),
 
         // coerthas central highlands
@@ -55,9 +55,13 @@ internal sealed class MovementOverrideController
         // moghome, mogmug's trial
         new BlacklistedPoint(400, new(384, -74, 648.75f), new(386.0543f, -72.409454f, 652.0184f), 3),
 
-        // leaving idyllshiret through the west gate attempts to run into this wall
+        // leaving Idyllshire through the west gate attempts to run into this wall
         new BlacklistedPoint(399, new(-514.4851f, 149.63762f, -480.58087f), new(-528.78656f, 151.17374f, -473.07077f), 5, true),
 
+        // Idyllshire: random rocks in the north, passable one way only
+        new BlacklistedPoint(478, new(14.5f, 215.25f, -101.5f), new(18.133032f, 215.44998f, -107.83075f), 5),
+        new BlacklistedPoint(478, new(11, 215.5f, -104.5f), new(18.133032f, 215.44998f, -107.83075f), 5),
+
         new BlacklistedPoint(1189, new(574f, -142.25f, 504.25f), new(574.44183f, -142.12766f, 507.60065f)),
 
         // kholusia, random rocks
@@ -66,7 +70,7 @@ internal sealed class MovementOverrideController
         // yak t'el, rock near cenote jayunja
         new BlacklistedPoint(1189, new(-115.75f, -213.75f, 336.5f), new(-112.40265f, -215.01514f, 339.0067f), 2),
 
-        // sheshenewezi springs aetheryte: couple of barrel rings that get in the way if you go north
+        // sheshenewezi springs aetheryte: couple of barrel rings that get in the way if you go north
         new BlacklistedPoint(1190, new(-292.29004f, 18.598045f, -133.83907f), new(-288.20895f, 18.652182f, -132.67445f),
             4),
 
index 1a083c17c36e9cf6c80cf0a71d7e6c79d1e584aa..18afd5a521ac569cbdeed4c0060ab28c051c1d0c 100644 (file)
@@ -19,6 +19,7 @@ internal static class GatheringRequiredItems
     internal sealed class Factory(
         IServiceProvider serviceProvider,
         MovementController movementController,
+        GatheringPointRegistry gatheringPointRegistry,
         IClientState clientState,
         GatheringData gatheringData,
         TerritoryData territoryData,
@@ -34,11 +35,10 @@ internal static class GatheringRequiredItems
                     classJob = (EClassJob)requiredGatheredItems.ClassJob.Value;
 
                 if (!gatheringData.TryGetGatheringPointId(requiredGatheredItems.ItemId, classJob,
-                        out var gatheringPointId))
+                        out GatheringPointId? gatheringPointId))
                     throw new TaskException($"No gathering point found for item {requiredGatheredItems.ItemId}");
 
-                if (!AssemblyGatheringLocationLoader.GetLocations()
-                        .TryGetValue(gatheringPointId, out GatheringRoot? gatheringRoot))
+                if (!gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot? gatheringRoot))
                     throw new TaskException($"No path found for gathering point {gatheringPointId}");
 
                 if (classJob != currentClassJob)
@@ -92,10 +92,10 @@ internal static class GatheringRequiredItems
 
     internal sealed class StartGathering(GatheringController gatheringController) : ITask
     {
-        private ushort _gatheringPointId;
+        private GatheringPointId _gatheringPointId = null!;
         private GatheredItem _gatheredItem = null!;
 
-        public ITask With(ushort gatheringPointId, GatheredItem gatheredItem)
+        public ITask With(GatheringPointId gatheringPointId, GatheredItem gatheredItem)
         {
             _gatheringPointId = gatheringPointId;
             _gatheredItem = gatheredItem;
index e1f6f2c5ec6d7477d096442087fb06f0ae27f250..c231f004a3b37163872c1b1b1ee9e5fc92d56bc9 100644 (file)
@@ -1,16 +1,18 @@
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using Dalamud.Plugin.Services;
 using LLib.GameData;
 using Lumina.Excel.GeneratedSheets;
 using Microsoft.Extensions.Logging;
+using Questionable.Model.Gathering;
 
 namespace Questionable.Data;
 
 internal sealed class GatheringData
 {
-    private readonly Dictionary<uint, ushort> _minerGatheringPoints = [];
-    private readonly Dictionary<uint, ushort> _botanistGatheringPoints = [];
+    private readonly Dictionary<uint, GatheringPointId> _minerGatheringPoints = [];
+    private readonly Dictionary<uint, GatheringPointId> _botanistGatheringPoints = [];
     private readonly Dictionary<uint, ushort> _itemIdToCollectability;
     private readonly Dictionary<uint, uint> _npcForCustomDeliveries;
 
@@ -27,9 +29,9 @@ internal sealed class GatheringData
                 if (gatheringItemToItem.TryGetValue((uint)gatheringItemId, out uint itemId))
                 {
                     if (gatheringPointBase.GatheringType.Row is 0 or 1)
-                        _minerGatheringPoints[itemId] = (ushort)gatheringPointBase.RowId;
+                        _minerGatheringPoints[itemId] = new GatheringPointId((ushort)gatheringPointBase.RowId);
                     else if (gatheringPointBase.GatheringType.Row is 2 or 3)
-                        _botanistGatheringPoints[itemId] = (ushort)gatheringPointBase.RowId;
+                        _botanistGatheringPoints[itemId] = new GatheringPointId((ushort)gatheringPointBase.RowId);
                 }
             }
         }
@@ -59,7 +61,8 @@ internal sealed class GatheringData
             .ToDictionary(x => x.ItemId, x => x.NpcId);
     }
 
-    public bool TryGetGatheringPointId(uint itemId, EClassJob classJobId, out ushort gatheringPointId)
+    public bool TryGetGatheringPointId(uint itemId, EClassJob classJobId,
+        [NotNullWhen(true)] out GatheringPointId? gatheringPointId)
     {
         if (classJobId == EClassJob.Miner)
             return _minerGatheringPoints.TryGetValue(itemId, out gatheringPointId);
@@ -67,7 +70,7 @@ internal sealed class GatheringData
             return _botanistGatheringPoints.TryGetValue(itemId, out gatheringPointId);
         else
         {
-            gatheringPointId = 0;
+            gatheringPointId = null;
             return false;
         }
     }
index 1cf0733e4e6a1ca535e38b7749c8cf120741bfba..a57f8531a78b26e2efde8f1f92e1dd63868d742e 100644 (file)
@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Collections.Immutable;
 using System.Linq;
 using Dalamud.Plugin.Services;
 using Lumina.Excel.GeneratedSheets;
index 70573fa51f923aeb6ad65f595dbc0222a6bf3eed..0a517a9a23e387c05ca1e63a814193f6e2450d11 100644 (file)
@@ -15,14 +15,11 @@ internal struct AgentSatisfactionSupply2
     [FieldOffset(0x70)] public ushort CurrentSatisfaction;
     [FieldOffset(0x72)] public ushort MaxSatisfaction;
 
-    public int TurnInsToNextRank
+    public int CalculateTurnInsToNextRank(int maxTurnIns)
     {
-        get
-        {
-            if (MaxSatisfaction == 0)
-                return 6;
+        if (MaxSatisfaction == 0)
+            return maxTurnIns;
 
-            return 6 * (MaxSatisfaction - CurrentSatisfaction) / MaxSatisfaction;
-        }
+        return maxTurnIns * (MaxSatisfaction - CurrentSatisfaction) / MaxSatisfaction;
     }
 }
index 766bb45d09f085424fb2c2ef1b05be047f99f44c..18033c6d181d323b43d38e33c4a2891914d51c73 100644 (file)
@@ -164,6 +164,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
     {
         serviceCollection.AddSingleton<MovementController>();
         serviceCollection.AddSingleton<MovementOverrideController>();
+        serviceCollection.AddSingleton<GatheringPointRegistry>();
         serviceCollection.AddSingleton<QuestRegistry>();
         serviceCollection.AddSingleton<QuestController>();
         serviceCollection.AddSingleton<GameUiController>();
@@ -211,6 +212,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
     private static void Initialize(IServiceProvider serviceProvider)
     {
         serviceProvider.GetRequiredService<QuestRegistry>().Reload();
+        serviceProvider.GetRequiredService<GatheringPointRegistry>().Reload();
         serviceProvider.GetRequiredService<CommandHandler>();
         serviceProvider.GetRequiredService<ContextMenuController>();
         serviceProvider.GetRequiredService<DalamudInitializer>();