using System; using System.Numerics; using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Component.GUI; using LLib.GameUI; using LLib.Shop.Model; namespace LLib.Shop; public class RegularShopBase { private readonly IShopWindow _parentWindow; private readonly string _addonName; private readonly IPluginLog _pluginLog; private readonly IGameGui _gameGui; private readonly IAddonLifecycle _addonLifecycle; public ItemForSale? ItemForSale { get; set; } public PurchaseState? PurchaseState { get; private set; } public bool AutoBuyEnabled => PurchaseState != null; public bool IsAwaitingYesNo { get { return PurchaseState?.IsAwaitingYesNo ?? false; } set { PurchaseState.IsAwaitingYesNo = value; } } public RegularShopBase(IShopWindow parentWindow, string addonName, IPluginLog pluginLog, IGameGui gameGui, IAddonLifecycle addonLifecycle) { _parentWindow = parentWindow; _addonName = addonName; _pluginLog = pluginLog; _gameGui = gameGui; _addonLifecycle = addonLifecycle; _addonLifecycle.RegisterListener(AddonEvent.PostSetup, _addonName, ShopPostSetup); _addonLifecycle.RegisterListener(AddonEvent.PreFinalize, _addonName, ShopPreFinalize); _addonLifecycle.RegisterListener(AddonEvent.PostUpdate, _addonName, ShopPostUpdate); } private unsafe void ShopPostSetup(AddonEvent type, AddonArgs args) { if (!_parentWindow.IsEnabled) { ItemForSale = null; _parentWindow.IsOpen = false; return; } _parentWindow.UpdateShopStock((AtkUnitBase*)args.Addon.Address); PostUpdateShopStock(); if (ItemForSale != null) { _parentWindow.IsOpen = true; } } private void ShopPreFinalize(AddonEvent type, AddonArgs args) { PurchaseState = null; _parentWindow.RestoreExternalPluginState(); _parentWindow.IsOpen = false; } private unsafe void ShopPostUpdate(AddonEvent type, AddonArgs args) { if (!_parentWindow.IsEnabled) { ItemForSale = null; _parentWindow.IsOpen = false; return; } _parentWindow.UpdateShopStock((AtkUnitBase*)args.Addon.Address); PostUpdateShopStock(); if (ItemForSale != null) { AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address; short num = 0; short num2 = 0; address->GetPosition(&num, &num2); short num3 = 0; short num4 = 0; address->GetSize(&num3, &num4, scaled: true); num += num3; Vector2? position = _parentWindow.Position; if (position.HasValue) { Vector2 valueOrDefault = position.GetValueOrDefault(); if ((short)valueOrDefault.X != num || (short)valueOrDefault.Y != num2) { _parentWindow.Position = new Vector2(num, num2); } } _parentWindow.IsOpen = true; } else { _parentWindow.IsOpen = false; } } private void PostUpdateShopStock() { if (ItemForSale != null && PurchaseState != null) { int ownedItems = (int)ItemForSale.OwnedItems; if (PurchaseState.OwnedItems != ownedItems) { PurchaseState.OwnedItems = ownedItems; PurchaseState.NextStep = DateTime.Now.AddSeconds(0.25); } } } public unsafe int GetItemCount(uint itemId) { return InventoryManager.Instance()->GetInventoryItemCount(itemId, isHq: false, checkEquipped: false, checkArmory: false, 0); } public int GetMaxItemsToPurchase() { if (ItemForSale == null) { return 0; } return (int)(_parentWindow.GetCurrencyCount() / ItemForSale.Price); } public void CancelAutoPurchase() { PurchaseState = null; _parentWindow.RestoreExternalPluginState(); } public void StartAutoPurchase(int toPurchase) { PurchaseState = new PurchaseState((int)ItemForSale.OwnedItems + toPurchase, (int)ItemForSale.OwnedItems); _parentWindow.SaveExternalPluginState(); } public unsafe void HandleNextPurchaseStep() { if (ItemForSale == null || PurchaseState == null) { return; } int num = DetermineMaxStackSize(ItemForSale.ItemId); if (num == 0 && !HasFreeInventorySlot()) { _pluginLog.Warning("No free inventory slots, can't buy more " + ItemForSale.ItemName); PurchaseState = null; _parentWindow.RestoreExternalPluginState(); } else if (!PurchaseState.IsComplete) { if (PurchaseState.NextStep <= DateTime.Now && _gameGui.TryGetAddonByName(_addonName, out var addonPtr)) { int num2 = Math.Min(PurchaseState.ItemsLeftToBuy, num); _pluginLog.Information($"Buying {num2}x {ItemForSale.ItemName}"); _parentWindow.TriggerPurchase(addonPtr, num2); PurchaseState.NextStep = DateTime.MaxValue; PurchaseState.IsAwaitingYesNo = true; } } else { _pluginLog.Information($"Stopping item purchase (desired = {PurchaseState.DesiredItems}, owned = {PurchaseState.OwnedItems})"); PurchaseState = null; _parentWindow.RestoreExternalPluginState(); } } public void Dispose() { _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, _addonName, ShopPostSetup); _addonLifecycle.UnregisterListener(AddonEvent.PreFinalize, _addonName, ShopPreFinalize); _addonLifecycle.UnregisterListener(AddonEvent.PostUpdate, _addonName, ShopPostUpdate); } public bool HasFreeInventorySlot() { return CountFreeInventorySlots() > 0; } public unsafe int CountFreeInventorySlots() { InventoryManager* ptr = InventoryManager.Instance(); if (ptr == null) { return 0; } int num = 0; for (InventoryType inventoryType = InventoryType.Inventory1; inventoryType <= InventoryType.Inventory4; inventoryType++) { InventoryContainer* inventoryContainer = ptr->GetInventoryContainer(inventoryType); for (int i = 0; i < inventoryContainer->Size; i++) { InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(i); if (inventorySlot == null || inventorySlot->ItemId == 0) { num++; } } } return num; } private unsafe int DetermineMaxStackSize(uint itemId) { InventoryManager* ptr = InventoryManager.Instance(); if (ptr == null) { return 0; } int num = 0; for (InventoryType inventoryType = InventoryType.Inventory1; inventoryType <= InventoryType.Inventory4; inventoryType++) { InventoryContainer* inventoryContainer = ptr->GetInventoryContainer(inventoryType); for (int i = 0; i < inventoryContainer->Size; i++) { InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(i); if (inventorySlot == null || inventorySlot->ItemId == 0) { return 99; } if (inventorySlot->ItemId == itemId) { num += 999 - inventorySlot->Quantity; if (num >= 99) { break; } } } } return Math.Min(99, num); } public unsafe int CountInventorySlotsWithCondition(uint itemId, Predicate predicate) { ArgumentNullException.ThrowIfNull(predicate, "predicate"); InventoryManager* ptr = InventoryManager.Instance(); if (ptr == null) { return 0; } int num = 0; for (InventoryType inventoryType = InventoryType.Inventory1; inventoryType <= InventoryType.Inventory4; inventoryType++) { InventoryContainer* inventoryContainer = ptr->GetInventoryContainer(inventoryType); for (int i = 0; i < inventoryContainer->Size; i++) { InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(i); if (inventorySlot != null && inventorySlot->ItemId != 0 && inventorySlot->ItemId == itemId && predicate(inventorySlot->Quantity)) { num++; } } } return num; } }