using System; using System.Linq; using System.Numerics; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Component.GUI; using LLib.GameUI; using LLib.Shop; using LLib.Shop.Model; using Microsoft.Extensions.Logging; using Questionable.Model.Questing; namespace Questionable.Controller.GameUi; internal sealed class ShopController : IDisposable, IShopWindow { private readonly QuestController _questController; private readonly IGameGui _gameGui; private readonly IFramework _framework; private readonly RegularShopBase _shop; private readonly ILogger _logger; public bool IsEnabled => _questController.IsRunning; public bool IsOpen { get; set; } public bool IsAutoBuyEnabled => _shop.AutoBuyEnabled; public bool IsAwaitingYesNo { get { return _shop.IsAwaitingYesNo; } set { _shop.IsAwaitingYesNo = value; } } public Vector2? Position { get; set; } public ShopController(QuestController questController, IGameGui gameGui, IAddonLifecycle addonLifecycle, IFramework framework, ILogger logger, IPluginLog pluginLog) { _questController = questController; _gameGui = gameGui; _framework = framework; _shop = new RegularShopBase(this, "Shop", pluginLog, gameGui, addonLifecycle); _logger = logger; _framework.Update += FrameworkUpdate; } public void Dispose() { _framework.Update -= FrameworkUpdate; _shop.Dispose(); } private void FrameworkUpdate(IFramework framework) { if (!IsOpen || _shop.ItemForSale == null) { return; } if (_shop.PurchaseState != null) { _shop.HandleNextPurchaseStep(); return; } QuestStep questStep = FindCurrentStep(); if (questStep != null && questStep.InteractionType == EInteractionType.PurchaseItem) { int num = Math.Max(0, questStep.ItemCount.GetValueOrDefault() - (int)_shop.ItemForSale.OwnedItems); if (Math.Min(_shop.GetMaxItemsToPurchase(), num) > 0) { _logger.LogDebug("Auto-buying {MissingItems} {ItemName}", num, _shop.ItemForSale.ItemName); _shop.StartAutoPurchase(num); _shop.HandleNextPurchaseStep(); } else { _shop.CancelAutoPurchase(); } } } public int GetCurrencyCount() { return _shop.GetItemCount(1u); } private QuestStep? FindCurrentStep() { QuestController.QuestProgress currentQuest = _questController.CurrentQuest; return (currentQuest?.Quest.FindSequence(currentQuest.Sequence))?.FindStep(currentQuest?.Step ?? 0); } public unsafe void UpdateShopStock(AtkUnitBase* addon) { QuestStep currentStep = FindCurrentStep(); if (currentStep == null || currentStep.InteractionType != EInteractionType.PurchaseItem) { _shop.ItemForSale = null; return; } if (addon->AtkValuesCount != 625) { _logger.LogError("Unexpected amount of atkvalues for Shop addon ({AtkValueCount})", addon->AtkValuesCount); _shop.ItemForSale = null; return; } AtkValue* atkValues = addon->AtkValues; if (atkValues->UInt != 0) { _shop.ItemForSale = null; return; } uint uInt = atkValues[2].UInt; if (uInt == 0) { _shop.ItemForSale = null; return; } _shop.ItemForSale = (from i in Enumerable.Range(0, (int)uInt) select new ItemForSale { Position = i, ItemName = atkValues[14 + i].ReadAtkString(), Price = atkValues[75 + i].UInt, OwnedItems = atkValues[136 + i].UInt, ItemId = atkValues[441 + i].UInt }).FirstOrDefault((ItemForSale x) => x.ItemId == currentStep.ItemId); } public unsafe void TriggerPurchase(AtkUnitBase* addonShop, int buyNow) { AtkValue* values = stackalloc AtkValue[4] { new AtkValue { Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int, Int = 0 }, new AtkValue { Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int, Int = _shop.ItemForSale.Position }, new AtkValue { Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int, Int = buyNow }, new AtkValue { Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Undefined, Int = 0 } }; addonShop->FireCallback(4u, values); } public void SaveExternalPluginState() { } public unsafe void RestoreExternalPluginState() { if (_gameGui.TryGetAddonByName("Shop", out var addonPtr)) { addonPtr->FireCallbackInt(-1); } } }