using System; using Dalamud.Game.Gui.ContextMenu; using Dalamud.Game.Text; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using LLib.GameData; using LLib.GameUI; using LLib.Shop; using Microsoft.Extensions.Logging; using Questionable.Controller.CustomDelivery; using Questionable.Data; using Questionable.Functions; using Questionable.Model.Gathering; namespace Questionable.Controller; internal sealed class ContextMenuController : IDisposable { private readonly IContextMenu _contextMenu; private readonly QuestController _questController; private readonly CustomDeliveryController _customDeliveryController; private readonly FateController _fateController; private readonly SeasonalDutyController _seasonalDutyController; private readonly AttunementController _attunementController; private readonly GatheringPointRegistry _gatheringPointRegistry; private readonly GatheringData _gatheringData; private readonly GameFunctions _gameFunctions; private readonly QuestFunctions _questFunctions; private readonly IGameGui _gameGui; private readonly IChatGui _chatGui; private readonly ILogger _logger; public ContextMenuController(IContextMenu contextMenu, QuestController questController, CustomDeliveryController customDeliveryController, FateController fateController, SeasonalDutyController seasonalDutyController, AttunementController attunementController, GatheringPointRegistry gatheringPointRegistry, GatheringData gatheringData, GameFunctions gameFunctions, QuestFunctions questFunctions, IGameGui gameGui, IChatGui chatGui, ILogger logger) { _contextMenu = contextMenu; _questController = questController; _customDeliveryController = customDeliveryController; _fateController = fateController; _seasonalDutyController = seasonalDutyController; _attunementController = attunementController; _gatheringPointRegistry = gatheringPointRegistry; _gatheringData = gatheringData; _gameFunctions = gameFunctions; _questFunctions = questFunctions; _gameGui = gameGui; _chatGui = chatGui; _logger = logger; _contextMenu.OnMenuOpened += MenuOpened; } private void MenuOpened(IMenuOpenedArgs args) { if (args.AddonName != null) { return; } uint num = GetHoveredSatisfactionSupplyItemId(); if (num == 0) { _logger.LogTrace("Ignoring context menu, no item hovered"); return; } if (num > 1000000) { num -= 1000000; } if (num >= 500000) { num -= 500000; } if (_gatheringData.TryGetCustomDeliveryNpc(num, out var _)) { AddContextMenuEntry(args, num); return; } _logger.LogDebug("No custom delivery NPC found for item {ItemId}.", num); } private unsafe uint GetHoveredSatisfactionSupplyItemId() { AgentSatisfactionSupply* ptr = AgentSatisfactionSupply.Instance(); if (ptr == null || !ptr->IsAgentActive()) { return 0u; } if (_gameGui.TryGetAddonByName("SatisfactionSupply", out var addonPtr) && LAddon.IsAddonReady(&addonPtr->AtkUnitBase)) { int hoveredElementIndex = addonPtr->HoveredElementIndex; if (hoveredElementIndex >= 0 && hoveredElementIndex <= 2) { return ptr->Items[addonPtr->HoveredElementIndex].Id; } } return 0u; } private unsafe void AddContextMenuEntry(IMenuOpenedArgs args, uint itemId) { GatheringPointId gatheringPointId; bool flag = _gatheringPointRegistry.TryGetGatheringPointId(itemId, EClassJob.Miner, out gatheringPointId); bool flag2 = _gatheringPointRegistry.TryGetGatheringPointId(itemId, EClassJob.Botanist, out gatheringPointId); if (!flag && !flag2) { _logger.LogDebug("No gathering point found for item {ItemId}.", itemId); } else { if (_gatheringData.GetRecommendedCollectability(itemId) == 0) { return; } AgentSatisfactionSupply* ptr = AgentSatisfactionSupply.Instance(); if (ptr != null && ptr->IsAgentActive()) { int npcIndex = (int)(ptr->NpcInfo.Id - 1); SatisfactionSupplyActions.NpcState npcState = SatisfactionSupplyActions.GetNpcState(npcIndex); int maxTurnIns = ((ptr->NpcInfo.SatisfactionRank == 1) ? 3 : 6); int num = Math.Min(ptr->NpcData.RemainingAllowances, SatisfactionSupplyHelper.CalculateTurnInsToNextRank((ushort)npcState.SatisfactionCurrent, (ushort)npcState.SatisfactionMax, maxTurnIns)); bool num2 = (flag && _questFunctions.IsClassJobUnlocked(EClassJob.Miner)) || (flag2 && _questFunctions.IsClassJobUnlocked(EClassJob.Botanist)); string text = string.Empty; if (!num2) { text = (flag ? "Miner not unlocked" : "Botanist not unlocked"); } else if (num == 0) { text = "No allowances"; } else if (num > _gameFunctions.GetFreeInventorySlots()) { text = "Inventory full"; } else if (_gameFunctions.IsOccupied()) { text = "Can't be used while interacting"; } else if (_customDeliveryController.IsRunning) { text = "Delivery already in progress"; } else if (_questController.IsRunning || _fateController.IsRunning || _seasonalDutyController.IsRunning || _attunementController.IsRunning) { text = "Automation is running"; } string text2 = "Deliver with Questionable"; if (!string.IsNullOrEmpty(text)) { text2 = text2 + " (" + text + ")"; } args.AddMenuItem(new MenuItem { Prefix = SeIconChar.Hyadelyn, PrefixColor = 52, Name = text2, OnClicked = delegate { StartDelivery(npcIndex); }, IsEnabled = string.IsNullOrEmpty(text) }); } } } private void StartDelivery(int npcIndex) { _questController.Stop("Custom delivery start"); _fateController.Stop("Custom delivery start"); _seasonalDutyController.Stop("Custom delivery start"); _attunementController.Stop("Custom delivery start"); if (_customDeliveryController.State != CustomDeliveryController.EDeliveryState.Idle) { _customDeliveryController.Stop("Custom delivery start"); } if (!_customDeliveryController.Start(npcIndex, EDeliverySlot.Gather)) { _chatGui.PrintError("Unable to start custom delivery.", "Questionable", 576); } } public void Dispose() { _contextMenu.OnMenuOpened -= MenuOpened; } }