Add GatheringLocation ExtraQuestItems to configure extra items that aren't in the...
authorLiza Carvelli <liza@carvel.li>
Mon, 10 Mar 2025 16:54:19 +0000 (17:54 +0100)
committerLiza Carvelli <liza@carvel.li>
Mon, 10 Mar 2025 16:54:19 +0000 (17:54 +0100)
GatheringPaths/gatheringlocation-v1.json
QuestPathGenerator/GatheringSourceGenerator.cs
Questionable.Model/Gathering/GatheringRoot.cs
Questionable/Controller/ContextMenuController.cs
Questionable/Controller/GameUi/InteractionUiController.cs
Questionable/Controller/GatheringPointRegistry.cs
Questionable/Controller/Steps/Shared/Gather.cs
Questionable/Data/GatheringData.cs

index 0a821158b881b2652444fd2b393215b96a3ee6e5..3c781fbeb811ec82654c9d06e3cbfbe9f1e767e2 100644 (file)
       "type": "boolean",
       "default": true
     },
+    "ExtraQuestItems": {
+      "description": "Some quests (such as Ixal) add quest items to gathering nodes, but there's no clear connection between the item and the node in the sheets",
+      "type": "array",
+      "items": {
+        "type": "integer",
+        "minimum": 2000000
+      }
+    },
     "Groups": {
       "type": "array",
       "items": {
index cf1dd6f038a05d457370feb6ac8ff1af4af8a529..cefc63e7a3feec3dfabec36ab8bae5beefdf784f 100644 (file)
@@ -164,6 +164,7 @@ public class GatheringSourceGenerator : ISourceGenerator
                                 Assignment(nameof(GatheringRoot.FlyBetweenNodes), root.FlyBetweenNodes,
                                         emptyRoot.FlyBetweenNodes)
                                     .AsSyntaxNodeOrToken(),
+                                AssignmentList(nameof(GatheringRoot.ExtraQuestItems), root.ExtraQuestItems).AsSyntaxNodeOrToken(),
                                 AssignmentList(nameof(GatheringRoot.Groups), root.Groups).AsSyntaxNodeOrToken()))));
         }
         catch (Exception e)
index 52da35eb24774eee7a754ea9f8ee292bf80dc02a..77383d6db1399eb0313cbb5edfad1568754bcbf8 100644 (file)
@@ -14,5 +14,6 @@ public sealed class GatheringRoot
 
     public List<QuestStep> Steps { get; set; } = [];
     public bool? FlyBetweenNodes { get; set; }
+    public List<uint> ExtraQuestItems { get; set; } = [];
     public List<GatheringNodeGroup> Groups { get; set; } = [];
 }
index 153dc2bf348f5f38d23991c1412829d286a5647e..68b982bab5c25f25bf040ff89c8fdb69dcceff03 100644 (file)
@@ -21,6 +21,7 @@ internal sealed class ContextMenuController : IDisposable
 {
     private readonly IContextMenu _contextMenu;
     private readonly QuestController _questController;
+    private readonly GatheringPointRegistry _gatheringPointRegistry;
     private readonly GatheringData _gatheringData;
     private readonly QuestRegistry _questRegistry;
     private readonly QuestData _questData;
@@ -34,6 +35,7 @@ internal sealed class ContextMenuController : IDisposable
     public ContextMenuController(
         IContextMenu contextMenu,
         QuestController questController,
+        GatheringPointRegistry gatheringPointRegistry,
         GatheringData gatheringData,
         QuestRegistry questRegistry,
         QuestData questData,
@@ -46,6 +48,7 @@ internal sealed class ContextMenuController : IDisposable
     {
         _contextMenu = contextMenu;
         _questController = questController;
+        _gatheringPointRegistry = gatheringPointRegistry;
         _gatheringData = gatheringData;
         _questRegistry = questRegistry;
         _questData = questData;
@@ -112,7 +115,7 @@ internal sealed class ContextMenuController : IDisposable
         if (classJob != currentClassJob && currentClassJob is EClassJob.Miner or EClassJob.Botanist)
             return;
 
-        if (!_gatheringData.TryGetGatheringPointId(itemId, classJob, out _))
+        if (!_gatheringPointRegistry.TryGetGatheringPointId(itemId, classJob, out _))
         {
             _logger.LogInformation("No gathering point found for {ClassJob}.", classJob);
             return;
index 74b5fd4ae70785fa40f31d834b5f2f67f9912001..950f7059efb754b02088c261971f8338fdf04f36 100644 (file)
@@ -37,7 +37,6 @@ internal sealed class InteractionUiController : IDisposable
     private readonly AetheryteFunctions _aetheryteFunctions;
     private readonly ExcelFunctions _excelFunctions;
     private readonly QuestController _questController;
-    private readonly GatheringData _gatheringData;
     private readonly GatheringPointRegistry _gatheringPointRegistry;
     private readonly QuestRegistry _questRegistry;
     private readonly QuestData _questData;
@@ -61,7 +60,6 @@ internal sealed class InteractionUiController : IDisposable
         AetheryteFunctions aetheryteFunctions,
         ExcelFunctions excelFunctions,
         QuestController questController,
-        GatheringData gatheringData,
         GatheringPointRegistry gatheringPointRegistry,
         QuestRegistry questRegistry,
         QuestData questData,
@@ -81,7 +79,6 @@ internal sealed class InteractionUiController : IDisposable
         _aetheryteFunctions = aetheryteFunctions;
         _excelFunctions = excelFunctions;
         _questController = questController;
-        _gatheringData = gatheringData;
         _gatheringPointRegistry = gatheringPointRegistry;
         _questRegistry = questRegistry;
         _questData = questData;
@@ -791,7 +788,7 @@ internal sealed class InteractionUiController : IDisposable
         if (step != null && (step.TerritoryId != _clientState.TerritoryType || step.TargetTerritoryId == null) &&
             step.InteractionType == EInteractionType.Gather)
         {
-            if (_gatheringData.TryGetGatheringPointId(step.ItemsToGather[0].ItemId,
+            if (_gatheringPointRegistry.TryGetGatheringPointId(step.ItemsToGather[0].ItemId,
                     (EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId ?? EClassJob.Adventurer,
                     out GatheringPointId? gatheringPointId) &&
                 _gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot? root))
index 8204215171a497adea07f3be64e94f5b67e9810b..86043020004af76daa980b73acfdc12956e982ce 100644 (file)
@@ -3,9 +3,12 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
+using System.Linq;
 using System.Text.Json;
 using Dalamud.Plugin;
+using LLib.GameData;
 using Microsoft.Extensions.Logging;
+using Questionable.Data;
 using Questionable.GatheringPaths;
 using Questionable.Model;
 using Questionable.Model.Gathering;
@@ -16,15 +19,19 @@ internal sealed class GatheringPointRegistry : IDisposable
 {
     private readonly IDalamudPluginInterface _pluginInterface;
     private readonly QuestRegistry _questRegistry;
+    private readonly GatheringData _gatheringData;
     private readonly ILogger<QuestRegistry> _logger;
 
     private readonly Dictionary<GatheringPointId, GatheringRoot> _gatheringPoints = new();
 
-    public GatheringPointRegistry(IDalamudPluginInterface pluginInterface, QuestRegistry questRegistry,
+    public GatheringPointRegistry(IDalamudPluginInterface pluginInterface,
+        QuestRegistry questRegistry,
+        GatheringData gatheringData,
         ILogger<QuestRegistry> logger)
     {
         _pluginInterface = pluginInterface;
         _questRegistry = questRegistry;
+        _gatheringData = gatheringData;
         _logger = logger;
 
         _questRegistry.Reloaded += OnReloaded;
@@ -141,6 +148,38 @@ internal sealed class GatheringPointRegistry : IDisposable
     public bool TryGetGatheringPoint(GatheringPointId gatheringPointId, [NotNullWhen(true)] out GatheringRoot? gatheringRoot)
         => _gatheringPoints.TryGetValue(gatheringPointId, out gatheringRoot);
 
+    public bool TryGetGatheringPointId(uint itemId, EClassJob classJobId,
+        [NotNullWhen(true)] out GatheringPointId? gatheringPointId)
+    {
+        if (classJobId == EClassJob.Miner)
+        {
+            if (_gatheringData.TryGetMinerGatheringPointByItemId(itemId, out gatheringPointId))
+                return true;
+
+            gatheringPointId = _gatheringPoints
+                .Where(x => x.Value.ExtraQuestItems.Contains(itemId))
+                .Select(x => x.Key)
+                .FirstOrDefault(x => _gatheringData.MinerGatheringPoints.Contains(x));
+            return gatheringPointId != null;
+        }
+        else if (classJobId == EClassJob.Botanist)
+        {
+            if (_gatheringData.TryGetBotanistGatheringPointByItemId(itemId, out gatheringPointId))
+                return true;
+
+            gatheringPointId = _gatheringPoints
+                .Where(x => x.Value.ExtraQuestItems.Contains(itemId))
+                .Select(x => x.Key)
+                .FirstOrDefault(x => _gatheringData.BotanistGatheringPoints.Contains(x));
+            return gatheringPointId != null;
+        }
+        else
+        {
+            gatheringPointId = null;
+            return false;
+        }
+    }
+
     public void Dispose()
     {
         _questRegistry.Reloaded -= OnReloaded;
index 45c007c429318f06e1d728e55af5c858f977aa2e..bc546559995657368a43152a15e6428f72cac3f1 100644 (file)
@@ -38,7 +38,6 @@ internal static class Gather
     }
 
     internal sealed class DelayedGatheringExecutor(
-        GatheringData gatheringData,
         GatheringPointRegistry gatheringPointRegistry,
         TerritoryData territoryData,
         IClientState clientState,
@@ -52,7 +51,7 @@ internal static class Gather
         public IEnumerable<ITask> CreateExtraTasks()
         {
             EClassJob currentClassJob = (EClassJob)clientState.LocalPlayer!.ClassJob.RowId;
-            if (!gatheringData.TryGetGatheringPointId(Task.GatheredItem.ItemId, currentClassJob,
+            if (!gatheringPointRegistry.TryGetGatheringPointId(Task.GatheredItem.ItemId, currentClassJob,
                     out GatheringPointId? gatheringPointId))
                 throw new TaskException($"No gathering point found for item {Task.GatheredItem.ItemId}");
 
index 41a6631b31e7303569074cebf68cbee60bcf4ffc..105911d5cd2ed3543c687040f8f378263cc69fb8 100644 (file)
@@ -62,19 +62,14 @@ internal sealed class GatheringData
             .ToDictionary(x => x.ItemId, x => x.NpcId);
     }
 
-    public bool TryGetGatheringPointId(uint itemId, EClassJob classJobId,
-        [NotNullWhen(true)] out GatheringPointId? gatheringPointId)
-    {
-        if (classJobId == EClassJob.Miner)
-            return _minerGatheringPoints.TryGetValue(itemId, out gatheringPointId);
-        else if (classJobId == EClassJob.Botanist)
-            return _botanistGatheringPoints.TryGetValue(itemId, out gatheringPointId);
-        else
-        {
-            gatheringPointId = null;
-            return false;
-        }
-    }
+    public IEnumerable<GatheringPointId> MinerGatheringPoints => _minerGatheringPoints.Values;
+    public IEnumerable<GatheringPointId> BotanistGatheringPoints => _botanistGatheringPoints.Values;
+
+    public bool TryGetMinerGatheringPointByItemId(uint itemId, [NotNullWhen(true)] out GatheringPointId? gatheringPointId)
+        => _minerGatheringPoints.TryGetValue(itemId, out gatheringPointId);
+
+    public bool TryGetBotanistGatheringPointByItemId(uint itemId, [NotNullWhen(true)] out GatheringPointId? gatheringPointId)
+        => _botanistGatheringPoints.TryGetValue(itemId, out gatheringPointId);
 
     public ushort GetRecommendedCollectability(uint itemId)
         => _itemIdToCollectability.GetValueOrDefault(itemId);