Add basic support for gathering custom delivery items automatically
authorLiza Carvelli <liza@carvel.li>
Sun, 4 Aug 2024 14:03:23 +0000 (16:03 +0200)
committerLiza Carvelli <liza@carvel.li>
Sun, 4 Aug 2024 14:03:23 +0000 (16:03 +0200)
73 files changed:
GatheringPathRenderer/packages.lock.json
GatheringPaths/4.x - Stormblood/Yanxia/731_Yuzuka Manor_BTN.json [new file with mode: 0644]
QuestPaths/3.x - Heavensward/Custom Deliveries/Zhloe/1551_Arms Wide Open.json [new file with mode: 0644]
QuestPaths/3.x - Heavensward/Custom Deliveries/Zhloe/S1_Zhloe Aliapoh.json [new file with mode: 0644]
QuestPaths/3.x - Heavensward/Unlocks/Custom Deliveries/1551_Arms Wide Open.json [deleted file]
QuestPaths/4.x - Stormblood/Custom Deliveries/Adkiragh/S4_Adkiragh.json [new file with mode: 0644]
QuestPaths/4.x - Stormblood/Custom Deliveries/Kurenai/S3_Kurenai.json [new file with mode: 0644]
QuestPaths/4.x - Stormblood/Custom Deliveries/M'naago/S2_M'naago.json [new file with mode: 0644]
QuestPaths/5.x - Shadowbringers/Custom Deliveries/Charlemend/S7_Charlemend.json [new file with mode: 0644]
QuestPaths/5.x - Shadowbringers/Custom Deliveries/Ehll Tou/S6_Ehll Tou.json [new file with mode: 0644]
QuestPaths/5.x - Shadowbringers/Custom Deliveries/Kai-Shirr/S5_Kai-Shirr.json [new file with mode: 0644]
QuestPaths/6.x - Endwalker/Custom Deliveries/Ameliance/S8_Ameliance.json [new file with mode: 0644]
QuestPaths/6.x - Endwalker/Custom Deliveries/Anden/S9_Anden.json [new file with mode: 0644]
QuestPaths/6.x - Endwalker/Custom Deliveries/Margrat/S10_Margrat.json [new file with mode: 0644]
QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/4473_The Faculty.json [new file with mode: 0644]
QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4153_Cultured Pursuits.json [new file with mode: 0644]
QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4154_Cooking Up a Culture.json [new file with mode: 0644]
QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4155_The Culture of Ceruleum.json [new file with mode: 0644]
QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4156_The Culture of Carrots.json [new file with mode: 0644]
QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4157_Hinageshi in Hingashi.json [new file with mode: 0644]
QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4158_The Culture of the Past.json [new file with mode: 0644]
QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4159_The Culture of Love.json [new file with mode: 0644]
QuestPaths/6.x - Endwalker/Studium Deliveries/4473_The Faculty.json [deleted file]
QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4153_Cultured Pursuits.json [deleted file]
QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4154_Cooking Up a Culture.json [deleted file]
QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4155_The Culture of Ceruleum.json [deleted file]
QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4156_The Culture of Carrots.json [deleted file]
QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4157_Hinageshi in Hingashi.json [deleted file]
QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4158_The Culture of the Past.json [deleted file]
QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4159_The Culture of Love.json [deleted file]
QuestPaths/QuestPaths.csproj
QuestPaths/quest-v1.json
Questionable.Model/Common/EAetheryteLocation.cs
Questionable.Model/Questing/Converter/AethernetShardConverter.cs
Questionable.Model/Questing/Converter/ElementIdConverter.cs
Questionable.Model/Questing/ElementId.cs
Questionable.Model/common-schema.json
Questionable/Controller/CommandHandler.cs
Questionable/Controller/ContextMenuController.cs [new file with mode: 0644]
Questionable/Controller/GameUiController.cs
Questionable/Controller/QuestController.cs
Questionable/Controller/QuestRegistry.cs
Questionable/Controller/Steps/Common/NextQuest.cs
Questionable/Controller/Steps/Interactions/Combat.cs
Questionable/Controller/Steps/Interactions/Interact.cs
Questionable/Controller/Steps/Interactions/UseItem.cs
Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs
Questionable/Controller/Steps/Shared/SkipCondition.cs
Questionable/Controller/Steps/Shared/WaitAtEnd.cs
Questionable/Data/AetheryteData.cs
Questionable/Data/GatheringData.cs
Questionable/Data/JournalData.cs
Questionable/Data/QuestData.cs
Questionable/External/LifestreamIpc.cs
Questionable/GameFunctions.cs
Questionable/Model/IQuestInfo.cs [new file with mode: 0644]
Questionable/Model/Quest.cs
Questionable/Model/QuestInfo.cs
Questionable/Model/SatisfactionSupplyInfo.cs [new file with mode: 0644]
Questionable/QuestionablePlugin.cs
Questionable/Validation/Validators/AethernetShortcutValidator.cs
Questionable/Validation/Validators/BasicSequenceValidator.cs
Questionable/Validation/Validators/CompletionFlagsValidator.cs
Questionable/Validation/Validators/JsonSchemaValidator.cs
Questionable/Validation/Validators/NextQuestValidator.cs
Questionable/Validation/Validators/QuestDisabledValidator.cs
Questionable/Validation/Validators/UniqueStartStopValidator.cs
Questionable/Windows/DebugOverlay.cs
Questionable/Windows/JournalProgressWindow.cs
Questionable/Windows/QuestComponents/ActiveQuestComponent.cs
Questionable/Windows/QuestComponents/QuestTooltipComponent.cs
Questionable/Windows/QuestSelectionWindow.cs
Questionable/Windows/UiUtils.cs

index c7a267af8b14e896060e790f880a2d0244a8cc5d..269c43d76b95de025d93b3700316e83cf395e624 100644 (file)
       "ecommons": {
         "type": "Project"
       },
-      "gatheringpaths": {
-        "type": "Project",
-        "dependencies": {
-          "Questionable.Model": "[1.0.0, )"
-        }
-      },
       "questionable.model": {
         "type": "Project",
         "dependencies": {
diff --git a/GatheringPaths/4.x - Stormblood/Yanxia/731_Yuzuka Manor_BTN.json b/GatheringPaths/4.x - Stormblood/Yanxia/731_Yuzuka Manor_BTN.json
new file mode 100644 (file)
index 0000000..23fae29
--- /dev/null
@@ -0,0 +1,115 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
+  "Author": "liza",
+  "TerritoryId": 614,
+  "AetheryteShortcut": "Yanxia - Namai",
+  "Groups": [
+    {
+      "Nodes": [
+        {
+          "DataId": 33334,
+          "Locations": [
+            {
+              "Position": {
+                "X": -222.386,
+                "Y": 23.28162,
+                "Z": 425.76
+              }
+            },
+            {
+              "Position": {
+                "X": -209.1725,
+                "Y": 22.35068,
+                "Z": 425.5524
+              }
+            }
+          ]
+        },
+        {
+          "DataId": 33333,
+          "Locations": [
+            {
+              "Position": {
+                "X": -219.9592,
+                "Y": 22.78741,
+                "Z": 431.5036
+              }
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "Nodes": [
+        {
+          "DataId": 33335,
+          "Locations": [
+            {
+              "Position": {
+                "X": -349.8553,
+                "Y": 33.90925,
+                "Z": 452.5893
+              },
+              "MinimumAngle": -90,
+              "MaximumAngle": 90
+            }
+          ]
+        },
+        {
+          "DataId": 33336,
+          "Locations": [
+            {
+              "Position": {
+                "X": -361.5062,
+                "Y": 33.49068,
+                "Z": 453.4639
+              }
+            },
+            {
+              "Position": {
+                "X": -359.826,
+                "Y": 35.47207,
+                "Z": 442.164
+              }
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "Nodes": [
+        {
+          "DataId": 33331,
+          "Locations": [
+            {
+              "Position": {
+                "X": -231.3864,
+                "Y": 17.74118,
+                "Z": 511.0694
+              }
+            }
+          ]
+        },
+        {
+          "DataId": 33332,
+          "Locations": [
+            {
+              "Position": {
+                "X": -219.0789,
+                "Y": 18.05494,
+                "Z": 525.418
+              }
+            },
+            {
+              "Position": {
+                "X": -220.9139,
+                "Y": 17.97838,
+                "Z": 514.0063
+              }
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/3.x - Heavensward/Custom Deliveries/Zhloe/1551_Arms Wide Open.json b/QuestPaths/3.x - Heavensward/Custom Deliveries/Zhloe/1551_Arms Wide Open.json
new file mode 100644 (file)
index 0000000..5ecce79
--- /dev/null
@@ -0,0 +1,37 @@
+{
+  "$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": 1019615,
+          "Position": {
+            "X": -71.763245,
+            "Y": 206.50021,
+            "Z": 32.638916
+          },
+          "StopDistance": 5,
+          "TerritoryId": 478,
+          "InteractionType": "CompleteQuest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/3.x - Heavensward/Custom Deliveries/Zhloe/S1_Zhloe Aliapoh.json b/QuestPaths/3.x - Heavensward/Custom Deliveries/Zhloe/S1_Zhloe Aliapoh.json
new file mode 100644 (file)
index 0000000..e909b81
--- /dev/null
@@ -0,0 +1,33 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "Position": {
+            "X": -71.31451,
+            "Y": 206.56206,
+            "Z": 29.3684
+          },
+          "TerritoryId": 478,
+          "InteractionType": "WalkTo",
+          "RequiredGatheredItems": [],
+          "AetheryteShortcut": "Idyllshire"
+        },
+        {
+          "DataId": 1019615,
+          "Position": {
+            "X": -71.763245,
+            "Y": 206.50021,
+            "Z": 32.638916
+          },
+          "StopDistance": 5,
+          "TerritoryId": 478,
+          "InteractionType": "Interact"
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/3.x - Heavensward/Unlocks/Custom Deliveries/1551_Arms Wide Open.json b/QuestPaths/3.x - Heavensward/Unlocks/Custom Deliveries/1551_Arms Wide Open.json
deleted file mode 100644 (file)
index 5ecce79..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-{
-  "$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": 1019615,
-          "Position": {
-            "X": -71.763245,
-            "Y": 206.50021,
-            "Z": 32.638916
-          },
-          "StopDistance": 5,
-          "TerritoryId": 478,
-          "InteractionType": "CompleteQuest"
-        }
-      ]
-    }
-  ]
-}
diff --git a/QuestPaths/4.x - Stormblood/Custom Deliveries/Adkiragh/S4_Adkiragh.json b/QuestPaths/4.x - Stormblood/Custom Deliveries/Adkiragh/S4_Adkiragh.json
new file mode 100644 (file)
index 0000000..07c9f82
--- /dev/null
@@ -0,0 +1,23 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1018393,
+          "Position": {
+            "X": -60.380005,
+            "Y": 206.50021,
+            "Z": 26.16919
+          },
+          "TerritoryId": 478,
+          "InteractionType": "Interact",
+          "RequiredGatheredItems": [],
+          "AetheryteShortcut": "Idyllshire"
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/4.x - Stormblood/Custom Deliveries/Kurenai/S3_Kurenai.json b/QuestPaths/4.x - Stormblood/Custom Deliveries/Kurenai/S3_Kurenai.json
new file mode 100644 (file)
index 0000000..edcd6dd
--- /dev/null
@@ -0,0 +1,23 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1025878,
+          "Position": {
+            "X": 343.984,
+            "Y": -120.32947,
+            "Z": -306.0197
+          },
+          "TerritoryId": 613,
+          "InteractionType": "Interact",
+          "RequiredGatheredItems": [],
+          "AetheryteShortcut": "Ruby Sea - Tamamizu"
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/4.x - Stormblood/Custom Deliveries/M'naago/S2_M'naago.json b/QuestPaths/4.x - Stormblood/Custom Deliveries/M'naago/S2_M'naago.json
new file mode 100644 (file)
index 0000000..435169e
--- /dev/null
@@ -0,0 +1,23 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1020337,
+          "Position": {
+            "X": 171.31299,
+            "Y": 13.02367,
+            "Z": -89.951965
+          },
+          "TerritoryId": 635,
+          "InteractionType": "Interact",
+          "RequiredGatheredItems": [],
+          "AetheryteShortcut": "Rhalgr's Reach"
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/5.x - Shadowbringers/Custom Deliveries/Charlemend/S7_Charlemend.json b/QuestPaths/5.x - Shadowbringers/Custom Deliveries/Charlemend/S7_Charlemend.json
new file mode 100644 (file)
index 0000000..0e73e7d
--- /dev/null
@@ -0,0 +1,26 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1035211,
+          "Position": {
+            "X": -116.96039,
+            "Y": 0,
+            "Z": -133.95898
+          },
+          "TerritoryId": 886,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Ishgard",
+          "AethernetShortcut": [
+            "[Ishgard] Aetheryte Plaza",
+            "[Ishgard] Firmament"
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/5.x - Shadowbringers/Custom Deliveries/Ehll Tou/S6_Ehll Tou.json b/QuestPaths/5.x - Shadowbringers/Custom Deliveries/Ehll Tou/S6_Ehll Tou.json
new file mode 100644 (file)
index 0000000..2e9c047
--- /dev/null
@@ -0,0 +1,26 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1033543,
+          "Position": {
+            "X": 113.38977,
+            "Y": -20,
+            "Z": -0.96136475
+          },
+          "TerritoryId": 886,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Ishgard",
+          "AethernetShortcut": [
+            "[Ishgard] Aetheryte Plaza",
+            "[Ishgard] Firmament"
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/5.x - Shadowbringers/Custom Deliveries/Kai-Shirr/S5_Kai-Shirr.json b/QuestPaths/5.x - Shadowbringers/Custom Deliveries/Kai-Shirr/S5_Kai-Shirr.json
new file mode 100644 (file)
index 0000000..9e279e1
--- /dev/null
@@ -0,0 +1,23 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1031801,
+          "Position": {
+            "X": 52.8114,
+            "Y": 83.001076,
+            "Z": -65.38495
+          },
+          "TerritoryId": 820,
+          "InteractionType": "Interact",
+          "RequiredGatheredItems": [],
+          "AetheryteShortcut": "Eulmore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/6.x - Endwalker/Custom Deliveries/Ameliance/S8_Ameliance.json b/QuestPaths/6.x - Endwalker/Custom Deliveries/Ameliance/S8_Ameliance.json
new file mode 100644 (file)
index 0000000..cc256d9
--- /dev/null
@@ -0,0 +1,27 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1042241,
+          "Position": {
+            "X": 222.85791,
+            "Y": 24.942732,
+            "Z": -197.77222
+          },
+          "TerritoryId": 962,
+          "InteractionType": "Interact",
+          "RequiredGatheredItems": [],
+          "AetheryteShortcut": "Old Sharlayan",
+          "AethernetShortcut": [
+            "[Old Sharlayan] Aetheryte Plaza",
+            "[Old Sharlayan] The Leveilleur Estate"
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/6.x - Endwalker/Custom Deliveries/Anden/S9_Anden.json b/QuestPaths/6.x - Endwalker/Custom Deliveries/Anden/S9_Anden.json
new file mode 100644 (file)
index 0000000..7cdc390
--- /dev/null
@@ -0,0 +1,24 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1044547,
+          "Position": {
+            "X": -241.68768,
+            "Y": 51.058994,
+            "Z": 620.8744
+          },
+          "TerritoryId": 816,
+          "InteractionType": "Interact",
+          "RequiredGatheredItems": [],
+          "AetheryteShortcut": "Il Mheg - Lydha Lran",
+          "Fly": true
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/6.x - Endwalker/Custom Deliveries/Margrat/S10_Margrat.json b/QuestPaths/6.x - Endwalker/Custom Deliveries/Margrat/S10_Margrat.json
new file mode 100644 (file)
index 0000000..3d9a80b
--- /dev/null
@@ -0,0 +1,33 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "Position": {
+            "X": -44.066154,
+            "Y": -29.530005,
+            "Z": -55.85129
+          },
+          "TerritoryId": 956,
+          "InteractionType": "WalkTo",
+          "AetheryteShortcut": "Labyrinthos - Sharlayan Hamlet",
+          "RequiredGatheredItems": [],
+          "Fly": true
+        },
+        {
+          "DataId": 1046073,
+          "Position": {
+            "X": -53.635498,
+            "Y": -29.497286,
+            "Z": -65.14081
+          },
+          "TerritoryId": 956,
+          "InteractionType": "Interact"
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/4473_The Faculty.json b/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/4473_The Faculty.json
new file mode 100644 (file)
index 0000000..e960c9f
--- /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": 1038500,
+          "Position": {
+            "X": -357.83936,
+            "Y": 21.84602,
+            "Z": -91.32526
+          },
+          "TerritoryId": 962,
+          "InteractionType": "AcceptQuest"
+        }
+      ]
+    },
+    {
+      "Sequence": 255,
+      "Steps": [
+        {
+          "DataId": 1038500,
+          "Position": {
+            "X": -357.83936,
+            "Y": 21.84602,
+            "Z": -91.32526
+          },
+          "TerritoryId": 962,
+          "InteractionType": "CompleteQuest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4153_Cultured Pursuits.json b/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4153_Cultured Pursuits.json
new file mode 100644 (file)
index 0000000..d9f54e2
--- /dev/null
@@ -0,0 +1,68 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1038504,
+          "Position": {
+            "X": -357.62573,
+            "Y": 21.64856,
+            "Z": -95.99457
+          },
+          "TerritoryId": 962,
+          "InteractionType": "AcceptQuest"
+        }
+      ]
+    },
+    {
+      "Sequence": 1,
+      "Steps": [
+        {
+          "DataId": 1038505,
+          "Position": {
+            "X": -376.45538,
+            "Y": 18.999998,
+            "Z": 37.00305
+          },
+          "TerritoryId": 962,
+          "InteractionType": "Interact"
+        }
+      ]
+    },
+    {
+      "Sequence": 2,
+      "Steps": [
+        {
+          "DataId": 1038503,
+          "Position": {
+            "X": -367.0863,
+            "Y": 21.84602,
+            "Z": -101.701416
+          },
+          "TerritoryId": 962,
+          "InteractionType": "Interact"
+        }
+      ]
+    },
+    {
+      "Sequence": 255,
+      "Steps": [
+        {
+          "DataId": 1038501,
+          "Position": {
+            "X": -367.3305,
+            "Y": 21.846018,
+            "Z": -102.983154
+          },
+          "StopDistance": 7,
+          "TerritoryId": 962,
+          "InteractionType": "CompleteQuest",
+          "NextQuestId": 4154
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4154_Cooking Up a Culture.json b/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4154_Cooking Up a Culture.json
new file mode 100644 (file)
index 0000000..3072030
--- /dev/null
@@ -0,0 +1,49 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1038501,
+          "Position": {
+            "X": -367.3305,
+            "Y": 21.846018,
+            "Z": -102.983154
+          },
+          "TerritoryId": 962,
+          "InteractionType": "AcceptQuest"
+        }
+      ]
+    },
+    {
+      "Sequence": 255,
+      "Steps": [
+        {
+          "DataId": 1038501,
+          "Position": {
+            "X": -367.3305,
+            "Y": 21.846018,
+            "Z": -102.983154
+          },
+          "TerritoryId": 962,
+          "InteractionType": "CompleteQuest",
+          "AetheryteShortcut": "Old Sharlayan",
+          "AethernetShortcut": [
+            "[Old Sharlayan] Aetheryte Plaza",
+            "[Old Sharlayan] The Studium"
+          ],
+          "RequiredGatheredItems": [
+            {
+              "ItemId": 35600,
+              "ItemCount": 6,
+              "Collectability": 600
+            }
+          ],
+          "NextQuestId": 4155
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4155_The Culture of Ceruleum.json b/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4155_The Culture of Ceruleum.json
new file mode 100644 (file)
index 0000000..ca59855
--- /dev/null
@@ -0,0 +1,49 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1038501,
+          "Position": {
+            "X": -367.3305,
+            "Y": 21.846018,
+            "Z": -102.983154
+          },
+          "TerritoryId": 962,
+          "InteractionType": "AcceptQuest"
+        }
+      ]
+    },
+    {
+      "Sequence": 255,
+      "Steps": [
+        {
+          "DataId": 1038501,
+          "Position": {
+            "X": -367.3305,
+            "Y": 21.846018,
+            "Z": -102.983154
+          },
+          "TerritoryId": 962,
+          "InteractionType": "CompleteQuest",
+          "AetheryteShortcut": "Old Sharlayan",
+          "AethernetShortcut": [
+            "[Old Sharlayan] Aetheryte Plaza",
+            "[Old Sharlayan] The Studium"
+          ],
+          "RequiredGatheredItems": [
+            {
+              "ItemId": 35601,
+              "ItemCount": 6,
+              "Collectability": 600
+            }
+          ],
+          "NextQuestId": 4156
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4156_The Culture of Carrots.json b/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4156_The Culture of Carrots.json
new file mode 100644 (file)
index 0000000..d25034d
--- /dev/null
@@ -0,0 +1,49 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1038501,
+          "Position": {
+            "X": -367.3305,
+            "Y": 21.846018,
+            "Z": -102.983154
+          },
+          "TerritoryId": 962,
+          "InteractionType": "AcceptQuest"
+        }
+      ]
+    },
+    {
+      "Sequence": 255,
+      "Steps": [
+        {
+          "DataId": 1038501,
+          "Position": {
+            "X": -367.3305,
+            "Y": 21.846018,
+            "Z": -102.983154
+          },
+          "TerritoryId": 962,
+          "InteractionType": "CompleteQuest",
+          "AetheryteShortcut": "Old Sharlayan",
+          "AethernetShortcut": [
+            "[Old Sharlayan] Aetheryte Plaza",
+            "[Old Sharlayan] The Studium"
+          ],
+          "RequiredGatheredItems": [
+            {
+              "ItemId": 35602,
+              "ItemCount": 6,
+              "Collectability": 600
+            }
+          ],
+          "NextQuestId": 4157
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4157_Hinageshi in Hingashi.json b/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4157_Hinageshi in Hingashi.json
new file mode 100644 (file)
index 0000000..e2ebf55
--- /dev/null
@@ -0,0 +1,164 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1038501,
+          "Position": {
+            "X": -367.3305,
+            "Y": 21.846018,
+            "Z": -102.983154
+          },
+          "TerritoryId": 962,
+          "InteractionType": "AcceptQuest"
+        }
+      ]
+    },
+    {
+      "Sequence": 1,
+      "Steps": [
+        {
+          "DataId": 2011719,
+          "Position": {
+            "X": -102.1897,
+            "Y": -7.0039062,
+            "Z": -59.92218
+          },
+          "TerritoryId": 628,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Kugane",
+          "AethernetShortcut": [
+            "[Kugane] Aetheryte Plaza",
+            "[Kugane] Shiokaze Hostelry"
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 2,
+      "Steps": [
+        {
+          "DataId": 1038506,
+          "Position": {
+            "X": -142.2904,
+            "Y": -4.7500057,
+            "Z": 199.6643
+          },
+          "TerritoryId": 628,
+          "InteractionType": "Interact",
+          "AethernetShortcut": [
+            "[Kugane] Kogane Dori Markets",
+            "[Kugane] Pier #1"
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 3,
+      "Steps": [
+        {
+          "DataId": 1038507,
+          "Position": {
+            "X": 52.750366,
+            "Y": 8.02,
+            "Z": 152.81909
+          },
+          "TerritoryId": 628,
+          "InteractionType": "Interact",
+          "AethernetShortcut": [
+            "[Kugane] Pier #1",
+            "[Kugane] Thavnairian Consulate"
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 4,
+      "Steps": [
+        {
+          "DataId": 2011720,
+          "Position": {
+            "X": 129.5033,
+            "Y": 24.979004,
+            "Z": 15.701477
+          },
+          "TerritoryId": 628,
+          "InteractionType": "Interact",
+          "AethernetShortcut": [
+            "[Kugane] Thavnairian Consulate",
+            "[Kugane] The Ruby Bazaar"
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 5,
+      "Steps": [
+        {
+          "DataId": 1038508,
+          "Position": {
+            "X": 114.70203,
+            "Y": 12,
+            "Z": 35.965454
+          },
+          "TerritoryId": 628,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Kugane",
+          "AethernetShortcut": [
+            "[Kugane] Aetheryte Plaza",
+            "[Kugane] The Ruby Bazaar"
+          ],
+          "RequiredGatheredItems": [
+            {
+              "ItemId": 35847,
+              "ItemCount": 1
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 6,
+      "Steps": [
+        {
+          "DataId": 1038510,
+          "Position": {
+            "X": -92.24078,
+            "Y": 11.799999,
+            "Z": -156.05408
+          },
+          "TerritoryId": 628,
+          "InteractionType": "Interact",
+          "AethernetShortcut": [
+            "[Kugane] The Ruby Bazaar",
+            "[Kugane] Bokairo Inn"
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 255,
+      "Steps": [
+        {
+          "DataId": 1038501,
+          "Position": {
+            "X": -367.3305,
+            "Y": 21.846018,
+            "Z": -102.983154
+          },
+          "TerritoryId": 962,
+          "InteractionType": "CompleteQuest",
+          "AetheryteShortcut": "Old Sharlayan",
+          "AethernetShortcut": [
+            "[Old Sharlayan] Aetheryte Plaza",
+            "[Old Sharlayan] The Studium"
+          ],
+          "NextQuestId": 4158
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4158_The Culture of the Past.json b/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4158_The Culture of the Past.json
new file mode 100644 (file)
index 0000000..aa12eb8
--- /dev/null
@@ -0,0 +1,49 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1038501,
+          "Position": {
+            "X": -367.3305,
+            "Y": 21.846018,
+            "Z": -102.983154
+          },
+          "TerritoryId": 962,
+          "InteractionType": "AcceptQuest"
+        }
+      ]
+    },
+    {
+      "Sequence": 255,
+      "Steps": [
+        {
+          "DataId": 1038501,
+          "Position": {
+            "X": -367.3305,
+            "Y": 21.846018,
+            "Z": -102.983154
+          },
+          "TerritoryId": 962,
+          "InteractionType": "CompleteQuest",
+          "AetheryteShortcut": "Old Sharlayan",
+          "AethernetShortcut": [
+            "[Old Sharlayan] Aetheryte Plaza",
+            "[Old Sharlayan] The Studium"
+          ],
+          "RequiredGatheredItems": [
+            {
+              "ItemId": 35603,
+              "ItemCount": 6,
+              "Collectability": 600
+            }
+          ],
+          "NextQuestId": 4159
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4159_The Culture of Love.json b/QuestPaths/6.x - Endwalker/Custom Deliveries/Studium/MIN, BTN/4159_The Culture of Love.json
new file mode 100644 (file)
index 0000000..a7dd1f4
--- /dev/null
@@ -0,0 +1,101 @@
+{
+  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
+  "Author": "liza",
+  "QuestSequence": [
+    {
+      "Sequence": 0,
+      "Steps": [
+        {
+          "DataId": 1038501,
+          "Position": {
+            "X": -367.3305,
+            "Y": 21.846018,
+            "Z": -102.983154
+          },
+          "TerritoryId": 962,
+          "InteractionType": "AcceptQuest"
+        }
+      ]
+    },
+    {
+      "Sequence": 1,
+      "Steps": [
+        {
+          "DataId": 1038503,
+          "Position": {
+            "X": -367.0863,
+            "Y": 21.84602,
+            "Z": -101.701416
+          },
+          "TerritoryId": 962,
+          "InteractionType": "Interact",
+          "AetheryteShortcut": "Old Sharlayan",
+          "AethernetShortcut": [
+            "[Old Sharlayan] Aetheryte Plaza",
+            "[Old Sharlayan] The Studium"
+          ],
+          "RequiredGatheredItems": [
+            {
+              "ItemId": 35848,
+              "ItemCount": 1
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 2,
+      "Steps": [
+        {
+          "DataId": 1038512,
+          "Position": {
+            "X": -73.47223,
+            "Y": -16.147001,
+            "Z": 191.27173
+          },
+          "TerritoryId": 962,
+          "InteractionType": "Interact",
+          "AethernetShortcut": [
+            "[Old Sharlayan] The Studium",
+            "[Old Sharlayan] Scholar's Harbor"
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 3,
+      "Steps": [
+        {
+          "DataId": 1038503,
+          "Position": {
+            "X": -367.0863,
+            "Y": 21.84602,
+            "Z": -101.701416
+          },
+          "TerritoryId": 962,
+          "InteractionType": "Interact",
+          "AethernetShortcut": [
+            "[Old Sharlayan] Scholar's Harbor",
+            "[Old Sharlayan] The Studium"
+          ]
+        }
+      ]
+    },
+    {
+      "Sequence": 255,
+      "Steps": [
+        {
+          "DataId": 1038501,
+          "Position": {
+            "X": -367.3305,
+            "Y": 21.846018,
+            "Z": -102.983154
+          },
+          "StopDistance": 7,
+          "TerritoryId": 962,
+          "InteractionType": "CompleteQuest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/4473_The Faculty.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/4473_The Faculty.json
deleted file mode 100644 (file)
index e960c9f..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-{
-  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
-  "Author": "liza",
-  "QuestSequence": [
-    {
-      "Sequence": 0,
-      "Steps": [
-        {
-          "DataId": 1038500,
-          "Position": {
-            "X": -357.83936,
-            "Y": 21.84602,
-            "Z": -91.32526
-          },
-          "TerritoryId": 962,
-          "InteractionType": "AcceptQuest"
-        }
-      ]
-    },
-    {
-      "Sequence": 255,
-      "Steps": [
-        {
-          "DataId": 1038500,
-          "Position": {
-            "X": -357.83936,
-            "Y": 21.84602,
-            "Z": -91.32526
-          },
-          "TerritoryId": 962,
-          "InteractionType": "CompleteQuest"
-        }
-      ]
-    }
-  ]
-}
diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4153_Cultured Pursuits.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4153_Cultured Pursuits.json
deleted file mode 100644 (file)
index d9f54e2..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-{
-  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
-  "Author": "liza",
-  "QuestSequence": [
-    {
-      "Sequence": 0,
-      "Steps": [
-        {
-          "DataId": 1038504,
-          "Position": {
-            "X": -357.62573,
-            "Y": 21.64856,
-            "Z": -95.99457
-          },
-          "TerritoryId": 962,
-          "InteractionType": "AcceptQuest"
-        }
-      ]
-    },
-    {
-      "Sequence": 1,
-      "Steps": [
-        {
-          "DataId": 1038505,
-          "Position": {
-            "X": -376.45538,
-            "Y": 18.999998,
-            "Z": 37.00305
-          },
-          "TerritoryId": 962,
-          "InteractionType": "Interact"
-        }
-      ]
-    },
-    {
-      "Sequence": 2,
-      "Steps": [
-        {
-          "DataId": 1038503,
-          "Position": {
-            "X": -367.0863,
-            "Y": 21.84602,
-            "Z": -101.701416
-          },
-          "TerritoryId": 962,
-          "InteractionType": "Interact"
-        }
-      ]
-    },
-    {
-      "Sequence": 255,
-      "Steps": [
-        {
-          "DataId": 1038501,
-          "Position": {
-            "X": -367.3305,
-            "Y": 21.846018,
-            "Z": -102.983154
-          },
-          "StopDistance": 7,
-          "TerritoryId": 962,
-          "InteractionType": "CompleteQuest",
-          "NextQuestId": 4154
-        }
-      ]
-    }
-  ]
-}
diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4154_Cooking Up a Culture.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4154_Cooking Up a Culture.json
deleted file mode 100644 (file)
index 3072030..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-{
-  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
-  "Author": "liza",
-  "QuestSequence": [
-    {
-      "Sequence": 0,
-      "Steps": [
-        {
-          "DataId": 1038501,
-          "Position": {
-            "X": -367.3305,
-            "Y": 21.846018,
-            "Z": -102.983154
-          },
-          "TerritoryId": 962,
-          "InteractionType": "AcceptQuest"
-        }
-      ]
-    },
-    {
-      "Sequence": 255,
-      "Steps": [
-        {
-          "DataId": 1038501,
-          "Position": {
-            "X": -367.3305,
-            "Y": 21.846018,
-            "Z": -102.983154
-          },
-          "TerritoryId": 962,
-          "InteractionType": "CompleteQuest",
-          "AetheryteShortcut": "Old Sharlayan",
-          "AethernetShortcut": [
-            "[Old Sharlayan] Aetheryte Plaza",
-            "[Old Sharlayan] The Studium"
-          ],
-          "RequiredGatheredItems": [
-            {
-              "ItemId": 35600,
-              "ItemCount": 6,
-              "Collectability": 600
-            }
-          ],
-          "NextQuestId": 4155
-        }
-      ]
-    }
-  ]
-}
diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4155_The Culture of Ceruleum.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4155_The Culture of Ceruleum.json
deleted file mode 100644 (file)
index ca59855..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-{
-  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
-  "Author": "liza",
-  "QuestSequence": [
-    {
-      "Sequence": 0,
-      "Steps": [
-        {
-          "DataId": 1038501,
-          "Position": {
-            "X": -367.3305,
-            "Y": 21.846018,
-            "Z": -102.983154
-          },
-          "TerritoryId": 962,
-          "InteractionType": "AcceptQuest"
-        }
-      ]
-    },
-    {
-      "Sequence": 255,
-      "Steps": [
-        {
-          "DataId": 1038501,
-          "Position": {
-            "X": -367.3305,
-            "Y": 21.846018,
-            "Z": -102.983154
-          },
-          "TerritoryId": 962,
-          "InteractionType": "CompleteQuest",
-          "AetheryteShortcut": "Old Sharlayan",
-          "AethernetShortcut": [
-            "[Old Sharlayan] Aetheryte Plaza",
-            "[Old Sharlayan] The Studium"
-          ],
-          "RequiredGatheredItems": [
-            {
-              "ItemId": 35601,
-              "ItemCount": 6,
-              "Collectability": 600
-            }
-          ],
-          "NextQuestId": 4156
-        }
-      ]
-    }
-  ]
-}
diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4156_The Culture of Carrots.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4156_The Culture of Carrots.json
deleted file mode 100644 (file)
index d25034d..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-{
-  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
-  "Author": "liza",
-  "QuestSequence": [
-    {
-      "Sequence": 0,
-      "Steps": [
-        {
-          "DataId": 1038501,
-          "Position": {
-            "X": -367.3305,
-            "Y": 21.846018,
-            "Z": -102.983154
-          },
-          "TerritoryId": 962,
-          "InteractionType": "AcceptQuest"
-        }
-      ]
-    },
-    {
-      "Sequence": 255,
-      "Steps": [
-        {
-          "DataId": 1038501,
-          "Position": {
-            "X": -367.3305,
-            "Y": 21.846018,
-            "Z": -102.983154
-          },
-          "TerritoryId": 962,
-          "InteractionType": "CompleteQuest",
-          "AetheryteShortcut": "Old Sharlayan",
-          "AethernetShortcut": [
-            "[Old Sharlayan] Aetheryte Plaza",
-            "[Old Sharlayan] The Studium"
-          ],
-          "RequiredGatheredItems": [
-            {
-              "ItemId": 35602,
-              "ItemCount": 6,
-              "Collectability": 600
-            }
-          ],
-          "NextQuestId": 4157
-        }
-      ]
-    }
-  ]
-}
diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4157_Hinageshi in Hingashi.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4157_Hinageshi in Hingashi.json
deleted file mode 100644 (file)
index e2ebf55..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-{
-  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
-  "Author": "liza",
-  "QuestSequence": [
-    {
-      "Sequence": 0,
-      "Steps": [
-        {
-          "DataId": 1038501,
-          "Position": {
-            "X": -367.3305,
-            "Y": 21.846018,
-            "Z": -102.983154
-          },
-          "TerritoryId": 962,
-          "InteractionType": "AcceptQuest"
-        }
-      ]
-    },
-    {
-      "Sequence": 1,
-      "Steps": [
-        {
-          "DataId": 2011719,
-          "Position": {
-            "X": -102.1897,
-            "Y": -7.0039062,
-            "Z": -59.92218
-          },
-          "TerritoryId": 628,
-          "InteractionType": "Interact",
-          "AetheryteShortcut": "Kugane",
-          "AethernetShortcut": [
-            "[Kugane] Aetheryte Plaza",
-            "[Kugane] Shiokaze Hostelry"
-          ]
-        }
-      ]
-    },
-    {
-      "Sequence": 2,
-      "Steps": [
-        {
-          "DataId": 1038506,
-          "Position": {
-            "X": -142.2904,
-            "Y": -4.7500057,
-            "Z": 199.6643
-          },
-          "TerritoryId": 628,
-          "InteractionType": "Interact",
-          "AethernetShortcut": [
-            "[Kugane] Kogane Dori Markets",
-            "[Kugane] Pier #1"
-          ]
-        }
-      ]
-    },
-    {
-      "Sequence": 3,
-      "Steps": [
-        {
-          "DataId": 1038507,
-          "Position": {
-            "X": 52.750366,
-            "Y": 8.02,
-            "Z": 152.81909
-          },
-          "TerritoryId": 628,
-          "InteractionType": "Interact",
-          "AethernetShortcut": [
-            "[Kugane] Pier #1",
-            "[Kugane] Thavnairian Consulate"
-          ]
-        }
-      ]
-    },
-    {
-      "Sequence": 4,
-      "Steps": [
-        {
-          "DataId": 2011720,
-          "Position": {
-            "X": 129.5033,
-            "Y": 24.979004,
-            "Z": 15.701477
-          },
-          "TerritoryId": 628,
-          "InteractionType": "Interact",
-          "AethernetShortcut": [
-            "[Kugane] Thavnairian Consulate",
-            "[Kugane] The Ruby Bazaar"
-          ]
-        }
-      ]
-    },
-    {
-      "Sequence": 5,
-      "Steps": [
-        {
-          "DataId": 1038508,
-          "Position": {
-            "X": 114.70203,
-            "Y": 12,
-            "Z": 35.965454
-          },
-          "TerritoryId": 628,
-          "InteractionType": "Interact",
-          "AetheryteShortcut": "Kugane",
-          "AethernetShortcut": [
-            "[Kugane] Aetheryte Plaza",
-            "[Kugane] The Ruby Bazaar"
-          ],
-          "RequiredGatheredItems": [
-            {
-              "ItemId": 35847,
-              "ItemCount": 1
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "Sequence": 6,
-      "Steps": [
-        {
-          "DataId": 1038510,
-          "Position": {
-            "X": -92.24078,
-            "Y": 11.799999,
-            "Z": -156.05408
-          },
-          "TerritoryId": 628,
-          "InteractionType": "Interact",
-          "AethernetShortcut": [
-            "[Kugane] The Ruby Bazaar",
-            "[Kugane] Bokairo Inn"
-          ]
-        }
-      ]
-    },
-    {
-      "Sequence": 255,
-      "Steps": [
-        {
-          "DataId": 1038501,
-          "Position": {
-            "X": -367.3305,
-            "Y": 21.846018,
-            "Z": -102.983154
-          },
-          "TerritoryId": 962,
-          "InteractionType": "CompleteQuest",
-          "AetheryteShortcut": "Old Sharlayan",
-          "AethernetShortcut": [
-            "[Old Sharlayan] Aetheryte Plaza",
-            "[Old Sharlayan] The Studium"
-          ],
-          "NextQuestId": 4158
-        }
-      ]
-    }
-  ]
-}
diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4158_The Culture of the Past.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4158_The Culture of the Past.json
deleted file mode 100644 (file)
index aa12eb8..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-{
-  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
-  "Author": "liza",
-  "QuestSequence": [
-    {
-      "Sequence": 0,
-      "Steps": [
-        {
-          "DataId": 1038501,
-          "Position": {
-            "X": -367.3305,
-            "Y": 21.846018,
-            "Z": -102.983154
-          },
-          "TerritoryId": 962,
-          "InteractionType": "AcceptQuest"
-        }
-      ]
-    },
-    {
-      "Sequence": 255,
-      "Steps": [
-        {
-          "DataId": 1038501,
-          "Position": {
-            "X": -367.3305,
-            "Y": 21.846018,
-            "Z": -102.983154
-          },
-          "TerritoryId": 962,
-          "InteractionType": "CompleteQuest",
-          "AetheryteShortcut": "Old Sharlayan",
-          "AethernetShortcut": [
-            "[Old Sharlayan] Aetheryte Plaza",
-            "[Old Sharlayan] The Studium"
-          ],
-          "RequiredGatheredItems": [
-            {
-              "ItemId": 35603,
-              "ItemCount": 6,
-              "Collectability": 600
-            }
-          ],
-          "NextQuestId": 4159
-        }
-      ]
-    }
-  ]
-}
diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4159_The Culture of Love.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4159_The Culture of Love.json
deleted file mode 100644 (file)
index a7dd1f4..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-{
-  "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
-  "Author": "liza",
-  "QuestSequence": [
-    {
-      "Sequence": 0,
-      "Steps": [
-        {
-          "DataId": 1038501,
-          "Position": {
-            "X": -367.3305,
-            "Y": 21.846018,
-            "Z": -102.983154
-          },
-          "TerritoryId": 962,
-          "InteractionType": "AcceptQuest"
-        }
-      ]
-    },
-    {
-      "Sequence": 1,
-      "Steps": [
-        {
-          "DataId": 1038503,
-          "Position": {
-            "X": -367.0863,
-            "Y": 21.84602,
-            "Z": -101.701416
-          },
-          "TerritoryId": 962,
-          "InteractionType": "Interact",
-          "AetheryteShortcut": "Old Sharlayan",
-          "AethernetShortcut": [
-            "[Old Sharlayan] Aetheryte Plaza",
-            "[Old Sharlayan] The Studium"
-          ],
-          "RequiredGatheredItems": [
-            {
-              "ItemId": 35848,
-              "ItemCount": 1
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "Sequence": 2,
-      "Steps": [
-        {
-          "DataId": 1038512,
-          "Position": {
-            "X": -73.47223,
-            "Y": -16.147001,
-            "Z": 191.27173
-          },
-          "TerritoryId": 962,
-          "InteractionType": "Interact",
-          "AethernetShortcut": [
-            "[Old Sharlayan] The Studium",
-            "[Old Sharlayan] Scholar's Harbor"
-          ]
-        }
-      ]
-    },
-    {
-      "Sequence": 3,
-      "Steps": [
-        {
-          "DataId": 1038503,
-          "Position": {
-            "X": -367.0863,
-            "Y": 21.84602,
-            "Z": -101.701416
-          },
-          "TerritoryId": 962,
-          "InteractionType": "Interact",
-          "AethernetShortcut": [
-            "[Old Sharlayan] Scholar's Harbor",
-            "[Old Sharlayan] The Studium"
-          ]
-        }
-      ]
-    },
-    {
-      "Sequence": 255,
-      "Steps": [
-        {
-          "DataId": 1038501,
-          "Position": {
-            "X": -367.3305,
-            "Y": 21.846018,
-            "Z": -102.983154
-          },
-          "StopDistance": 7,
-          "TerritoryId": 962,
-          "InteractionType": "CompleteQuest"
-        }
-      ]
-    }
-  ]
-}
index 5d91a016f3785ea43860cd1011a60edd6248ef16..1e0389ef9d1fd32a33cacb6d2915c074e7d33caf 100644 (file)
@@ -40,8 +40,4 @@
         <AdditionalFiles Include="6.x - Endwalker\**\*.json" />
         <AdditionalFiles Include="7.x - Dawntrail\**\*.json" />
     </ItemGroup>
-
-    <ItemGroup>
-      <Folder Include="7.x - Dawntrail\Custom Deliveries\" />
-    </ItemGroup>
 </Project>
index 9a031631e444348d5382093fa8d3268db9d83d95..e7d7ee2b4ebe721b07a6aa96cba2421cb0b4c184 100644 (file)
                       "PickUpQuestId": {
                         "type": [
                           "null",
-                          "number"
+                          "number",
+                          "string"
                         ],
                         "description": "Determines the quest which should be accepted. If empty/null, accepts the quest corresponding to the file name."
                       }
                       "TurnInQuestId": {
                         "type": [
                           "null",
-                          "number"
+                          "number",
+                          "string"
                         ],
                         "description": "Determines the quest which should be turned in. If empty/null, turns in the quest corresponding to the file name."
                       },
                       "NextQuestId": {
                         "type": [
                           "null",
-                          "number"
+                          "number",
+                          "string"
                         ],
                         "description": "For quest chains (e.g. DT healer role quests) Which quest to do next, given that you meet the required level."
                       }
index a00f7b51cb54eced0407401ebd0960dc680c3c5a..3313d4df37eef4c0dd42084c8d2b00f604a5e914 100644 (file)
@@ -85,6 +85,7 @@ public enum EAetheryteLocation
     IshgardTribunal = 86,
     IshgardLastVigil = 87,
     IshgardGatesOfJudgement = 88,
+    IshgardFirmament = 100001,
 
     Idyllshire = 75,
     IdyllshireWest = 90,
index bd07bc39381f6e515ae95bb320d2169858f14981..b1560c7c551e7dd654df726444008e9c10bc158e 100644 (file)
@@ -56,6 +56,7 @@ public sealed class AethernetShardConverter() : EnumConverter<EAetheryteLocation
         { EAetheryteLocation.IshgardTribunal, "[Ishgard] The Tribunal" },
         { EAetheryteLocation.IshgardLastVigil, "[Ishgard] The Last Vigil" },
         { EAetheryteLocation.IshgardGatesOfJudgement, "[Ishgard] The Gates of Judgement (Coerthas Central Highlands)" },
+        { EAetheryteLocation.IshgardFirmament, "[Ishgard] Firmament" },
 
         { EAetheryteLocation.Idyllshire, "[Idyllshire] Aetheryte Plaza" },
         { EAetheryteLocation.IdyllshireWest, "[Idyllshire] West Idyllshire" },
index 27cecb9913ec8aa99d89437c1786f0a7f355c95a..b8b6e974ce88b49c75632f592f43af18afb92f89 100644 (file)
@@ -4,12 +4,14 @@ using System.Text.Json.Serialization;
 
 namespace Questionable.Model.Questing.Converter;
 
-public class ElementIdConverter : JsonConverter<ElementId>
+public sealed class ElementIdConverter : JsonConverter<ElementId>
 {
     public override ElementId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
     {
-        uint value = reader.GetUInt32();
-        return ElementId.From(value);
+        if (reader.TokenType == JsonTokenType.Number)
+            return new QuestId(reader.GetUInt16());
+        else
+            return ElementId.FromString(reader.GetString() ?? throw new JsonException());
     }
 
     public override void Write(Utf8JsonWriter writer, ElementId value, JsonSerializerOptions options)
index e4b3c71ce67a471fc981a52c4aa4ac3ae4700ea7..396c3524b75896c0e93c1087550efb41dfb33b03 100644 (file)
@@ -50,37 +50,51 @@ public abstract class ElementId : IComparable<ElementId>, IEquatable<ElementId>
         return !Equals(left, right);
     }
 
-    public static ElementId From(uint value)
+    public static ElementId FromString(string value)
     {
-        if (value >= 100_000 && value < 200_000)
-            return new LeveId((ushort)(value - 100_000));
+        if (value.StartsWith("L"))
+            return new LeveId(ushort.Parse(value.Substring(1), CultureInfo.InvariantCulture));
+        else if (value.StartsWith("S"))
+            return new SatisfactionSupplyNpcId(ushort.Parse(value.Substring(1), CultureInfo.InvariantCulture));
         else
-            return new QuestId((ushort)value);
+            return new QuestId(ushort.Parse(value, CultureInfo.InvariantCulture));
     }
-}
 
-public sealed class QuestId : ElementId
-{
-    public QuestId(ushort value)
-        : base(value)
+    public static bool TryFromString(string value, out ElementId? elementId)
     {
+        try
+        {
+            elementId = FromString(value);
+            return true;
+        }
+        catch (Exception)
+        {
+            elementId = null;
+            return false;
+        }
     }
+}
 
+public sealed class QuestId(ushort value) : ElementId(value)
+{
     public override string ToString()
     {
         return Value.ToString(CultureInfo.InvariantCulture);
     }
 }
 
-public sealed class LeveId : ElementId
+public sealed class LeveId(ushort value) : ElementId(value)
 {
-    public LeveId(ushort value)
-        : base(value)
+    public override string ToString()
     {
+        return "L" + Value.ToString(CultureInfo.InvariantCulture);
     }
+}
 
+public sealed class SatisfactionSupplyNpcId(ushort value) : ElementId(value)
+{
     public override string ToString()
     {
-        return "L" + Value.ToString(CultureInfo.InvariantCulture);
+        return "S" + Value.ToString(CultureInfo.InvariantCulture);
     }
 }
index 6017ecb7ff22a83958f0d3666a4be0ec093507ee..01d1ef6c359b82e59786de7046cd893af733e1d4 100644 (file)
         "[Ishgard] The Tribunal",
         "[Ishgard] The Last Vigil",
         "[Ishgard] The Gates of Judgement (Coerthas Central Highlands)",
+        "[Ishgard] Firmament",
         "[Idyllshire] Aetheryte Plaza",
         "[Idyllshire] West Idyllshire",
         "[Idyllshire] Prologue Gate (Western Hinterlands)",
index 5e88cf85aef8ff19bce731ebd1971cdb20b56917..142d3263a1e66b4db53ce5271bf3fa71bb4b0b4f 100644 (file)
@@ -77,7 +77,7 @@ internal sealed class CommandHandler : IDisposable
 
             case "start":
                 _questWindow.IsOpen = true;
-                _questController.ExecuteNextStep(true);
+                _questController.ExecuteNextStep(QuestController.EAutomationType.Automatic);
                 break;
 
             case "stop":
@@ -128,11 +128,11 @@ internal sealed class CommandHandler : IDisposable
             return;
         }
 
-        if (arguments.Length >= 1 && uint.TryParse(arguments[0], out uint questId))
+        if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId? questId) && questId != null)
         {
-            if (_questRegistry.TryGetQuest(ElementId.From(questId), out Quest? quest))
+            if (_questRegistry.TryGetQuest(questId, out Quest? quest))
             {
-                _debugOverlay.HighlightedQuest = quest.QuestElementId;
+                _debugOverlay.HighlightedQuest = quest.Id;
                 _chatGui.Print($"[Questionable] Set highlighted quest to {questId} ({quest.Info.Name}).");
             }
             else
@@ -147,11 +147,11 @@ internal sealed class CommandHandler : IDisposable
 
     private void SetNextQuest(string[] arguments)
     {
-        if (arguments.Length >= 1 && uint.TryParse(arguments[0], out uint questId))
+        if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId? questId) && questId != null)
         {
-            if (_gameFunctions.IsQuestLocked(ElementId.From(questId)))
+            if (_gameFunctions.IsQuestLocked(questId))
                 _chatGui.PrintError($"[Questionable] Quest {questId} is locked.");
-            else if (_questRegistry.TryGetQuest(ElementId.From(questId), out Quest? quest))
+            else if (_questRegistry.TryGetQuest(questId, out Quest? quest))
             {
                 _questController.SetNextQuest(quest);
                 _chatGui.Print($"[Questionable] Set next quest to {questId} ({quest.Info.Name}).");
@@ -170,9 +170,9 @@ internal sealed class CommandHandler : IDisposable
 
     private void SetSimulatedQuest(string[] arguments)
     {
-        if (arguments.Length >= 1 && ushort.TryParse(arguments[0], out ushort questId))
+        if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId? questId) && questId != null)
         {
-            if (_questRegistry.TryGetQuest(ElementId.From(questId), out Quest? quest))
+            if (_questRegistry.TryGetQuest(questId, out Quest? quest))
             {
                 _questController.SimulateQuest(quest);
                 _chatGui.Print($"[Questionable] Simulating quest {questId} ({quest.Info.Name}).");
diff --git a/Questionable/Controller/ContextMenuController.cs b/Questionable/Controller/ContextMenuController.cs
new file mode 100644 (file)
index 0000000..13c5e03
--- /dev/null
@@ -0,0 +1,111 @@
+using System;
+using System.Linq;
+using Dalamud.Game.Gui.ContextMenu;
+using Dalamud.Game.Text;
+using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using Microsoft.Extensions.Logging;
+using Questionable.Data;
+using Questionable.Model;
+using Questionable.Model.Questing;
+
+namespace Questionable.Controller;
+
+internal sealed class ContextMenuController : IDisposable
+{
+    private readonly IContextMenu _contextMenu;
+    private readonly QuestController _questController;
+    private readonly GatheringData _gatheringData;
+    private readonly QuestRegistry _questRegistry;
+    private readonly QuestData _questData;
+    private readonly IGameGui _gameGui;
+    private readonly IChatGui _chatGui;
+    private readonly IClientState _clientState;
+    private readonly ILogger<ContextMenuController> _logger;
+
+    public ContextMenuController(
+        IContextMenu contextMenu,
+        QuestController questController,
+        GatheringData gatheringData,
+        QuestRegistry questRegistry,
+        QuestData questData,
+        IGameGui gameGui,
+        IChatGui chatGui,
+        IClientState clientState,
+        ILogger<ContextMenuController> logger)
+    {
+        _contextMenu = contextMenu;
+        _questController = questController;
+        _gatheringData = gatheringData;
+        _questRegistry = questRegistry;
+        _questData = questData;
+        _gameGui = gameGui;
+        _chatGui = chatGui;
+        _clientState = clientState;
+        _logger = logger;
+
+        _contextMenu.OnMenuOpened += MenuOpened;
+    }
+
+    private void MenuOpened(IMenuOpenedArgs args)
+    {
+        uint itemId = (uint) _gameGui.HoveredItem;
+        if (itemId == 0)
+            return;
+
+        if (itemId > 1_000_000)
+            itemId -= 1_000_000;
+
+        if (itemId >= 500_000)
+            itemId -= 500_000;
+
+        if (!_gatheringData.TryGetGatheringPointId(itemId, _clientState.LocalPlayer!.ClassJob.Id, out _))
+        {
+            _logger.LogInformation("No gathering point found for current job.");
+            return;
+        }
+
+        if (_gatheringData.TryGetCustomDeliveryNpc(itemId, out uint npcId))
+        {
+            ushort collectability = _gatheringData.GetRecommendedCollectability(itemId);
+            int quantityToGather = collectability > 0 ? 6 : int.MaxValue;
+            if (collectability == 0)
+                return;
+
+            args.AddMenuItem(new MenuItem
+            {
+                Prefix = SeIconChar.Hyadelyn,
+                PrefixColor = 52,
+                Name = "Gather with Questionable",
+                OnClicked = _ => StartGathering(npcId, itemId, quantityToGather, collectability),
+            });
+        }
+    }
+
+    private void StartGathering(uint npcId, uint itemId, int quantity, ushort collectability)
+    {
+        var info = (SatisfactionSupplyInfo)_questData.GetAllByIssuerDataId(npcId).Single(x => x is SatisfactionSupplyInfo);
+        if (_questRegistry.TryGetQuest(info.QuestId, out Quest? quest))
+        {
+            var step = quest.FindSequence(0)!.FindStep(0)!;
+            step.RequiredGatheredItems =
+            [
+                new GatheredItem
+                {
+                    ItemId = itemId,
+                    ItemCount = quantity,
+                    Collectability = collectability
+                }
+            ];
+            _questController.SetNextQuest(quest);
+            _questController.ExecuteNextStep(QuestController.EAutomationType.CurrentQuestOnly);
+        }
+        else
+            _chatGui.PrintError($"No associated quest ({info.QuestId}).", "Questionable");
+    }
+
+    public void Dispose()
+    {
+        _contextMenu.OnMenuOpened -= MenuOpened;
+    }
+}
index e7df22139d38e1a4e29b3b26896f0ebaf2920ff6..7f8a6223d2d6b74314d1972534062c746f4ed95e 100644 (file)
@@ -600,7 +600,7 @@ internal sealed class GameUiController : IDisposable
 
     private unsafe void UnendingCodexPostSetup(AddonEvent type, AddonArgs args)
     {
-        if (_questController.StartedQuest?.Quest.QuestElementId.Value == 4526)
+        if (_questController.StartedQuest?.Quest.Id.Value == 4526)
         {
             _logger.LogInformation("Closing Unending Codex");
             AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
@@ -610,7 +610,7 @@ internal sealed class GameUiController : IDisposable
 
     private unsafe void ContentsTutorialPostSetup(AddonEvent type, AddonArgs args)
     {
-        if (_questController.StartedQuest?.Quest.QuestElementId.Value == 245)
+        if (_questController.StartedQuest?.Quest.Id.Value == 245)
         {
             _logger.LogInformation("Closing ContentsTutorial");
             AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
@@ -623,7 +623,7 @@ internal sealed class GameUiController : IDisposable
     /// </summary>
     private unsafe void MultipleHelpWindowPostSetup(AddonEvent type, AddonArgs args)
     {
-        if (_questController.StartedQuest?.Quest.QuestElementId.Value == 245)
+        if (_questController.StartedQuest?.Quest.Id.Value == 245)
         {
             _logger.LogInformation("Closing MultipleHelpWindow");
             AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
index dcd3b8b0297bf805d5a801e9f823126767b8b42c..c953b851e2ca27c27c6c830dd16a75ea8709e9d1 100644 (file)
@@ -33,7 +33,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
     private QuestProgress? _startedQuest;
     private QuestProgress? _nextQuest;
     private QuestProgress? _simulatedQuest;
-    private bool _automatic;
+    private EAutomationType _automationType;
 
     /// <summary>
     /// Some combat encounters finish relatively early (i.e. they're done as part of progressing the quest, but not
@@ -71,16 +71,16 @@ internal sealed class QuestController : MiniTaskController<QuestController>
         _taskFactories = taskFactories.ToList().AsReadOnly();
     }
 
-    public (QuestProgress Progress, CurrentQuestType Type)? CurrentQuestDetails
+    public (QuestProgress Progress, ECurrentQuestType Type)? CurrentQuestDetails
     {
         get
         {
             if (_simulatedQuest != null)
-                return (_simulatedQuest, CurrentQuestType.Simulated);
-            else if (_nextQuest != null && _gameFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.QuestElementId))
-                return (_nextQuest, CurrentQuestType.Next);
+                return (_simulatedQuest, ECurrentQuestType.Simulated);
+            else if (_nextQuest != null && _gameFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.Id))
+                return (_nextQuest, ECurrentQuestType.Next);
             else if (_startedQuest != null)
-                return (_startedQuest, CurrentQuestType.Normal);
+                return (_startedQuest, ECurrentQuestType.Normal);
             else
                 return null;
         }
@@ -153,10 +153,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>
         if (CurrentQuest != null && CurrentQuest.Quest.Root.TerritoryBlacklist.Contains(_clientState.TerritoryType))
             return;
 
-        if (_automatic && ((_currentTask == null && _taskQueue.Count == 0) ||
-                           _currentTask is WaitAtEnd.WaitQuestAccepted)
-                       && CurrentQuest is { Sequence: 0, Step: 0 } or { Sequence: 0, Step: 255 }
-                       && DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15))
+        if (_automationType == EAutomationType.Automatic &&
+            ((_currentTask == null && _taskQueue.Count == 0) ||
+             _currentTask is WaitAtEnd.WaitQuestAccepted)
+            && CurrentQuest is { Sequence: 0, Step: 0 } or { Sequence: 0, Step: 255 }
+            && DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15))
         {
             lock (_progressLock)
             {
@@ -164,7 +165,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
                 CurrentQuest.SetStep(0);
             }
 
-            ExecuteNextStep(true);
+            ExecuteNextStep(_automationType);
             return;
         }
 
@@ -182,13 +183,14 @@ internal sealed class QuestController : MiniTaskController<QuestController>
                 // if the quest is accepted, we no longer track it
                 bool canUseNextQuest;
                 if (_nextQuest.Quest.Info.IsRepeatable)
-                    canUseNextQuest = !_gameFunctions.IsQuestAccepted(_nextQuest.Quest.QuestElementId);
+                    canUseNextQuest = !_gameFunctions.IsQuestAccepted(_nextQuest.Quest.Id);
                 else
-                    canUseNextQuest = !_gameFunctions.IsQuestAcceptedOrComplete(_nextQuest.Quest.QuestElementId);
+                    canUseNextQuest = !_gameFunctions.IsQuestAcceptedOrComplete(_nextQuest.Quest.Id);
 
                 if (!canUseNextQuest)
                 {
-                    _logger.LogInformation("Next quest {QuestId} accepted or completed", _nextQuest.Quest.QuestElementId);
+                    _logger.LogInformation("Next quest {QuestId} accepted or completed",
+                        _nextQuest.Quest.Id);
                     _nextQuest = null;
                 }
             }
@@ -200,12 +202,15 @@ internal sealed class QuestController : MiniTaskController<QuestController>
                 currentSequence = _simulatedQuest.Sequence;
                 questToRun = _simulatedQuest;
             }
-            else if (_nextQuest != null && _gameFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.QuestElementId))
+            else if (_nextQuest != null && _gameFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.Id))
             {
                 questToRun = _nextQuest;
                 currentSequence = _nextQuest.Sequence; // by definition, this should always be 0
-                if (_nextQuest.Step == 0 && _currentTask == null && _taskQueue.Count == 0 && _automatic)
-                    ExecuteNextStep(true);
+                if (_nextQuest.Step == 0 &&
+                    _currentTask == null &&
+                    _taskQueue.Count == 0 &&
+                    _automationType == EAutomationType.Automatic)
+                    ExecuteNextStep(_automationType);
             }
             else
             {
@@ -221,7 +226,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
 
                     questToRun = null;
                 }
-                else if (_startedQuest == null || _startedQuest.Quest.QuestElementId != currentQuestId)
+                else if (_startedQuest == null || _startedQuest.Quest.Id != currentQuestId)
                 {
                     if (_questRegistry.TryGetQuest(currentQuestId, out var quest))
                     {
@@ -341,11 +346,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>
                 return;
             }
 
-            if (questId != null && CurrentQuest.Quest.QuestElementId != questId)
+            if (questId != null && CurrentQuest.Quest.Id != questId)
             {
                 _logger.LogWarning(
                     "Ignoring 'increase step count' for different quest (expected {ExpectedQuestId}, but we are at {CurrentQuestId}",
-                    questId, CurrentQuest.Quest.QuestElementId);
+                    questId, CurrentQuest.Quest.Id);
                 return;
             }
 
@@ -363,8 +368,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
                 CurrentQuest.SetStep(255);
         }
 
-        if (shouldContinue && _automatic)
-            ExecuteNextStep(true);
+        if (shouldContinue && _automationType != EAutomationType.Manual)
+            ExecuteNextStep(_automationType);
     }
 
     private void ClearTasksInternal()
@@ -387,17 +392,17 @@ internal sealed class QuestController : MiniTaskController<QuestController>
         ClearTasksInternal();
 
         // reset task queue
-        if (continueIfAutomatic && _automatic)
+        if (continueIfAutomatic && _automationType == EAutomationType.Automatic)
         {
             if (CurrentQuest?.Step is >= 0 and < 255)
-                ExecuteNextStep(true);
+                ExecuteNextStep(_automationType);
             else
                 _logger.LogInformation("Couldn't execute next step during Stop() call");
         }
-        else if (_automatic)
+        else if (_automationType != EAutomationType.Manual)
         {
             _logger.LogInformation("Stopping automatic questing");
-            _automatic = false;
+            _automationType = EAutomationType.Manual;
             _nextQuest = null;
         }
     }
@@ -406,7 +411,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
 
     public void SimulateQuest(Quest? quest)
     {
-        _logger.LogInformation("SimulateQuest: {QuestId}", quest?.QuestElementId);
+        _logger.LogInformation("SimulateQuest: {QuestId}", quest?.Id);
         if (quest != null)
             _simulatedQuest = new QuestProgress(quest);
         else
@@ -415,7 +420,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
 
     public void SetNextQuest(Quest? quest)
     {
-        _logger.LogInformation("NextQuest: {QuestId}", quest?.QuestElementId);
+        _logger.LogInformation("NextQuest: {QuestId}", quest?.Id);
         if (quest != null)
             _nextQuest = new QuestProgress(quest);
         else
@@ -441,10 +446,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>
         IncreaseStepCount(task.QuestElementId, task.Sequence, true);
     }
 
-    public void ExecuteNextStep(bool automatic)
+    public void ExecuteNextStep(EAutomationType automatic)
     {
         ClearTasksInternal();
-        _automatic = automatic;
+        _automationType = automatic;
 
         if (TryPickPriorityQuest())
             _logger.LogInformation("Using priority quest over current quest");
@@ -452,8 +457,21 @@ internal sealed class QuestController : MiniTaskController<QuestController>
         (QuestSequence? seq, QuestStep? step) = GetNextStep();
         if (CurrentQuest == null || seq == null || step == null)
         {
-            _logger.LogWarning("Could not retrieve next quest step, not doing anything [{QuestId}, {Sequence}, {Step}]",
-                CurrentQuest?.Quest.QuestElementId, CurrentQuest?.Sequence, CurrentQuest?.Step);
+            if (CurrentQuestDetails?.Progress.Quest.Id is SatisfactionSupplyNpcId &&
+                CurrentQuestDetails?.Progress.Sequence == 0 &&
+                CurrentQuestDetails?.Progress.Step == 255 &&
+                CurrentQuestDetails?.Type == ECurrentQuestType.Next)
+            {
+                _logger.LogInformation("Completed delivery quest");
+                SetNextQuest(null);
+            }
+            else
+            {
+                _logger.LogWarning(
+                    "Could not retrieve next quest step, not doing anything [{QuestId}, {Sequence}, {Step}]",
+                    CurrentQuest?.Quest.Id, CurrentQuest?.Sequence, CurrentQuest?.Step);
+            }
+
             return;
         }
 
@@ -488,7 +506,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
             }
 
             _logger.LogInformation("Tasks for {QuestId}, {Sequence}, {Step}: {Tasks}",
-                CurrentQuest.Quest.QuestElementId, seq.Sequence, seq.Steps.IndexOf(step),
+                CurrentQuest.Quest.Id, seq.Sequence, seq.Steps.IndexOf(step),
                 string.Join(", ", newTasks.Select(x => x.ToString())));
             foreach (var task in newTasks)
                 _taskQueue.Enqueue(task);
@@ -587,7 +605,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
             return false;
 
         var (currentQuest, type) = details.Value;
-        if (type != CurrentQuestType.Normal)
+        if (type != ECurrentQuestType.Normal)
             return false;
 
         QuestSequence? currentSequence = currentQuest.Quest.FindSequence(currentQuest.Sequence);
@@ -628,10 +646,17 @@ internal sealed class QuestController : MiniTaskController<QuestController>
         DateTime StartedAt,
         int PointMenuCounter = 0);
 
-    public enum CurrentQuestType
+    public enum ECurrentQuestType
     {
         Normal,
         Next,
         Simulated,
     }
+
+    public enum EAutomationType
+    {
+        Manual,
+        Automatic,
+        CurrentQuestOnly,
+    }
 }
index 67cd658e21a675e83592f63b39dff81d47adaeff..58b93f3ff287eddec4c4c263a43e68ee32e5e68b 100644 (file)
@@ -91,12 +91,12 @@ internal sealed class QuestRegistry
         {
             Quest quest = new()
             {
-                QuestElementId = new QuestId(questId),
+                Id = new QuestId(questId),
                 Root = questRoot,
                 Info = _questData.GetQuestInfo(new QuestId(questId)),
                 ReadOnly = true,
             };
-            _quests[quest.QuestElementId] = quest;
+            _quests[quest.Id] = quest;
         }
 
         _logger.LogInformation("Loaded {Count} quests from assembly", _quests.Count);
@@ -145,12 +145,12 @@ internal sealed class QuestRegistry
 
         Quest quest = new Quest
         {
-            QuestElementId = questId,
+            Id = questId,
             Root = questNode.Deserialize<QuestRoot>()!,
             Info = _questData.GetQuestInfo(questId),
             ReadOnly = false,
         };
-        _quests[quest.QuestElementId] = quest;
+        _quests[quest.Id] = quest;
     }
 
     private void LoadFromDirectory(DirectoryInfo directory, LogLevel logLevel = LogLevel.Information)
@@ -188,30 +188,11 @@ internal sealed class QuestRegistry
             return null;
 
         string[] parts = name.Split('_', 2);
-        return ElementId.From(uint.Parse(parts[0], CultureInfo.InvariantCulture));
+        return ElementId.FromString(parts[0]);
     }
 
-    public bool IsKnownQuest(ElementId elementId)
-    {
-        if (elementId is QuestId questId)
-            return IsKnownQuest(questId);
-        else
-            return false;
-    }
-
-    public bool IsKnownQuest(QuestId questId) => _quests.ContainsKey(questId);
-
-    public bool TryGetQuest(ElementId elementId, [NotNullWhen(true)] out Quest? quest)
-    {
-        if (elementId is QuestId questId)
-            return TryGetQuest(questId, out quest);
-        else
-        {
-            quest = null;
-            return false;
-        }
-    }
+    public bool IsKnownQuest(ElementId questId) => _quests.ContainsKey(questId);
 
-    public bool TryGetQuest(QuestId questId, [NotNullWhen(true)] out Quest? quest)
+    public bool TryGetQuest(ElementId questId, [NotNullWhen(true)] out Quest? quest)
         => _quests.TryGetValue(questId, out quest);
 }
index ecc521b3af5f1f0f417f436a06ee76fa83635108..2afc6ecb124592090e4865e9045eea35ad1179c2 100644 (file)
@@ -18,11 +18,11 @@ internal static class NextQuest
             if (step.NextQuestId == null)
                 return null;
 
-            if (step.NextQuestId == quest.QuestElementId)
+            if (step.NextQuestId == quest.Id)
                 return null;
 
             return serviceProvider.GetRequiredService<SetQuest>()
-                .With(step.NextQuestId, quest.QuestElementId);
+                .With(step.NextQuestId, quest.Id);
         }
     }
 
index d6a3f52a8c3dee6ba41bec1821329c83583df757..d23608f0bdc7e9fe890c4cc6408bf9e0cc1babd0 100644 (file)
@@ -47,7 +47,7 @@ internal static class Combat
                     ArgumentNullException.ThrowIfNull(step.ItemId);
 
                     yield return serviceProvider.GetRequiredService<UseItem.UseOnObject>()
-                        .With(quest.QuestElementId, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags,
+                        .With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags,
                             true);
                     yield return CreateTask(quest, sequence, step);
                     break;
@@ -73,7 +73,7 @@ internal static class Combat
 
             bool isLastStep = sequence.Steps.Last() == step;
             return serviceProvider.GetRequiredService<HandleCombat>()
-                .With(quest.QuestElementId, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds,
+                .With(quest.Id, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds,
                     step.CompletionQuestVariablesFlags, step.ComplexCombatData);
         }
     }
index df27d3c21062c50e64eb66a1b2b74ad09a8e0be2..869a1fcfdc0b754ceb6330da8cbb52ba59bca93b 100644 (file)
@@ -33,7 +33,8 @@ internal static class Interact
                 yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>();
 
             yield return serviceProvider.GetRequiredService<DoInteract>()
-                .With(step.DataId.Value, step.TargetTerritoryId != null);
+                .With(step.DataId.Value,
+                    step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId);
         }
 
         public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
index ea46939ce535db49636f4c05e87367cf2cd7d6a1..cf86641440f47d080afe7b6e894339884baa8d3a 100644 (file)
@@ -48,7 +48,7 @@ internal static class UseItem
                 }
 
                 var task = serviceProvider.GetRequiredService<Use>()
-                    .With(quest.QuestElementId, step.ItemId.Value, step.CompletionQuestVariablesFlags);
+                    .With(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
                 return
                 [
                     unmount, task,
@@ -65,12 +65,12 @@ internal static class UseItem
                 ITask task;
                 if (step.DataId != null)
                     task = serviceProvider.GetRequiredService<UseOnGround>()
-                        .With(quest.QuestElementId, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
+                        .With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
                 else
                 {
                     ArgumentNullException.ThrowIfNull(step.Position);
                     task = serviceProvider.GetRequiredService<UseOnPosition>()
-                        .With(quest.QuestElementId, step.Position.Value, step.ItemId.Value,
+                        .With(quest.Id, step.Position.Value, step.ItemId.Value,
                             step.CompletionQuestVariablesFlags);
                 }
 
@@ -79,13 +79,13 @@ internal static class UseItem
             else if (step.DataId != null)
             {
                 var task = serviceProvider.GetRequiredService<UseOnObject>()
-                    .With(quest.QuestElementId, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
+                    .With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
                 return [unmount, task];
             }
             else
             {
                 var task = serviceProvider.GetRequiredService<Use>()
-                    .With(quest.QuestElementId, step.ItemId.Value, step.CompletionQuestVariablesFlags);
+                    .With(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
                 return [unmount, task];
             }
         }
index aebb66dab61badecc7bb560b79dc58f275f7fa2b..a2428a8d1398f09c72fd950f9379055dc987fb58 100644 (file)
@@ -33,7 +33,7 @@ internal static class GatheringRequiredItems
 
                 if (!AssemblyGatheringLocationLoader.GetLocations()
                         .TryGetValue(gatheringPointId, out GatheringRoot? gatheringRoot))
-                    throw new TaskException("No path found for gathering point");
+                    throw new TaskException($"No path found for gathering point {gatheringPointId}");
 
                 if (HasRequiredItems(requiredGatheredItems))
                     continue;
index a122d57cb30b06b296b84b5897b2252ad07f7bfa..c95894eb86ab52500b428a1bdcace4c6fa260c16 100644 (file)
@@ -34,7 +34,7 @@ internal static class SkipCondition
                 return null;
 
             return serviceProvider.GetRequiredService<CheckSkip>()
-                .With(step, skipConditions ?? new(), quest.QuestElementId);
+                .With(step, skipConditions ?? new(), quest.Id);
         }
     }
 
index 1b84c2bc06732582eb5fd7b72b20445bbb1046a2..fcaacdf649731f0a28b8aa6a5c88a17eeda947a4 100644 (file)
@@ -30,7 +30,7 @@ internal static class WaitAtEnd
             if (step.CompletionQuestVariablesFlags.Count == 6 && QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
             {
                 var task = serviceProvider.GetRequiredService<WaitForCompletionFlags>()
-                    .With((QuestId)quest.QuestElementId, step);
+                    .With((QuestId)quest.Id, step);
                 var delay = serviceProvider.GetRequiredService<WaitDelay>();
                 return [task, delay, Next(quest, sequence)];
             }
@@ -110,7 +110,7 @@ internal static class WaitAtEnd
                 case EInteractionType.AcceptQuest:
                 {
                     var accept = serviceProvider.GetRequiredService<WaitQuestAccepted>()
-                        .With(step.PickUpQuestId ?? quest.QuestElementId);
+                        .With(step.PickUpQuestId ?? quest.Id);
                     var delay = serviceProvider.GetRequiredService<WaitDelay>();
                     if (step.PickUpQuestId != null)
                         return [accept, delay, Next(quest, sequence)];
@@ -121,7 +121,7 @@ internal static class WaitAtEnd
                 case EInteractionType.CompleteQuest:
                 {
                     var complete = serviceProvider.GetRequiredService<WaitQuestCompleted>()
-                        .With(step.TurnInQuestId ?? quest.QuestElementId);
+                        .With(step.TurnInQuestId ?? quest.Id);
                     var delay = serviceProvider.GetRequiredService<WaitDelay>();
                     if (step.TurnInQuestId != null)
                         return [complete, delay, Next(quest, sequence)];
@@ -140,7 +140,7 @@ internal static class WaitAtEnd
 
         private static NextStep Next(Quest quest, QuestSequence sequence)
         {
-            return new NextStep(quest.QuestElementId, sequence.Sequence);
+            return new NextStep(quest.Id, sequence.Sequence);
         }
     }
 
index 28d8e459d39aba6dcbb2d19054cb7477f79a0862..74d183130346df9a5388d5e2649c2e8a9497639c 100644 (file)
@@ -28,6 +28,10 @@ internal sealed class AetheryteData
                 aethernetGroups[(EAetheryteLocation)aetheryte.RowId] = aetheryte.AethernetGroup;
         }
 
+        aethernetNames[EAetheryteLocation.IshgardFirmament] = "Firmament";
+        territoryIds[EAetheryteLocation.IshgardFirmament] = 886;
+        aethernetGroups[EAetheryteLocation.IshgardFirmament] = aethernetGroups[EAetheryteLocation.Ishgard];
+
         AethernetNames = aethernetNames.AsReadOnly();
         TerritoryIds = territoryIds.AsReadOnly();
         AethernetGroups = aethernetGroups.AsReadOnly();
@@ -267,6 +271,7 @@ internal sealed class AetheryteData
             { EAetheryteLocation.GridaniaAirship, new(24.86354f, -19.000002f, 96f) },
             { EAetheryteLocation.UldahAirship, new(-16.954851f, 82.999985f, -9.421141f) },
             { EAetheryteLocation.KuganeAirship, new(-55.72525f, 79.10602f, 46.23109f) },
+            { EAetheryteLocation.IshgardFirmament, new(9.92315f, -15.2f, 173.5059f) },
         }.AsReadOnly();
 
     public ReadOnlyDictionary<EAetheryteLocation, string> AethernetNames { get; }
@@ -298,6 +303,9 @@ internal sealed class AetheryteData
 
     public bool IsCityAetheryte(EAetheryteLocation aetheryte)
     {
+        if (aetheryte == EAetheryteLocation.IshgardFirmament)
+            return true;
+
         var territoryId = TerritoryIds[aetheryte];
         return TownTerritoryIds.Contains(territoryId);
     }
index ed44fdc2c90d74a4d754e7c63a16c0bb81e0eae2..5877a1a3bdaa6655120092888cad94a0dfe3742e 100644 (file)
@@ -1,20 +1,21 @@
 ï»¿using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using Dalamud.Plugin.Services;
 using Lumina.Excel.GeneratedSheets;
+using Microsoft.Extensions.Logging;
 
 namespace Questionable.Data;
 
 internal sealed class GatheringData
 {
-    private readonly Dictionary<uint, uint> _gatheringItemToItem;
     private readonly Dictionary<uint, ushort> _minerGatheringPoints = [];
     private readonly Dictionary<uint, ushort> _botanistGatheringPoints = [];
+    private readonly Dictionary<uint, ushort> _itemIdToCollectability;
+    private readonly Dictionary<uint, uint> _npcForCustomDeliveries;
 
     public GatheringData(IDataManager dataManager)
     {
-        _gatheringItemToItem = dataManager.GetExcelSheet<GatheringItem>()!
+        Dictionary<uint, uint> gatheringItemToItem = dataManager.GetExcelSheet<GatheringItem>()!
             .Where(x => x.RowId != 0 && x.Item != 0)
             .ToDictionary(x => x.RowId, x => (uint)x.Item);
 
@@ -22,7 +23,7 @@ internal sealed class GatheringData
         {
             foreach (var gatheringItemId in gatheringPointBase.Item.Where(x => x != 0))
             {
-                if (_gatheringItemToItem.TryGetValue((uint)gatheringItemId, out uint itemId))
+                if (gatheringItemToItem.TryGetValue((uint)gatheringItemId, out uint itemId))
                 {
                     if (gatheringPointBase.GatheringType.Row is 0 or 1)
                         _minerGatheringPoints[itemId] = (ushort)gatheringPointBase.RowId;
@@ -31,8 +32,31 @@ internal sealed class GatheringData
                 }
             }
         }
-    }
 
+        _itemIdToCollectability = dataManager.GetExcelSheet<SatisfactionSupply>()!
+            .Where(x => x.RowId > 0)
+            .Where(x => x.Slot is 2)
+            .Select(x => new
+            {
+                ItemId = x.Item.Row,
+                Collectability = x.CollectabilityHigh,
+            })
+            .Distinct()
+            .ToDictionary(x => x.ItemId, x => x.Collectability);
+
+        _npcForCustomDeliveries = dataManager.GetExcelSheet<SatisfactionNpc>()!
+            .Where(x => x.RowId > 0)
+            .SelectMany(x => dataManager.GetExcelSheet<SatisfactionSupply>()!
+                .Where(y => y.RowId == x.SupplyIndex.Last())
+                .Select(y => new
+                {
+                    ItemId = y.Item.Row,
+                    NpcId = x.Npc.Row
+                }))
+            .Where(x => x.ItemId > 0)
+            .Distinct()
+            .ToDictionary(x => x.ItemId, x => x.NpcId);
+    }
 
     public bool TryGetGatheringPointId(uint itemId, uint classJobId, out ushort gatheringPointId)
     {
@@ -46,4 +70,10 @@ internal sealed class GatheringData
             return false;
         }
     }
+
+    public ushort GetRecommendedCollectability(uint itemId)
+        => _itemIdToCollectability.GetValueOrDefault(itemId);
+
+    public bool TryGetCustomDeliveryNpc(uint itemId, out uint npcId)
+        => _npcForCustomDeliveries.TryGetValue(itemId, out npcId);
 }
index 171dfe827e7f3371ba8f60a32d70f9f0a4878cf1..ae6c6ac2a55af49f48369290bc69742374c90a2b 100644 (file)
@@ -22,17 +22,17 @@ internal sealed class JournalData
         var genreLimsa = new Genre(uint.MaxValue - 3, "Starting in Limsa Lominsa", 1,
             new uint[] { 108, 109 }.Concat(limsaStart.Quest.Select(x => x.Row))
                 .Where(x => x != 0)
-                .Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
+                .Select(x => (QuestInfo)questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
                 .ToList());
         var genreGridania = new Genre(uint.MaxValue - 2, "Starting in Gridania", 1,
             new uint[] { 85, 123, 124 }.Concat(gridaniaStart.Quest.Select(x => x.Row))
                 .Where(x => x != 0)
-                .Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
+                .Select(x => (QuestInfo)questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
                 .ToList());
         var genreUldah = new Genre(uint.MaxValue - 1, "Starting in Ul'dah", 1,
             new uint[] { 568, 569, 570 }.Concat(uldahStart.Quest.Select(x => x.Row))
                 .Where(x => x != 0)
-                .Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
+                .Select(x => (QuestInfo)questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
                 .ToList());
         genres.InsertRange(0, [genreLimsa, genreGridania, genreUldah]);
         genres.Single(x => x.Id == 1)
index aef7b726d9a5800690bf2e6ff2745a91c8e52bdc..1cf0733e4e6a1ca535e38b7749c8cf120741bfba 100644 (file)
@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Linq;
 using Dalamud.Plugin.Services;
+using Lumina.Excel.GeneratedSheets;
 using Questionable.Model;
 using Questionable.Model.Questing;
 using Quest = Lumina.Excel.GeneratedSheets.Quest;
@@ -11,32 +12,30 @@ namespace Questionable.Data;
 
 internal sealed class QuestData
 {
-    private readonly ImmutableDictionary<QuestId, QuestInfo> _quests;
+    private readonly Dictionary<ElementId, IQuestInfo> _quests;
 
     public QuestData(IDataManager dataManager)
     {
-        _quests = dataManager.GetExcelSheet<Quest>()!
-            .Where(x => x.RowId > 0)
-            .Where(x => x.IssuerLocation.Row > 0)
-            .Where(x => x.Festival.Row == 0)
-            .Select(x => new QuestInfo(x))
-            .ToImmutableDictionary(x => x.QuestId, x => x);
+        List<IQuestInfo> quests =
+        [
+            ..dataManager.GetExcelSheet<Quest>()!
+                .Where(x => x.RowId > 0)
+                .Where(x => x.IssuerLocation.Row > 0)
+                .Where(x => x.Festival.Row == 0)
+                .Select(x => new QuestInfo(x)),
+            ..dataManager.GetExcelSheet<SatisfactionNpc>()!
+                .Where(x => x.RowId > 0)
+                .Select(x => new SatisfactionSupplyInfo(x))
+        ];
+        _quests = quests.ToDictionary(x => x.QuestId, x => x);
     }
 
-    public QuestInfo GetQuestInfo(ElementId elementId)
+    public IQuestInfo GetQuestInfo(ElementId elementId)
     {
-        if (elementId is QuestId questId)
-            return GetQuestInfo(questId);
-
-        throw new ArgumentException("Invalid id", nameof(elementId));
-    }
-
-    public QuestInfo GetQuestInfo(QuestId questId)
-    {
-        return _quests[questId] ?? throw new ArgumentOutOfRangeException(nameof(questId));
+        return _quests[elementId] ?? throw new ArgumentOutOfRangeException(nameof(elementId));
     }
 
-    public List<QuestInfo> GetAllByIssuerDataId(uint targetId)
+    public List<IQuestInfo> GetAllByIssuerDataId(uint targetId)
     {
         return _quests.Values
             .Where(x => x.IssuerDataId == targetId)
@@ -48,6 +47,8 @@ internal sealed class QuestData
     public List<QuestInfo> GetAllByJournalGenre(uint journalGenre)
     {
         return _quests.Values
+            .Where(x => x is QuestInfo)
+            .Cast<QuestInfo>()
             .Where(x => x.JournalGenre == journalGenre)
             .OrderBy(x => x.SortKey)
             .ThenBy(x => x.QuestId)
index f326770902db124a75b673f1a8858bc2fa1d5d56..f5bd454482e74072e2bd618a260fb7add83c4b76 100644 (file)
@@ -18,6 +18,12 @@ internal sealed class LifestreamIpc
 
     public bool Teleport(EAetheryteLocation aetheryteLocation)
     {
+        if (aetheryteLocation == EAetheryteLocation.IshgardFirmament)
+        {
+            // TODO does this even work on non-EN clients?
+            return _aethernetTeleport.InvokeFunc("Firmament");
+        }
+
         if (!_aetheryteData.AethernetNames.TryGetValue(aetheryteLocation, out string? name))
             return false;
 
index 83f3b27dbe885380536a6c10b9c056a63e2b1565..b247cd8006b819370b62a52ddbe3f6a8a8625b89 100644 (file)
@@ -246,7 +246,10 @@ internal sealed unsafe class GameFunctions
     {
         if (elementId is QuestId questId)
             return IsReadyToAcceptQuest(questId);
-        return false;
+        else if (elementId is SatisfactionSupplyNpcId)
+            return true;
+        else
+            throw new ArgumentOutOfRangeException(nameof(elementId));
     }
 
     public bool IsReadyToAcceptQuest(QuestId questId)
@@ -283,7 +286,10 @@ internal sealed unsafe class GameFunctions
     {
         if (elementId is QuestId questId)
             return IsQuestAccepted(questId);
-        return false;
+        else if (elementId is SatisfactionSupplyNpcId)
+            return false;
+        else
+            throw new ArgumentOutOfRangeException(nameof(elementId));
     }
 
     public bool IsQuestAccepted(QuestId questId)
@@ -296,7 +302,10 @@ internal sealed unsafe class GameFunctions
     {
         if (elementId is QuestId questId)
             return IsQuestComplete(questId);
-        return false;
+        else if (elementId is SatisfactionSupplyNpcId)
+            return false;
+        else
+            throw new ArgumentOutOfRangeException(nameof(elementId));
     }
 
     [SuppressMessage("Performance", "CA1822")]
@@ -309,12 +318,15 @@ internal sealed unsafe class GameFunctions
     {
         if (elementId is QuestId questId)
             return IsQuestLocked(questId, extraCompletedQuest);
-        return false;
+        else if (elementId is SatisfactionSupplyNpcId)
+            return false;
+        else
+            throw new ArgumentOutOfRangeException(nameof(elementId));
     }
 
     public bool IsQuestLocked(QuestId questId, ElementId? extraCompletedQuest = null)
     {
-        var questInfo = _questData.GetQuestInfo(questId);
+        var questInfo = (QuestInfo) _questData.GetQuestInfo(questId);
         if (questInfo.QuestLocks.Count > 0)
         {
             var completedQuests = questInfo.QuestLocks.Count(x => IsQuestComplete(x) || x.Equals(extraCompletedQuest));
@@ -369,7 +381,11 @@ internal sealed unsafe class GameFunctions
     }
 
     public bool IsAetheryteUnlocked(EAetheryteLocation aetheryteLocation)
-        => IsAetheryteUnlocked((uint)aetheryteLocation, out _);
+    {
+        if (aetheryteLocation == EAetheryteLocation.IshgardFirmament)
+            return IsQuestComplete(new QuestId(3672));
+        return IsAetheryteUnlocked((uint)aetheryteLocation, out _);
+    }
 
     public bool CanTeleport(EAetheryteLocation aetheryteLocation)
     {
@@ -707,15 +723,15 @@ internal sealed unsafe class GameFunctions
         if (excelSheetName == null)
         {
             var questRow =
-                _dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.QuestElementId.Value +
+                _dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.Id.Value +
                     0x10000);
             if (questRow == null)
             {
-                _logger.LogError("Could not find quest row for {QuestId}", currentQuest.QuestElementId);
+                _logger.LogError("Could not find quest row for {QuestId}", currentQuest.Id);
                 return null;
             }
 
-            excelSheetName = $"quest/{(currentQuest.QuestElementId.Value / 100):000}/{questRow.Id}";
+            excelSheetName = $"quest/{(currentQuest.Id.Value / 100):000}/{questRow.Id}";
         }
 
         var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName);
diff --git a/Questionable/Model/IQuestInfo.cs b/Questionable/Model/IQuestInfo.cs
new file mode 100644 (file)
index 0000000..6822cce
--- /dev/null
@@ -0,0 +1,20 @@
+using System;
+using Dalamud.Game.Text;
+using Questionable.Model.Questing;
+
+namespace Questionable.Model;
+
+public interface IQuestInfo
+{
+    public ElementId QuestId { get; }
+    public string Name { get; }
+    public uint IssuerDataId { get; }
+    public bool IsRepeatable { get; }
+    public ushort Level { get; }
+    public EBeastTribe BeastTribe { get; }
+    public bool IsMainScenarioQuest { get; }
+
+    public string SimplifiedName => Name
+        .Replace(".", "", StringComparison.Ordinal)
+        .TrimStart(SeIconChar.QuestSync.ToIconChar(), SeIconChar.QuestRepeatable.ToIconChar(), ' ');
+}
index 391e0602be592e9516486dcc3778b19b683ef3bb..3befbc6983b06020db1730e34e039b9813797ada 100644 (file)
@@ -6,9 +6,9 @@ namespace Questionable.Model;
 
 internal sealed class Quest
 {
-    public required ElementId QuestElementId { get; init; }
+    public required ElementId Id { get; init; }
     public required QuestRoot Root { get; init; }
-    public required QuestInfo Info { get; init; }
+    public required IQuestInfo Info { get; init; }
     public required bool ReadOnly { get; init; }
 
     public QuestSequence? FindSequence(byte currentSequence)
index 59a3adad61b77dd5d8b54089597a0609b280974c..526b973a2cded94f12d8203d3d0a37dabdb447ef 100644 (file)
@@ -10,7 +10,7 @@ using ExcelQuest = Lumina.Excel.GeneratedSheets.Quest;
 
 namespace Questionable.Model;
 
-internal sealed class QuestInfo
+internal sealed class QuestInfo : IQuestInfo
 {
     public QuestInfo(ExcelQuest quest)
     {
@@ -56,7 +56,7 @@ internal sealed class QuestInfo
     }
 
 
-    public QuestId QuestId { get; }
+    public ElementId QuestId { get; }
     public string Name { get; }
     public ushort Level { get; }
     public uint IssuerDataId { get; }
@@ -74,10 +74,6 @@ internal sealed class QuestInfo
     public GrandCompany GrandCompany { get; }
     public EBeastTribe BeastTribe { get; }
 
-    public string SimplifiedName => Name
-        .Replace(".", "", StringComparison.Ordinal)
-        .TrimStart(SeIconChar.QuestSync.ToIconChar(), SeIconChar.QuestRepeatable.ToIconChar(), ' ');
-
     [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)]
     public enum QuestJoin : byte
     {
diff --git a/Questionable/Model/SatisfactionSupplyInfo.cs b/Questionable/Model/SatisfactionSupplyInfo.cs
new file mode 100644 (file)
index 0000000..b5d11bf
--- /dev/null
@@ -0,0 +1,23 @@
+using Lumina.Excel.GeneratedSheets;
+using Questionable.Model.Questing;
+
+namespace Questionable.Model;
+
+internal sealed class SatisfactionSupplyInfo : IQuestInfo
+{
+    public SatisfactionSupplyInfo(SatisfactionNpc npc)
+    {
+        QuestId = new SatisfactionSupplyNpcId((ushort)npc.RowId);
+        Name = npc.Npc.Value!.Singular;
+        IssuerDataId = npc.Npc.Row;
+        Level = npc.LevelUnlock;
+    }
+
+    public ElementId QuestId { get; }
+    public string Name { get; }
+    public uint IssuerDataId { get; }
+    public bool IsRepeatable => true;
+    public ushort Level { get; }
+    public EBeastTribe BeastTribe => EBeastTribe.None;
+    public bool IsMainScenarioQuest => false;
+}
index 944e6bebb63cdf57831b1180f2e298d1ab38ac56..477a37232b951de96a14ef0852f7992ce90acc77 100644 (file)
@@ -43,7 +43,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         IChatGui chatGui,
         ICommandManager commandManager,
         IAddonLifecycle addonLifecycle,
-        IKeyState keyState)
+        IKeyState keyState,
+        IContextMenu contextMenu)
     {
         ArgumentNullException.ThrowIfNull(pluginInterface);
 
@@ -66,6 +67,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         serviceCollection.AddSingleton(commandManager);
         serviceCollection.AddSingleton(addonLifecycle);
         serviceCollection.AddSingleton(keyState);
+        serviceCollection.AddSingleton(contextMenu);
         serviceCollection.AddSingleton(new WindowSystem(nameof(Questionable)));
         serviceCollection.AddSingleton((Configuration?)pluginInterface.GetPluginConfig() ?? new Configuration());
 
@@ -81,6 +83,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         _serviceProvider = serviceCollection.BuildServiceProvider();
         _serviceProvider.GetRequiredService<QuestRegistry>().Reload();
         _serviceProvider.GetRequiredService<CommandHandler>();
+        _serviceProvider.GetRequiredService<ContextMenuController>();
         _serviceProvider.GetRequiredService<DalamudInitializer>();
     }
 
@@ -156,6 +159,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         serviceCollection.AddSingleton<NavigationShortcutController>();
         serviceCollection.AddSingleton<CombatController>();
         serviceCollection.AddSingleton<GatheringController>();
+        serviceCollection.AddSingleton<ContextMenuController>();
 
         serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>();
     }
index f5820ac2412b3e2704f8e2452a6567c60e0d9159..af380f8a58fbd8310078a064435a250f15413219 100644 (file)
@@ -18,7 +18,7 @@ internal sealed class AethernetShortcutValidator : IQuestValidator
     public IEnumerable<ValidationIssue> Validate(Quest quest)
     {
         return quest.AllSteps()
-            .Select(x => Validate(quest.QuestElementId, x.Sequence.Sequence, x.StepId, x.Step.AethernetShortcut))
+            .Select(x => Validate(quest.Id, x.Sequence.Sequence, x.StepId, x.Step.AethernetShortcut))
             .Where(x => x != null)
             .Cast<ValidationIssue>();
     }
index a7330d8ff8320eca6cfe9a0ef72110d0d3e813c2..7fa73dfb915568d2322a65c5f517208d75de7394 100644 (file)
@@ -18,7 +18,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
         {
             yield return new ValidationIssue
             {
-                QuestId = quest.QuestElementId,
+                QuestId = quest.Id,
                 Sequence = 0,
                 Step = null,
                 Type = EIssueType.MissingSequence0,
@@ -28,7 +28,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
             yield break;
         }
 
-        if (quest.Info.CompletesInstantly)
+        if (quest.Info is QuestInfo { CompletesInstantly: true })
         {
             foreach (var sequence in sequences)
             {
@@ -37,7 +37,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
 
                 yield return new ValidationIssue
                 {
-                    QuestId = quest.QuestElementId,
+                    QuestId = quest.Id,
                     Sequence = (byte)sequence.Sequence,
                     Step = null,
                     Type = EIssueType.InstantQuestWithMultipleSteps,
@@ -46,7 +46,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
                 };
             }
         }
-        else
+        else if (quest.Info is QuestInfo)
         {
             int maxSequence = sequences.Select(x => x.Sequence)
                 .Where(x => x != 255)
@@ -73,7 +73,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
         {
             return new ValidationIssue
             {
-                QuestId = quest.QuestElementId,
+                QuestId = quest.Id,
                 Sequence = (byte)sequenceNo,
                 Step = null,
                 Type = EIssueType.MissingSequence,
@@ -85,7 +85,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
         {
             return new ValidationIssue
             {
-                QuestId = quest.QuestElementId,
+                QuestId = quest.Id,
                 Sequence = (byte)sequenceNo,
                 Step = null,
                 Type = EIssueType.DuplicateSequence,
index 76b539e208db2873f156f88f003f5ff02e0ed423..9aa67760e0557bad2ddd8629766ae659e6700c62 100644 (file)
@@ -45,7 +45,7 @@ internal sealed class CompletionFlagsValidator : IQuestValidator
                 {
                     yield return new ValidationIssue
                     {
-                        QuestId = quest.QuestElementId,
+                        QuestId = quest.Id,
                         Sequence = (byte)sequence.Sequence,
                         Step = i,
                         Type = EIssueType.DuplicateCompletionFlags,
index ca3625608bd9f5ebfe1e1c8e95c68e2e4e3d7626..b68fa96798e373558b8f2538c1e3b30c4af6a8f3 100644 (file)
@@ -25,7 +25,7 @@ internal sealed class JsonSchemaValidator : IQuestValidator
     {
         _questSchema ??= JsonSchema.FromStream(AssemblyQuestLoader.QuestSchema).AsTask().Result;
 
-        if (_questNodes.TryGetValue(quest.QuestElementId, out JsonNode? questNode))
+        if (_questNodes.TryGetValue(quest.Id, out JsonNode? questNode))
         {
             var evaluationResult = _questSchema.Evaluate(questNode, new EvaluationOptions
             {
@@ -36,7 +36,7 @@ internal sealed class JsonSchemaValidator : IQuestValidator
             {
                 yield return new ValidationIssue
                 {
-                    QuestId = quest.QuestElementId,
+                    QuestId = quest.Id,
                     Sequence = null,
                     Step = null,
                     Type = EIssueType.InvalidJsonSchema,
index 9c0afbc0bb472dfa19f26706ba50cd90727e1b69..c2e899a4f49c9c4d0f840e3bcf356e6e2c3f3c0b 100644 (file)
@@ -8,11 +8,11 @@ internal sealed class NextQuestValidator : IQuestValidator
 {
     public IEnumerable<ValidationIssue> Validate(Quest quest)
     {
-        foreach (var invalidNextQuest in quest.AllSteps().Where(x => x.Step.NextQuestId == quest.QuestElementId))
+        foreach (var invalidNextQuest in quest.AllSteps().Where(x => x.Step.NextQuestId == quest.Id))
         {
             yield return new ValidationIssue
             {
-                QuestId = quest.QuestElementId,
+                QuestId = quest.Id,
                 Sequence = (byte)invalidNextQuest.Sequence.Sequence,
                 Step = invalidNextQuest.StepId,
                 Type = EIssueType.InvalidNextQuestId,
index 18764ecb0b4bd82eb17a9e3960358fe39afe743f..60539d5bd4713f574a97a9c40902367857b789c7 100644 (file)
@@ -11,7 +11,7 @@ internal sealed class QuestDisabledValidator : IQuestValidator
         {
             yield return new ValidationIssue
             {
-                QuestId = quest.QuestElementId,
+                QuestId = quest.Id,
                 Sequence = null,
                 Step = null,
                 Type = EIssueType.QuestDisabled,
index 12adeecb6e4e553614038ea39b640b644c1fdbe8..5ffe6e0f001bed8f68ead6994a8cad7ecb838c18 100644 (file)
@@ -9,6 +9,9 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
 {
     public IEnumerable<ValidationIssue> Validate(Quest quest)
     {
+        if (quest.Id is SatisfactionSupplyNpcId)
+            yield break;
+
         var questAccepts = FindQuestStepsWithInteractionType(quest, EInteractionType.AcceptQuest)
             .Where(x => x.Step.PickUpQuestId == null)
             .ToList();
@@ -18,7 +21,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
             {
                 yield return new ValidationIssue
                 {
-                    QuestId = quest.QuestElementId,
+                    QuestId = quest.Id,
                     Sequence = (byte)accept.Sequence.Sequence,
                     Step = accept.StepId,
                     Type = EIssueType.UnexpectedAcceptQuestStep,
@@ -32,7 +35,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
         {
             yield return new ValidationIssue
             {
-                QuestId = quest.QuestElementId,
+                QuestId = quest.Id,
                 Sequence = 0,
                 Step = null,
                 Type = EIssueType.MissingQuestAccept,
@@ -50,7 +53,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
             {
                 yield return new ValidationIssue
                 {
-                    QuestId = quest.QuestElementId,
+                    QuestId = quest.Id,
                     Sequence = (byte)complete.Sequence.Sequence,
                     Step = complete.StepId,
                     Type = EIssueType.UnexpectedCompleteQuestStep,
@@ -64,7 +67,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
         {
             yield return new ValidationIssue
             {
-                QuestId = quest.QuestElementId,
+                QuestId = quest.Id,
                 Sequence = 255,
                 Step = null,
                 Type = EIssueType.MissingQuestComplete,
index 5c7286de6df1f1274132117a8bc6070dc7519837..4fbb9074f698f980d35ac2a7eb9f6ff48ff2cdb1 100644 (file)
@@ -103,7 +103,7 @@ internal sealed class DebugOverlay : Window
                 QuestStep? step = sequence.FindStep(i);
                 if (step != null && TryGetPosition(step, out Vector3? position))
                 {
-                    DrawStep($"{quest.QuestElementId} / {sequence.Sequence} / {i}", step, position.Value, 0xFFFFFFFF);
+                    DrawStep($"{quest.Id} / {sequence.Sequence} / {i}", step, position.Value, 0xFFFFFFFF);
                 }
             }
         }
index c9dcd9fc1e72be8a7e670ce6682e7661dde67330..bdad03da29de2f392dfb8149a7b5663167cd8be0 100644 (file)
@@ -201,7 +201,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
 
         if (ImGui.IsItemClicked() && _commandManager.Commands.TryGetValue("/questinfo", out var commandInfo))
         {
-            _commandManager.DispatchCommand("/questinfo", questInfo.QuestId.ToString(), commandInfo);
+            _commandManager.DispatchCommand("/questinfo", questInfo.QuestId.ToString() ?? string.Empty, commandInfo);
         }
 
         if (ImGui.IsItemHovered())
index 4f83cca88ee4de3e4949ecbe88c62368ccfdbdd5..eb48f4c431c0c5eb5b4f57a0fcdd928d03f54c69 100644 (file)
@@ -58,7 +58,7 @@ internal sealed class ActiveQuestComponent
     {
         var currentQuestDetails = _questController.CurrentQuestDetails;
         QuestController.QuestProgress? currentQuest = currentQuestDetails?.Progress;
-        QuestController.CurrentQuestType? currentQuestType = currentQuestDetails?.Type;
+        QuestController.ECurrentQuestType? currentQuestType = currentQuestDetails?.Type;
         if (currentQuest != null)
         {
             DrawQuestNames(currentQuest, currentQuestType);
@@ -108,9 +108,9 @@ internal sealed class ActiveQuestComponent
     }
 
     private void DrawQuestNames(QuestController.QuestProgress currentQuest,
-        QuestController.CurrentQuestType? currentQuestType)
+        QuestController.ECurrentQuestType? currentQuestType)
     {
-        if (currentQuestType == QuestController.CurrentQuestType.Simulated)
+        if (currentQuestType == QuestController.ECurrentQuestType.Simulated)
         {
             using var _ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
             ImGui.TextUnformatted(
@@ -151,7 +151,7 @@ internal sealed class ActiveQuestComponent
 
     private QuestWork? DrawQuestWork(QuestController.QuestProgress currentQuest)
     {
-        if (currentQuest.Quest.QuestElementId is not QuestId questId)
+        if (currentQuest.Quest.Id is not QuestId questId)
             return null;
 
         var questWork = _gameFunctions.GetQuestEx(questId);
@@ -210,7 +210,7 @@ internal sealed class ActiveQuestComponent
         {
             using var disabled = ImRaii.Disabled();
 
-            if (currentQuest.Quest.QuestElementId == _questController.NextQuest?.Quest.QuestElementId)
+            if (currentQuest.Quest.Id == _questController.NextQuest?.Quest.Id)
                 ImGui.TextUnformatted("(Next quest in story line not accepted)");
             else
                 ImGui.TextUnformatted("(Not accepted)");
@@ -229,14 +229,14 @@ internal sealed class ActiveQuestComponent
             if (questWork == null)
                 _questController.SetNextQuest(currentQuest.Quest);
 
-            _questController.ExecuteNextStep(true);
+            _questController.ExecuteNextStep(QuestController.EAutomationType.Automatic);
         }
 
         ImGui.SameLine();
 
         if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.StepForward, "Step"))
         {
-            _questController.ExecuteNextStep(false);
+            _questController.ExecuteNextStep(QuestController.EAutomationType.Manual);
         }
 
         ImGui.EndDisabled();
@@ -262,7 +262,7 @@ internal sealed class ActiveQuestComponent
         if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.ArrowCircleRight, "Skip"))
         {
             _movementController.Stop();
-            _questController.Skip(currentQuest.Quest.QuestElementId, currentQuest.Sequence);
+            _questController.Skip(currentQuest.Quest.Id, currentQuest.Sequence);
         }
 
         if (colored)
@@ -274,7 +274,7 @@ internal sealed class ActiveQuestComponent
             ImGui.SameLine();
             if (ImGuiComponents.IconButton(FontAwesomeIcon.Atlas))
                 _commandManager.DispatchCommand("/questinfo",
-                    currentQuest.Quest.QuestElementId.ToString() ?? string.Empty, commandInfo);
+                    currentQuest.Quest.Id.ToString() ?? string.Empty, commandInfo);
         }
 
         bool autoAcceptNextQuest = _configuration.General.AutoAcceptNextQuest;
index 41b0421416b16fa32b65090f0dc7f4786ab34a7f..2a19a858b9baa785fe011f10ed76408858d17a46 100644 (file)
@@ -6,6 +6,7 @@ using ImGuiNET;
 using Questionable.Controller;
 using Questionable.Data;
 using Questionable.Model;
+using Questionable.Model.Questing;
 
 namespace Questionable.Windows.QuestComponents;
 
@@ -31,6 +32,12 @@ internal sealed class QuestTooltipComponent
         _uiUtils = uiUtils;
     }
 
+    public void Draw(IQuestInfo quest)
+    {
+        if (quest is QuestInfo questInfo)
+            Draw(questInfo);
+    }
+
     public void Draw(QuestInfo quest)
     {
         using var tooltip = ImRaii.Tooltip();
@@ -93,8 +100,8 @@ internal sealed class QuestTooltipComponent
 
                 _uiUtils.ChecklistItem(FormatQuestUnlockName(qInfo), iconColor, icon);
 
-                if (counter <= 2 || icon != FontAwesomeIcon.Check)
-                    DrawQuestUnlocks(qInfo, counter + 1);
+                if (qInfo is QuestInfo qstInfo && (counter <= 2 || icon != FontAwesomeIcon.Check))
+                    DrawQuestUnlocks(qstInfo, counter + 1);
             }
         }
 
@@ -162,7 +169,7 @@ internal sealed class QuestTooltipComponent
             ImGui.Unindent();
     }
 
-    private static string FormatQuestUnlockName(QuestInfo questInfo)
+    private static string FormatQuestUnlockName(IQuestInfo questInfo)
     {
         if (questInfo.IsMainScenarioQuest)
             return $"{questInfo.Name} ({questInfo.QuestId}, MSQ)";
index 12375337567249dd789ae7056d83562557e8dad3..96d0cfd72b22518fdf945478e6d1a56655ead719 100644 (file)
@@ -39,8 +39,8 @@ internal sealed class QuestSelectionWindow : LWindow
     private readonly UiUtils _uiUtils;
     private readonly QuestTooltipComponent _questTooltipComponent;
 
-    private List<QuestInfo> _quests = [];
-    private List<QuestInfo> _offeredQuests = [];
+    private List<IQuestInfo> _quests = [];
+    private List<IQuestInfo> _offeredQuests = [];
     private bool _onlyAvailableQuests = true;
 
     public QuestSelectionWindow(QuestData questData, IGameGui gameGui, IChatGui chatGui, GameFunctions gameFunctions,
@@ -105,7 +105,7 @@ internal sealed class QuestSelectionWindow : LWindow
 
         _quests = _questRegistry.AllQuests
             .Where(x => x.FindSequence(0)?.FindStep(0)?.TerritoryId == territoryId)
-            .Select(x => _questData.GetQuestInfo(x.QuestElementId))
+            .Select(x => _questData.GetQuestInfo(x.Id))
             .ToList();
 
         foreach (var unacceptedQuest in Map.Instance()->UnacceptedQuestMarkers)
@@ -157,11 +157,11 @@ internal sealed class QuestSelectionWindow : LWindow
         ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, actionIconSize);
         ImGui.TableHeadersRow();
 
-        foreach (QuestInfo quest in (_offeredQuests.Count != 0 && _onlyAvailableQuests) ? _offeredQuests : _quests)
+        foreach (IQuestInfo quest in (_offeredQuests.Count != 0 && _onlyAvailableQuests) ? _offeredQuests : _quests)
         {
             ImGui.TableNextRow();
 
-            string questId = quest.QuestId.ToString();
+            string questId = quest.QuestId.ToString() ?? string.Empty;
             bool isKnownQuest = _questRegistry.TryGetQuest(quest.QuestId, out var knownQuest);
 
             if (ImGui.TableNextColumn())
@@ -228,7 +228,7 @@ internal sealed class QuestSelectionWindow : LWindow
                     if (startNextQuest)
                     {
                         _questController.SetNextQuest(knownQuest);
-                        _questController.ExecuteNextStep(true);
+                        _questController.ExecuteNextStep(QuestController.EAutomationType.Automatic);
                     }
 
                     ImGui.SameLine();
@@ -245,7 +245,7 @@ internal sealed class QuestSelectionWindow : LWindow
         }
     }
 
-    private void CopyToClipboard(QuestInfo quest, bool suffix)
+    private void CopyToClipboard(IQuestInfo quest, bool suffix)
     {
         string fileName = $"{quest.QuestId}_{quest.SimplifiedName}{(suffix ? ".json" : "")}";
         ImGui.SetClipboardText(fileName);
index 6e750e9d3bbeb442dbde24202bcb839521ad7eec..d6bdba36b18a6b353b3c42cc4b0ffcb8cf0b270e 100644 (file)
@@ -22,13 +22,13 @@ internal sealed class UiUtils
     public (Vector4 color, FontAwesomeIcon icon, string status) GetQuestStyle(ElementId questElementId)
     {
         if (_gameFunctions.IsQuestAccepted(questElementId))
-            return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Active");
+            return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight, "Active");
         else if (_gameFunctions.IsQuestAcceptedOrComplete(questElementId))
             return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check, "Complete");
         else if (_gameFunctions.IsQuestLocked(questElementId))
             return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times, "Locked");
         else
-            return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight, "Available");
+            return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Available");
     }
 
     public static (Vector4 color, FontAwesomeIcon icon) GetInstanceStyle(ushort instanceId)
@@ -36,7 +36,7 @@ internal sealed class UiUtils
         if (UIState.IsInstanceContentCompleted(instanceId))
             return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check);
         else if (UIState.IsInstanceContentUnlocked(instanceId))
-            return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight);
+            return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running);
         else
             return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times);
     }