qstbak/Questionable/Questionable.Controller/ContextMenuController.cs
2025-10-09 07:47:19 +10:00

217 lines
6.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
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 Microsoft.Extensions.Logging;
using Questionable.Data;
using Questionable.Functions;
using Questionable.GameStructs;
using Questionable.Model;
using Questionable.Model.Gathering;
using Questionable.Model.Questing;
namespace Questionable.Controller;
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;
private readonly GameFunctions _gameFunctions;
private readonly QuestFunctions _questFunctions;
private readonly IGameGui _gameGui;
private readonly IChatGui _chatGui;
private readonly IClientState _clientState;
private readonly ILogger<ContextMenuController> _logger;
public ContextMenuController(IContextMenu contextMenu, QuestController questController, GatheringPointRegistry gatheringPointRegistry, GatheringData gatheringData, QuestRegistry questRegistry, QuestData questData, GameFunctions gameFunctions, QuestFunctions questFunctions, IGameGui gameGui, IChatGui chatGui, IClientState clientState, ILogger<ContextMenuController> logger)
{
_contextMenu = contextMenu;
_questController = questController;
_gatheringPointRegistry = gatheringPointRegistry;
_gatheringData = gatheringData;
_questRegistry = questRegistry;
_questData = questData;
_gameFunctions = gameFunctions;
_questFunctions = questFunctions;
_gameGui = gameGui;
_chatGui = chatGui;
_clientState = clientState;
_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 npcId))
{
AddContextMenuEntry(args, num, npcId, EClassJob.Miner, "Mine");
AddContextMenuEntry(args, num, npcId, EClassJob.Botanist, "Harvest");
}
else
{
_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<AddonSatisfactionSupply>("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, uint npcId, EClassJob classJob, string verb)
{
EClassJob rowId = (EClassJob)_clientState.LocalPlayer.ClassJob.RowId;
bool flag = classJob != rowId;
if (flag)
{
bool flag2 = rowId - 16 <= EClassJob.Gladiator;
flag = flag2;
}
if (flag)
{
return;
}
if (!_gatheringPointRegistry.TryGetGatheringPointId(itemId, classJob, out GatheringPointId _))
{
_logger.LogInformation("No gathering point found for {ClassJob}.", classJob);
return;
}
ushort collectability = _gatheringData.GetRecommendedCollectability(itemId);
int quantityToGather = ((collectability > 0) ? 6 : int.MaxValue);
if (collectability != 0)
{
AgentSatisfactionSupply* ptr = AgentSatisfactionSupply.Instance();
if (ptr->IsAgentActive())
{
int maxTurnIns = ((ptr->NpcInfo.SatisfactionRank == 1) ? 3 : 6);
quantityToGather = Math.Min(ptr->NpcData.RemainingAllowances, ((AgentSatisfactionSupply2*)ptr)->CalculateTurnInsToNextRank(maxTurnIns));
}
string text = string.Empty;
if (!_questFunctions.IsClassJobUnlocked(classJob))
{
text = $"{classJob} not unlocked";
}
else if (quantityToGather == 0)
{
text = "No allowances";
}
else if (quantityToGather > _gameFunctions.GetFreeInventorySlots())
{
text = "Inventory full";
}
else if (_gameFunctions.IsOccupied())
{
text = "Can't be used while interacting";
}
string text2 = verb + " with Questionable";
if (!string.IsNullOrEmpty(text))
{
text2 = text2 + " (" + text + ")";
}
args.AddMenuItem(new MenuItem
{
Prefix = SeIconChar.Hyadelyn,
PrefixColor = 52,
Name = text2,
OnClicked = delegate
{
StartGathering(npcId, itemId, quantityToGather, collectability, classJob);
},
IsEnabled = string.IsNullOrEmpty(text)
});
}
}
private void StartGathering(uint npcId, uint itemId, int quantity, ushort collectability, EClassJob classJob)
{
SatisfactionSupplyInfo satisfactionSupplyInfo = (SatisfactionSupplyInfo)_questData.GetAllByIssuerDataId(npcId).Single((IQuestInfo x) => x is SatisfactionSupplyInfo);
if (_questRegistry.TryGetQuest(satisfactionSupplyInfo.QuestId, out Quest quest))
{
QuestSequence questSequence = quest.FindSequence(0);
QuestStep questStep = questSequence.Steps.Single((QuestStep x) => x.InteractionType == EInteractionType.SwitchClass);
questStep.TargetClass = classJob switch
{
EClassJob.Miner => EExtendedClassJob.Miner,
EClassJob.Botanist => EExtendedClassJob.Botanist,
_ => throw new ArgumentOutOfRangeException("classJob", classJob, null),
};
QuestStep questStep2 = questSequence.Steps.Single((QuestStep x) => x.InteractionType == EInteractionType.Gather);
int num = 1;
List<GatheredItem> list = new List<GatheredItem>(num);
CollectionsMarshal.SetCount(list, num);
Span<GatheredItem> span = CollectionsMarshal.AsSpan(list);
int index = 0;
span[index] = new GatheredItem
{
ItemId = itemId,
ItemCount = quantity,
Collectability = collectability
};
questStep2.ItemsToGather = list;
_questController.SetGatheringQuest(quest);
_questController.StartGatheringQuest("SatisfactionSupply prepare gathering");
}
else
{
_chatGui.PrintError($"No associated quest ({satisfactionSupplyInfo.QuestId}).", "Questionable");
}
}
public void Dispose()
{
_contextMenu.OnMenuOpened -= MenuOpened;
}
}