diff --git a/GatheringPathRenderer/Windows/EditorWindow.cs b/GatheringPathRenderer/Windows/EditorWindow.cs
index dd5fd09d..678286f6 100644
--- a/GatheringPathRenderer/Windows/EditorWindow.cs
+++ b/GatheringPathRenderer/Windows/EditorWindow.cs
@@ -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
index 00000000..f16197d3
--- /dev/null
+++ b/GatheringPaths/4.x - Stormblood/The Lochs/526_Abalathia's Skull_BTN.json
@@ -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
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/GatheringPaths/4.x - Stormblood/The Ruby Sea/507_Onokoro_BTN.json b/GatheringPaths/4.x - Stormblood/The Ruby Sea/507_Onokoro_BTN.json
index cbe79c54..9ba00a29 100644
--- a/GatheringPaths/4.x - Stormblood/The Ruby Sea/507_Onokoro_BTN.json
+++ b/GatheringPaths/4.x - Stormblood/The Ruby Sea/507_Onokoro_BTN.json
@@ -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
index 00000000..f3621a43
--- /dev/null
+++ b/GatheringPaths/4.x - Stormblood/The Ruby Sea/510_East Othard Coastline_BTN.json
@@ -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
index 00000000..0972e666
--- /dev/null
+++ b/GatheringPaths/4.x - Stormblood/The Ruby Sea/511_Rasen Kaikyo_BTN.json
@@ -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
index 00000000..71f30298
--- /dev/null
+++ b/GatheringPaths/4.x - Stormblood/The Ruby Sea/529_Rasen Kaikyo_BTN.json
@@ -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
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/GatheringPaths/AssemblyGatheringLocationLoader.cs b/GatheringPaths/AssemblyGatheringLocationLoader.cs
index e74e59f8..0a292b84 100644
--- a/GatheringPaths/AssemblyGatheringLocationLoader.cs
+++ b/GatheringPaths/AssemblyGatheringLocationLoader.cs
@@ -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")]
diff --git a/GatheringPaths/GatheringPaths.csproj b/GatheringPaths/GatheringPaths.csproj
index 8a5f3291..f9e9725f 100644
--- a/GatheringPaths/GatheringPaths.csproj
+++ b/GatheringPaths/GatheringPaths.csproj
@@ -26,7 +26,7 @@
-
+
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
index 00000000..c59687c6
--- /dev/null
+++ b/QuestPaths/4.x - Stormblood/Class Quests/BTN/2622_Walking for Walker's.json
@@ -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
index 00000000..fcc2e5f8
--- /dev/null
+++ b/QuestPaths/4.x - Stormblood/Class Quests/BTN/2623_The White Death.json
@@ -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
index 00000000..b682928e
--- /dev/null
+++ b/QuestPaths/4.x - Stormblood/Class Quests/BTN/2624_Edgyth's Winning Streak.json
@@ -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
index 00000000..8445dfd6
--- /dev/null
+++ b/QuestPaths/4.x - Stormblood/Custom Deliveries/Adkiragh/3177_Between a Rock and the Hard Place.json
@@ -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"
+ }
+ ]
+ }
+ ]
+}
diff --git a/QuestPaths/QuestPaths.csproj b/QuestPaths/QuestPaths.csproj
index 1e0389ef..f0bd1b42 100644
--- a/QuestPaths/QuestPaths.csproj
+++ b/QuestPaths/QuestPaths.csproj
@@ -22,6 +22,7 @@
Questionable.QuestPaths.QuestSchema
+
diff --git a/Questionable.Model/Gathering/GatheringPointId.cs b/Questionable.Model/Gathering/GatheringPointId.cs
new file mode 100644
index 00000000..641f255d
--- /dev/null
+++ b/Questionable.Model/Gathering/GatheringPointId.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Globalization;
+
+namespace Questionable.Model.Gathering;
+
+public class GatheringPointId : IComparable, IEquatable
+{
+ 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));
+ }
+}
diff --git a/Questionable/Controller/ContextMenuController.cs b/Questionable/Controller/ContextMenuController.cs
index 59f0d876..bd7aef2d 100644
--- a/Questionable/Controller/ContextMenuController.cs
+++ b/Questionable/Controller/ContextMenuController.cs
@@ -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));
}
}
diff --git a/Questionable/Controller/GatheringController.cs b/Questionable/Controller/GatheringController.cs
index 87a1eee1..52f251a8 100644
--- a/Questionable/Controller/GatheringController.cs
+++ b/Questionable/Controller/GatheringController.cs
@@ -23,6 +23,7 @@ namespace Questionable.Controller;
internal sealed unsafe class GatheringController : MiniTaskController
{
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 _logger;
+
+ private readonly Dictionary _gatheringPoints = new();
+
+ public GatheringPointRegistry(IDalamudPluginInterface pluginInterface, QuestRegistry questRegistry,
+ ILogger 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(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;
+ }
+}
diff --git a/Questionable/Controller/NavigationOverrides/MovementOverrideController.cs b/Questionable/Controller/NavigationOverrides/MovementOverrideController.cs
index 3896aa21..f94d0b0f 100644
--- a/Questionable/Controller/NavigationOverrides/MovementOverrideController.cs
+++ b/Questionable/Controller/NavigationOverrides/MovementOverrideController.cs
@@ -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: a 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),
diff --git a/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs b/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs
index 1a083c17..18afd5a5 100644
--- a/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs
+++ b/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs
@@ -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;
diff --git a/Questionable/Data/GatheringData.cs b/Questionable/Data/GatheringData.cs
index e1f6f2c5..c231f004 100644
--- a/Questionable/Data/GatheringData.cs
+++ b/Questionable/Data/GatheringData.cs
@@ -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 _minerGatheringPoints = [];
- private readonly Dictionary _botanistGatheringPoints = [];
+ private readonly Dictionary _minerGatheringPoints = [];
+ private readonly Dictionary _botanistGatheringPoints = [];
private readonly Dictionary _itemIdToCollectability;
private readonly Dictionary _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;
}
}
diff --git a/Questionable/Data/QuestData.cs b/Questionable/Data/QuestData.cs
index 1cf0733e..a57f8531 100644
--- a/Questionable/Data/QuestData.cs
+++ b/Questionable/Data/QuestData.cs
@@ -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;
diff --git a/Questionable/GameStructs/AgentSatisfactionSupply2.cs b/Questionable/GameStructs/AgentSatisfactionSupply2.cs
index 70573fa5..0a517a9a 100644
--- a/Questionable/GameStructs/AgentSatisfactionSupply2.cs
+++ b/Questionable/GameStructs/AgentSatisfactionSupply2.cs
@@ -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;
}
}
diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs
index 766bb45d..18033c6d 100644
--- a/Questionable/QuestionablePlugin.cs
+++ b/Questionable/QuestionablePlugin.cs
@@ -164,6 +164,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
{
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
@@ -211,6 +212,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
private static void Initialize(IServiceProvider serviceProvider)
{
serviceProvider.GetRequiredService().Reload();
+ serviceProvider.GetRequiredService().Reload();
serviceProvider.GetRequiredService();
serviceProvider.GetRequiredService();
serviceProvider.GetRequiredService();