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