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 0a82115..3c781fb 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 cf1dd6f..cefc63e 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 52da35e..77383d6 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 153dc2b..68b982b 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 74b5fd4..950f705 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 8204215..8604302 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 45c007c..bc54655 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 41a6631..105911d 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);