From 4aee22510b7aed1c50b5b42c9e0fbfd31b9e5547 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Sun, 18 Aug 2024 01:55:38 +0200 Subject: [PATCH] Add Wachumeqimeqi deliveries --- .../Yak T'el/1001_Iq Rrax Tsoly_BTN.json | 178 +++++++++++ .../Yak T'el/980_Iq Rrax Tsoly_MIN.json | 200 ++++++++++++ .../MIN, BTN/4989_Hands for Hire.json | 3 +- .../MIN, BTN/4990_Test of Talents.json | 19 +- .../MIN, BTN/4991_A Discerning Eye.json | 53 ++++ .../MIN, BTN/4992_As Nature Intends.json | 53 ++++ .../MIN, BTN/4993_The Cycle of Life.json | 71 +++++ .../MIN, BTN/4994_Digging Up the Truth.json | 53 ++++ .../MIN, BTN/4995_Wellspring of Tears.json | 284 ++++++++++++++++++ .../GameUi/CraftworksSupplyController.cs | 123 ++++++++ .../Controller/GameUi/CreditsController.cs | 59 ++++ .../Controller/GameUi/HelpUiController.cs | 78 +++++ .../InteractionUiController.cs} | 202 +------------ .../Controller/GameUi/LeveUiController.cs | 154 ++++++++++ Questionable/Controller/MiniTaskController.cs | 2 +- .../Steps/Shared/GatheringRequiredItems.cs | 13 +- Questionable/DalamudInitializer.cs | 5 +- Questionable/QuestionablePlugin.cs | 18 +- Questionable/Validation/QuestValidator.cs | 2 + Questionable/Windows/QuestSelectionWindow.cs | 3 +- Questionable/Windows/QuestWindow.cs | 9 +- 21 files changed, 1373 insertions(+), 209 deletions(-) create mode 100644 GatheringPaths/7.x - Dawntrail/Yak T'el/1001_Iq Rrax Tsoly_BTN.json create mode 100644 GatheringPaths/7.x - Dawntrail/Yak T'el/980_Iq Rrax Tsoly_MIN.json create mode 100644 QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4991_A Discerning Eye.json create mode 100644 QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4992_As Nature Intends.json create mode 100644 QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4993_The Cycle of Life.json create mode 100644 QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4994_Digging Up the Truth.json create mode 100644 QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4995_Wellspring of Tears.json create mode 100644 Questionable/Controller/GameUi/CraftworksSupplyController.cs create mode 100644 Questionable/Controller/GameUi/CreditsController.cs create mode 100644 Questionable/Controller/GameUi/HelpUiController.cs rename Questionable/Controller/{GameUiController.cs => GameUi/InteractionUiController.cs} (80%) create mode 100644 Questionable/Controller/GameUi/LeveUiController.cs diff --git a/GatheringPaths/7.x - Dawntrail/Yak T'el/1001_Iq Rrax Tsoly_BTN.json b/GatheringPaths/7.x - Dawntrail/Yak T'el/1001_Iq Rrax Tsoly_BTN.json new file mode 100644 index 00000000..c9d98f76 --- /dev/null +++ b/GatheringPaths/7.x - Dawntrail/Yak T'el/1001_Iq Rrax Tsoly_BTN.json @@ -0,0 +1,178 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json", + "Author": "liza", + "Steps": [ + { + "Position": { + "X": 417.1447, + "Y": -0.6, + "Z": -647.60004 + }, + "TerritoryId": 1189, + "InteractionType": "Dive", + "AetheryteShortcut": "Yak T'el - Iq Br'aax", + "SkipConditions": { + "StepIf": { + "Flying": "Unlocked" + }, + "AetheryteShortcutIf": { + "InSameTerritory": true + } + } + }, + { + "Position": { + "X": 417.1447, + "Y": 3, + "Z": -647.60004 + }, + "TerritoryId": 1189, + "InteractionType": "WalkTo", + "Fly": true, + "SkipConditions": { + "StepIf": { + "Flying": "Locked" + } + } + }, + { + "Position": { + "X": 419.8578, + "Y": -32.6974, + "Z": -653.75275 + }, + "TerritoryId": 1189, + "InteractionType": "WalkTo", + "DisableNavmesh": true, + "Fly": true + } + ], + "Groups": [ + { + "Nodes": [ + { + "DataId": 34912, + "Locations": [ + { + "Position": { + "X": 458.8916, + "Y": -51.02777, + "Z": -689.8627 + } + }, + { + "Position": { + "X": 430.228, + "Y": -56.21914, + "Z": -693.9346 + } + }, + { + "Position": { + "X": 462.8787, + "Y": -58.29268, + "Z": -704.244 + } + } + ] + }, + { + "DataId": 34911, + "Locations": [ + { + "Position": { + "X": 448.169, + "Y": -53.1458, + "Z": -696.1208 + } + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 34914, + "Locations": [ + { + "Position": { + "X": 453.7438, + "Y": -59.20442, + "Z": -884.0787 + } + }, + { + "Position": { + "X": 399.0516, + "Y": -48.41589, + "Z": -900.1575 + } + }, + { + "Position": { + "X": 470.4918, + "Y": -54.81378, + "Z": -912.1257 + } + } + ] + }, + { + "DataId": 34913, + "Locations": [ + { + "Position": { + "X": 433.2036, + "Y": -56.63199, + "Z": -898.0532 + } + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 34915, + "Locations": [ + { + "Position": { + "X": 263.8979, + "Y": -44.71192, + "Z": -873.9875 + } + } + ] + }, + { + "DataId": 34916, + "Locations": [ + { + "Position": { + "X": 287.7073, + "Y": -43.04572, + "Z": -886.5245 + } + }, + { + "Position": { + "X": 266.3744, + "Y": -47.55014, + "Z": -846.1501 + } + }, + { + "Position": { + "X": 259.2106, + "Y": -44.82758, + "Z": -817.9664 + } + } + ] + } + ] + } + ] +} diff --git a/GatheringPaths/7.x - Dawntrail/Yak T'el/980_Iq Rrax Tsoly_MIN.json b/GatheringPaths/7.x - Dawntrail/Yak T'el/980_Iq Rrax Tsoly_MIN.json new file mode 100644 index 00000000..0b135d8b --- /dev/null +++ b/GatheringPaths/7.x - Dawntrail/Yak T'el/980_Iq Rrax Tsoly_MIN.json @@ -0,0 +1,200 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json", + "Author": "liza", + "Steps": [ + { + "Position": { + "X": 417.1447, + "Y": -0.6, + "Z": -647.60004 + }, + "TerritoryId": 1189, + "InteractionType": "Dive", + "AetheryteShortcut": "Yak T'el - Iq Br'aax", + "SkipConditions": { + "StepIf": { + "Flying": "Unlocked" + }, + "AetheryteShortcutIf": { + "InSameTerritory": true + } + } + }, + { + "Position": { + "X": 417.1447, + "Y": 3, + "Z": -647.60004 + }, + "TerritoryId": 1189, + "InteractionType": "WalkTo", + "Fly": true, + "SkipConditions": { + "StepIf": { + "Flying": "Locked" + } + } + }, + { + "Position": { + "X": 419.8578, + "Y": -32.6974, + "Z": -653.75275 + }, + "TerritoryId": 1189, + "InteractionType": "WalkTo", + "DisableNavmesh": true, + "Fly": true + } + ], + "Groups": [ + { + "Nodes": [ + { + "DataId": 34787, + "Locations": [ + { + "Position": { + "X": 482.7197, + "Y": -38.14573, + "Z": -612.8046 + }, + "MinimumAngle": 100, + "MaximumAngle": 275 + } + ] + }, + { + "DataId": 34788, + "Locations": [ + { + "Position": { + "X": 503.5652, + "Y": -41.40348, + "Z": -600.9512 + }, + "MinimumAngle": 185, + "MaximumAngle": 275 + }, + { + "Position": { + "X": 441.1733, + "Y": -36.58192, + "Z": -610.3331 + }, + "MinimumAngle": 120, + "MaximumAngle": 265 + }, + { + "Position": { + "X": 457.5484, + "Y": -40.0437, + "Z": -608.3312 + }, + "MinimumAngle": 115, + "MaximumAngle": 240 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 34790, + "Locations": [ + { + "Position": { + "X": 584.035, + "Y": -49.84215, + "Z": -759.925 + }, + "MinimumAngle": 115, + "MaximumAngle": 240 + }, + { + "Position": { + "X": 624.3585, + "Y": -61.07853, + "Z": -748.2542 + } + }, + { + "Position": { + "X": 605.4849, + "Y": -59.0002, + "Z": -772.6049 + }, + "MinimumAngle": 175, + "MaximumAngle": 275 + } + ] + }, + { + "DataId": 34789, + "Locations": [ + { + "Position": { + "X": 601.6854, + "Y": -53.68699, + "Z": -741.3439 + }, + "MinimumAngle": 185, + "MaximumAngle": 355 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 34785, + "Locations": [ + { + "Position": { + "X": 754.1298, + "Y": -57.09224, + "Z": -571.5818 + }, + "MinimumAngle": 100, + "MaximumAngle": 250 + } + ] + }, + { + "DataId": 34786, + "Locations": [ + { + "Position": { + "X": 734.2795, + "Y": -55.15427, + "Z": -573.6763 + }, + "MinimumAngle": 90, + "MaximumAngle": 260 + }, + { + "Position": { + "X": 714.931, + "Y": -53.3118, + "Z": -569.4072 + }, + "MinimumAngle": 115, + "MaximumAngle": 250 + }, + { + "Position": { + "X": 773.049, + "Y": -55.97124, + "Z": -569.7167 + }, + "MinimumAngle": 105, + "MaximumAngle": 240 + } + ] + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4989_Hands for Hire.json b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4989_Hands for Hire.json index 36eb6d7b..ad7a2c8c 100644 --- a/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4989_Hands for Hire.json +++ b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4989_Hands for Hire.json @@ -28,7 +28,8 @@ "Z": -8.316223 }, "TerritoryId": 1185, - "InteractionType": "CompleteQuest" + "InteractionType": "CompleteQuest", + "NextQuestId": 4990 } ] } diff --git a/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4990_Test of Talents.json b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4990_Test of Talents.json index 5693f6bf..6a72ac37 100644 --- a/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4990_Test of Talents.json +++ b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4990_Test of Talents.json @@ -20,6 +20,22 @@ { "Sequence": 255, "Steps": [ + { + "TerritoryId": 1185, + "InteractionType": "None", + "RequiredGatheredItems": [ + { + "ItemId": 43899, + "ItemCount": 6, + "Collectability": 600 + } + ], + "AetheryteShortcut": "Tuliyollal", + "AethernetShortcut": [ + "[Tuliyollal] Aetheryte Plaza", + "[Tuliyollal] Wachumeqimeqi" + ] + }, { "DataId": 1047132, "Position": { @@ -28,7 +44,8 @@ "Z": -5.6916504 }, "TerritoryId": 1185, - "InteractionType": "CompleteQuest" + "InteractionType": "CompleteQuest", + "NextQuestId": 4991 } ] } diff --git a/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4991_A Discerning Eye.json b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4991_A Discerning Eye.json new file mode 100644 index 00000000..7150a44c --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4991_A Discerning Eye.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1047132, + "Position": { + "X": 217.36475, + "Y": -14.000001, + "Z": -5.6916504 + }, + "TerritoryId": 1185, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "TerritoryId": 1185, + "InteractionType": "None", + "RequiredGatheredItems": [ + { + "ItemId": 43900, + "ItemCount": 6, + "Collectability": 600 + } + ], + "AetheryteShortcut": "Tuliyollal", + "AethernetShortcut": [ + "[Tuliyollal] Aetheryte Plaza", + "[Tuliyollal] Wachumeqimeqi" + ] + }, + { + "DataId": 1047132, + "Position": { + "X": 217.36475, + "Y": -14.000001, + "Z": -5.6916504 + }, + "TerritoryId": 1185, + "InteractionType": "CompleteQuest", + "NextQuestId": 4992 + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4992_As Nature Intends.json b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4992_As Nature Intends.json new file mode 100644 index 00000000..dd99b09b --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4992_As Nature Intends.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1047132, + "Position": { + "X": 217.36475, + "Y": -14.000001, + "Z": -5.6916504 + }, + "TerritoryId": 1185, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "TerritoryId": 1185, + "InteractionType": "None", + "RequiredGatheredItems": [ + { + "ItemId": 43901, + "ItemCount": 6, + "Collectability": 600 + } + ], + "AetheryteShortcut": "Tuliyollal", + "AethernetShortcut": [ + "[Tuliyollal] Aetheryte Plaza", + "[Tuliyollal] Wachumeqimeqi" + ] + }, + { + "DataId": 1047132, + "Position": { + "X": 217.36475, + "Y": -14.000001, + "Z": -5.6916504 + }, + "TerritoryId": 1185, + "InteractionType": "CompleteQuest", + "NextQuestId": 4993 + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4993_The Cycle of Life.json b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4993_The Cycle of Life.json new file mode 100644 index 00000000..9b9155ff --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4993_The Cycle of Life.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1047132, + "Position": { + "X": 217.36475, + "Y": -14.000001, + "Z": -5.6916504 + }, + "TerritoryId": 1185, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 1, + "Steps": [ + { + "DataId": 1047153, + "Position": { + "X": -270.4662, + "Y": 40.0732, + "Z": -12.253052 + }, + "TerritoryId": 1185, + "InteractionType": "Interact", + "AethernetShortcut": [ + "[Tuliyollal] Wachumeqimeqi", + "[Tuliyollal] The Resplendent Quarter" + ] + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "TerritoryId": 1185, + "InteractionType": "None", + "RequiredGatheredItems": [ + { + "ItemId": 43913, + "ItemCount": 1 + } + ], + "AetheryteShortcut": "Tuliyollal", + "AethernetShortcut": [ + "[Tuliyollal] Aetheryte Plaza", + "[Tuliyollal] Wachumeqimeqi" + ] + }, + { + "DataId": 1047132, + "Position": { + "X": 217.36475, + "Y": -14.000001, + "Z": -5.6916504 + }, + "TerritoryId": 1185, + "InteractionType": "CompleteQuest", + "NextQuestId": 4994 + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4994_Digging Up the Truth.json b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4994_Digging Up the Truth.json new file mode 100644 index 00000000..1202151e --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4994_Digging Up the Truth.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1047132, + "Position": { + "X": 217.36475, + "Y": -14.000001, + "Z": -5.6916504 + }, + "TerritoryId": 1185, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "TerritoryId": 1185, + "InteractionType": "None", + "RequiredGatheredItems": [ + { + "ItemId": 43902, + "ItemCount": 6, + "Collectability": 600 + } + ], + "AetheryteShortcut": "Tuliyollal", + "AethernetShortcut": [ + "[Tuliyollal] Aetheryte Plaza", + "[Tuliyollal] Wachumeqimeqi" + ] + }, + { + "DataId": 1047132, + "Position": { + "X": 217.36475, + "Y": -14.000001, + "Z": -5.6916504 + }, + "TerritoryId": 1185, + "InteractionType": "CompleteQuest", + "NextQuestId": 4995 + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4995_Wellspring of Tears.json b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4995_Wellspring of Tears.json new file mode 100644 index 00000000..03be2b82 --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4995_Wellspring of Tears.json @@ -0,0 +1,284 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1047132, + "Position": { + "X": 217.36475, + "Y": -14.000001, + "Z": -5.6916504 + }, + "TerritoryId": 1185, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 1, + "Steps": [ + { + "DataId": 1047155, + "Position": { + "X": 746.24243, + "Y": -133.18861, + "Z": 507.5608 + }, + "TerritoryId": 1189, + "InteractionType": "Interact", + "AetheryteShortcut": "Yak T'el - Mamook" + } + ] + }, + { + "Sequence": 2, + "Steps": [ + { + "DataId": 1048981, + "Position": { + "X": 686.51855, + "Y": -137.174, + "Z": 534.8745 + }, + "TerritoryId": 1189, + "InteractionType": "Interact", + "Fly": true, + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 16 + ] + }, + { + "DataId": 1048973, + "Position": { + "X": 661.86, + "Y": -135.17876, + "Z": 582.11633 + }, + "StopDistance": 4, + "TerritoryId": 1189, + "InteractionType": "Interact", + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + }, + { + "DataId": 1047156, + "Position": { + "X": 632.5017, + "Y": -137.17401, + "Z": 590.8445 + }, + "TerritoryId": 1189, + "InteractionType": "Interact", + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + }, + { + "DataId": 1048974, + "Position": { + "X": 621.51514, + "Y": -135.12726, + "Z": 531.1207 + }, + "TerritoryId": 1189, + "InteractionType": "Interact", + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + ] + }, + { + "Sequence": 3, + "Steps": [ + { + "DataId": 1047158, + "Position": { + "X": 539.69617, + "Y": -142.49185, + "Z": 481.65088 + }, + "TerritoryId": 1189, + "InteractionType": "Interact", + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ], + "Fly": true + }, + { + "DataId": 1047157, + "Position": { + "X": 586.26685, + "Y": -142.4984, + "Z": 462.97388 + }, + "TerritoryId": 1189, + "InteractionType": "Interact", + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ], + "Fly": true + } + ] + }, + { + "Sequence": 4, + "Steps": [ + { + "DataId": 1047159, + "Position": { + "X": 191.45496, + "Y": -160.64616, + "Z": 414.0536 + }, + "TerritoryId": 1189, + "InteractionType": "Interact", + "Fly": true + } + ] + }, + { + "Sequence": 5, + "Steps": [ + { + "DataId": 1047160, + "Position": { + "X": 664.6067, + "Y": 1.554378, + "Z": -477.22595 + }, + "TerritoryId": 1189, + "InteractionType": "Interact", + "AetheryteShortcut": "Yak T'el - Mamook", + "Fly": true + } + ] + }, + { + "Sequence": 6, + "Steps": [ + { + "Position": { + "X": 436.87848, + "Y": 4.0999737, + "Z": -551.09174 + }, + "TerritoryId": 1189, + "InteractionType": "WalkTo", + "SkipConditions": { + "StepIf": { + "Flying": "Unlocked" + } + } + }, + { + "Position": { + "X": 674.17834, + "Y": -33.187485, + "Z": -598.0982 + }, + "TerritoryId": 1189, + "InteractionType": "WalkTo", + "Fly": true, + "RequiredGatheredItems": [ + { + "ItemId": 43914, + "ItemCount": 1 + } + ] + }, + { + "Position": { + "X": 674.17834, + "Y": -0.6, + "Z": -598.0982 + }, + "TerritoryId": 1189, + "InteractionType": "WalkTo", + "Fly": true, + "DisableNavmesh": true + }, + { + "DataId": 1047160, + "Position": { + "X": 664.6067, + "Y": 1.554378, + "Z": -477.22595 + }, + "TerritoryId": 1189, + "InteractionType": "Interact", + "Fly": true + } + ] + }, + { + "Sequence": 7, + "Steps": [ + { + "DataId": 1047132, + "Position": { + "X": 217.36475, + "Y": -14.000001, + "Z": -5.6916504 + }, + "TerritoryId": 1185, + "InteractionType": "Interact", + "AetheryteShortcut": "Tuliyollal", + "AethernetShortcut": [ + "[Tuliyollal] Aetheryte Plaza", + "[Tuliyollal] Wachumeqimeqi" + ] + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "DataId": 1047132, + "Position": { + "X": 217.36475, + "Y": -14.000001, + "Z": -5.6916504 + }, + "TerritoryId": 1185, + "InteractionType": "CompleteQuest" + } + ] + } + ] +} diff --git a/Questionable/Controller/GameUi/CraftworksSupplyController.cs b/Questionable/Controller/GameUi/CraftworksSupplyController.cs new file mode 100644 index 00000000..97c5003f --- /dev/null +++ b/Questionable/Controller/GameUi/CraftworksSupplyController.cs @@ -0,0 +1,123 @@ +using System; +using Dalamud.Game.Addon.Lifecycle; +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; +using LLib.GameUI; +using Microsoft.Extensions.Logging; +using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; + +namespace Questionable.Controller.GameUi; + +internal sealed class CraftworksSupplyController : IDisposable +{ + private readonly QuestController _questController; + private readonly IAddonLifecycle _addonLifecycle; + private readonly IGameGui _gameGui; + private readonly IFramework _framework; + private readonly ILogger _logger; + + public CraftworksSupplyController(QuestController questController, IAddonLifecycle addonLifecycle, + IGameGui gameGui, IFramework framework, ILogger logger) + { + _questController = questController; + _addonLifecycle = addonLifecycle; + _gameGui = gameGui; + _framework = framework; + _logger = logger; + + _addonLifecycle.RegisterListener(AddonEvent.PostReceiveEvent, "ContextIconMenu", ContextIconMenuPostReceiveEvent); + _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "BankaCraftworksSupply", + BankaCraftworksSupplyPostUpdate); + } + + private bool ShouldHandleUiInteractions => _questController.IsRunning; + + private unsafe void BankaCraftworksSupplyPostUpdate(AddonEvent type, AddonArgs args) + { + if (!ShouldHandleUiInteractions) + return; + + AtkUnitBase* addon = (AtkUnitBase*)args.Addon; + InteractWithBankaCraftworksSupply(addon); + } + + private unsafe void InteractWithBankaCraftworksSupply() + { + if (_gameGui.TryGetAddonByName("BankaCraftworksSupply", out AtkUnitBase* addon)) + InteractWithBankaCraftworksSupply(addon); + } + + private unsafe void InteractWithBankaCraftworksSupply(AtkUnitBase* addon) + { + AtkValue* atkValues = addon->AtkValues; + + uint completedCount = atkValues[7].UInt; + uint missingCount = 6 - completedCount; + for (int slot = 0; slot < missingCount; ++slot) + { + if (atkValues[31 + slot].UInt != 0) + continue; + + _logger.LogInformation("Selecting an item for slot {Slot}", slot); + var selectSlot = stackalloc AtkValue[] + { + new() { Type = ValueType.Int, Int = 2 }, + new() { Type = ValueType.Int, Int = slot /* slot */ }, + }; + addon->FireCallback(2, selectSlot); + return; + } + + // do turn-in if any item is provided + if (atkValues[31].UInt != 0) + { + _logger.LogInformation("Confirming turn-in"); + addon->FireCallbackInt(0); + } + } + + // FIXME: This seems to not work if the mouse isn't over the FFXIV window? + private unsafe void ContextIconMenuPostReceiveEvent(AddonEvent type, AddonArgs args) + { + if (!ShouldHandleUiInteractions) + return; + + AddonContextIconMenu* addonContextIconMenu = (AddonContextIconMenu*)args.Addon; + if (!addonContextIconMenu->IsVisible) + return; + + ushort parentId = addonContextIconMenu->ContextMenuParentId; + if (parentId == 0) + return; + + AtkUnitBase* parentAddon = AtkStage.Instance()->RaptureAtkUnitManager->GetAddonById(parentId); + if (parentAddon->NameString is "BankaCraftworksSupply") + { + _logger.LogInformation("Picking item for {AddonName}", parentAddon->NameString); + var selectSlot = stackalloc AtkValue[] + { + new() { Type = ValueType.Int, Int = 0 }, + new() { Type = ValueType.Int, Int = 0 /* slot */ }, + new() { Type = ValueType.UInt, UInt = 20802 /* probably the item's icon */ }, + new() { Type = ValueType.UInt, UInt = 0 }, + new() { Type = 0, Int = 0 }, + }; + addonContextIconMenu->FireCallback(5, selectSlot); + addonContextIconMenu->Close(true); + + if (parentAddon->NameString == "BankaCraftworksSupply") + _framework.RunOnTick(InteractWithBankaCraftworksSupply, TimeSpan.FromMilliseconds(50)); + } + else + _logger.LogTrace("Ignoring contextmenu event for {AddonName}", parentAddon->NameString); + } + + public void Dispose() + { + _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "BankaCraftworksSupply", + BankaCraftworksSupplyPostUpdate); + _addonLifecycle.UnregisterListener(AddonEvent.PostReceiveEvent, "ContextIconMenu", ContextIconMenuPostReceiveEvent); + } +} diff --git a/Questionable/Controller/GameUi/CreditsController.cs b/Questionable/Controller/GameUi/CreditsController.cs new file mode 100644 index 00000000..121f5bde --- /dev/null +++ b/Questionable/Controller/GameUi/CreditsController.cs @@ -0,0 +1,59 @@ +using System; +using Dalamud.Game.Addon.Lifecycle; +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Component.GUI; +using Microsoft.Extensions.Logging; + +namespace Questionable.Controller.GameUi; + +internal sealed class CreditsController : IDisposable +{ + private readonly IAddonLifecycle _addonLifecycle; + private readonly ILogger _logger; + + public CreditsController(IAddonLifecycle addonLifecycle, ILogger logger) + { + _addonLifecycle = addonLifecycle; + _logger = logger; + + _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "CreditScroll", CreditScrollPostSetup); + _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "Credit", CreditPostSetup); + _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "CreditPlayer", CreditPlayerPostSetup); + } + + + /// + /// ARR Credits. + /// + private unsafe void CreditScrollPostSetup(AddonEvent type, AddonArgs args) + { + _logger.LogInformation("Closing Credits sequence"); + AtkUnitBase* addon = (AtkUnitBase*)args.Addon; + addon->FireCallbackInt(-2); + } + + /// + /// Credits for (possibly all?) expansions, not used for ARR. + /// + private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args) + { + _logger.LogInformation("Closing Credits sequence"); + AtkUnitBase* addon = (AtkUnitBase*)args.Addon; + addon->FireCallbackInt(-2); + } + + private unsafe void CreditPlayerPostSetup(AddonEvent type, AddonArgs args) + { + _logger.LogInformation("Closing CreditPlayer"); + AtkUnitBase* addon = (AtkUnitBase*)args.Addon; + addon->Close(true); + } + + public void Dispose() + { + _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CreditPlayer", CreditPlayerPostSetup); + _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "Credit", CreditPostSetup); + _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CreditScroll", CreditScrollPostSetup); + } +} diff --git a/Questionable/Controller/GameUi/HelpUiController.cs b/Questionable/Controller/GameUi/HelpUiController.cs new file mode 100644 index 00000000..a7b23398 --- /dev/null +++ b/Questionable/Controller/GameUi/HelpUiController.cs @@ -0,0 +1,78 @@ +using System; +using Dalamud.Game.Addon.Lifecycle; +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Component.GUI; +using Microsoft.Extensions.Logging; + +namespace Questionable.Controller.GameUi; + +internal sealed class HelpUiController : IDisposable +{ + private readonly QuestController _questController; + private readonly IAddonLifecycle _addonLifecycle; + private readonly ILogger _logger; + + public HelpUiController(QuestController questController, IAddonLifecycle addonLifecycle, ILogger logger) + { + _questController = questController; + _addonLifecycle = addonLifecycle; + _logger = logger; + + _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "AkatsukiNote", UnendingCodexPostSetup); + _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "ContentsTutorial", ContentsTutorialPostSetup); + _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "MultipleHelpWindow", MultipleHelpWindowPostSetup); + } + + private bool ShouldHandleUiInteractions => _questController.IsRunning; + + private unsafe void UnendingCodexPostSetup(AddonEvent type, AddonArgs args) + { + if (!ShouldHandleUiInteractions) + return; + + if (_questController.StartedQuest?.Quest.Id.Value == 4526) + { + _logger.LogInformation("Closing Unending Codex"); + AtkUnitBase* addon = (AtkUnitBase*)args.Addon; + addon->FireCallbackInt(-2); + } + } + + private unsafe void ContentsTutorialPostSetup(AddonEvent type, AddonArgs args) + { + if (!ShouldHandleUiInteractions) + return; + + if (_questController.StartedQuest?.Quest.Id.Value == 245) + { + _logger.LogInformation("Closing ContentsTutorial"); + AtkUnitBase* addon = (AtkUnitBase*)args.Addon; + addon->FireCallbackInt(13); + } + } + + /// + /// Opened e.g. the first time you open the duty finder window during Sastasha. + /// + private unsafe void MultipleHelpWindowPostSetup(AddonEvent type, AddonArgs args) + { + if (!ShouldHandleUiInteractions) + return; + + if (_questController.StartedQuest?.Quest.Id.Value == 245) + { + _logger.LogInformation("Closing MultipleHelpWindow"); + AtkUnitBase* addon = (AtkUnitBase*)args.Addon; + addon->FireCallbackInt(-2); + addon->FireCallbackInt(-1); + } + } + + public void Dispose() + { + _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "MultipleHelpWindow", MultipleHelpWindowPostSetup); + _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "ContentsTutorial", ContentsTutorialPostSetup); + _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "AkatsukiNote", UnendingCodexPostSetup); + } +} diff --git a/Questionable/Controller/GameUiController.cs b/Questionable/Controller/GameUi/InteractionUiController.cs similarity index 80% rename from Questionable/Controller/GameUiController.cs rename to Questionable/Controller/GameUi/InteractionUiController.cs index cbc312a3..4c8367e7 100644 --- a/Questionable/Controller/GameUiController.cs +++ b/Questionable/Controller/GameUi/InteractionUiController.cs @@ -10,7 +10,6 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Event; using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.UI; -using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Component.GUI; using LLib; using LLib.GameData; @@ -25,12 +24,13 @@ using Questionable.Model.Common; using Questionable.Model.Gathering; using Questionable.Model.Questing; using AethernetShortcut = Questionable.Controller.Steps.Shared.AethernetShortcut; +using EAetheryteLocationExtensions = Questionable.Model.Common.EAetheryteLocationExtensions; using Quest = Questionable.Model.Quest; using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; -namespace Questionable.Controller; +namespace Questionable.Controller.GameUi; -internal sealed class GameUiController : IDisposable +internal sealed class InteractionUiController : IDisposable { private readonly IAddonLifecycle _addonLifecycle; private readonly IDataManager _dataManager; @@ -44,14 +44,13 @@ internal sealed class GameUiController : IDisposable private readonly QuestData _questData; private readonly IGameGui _gameGui; private readonly ITargetManager _targetManager; - private readonly IFramework _framework; private readonly IClientState _clientState; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly Regex _returnRegex; private bool _isInitialCheck; - public GameUiController( + public InteractionUiController( IAddonLifecycle addonLifecycle, IDataManager dataManager, QuestFunctions questFunctions, @@ -67,7 +66,7 @@ internal sealed class GameUiController : IDisposable IFramework framework, IPluginLog pluginLog, IClientState clientState, - ILogger logger) + ILogger logger) { _addonLifecycle = addonLifecycle; _dataManager = dataManager; @@ -81,7 +80,6 @@ internal sealed class GameUiController : IDisposable _questData = questData; _gameGui = gameGui; _targetManager = targetManager; - _framework = framework; _clientState = clientState; _logger = logger; @@ -92,15 +90,7 @@ internal sealed class GameUiController : IDisposable _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectIconString", SelectIconStringPostSetup); _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup); _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup); - _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "CreditScroll", CreditScrollPostSetup); - _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "Credit", CreditPostSetup); - _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "CreditPlayer", CreditPlayerPostSetup); - _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "AkatsukiNote", UnendingCodexPostSetup); - _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "ContentsTutorial", ContentsTutorialPostSetup); - _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "MultipleHelpWindow", MultipleHelpWindowPostSetup); _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup); - _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "JournalResult", JournalResultPostSetup); - _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "GuildLeve", GuildLevePostSetup); _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "TelepotTown", TeleportTownPostSetup); } @@ -774,76 +764,6 @@ internal sealed class GameUiController : IDisposable currentQuest.IncreasePointMenuCounter(); } - /// - /// ARR Credits. - /// - private unsafe void CreditScrollPostSetup(AddonEvent type, AddonArgs args) - { - _logger.LogInformation("Closing Credits sequence"); - AtkUnitBase* addon = (AtkUnitBase*)args.Addon; - addon->FireCallbackInt(-2); - } - - /// - /// Credits for (possibly all?) expansions, not used for ARR. - /// - private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args) - { - _logger.LogInformation("Closing Credits sequence"); - AtkUnitBase* addon = (AtkUnitBase*)args.Addon; - addon->FireCallbackInt(-2); - } - - private unsafe void CreditPlayerPostSetup(AddonEvent type, AddonArgs args) - { - _logger.LogInformation("Closing CreditPlayer"); - AtkUnitBase* addon = (AtkUnitBase*)args.Addon; - addon->Close(true); - } - - private unsafe void UnendingCodexPostSetup(AddonEvent type, AddonArgs args) - { - if (!ShouldHandleUiInteractions) - return; - - if (_questController.StartedQuest?.Quest.Id.Value == 4526) - { - _logger.LogInformation("Closing Unending Codex"); - AtkUnitBase* addon = (AtkUnitBase*)args.Addon; - addon->FireCallbackInt(-2); - } - } - - private unsafe void ContentsTutorialPostSetup(AddonEvent type, AddonArgs args) - { - if (!ShouldHandleUiInteractions) - return; - - if (_questController.StartedQuest?.Quest.Id.Value == 245) - { - _logger.LogInformation("Closing ContentsTutorial"); - AtkUnitBase* addon = (AtkUnitBase*)args.Addon; - addon->FireCallbackInt(13); - } - } - - /// - /// Opened e.g. the first time you open the duty finder window during Sastasha. - /// - private unsafe void MultipleHelpWindowPostSetup(AddonEvent type, AddonArgs args) - { - if (!ShouldHandleUiInteractions) - return; - - if (_questController.StartedQuest?.Quest.Id.Value == 245) - { - _logger.LogInformation("Closing MultipleHelpWindow"); - AtkUnitBase* addon = (AtkUnitBase*)args.Addon; - addon->FireCallbackInt(-2); - addon->FireCallbackInt(-1); - } - } - private unsafe void HousingSelectBlockPostSetup(AddonEvent type, AddonArgs args) { if (!ShouldHandleUiInteractions) @@ -854,111 +774,11 @@ internal sealed class GameUiController : IDisposable addon->FireCallbackInt(0); } - private unsafe void JournalResultPostSetup(AddonEvent type, AddonArgs args) - { - if (!ShouldHandleUiInteractions) - return; - - _logger.LogInformation("Checking for quest name of journal result"); - AddonJournalResult* addon = (AddonJournalResult*)args.Addon; - - string questName = addon->AtkTextNode250->NodeText.ToString(); - if (_questController.CurrentQuest is { Quest.Id: LeveId } && - GameFunctions.GameStringEquals(_questController.CurrentQuest.Quest.Info.Name, questName)) - { - _logger.LogInformation("JournalResult has the current leve, auto-accepting it"); - addon->FireCallbackInt(0); - } - else if (_targetManager.Target is { } target) - { - var issuedLeves = _questData.GetAllByIssuerDataId(target.DataId) - .Where(x => x.QuestId is LeveId) - .ToList(); - - if (issuedLeves.Any(x => GameFunctions.GameStringEquals(x.Name, questName))) - { - _logger.LogInformation( - "JournalResult has a leve but not the one we're currently on, auto-declining it"); - addon->FireCallbackInt(1); - } - } - } - - private unsafe void GuildLevePostSetup(AddonEvent type, AddonArgs args) - { - var target = _targetManager.Target; - if (target == null) - return; - - if (_questController is { IsRunning: true, NextQuest: { Quest.Id: LeveId } nextQuest } && - _questFunctions.IsReadyToAcceptQuest(nextQuest.Quest.Id)) - { - var addon = (AddonGuildLeve*)args.Addon; - /* - var atkValues = addon->AtkValues; - - var availableLeves = _questData.GetAllByIssuerDataId(target.DataId); - List<(int, IQuestInfo)> offeredLeves = []; - for (int i = 0; i <= 20; ++i) // 3 leves per group, 1 label for group - { - string? leveName = atkValues[626 + i * 2].ReadAtkString(); - if (leveName == null) - continue; - - var questInfo = availableLeves.FirstOrDefault(x => GameFunctions.GameStringEquals(x.Name, leveName)); - if (questInfo == null) - continue; - - offeredLeves.Add((i, questInfo)); - - } - - foreach (var (i, questInfo) in offeredLeves) - _logger.LogInformation("Leve {Index} = {Id}, {Name}", i, questInfo.QuestId, questInfo.Name); - */ - - _framework.RunOnTick(() => AcceptLeveOrWait(nextQuest), TimeSpan.FromMilliseconds(100)); - } - } - - private unsafe void AcceptLeveOrWait(QuestController.QuestProgress nextQuest, int counter = 0) - { - var agent = UIModule.Instance()->GetAgentModule()->GetAgentByInternalId(AgentId.LeveQuest); - if (agent->IsAgentActive() && - _gameGui.TryGetAddonByName("GuildLeve", out AddonGuildLeve* addonGuildLeve) && - LAddon.IsAddonReady(&addonGuildLeve->AtkUnitBase) && - _gameGui.TryGetAddonByName("JournalDetail", out AtkUnitBase* addonJournalDetail) && - LAddon.IsAddonReady(addonJournalDetail)) - { - AcceptLeve(agent, addonGuildLeve, nextQuest); - } - else if (counter >= 10) - _logger.LogWarning("Unable to accept leve?"); - else - _framework.RunOnTick(() => AcceptLeveOrWait(nextQuest, counter + 1), TimeSpan.FromMilliseconds(100)); - } - - private unsafe void AcceptLeve(AgentInterface* agent, AddonGuildLeve* addon, - QuestController.QuestProgress nextQuest) - { - _questController.SetPendingQuest(nextQuest); - _questController.SetNextQuest(null); - - var returnValue = stackalloc AtkValue[1]; - var selectQuest = stackalloc AtkValue[] - { - new() { Type = ValueType.Int, Int = 3 }, - new() { Type = ValueType.UInt, UInt = nextQuest.Quest.Id.Value } - }; - agent->ReceiveEvent(returnValue, selectQuest, 2, 0); - addon->Close(true); - } - private void TeleportTownPostSetup(AddonEvent type, AddonArgs args) { if (ShouldHandleUiInteractions && _questController.HasCurrentTaskMatching(out AethernetShortcut.UseAethernetShortcut? aethernetShortcut) && - aethernetShortcut.From.IsFirmamentAetheryte()) + EAetheryteLocationExtensions.IsFirmamentAetheryte(aethernetShortcut.From)) { // this might be better via atkvalues; but this works for now uint toIndex = aethernetShortcut.To switch @@ -1012,15 +832,7 @@ internal sealed class GameUiController : IDisposable public void Dispose() { _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "TelepotTown", TeleportTownPostSetup); - _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "GuildLeve", GuildLevePostSetup); - _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "JournalResult", JournalResultPostSetup); _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup); - _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "MultipleHelpWindow", MultipleHelpWindowPostSetup); - _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "ContentsTutorial", ContentsTutorialPostSetup); - _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "AkatsukiNote", UnendingCodexPostSetup); - _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CreditPlayer", CreditPlayerPostSetup); - _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "Credit", CreditPostSetup); - _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CreditScroll", CreditScrollPostSetup); _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup); _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup); _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectIconString", SelectIconStringPostSetup); diff --git a/Questionable/Controller/GameUi/LeveUiController.cs b/Questionable/Controller/GameUi/LeveUiController.cs new file mode 100644 index 00000000..533d5d3c --- /dev/null +++ b/Questionable/Controller/GameUi/LeveUiController.cs @@ -0,0 +1,154 @@ +using System; +using System.Linq; +using Dalamud.Game.Addon.Lifecycle; +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +using Dalamud.Game.ClientState.Objects; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Component.GUI; +using LLib.GameUI; +using Microsoft.Extensions.Logging; +using Questionable.Data; +using Questionable.Functions; +using Questionable.Model.Questing; +using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; + +namespace Questionable.Controller.GameUi; + +internal sealed class LeveUiController : IDisposable +{ + private readonly QuestController _questController; + private readonly QuestData _questData; + private readonly QuestFunctions _questFunctions; + private readonly IAddonLifecycle _addonLifecycle; + private readonly IGameGui _gameGui; + private readonly ITargetManager _targetManager; + private readonly IFramework _framework; + private readonly ILogger _logger; + + public LeveUiController(QuestController questController, QuestData questData, QuestFunctions questFunctions, + IAddonLifecycle addonLifecycle, IGameGui gameGui, ITargetManager targetManager, IFramework framework, + ILogger logger) + { + _questController = questController; + _questData = questData; + _questFunctions = questFunctions; + _addonLifecycle = addonLifecycle; + _gameGui = gameGui; + _targetManager = targetManager; + _framework = framework; + _logger = logger; + + _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "JournalResult", JournalResultPostSetup); + _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "GuildLeve", GuildLevePostSetup); + } + + private bool ShouldHandleUiInteractions => _questController.IsRunning; + + private unsafe void JournalResultPostSetup(AddonEvent type, AddonArgs args) + { + if (!ShouldHandleUiInteractions) + return; + + _logger.LogInformation("Checking for quest name of journal result"); + AddonJournalResult* addon = (AddonJournalResult*)args.Addon; + + string questName = addon->AtkTextNode250->NodeText.ToString(); + if (_questController.CurrentQuest is { Quest.Id: LeveId } && + GameFunctions.GameStringEquals(_questController.CurrentQuest.Quest.Info.Name, questName)) + { + _logger.LogInformation("JournalResult has the current leve, auto-accepting it"); + addon->FireCallbackInt(0); + } + else if (_targetManager.Target is { } target) + { + var issuedLeves = _questData.GetAllByIssuerDataId(target.DataId) + .Where(x => x.QuestId is LeveId) + .ToList(); + + if (issuedLeves.Any(x => GameFunctions.GameStringEquals(x.Name, questName))) + { + _logger.LogInformation( + "JournalResult has a leve but not the one we're currently on, auto-declining it"); + addon->FireCallbackInt(1); + } + } + } + + private unsafe void GuildLevePostSetup(AddonEvent type, AddonArgs args) + { + var target = _targetManager.Target; + if (target == null) + return; + + if (_questController is { IsRunning: true, NextQuest: { Quest.Id: LeveId } nextQuest } && + _questFunctions.IsReadyToAcceptQuest(nextQuest.Quest.Id)) + { + var addon = (AddonGuildLeve*)args.Addon; + /* + var atkValues = addon->AtkValues; + + var availableLeves = _questData.GetAllByIssuerDataId(target.DataId); + List<(int, IQuestInfo)> offeredLeves = []; + for (int i = 0; i <= 20; ++i) // 3 leves per group, 1 label for group + { + string? leveName = atkValues[626 + i * 2].ReadAtkString(); + if (leveName == null) + continue; + + var questInfo = availableLeves.FirstOrDefault(x => GameFunctions.GameStringEquals(x.Name, leveName)); + if (questInfo == null) + continue; + + offeredLeves.Add((i, questInfo)); + + } + + foreach (var (i, questInfo) in offeredLeves) + _logger.LogInformation("Leve {Index} = {Id}, {Name}", i, questInfo.QuestId, questInfo.Name); + */ + + _framework.RunOnTick(() => AcceptLeveOrWait(nextQuest), TimeSpan.FromMilliseconds(100)); + } + } + + private unsafe void AcceptLeveOrWait(QuestController.QuestProgress nextQuest, int counter = 0) + { + var agent = UIModule.Instance()->GetAgentModule()->GetAgentByInternalId(AgentId.LeveQuest); + if (agent->IsAgentActive() && + _gameGui.TryGetAddonByName("GuildLeve", out AddonGuildLeve* addonGuildLeve) && + LAddon.IsAddonReady(&addonGuildLeve->AtkUnitBase) && + _gameGui.TryGetAddonByName("JournalDetail", out AtkUnitBase* addonJournalDetail) && + LAddon.IsAddonReady(addonJournalDetail)) + { + AcceptLeve(agent, addonGuildLeve, nextQuest); + } + else if (counter >= 10) + _logger.LogWarning("Unable to accept leve?"); + else + _framework.RunOnTick(() => AcceptLeveOrWait(nextQuest, counter + 1), TimeSpan.FromMilliseconds(100)); + } + + private unsafe void AcceptLeve(AgentInterface* agent, AddonGuildLeve* addon, + QuestController.QuestProgress nextQuest) + { + _questController.SetPendingQuest(nextQuest); + _questController.SetNextQuest(null); + + var returnValue = stackalloc AtkValue[1]; + var selectQuest = stackalloc AtkValue[] + { + new() { Type = ValueType.Int, Int = 3 }, + new() { Type = ValueType.UInt, UInt = nextQuest.Quest.Id.Value } + }; + agent->ReceiveEvent(returnValue, selectQuest, 2, 0); + addon->Close(true); + } + + public void Dispose() + { + _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "GuildLeve", GuildLevePostSetup); + _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "JournalResult", JournalResultPostSetup); + } +} diff --git a/Questionable/Controller/MiniTaskController.cs b/Questionable/Controller/MiniTaskController.cs index 4d19d73b..63dfba10 100644 --- a/Questionable/Controller/MiniTaskController.cs +++ b/Questionable/Controller/MiniTaskController.cs @@ -81,7 +81,7 @@ internal abstract class MiniTaskController while (_taskQueue.TryDequeue(out ITask? nextTask)) { - if (nextTask is ILastTask) + if (nextTask is ILastTask or GatheringRequiredItems.SkipMarker) { _currentTask = nextTask; return; diff --git a/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs b/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs index 9fdf9033..9318ce13 100644 --- a/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs +++ b/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs @@ -68,7 +68,9 @@ internal static class GatheringRequiredItems { foreach (var task in serviceProvider.GetRequiredService() .CreateTasks(quest, gatheringSequence, gatheringStep)) - if (task is not WaitAtEnd.NextStep) + if (task is WaitAtEnd.NextStep) + yield return serviceProvider.GetRequiredService(); + else yield return task; } } @@ -143,4 +145,13 @@ internal static class GatheringRequiredItems $"Gather({_gatheredItem.ItemCount}x {_gatheredItem.ItemId} {SeIconChar.Collectible.ToIconString()} {_gatheredItem.Collectability})"; } } + + /// + /// A task that does nothing, but if we're skipping a step, this will be the task next in queue to be executed (instead of progressing to the next step) if gathering. + /// + internal sealed class SkipMarker : ITask + { + public bool Start() => true; + public ETaskResult Update() => ETaskResult.TaskComplete; + } } diff --git a/Questionable/DalamudInitializer.cs b/Questionable/DalamudInitializer.cs index 7cd37391..f510fb71 100644 --- a/Questionable/DalamudInitializer.cs +++ b/Questionable/DalamudInitializer.cs @@ -6,6 +6,7 @@ using Dalamud.Plugin; using Dalamud.Plugin.Services; using Microsoft.Extensions.Logging; using Questionable.Controller; +using Questionable.Controller.GameUi; using Questionable.Windows; namespace Questionable; @@ -27,7 +28,7 @@ internal sealed class DalamudInitializer : IDisposable IFramework framework, QuestController questController, MovementController movementController, - GameUiController gameUiController, + InteractionUiController interactionUiController, WindowSystem windowSystem, QuestWindow questWindow, DebugOverlay debugOverlay, @@ -59,7 +60,7 @@ internal sealed class DalamudInitializer : IDisposable _pluginInterface.UiBuilder.OpenMainUi += _questWindow.Toggle; _pluginInterface.UiBuilder.OpenConfigUi += _configWindow.Toggle; _framework.Update += FrameworkUpdate; - _framework.RunOnTick(gameUiController.HandleCurrentDialogueChoices, TimeSpan.FromMilliseconds(200)); + _framework.RunOnTick(interactionUiController.HandleCurrentDialogueChoices, TimeSpan.FromMilliseconds(200)); _toastGui.Toast += OnToast; _toastGui.ErrorToast += OnErrorToast; _toastGui.QuestToast += OnQuestToast; diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index 8c3a1816..77440ad6 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Controller; using Questionable.Controller.CombatModules; +using Questionable.Controller.GameUi; using Questionable.Controller.NavigationOverrides; using Questionable.Controller.Steps; using Questionable.Controller.Steps.Shared; @@ -48,7 +49,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin IAddonLifecycle addonLifecycle, IKeyState keyState, IContextMenu contextMenu, - IToastGui toastGui) + IToastGui toastGui, + IGameInteropProvider gameInteropProvider) { ArgumentNullException.ThrowIfNull(pluginInterface); ArgumentNullException.ThrowIfNull(chatGui); @@ -75,6 +77,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(keyState); serviceCollection.AddSingleton(contextMenu); serviceCollection.AddSingleton(toastGui); + serviceCollection.AddSingleton(gameInteropProvider); serviceCollection.AddSingleton(new WindowSystem(nameof(Questionable))); serviceCollection.AddSingleton((Configuration?)pluginInterface.GetPluginConfig() ?? new Configuration()); @@ -131,7 +134,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin // task factories serviceCollection.AddTaskWithFactory(); serviceCollection.AddSingleton(); - serviceCollection.AddTaskWithFactory(); + serviceCollection.AddTaskWithFactory(); serviceCollection.AddTaskWithFactory(); serviceCollection.AddTaskWithFactory(); serviceCollection.AddTaskWithFactory(); @@ -184,11 +187,16 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); } @@ -231,6 +239,10 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceProvider.GetRequiredService().Reload(); serviceProvider.GetRequiredService(); serviceProvider.GetRequiredService(); + serviceProvider.GetRequiredService(); + serviceProvider.GetRequiredService(); + serviceProvider.GetRequiredService(); + serviceProvider.GetRequiredService(); serviceProvider.GetRequiredService(); } diff --git a/Questionable/Validation/QuestValidator.cs b/Questionable/Validation/QuestValidator.cs index c91f0df2..80050d40 100644 --- a/Questionable/Validation/QuestValidator.cs +++ b/Questionable/Validation/QuestValidator.cs @@ -51,12 +51,14 @@ internal sealed class QuestValidator { foreach (var issue in validator.Validate(quest)) { + /* var level = issue.Severity == EIssueSeverity.Error ? LogLevel.Warning : LogLevel.Debug; _logger.Log(level, "Validation failed: {QuestId} ({QuestName}) / {QuestSequence} / {QuestStep} - {Description}", issue.ElementId, quest.Info.Name, issue.Sequence, issue.Step, issue.Description); + */ if (issue.Type == EIssueType.QuestDisabled && quest.Info.AlliedSociety != EAlliedSociety.None) { disabledTribeQuests.TryAdd(quest.Info.AlliedSociety, 0); diff --git a/Questionable/Windows/QuestSelectionWindow.cs b/Questionable/Windows/QuestSelectionWindow.cs index 48e0b333..ba9b5ee1 100644 --- a/Questionable/Windows/QuestSelectionWindow.cs +++ b/Questionable/Windows/QuestSelectionWindow.cs @@ -16,6 +16,7 @@ using ImGuiNET; using LLib.GameUI; using LLib.ImGui; using Questionable.Controller; +using Questionable.Controller.GameUi; using Questionable.Data; using Questionable.Functions; using Questionable.Model; @@ -88,7 +89,7 @@ internal sealed class QuestSelectionWindow : LWindow _quests = _questData.GetAllByIssuerDataId(targetId); if (_gameGui.TryGetAddonByName("SelectIconString", out var addonSelectIconString)) { - var answers = GameUiController.GetChoices(addonSelectIconString); + var answers = InteractionUiController.GetChoices(addonSelectIconString); _offeredQuests = _quests .Where(x => answers.Any(y => GameFunctions.GameStringEquals(x.Name, y))) .ToList(); diff --git a/Questionable/Windows/QuestWindow.cs b/Questionable/Windows/QuestWindow.cs index b5c30076..296f0ac5 100644 --- a/Questionable/Windows/QuestWindow.cs +++ b/Questionable/Windows/QuestWindow.cs @@ -7,6 +7,7 @@ using Dalamud.Plugin.Services; using ImGuiNET; using LLib.ImGui; using Questionable.Controller; +using Questionable.Controller.GameUi; using Questionable.Data; using Questionable.Windows.QuestComponents; @@ -27,7 +28,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig private readonly QuickAccessButtonsComponent _quickAccessButtonsComponent; private readonly RemainingTasksComponent _remainingTasksComponent; private readonly IFramework _framework; - private readonly GameUiController _gameUiController; + private readonly InteractionUiController _interactionUiController; private readonly TitleBarButton _minimizeButton; public QuestWindow(IDalamudPluginInterface pluginInterface, @@ -41,7 +42,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig QuickAccessButtonsComponent quickAccessButtonsComponent, RemainingTasksComponent remainingTasksComponent, IFramework framework, - GameUiController gameUiController) + InteractionUiController interactionUiController) : base($"Questionable v{PluginVersion.ToString(2)}###Questionable", ImGuiWindowFlags.AlwaysAutoResize) { @@ -56,7 +57,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig _quickAccessButtonsComponent = quickAccessButtonsComponent; _remainingTasksComponent = remainingTasksComponent; _framework = framework; - _gameUiController = gameUiController; + _interactionUiController = interactionUiController; #if DEBUG IsOpen = true; @@ -152,7 +153,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig internal void Reload() { _questController.Reload(); - _framework.RunOnTick(() => _gameUiController.HandleCurrentDialogueChoices(), + _framework.RunOnTick(() => _interactionUiController.HandleCurrentDialogueChoices(), TimeSpan.FromMilliseconds(200)); } }