forked from aly/qstbak
muffin v7.4.10
This commit is contained in:
parent
2df81c5d15
commit
b8dd142c23
47 changed files with 3604 additions and 1058 deletions
12
LLib/LLib.Shop.Model/GrandCompanyItem.cs
Normal file
12
LLib/LLib.Shop.Model/GrandCompanyItem.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
namespace LLib.Shop.Model;
|
||||
|
||||
public sealed class GrandCompanyItem
|
||||
{
|
||||
public required int Index { get; init; }
|
||||
|
||||
public required uint ItemId { get; init; }
|
||||
|
||||
public required uint IconId { get; init; }
|
||||
|
||||
public required uint SealCost { get; init; }
|
||||
}
|
||||
288
LLib/LLib.Shop/GrandCompanyShop.cs
Normal file
288
LLib/LLib.Shop/GrandCompanyShop.cs
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
using System;
|
||||
using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using LLib.GameUI;
|
||||
using LLib.Shop.Model;
|
||||
|
||||
namespace LLib.Shop;
|
||||
|
||||
public sealed class GrandCompanyShop : IDisposable
|
||||
{
|
||||
private sealed class GrandCompanyPurchaseRequest
|
||||
{
|
||||
public required uint ItemId { get; init; }
|
||||
|
||||
public required int RankTabIndex { get; init; }
|
||||
|
||||
public required int CategoryTabIndex { get; init; }
|
||||
}
|
||||
|
||||
private const string AddonName = "GrandCompanyExchange";
|
||||
|
||||
private const string SelectYesNoAddonName = "SelectYesno";
|
||||
|
||||
private readonly IPluginLog _pluginLog;
|
||||
|
||||
private readonly IGameGui _gameGui;
|
||||
|
||||
private readonly IAddonLifecycle _addonLifecycle;
|
||||
|
||||
private int _navigationStep;
|
||||
|
||||
private DateTime _lastActionTime = DateTime.MinValue;
|
||||
|
||||
private GrandCompanyPurchaseRequest? _pendingPurchase;
|
||||
|
||||
public bool IsOpen { get; private set; }
|
||||
|
||||
public bool IsPurchaseInProgress => _pendingPurchase != null;
|
||||
|
||||
public bool IsAwaitingConfirmation { get; private set; }
|
||||
|
||||
public event EventHandler<PurchaseCompletedEventArgs>? PurchaseCompleted;
|
||||
|
||||
public event EventHandler? ShopOpened;
|
||||
|
||||
public event EventHandler? ShopClosed;
|
||||
|
||||
public GrandCompanyShop(IPluginLog pluginLog, IGameGui gameGui, IAddonLifecycle addonLifecycle)
|
||||
{
|
||||
_pluginLog = pluginLog;
|
||||
_gameGui = gameGui;
|
||||
_addonLifecycle = addonLifecycle;
|
||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "GrandCompanyExchange", OnShopPostSetup);
|
||||
_addonLifecycle.RegisterListener(AddonEvent.PreFinalize, "GrandCompanyExchange", OnShopPreFinalize);
|
||||
_addonLifecycle.RegisterListener(AddonEvent.PostUpdate, "SelectYesno", OnSelectYesNoPostUpdate);
|
||||
}
|
||||
|
||||
private void OnShopPostSetup(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
IsOpen = true;
|
||||
_navigationStep = 0;
|
||||
_pluginLog.Debug("[GCShop] Shop opened");
|
||||
this.ShopOpened?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void OnShopPreFinalize(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
IsOpen = false;
|
||||
_pendingPurchase = null;
|
||||
IsAwaitingConfirmation = false;
|
||||
_pluginLog.Debug("[GCShop] Shop closed");
|
||||
this.ShopClosed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private unsafe void OnSelectYesNoPostUpdate(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
if (_pendingPurchase != null && IsAwaitingConfirmation)
|
||||
{
|
||||
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
|
||||
if (LAddon.IsAddonReady(address))
|
||||
{
|
||||
_pluginLog.Information("[GCShop] Confirming purchase dialog");
|
||||
ClickYes(address);
|
||||
address->Close(fireCallback: true);
|
||||
IsAwaitingConfirmation = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool StartPurchase(uint itemId, int rankTabIndex, int categoryTabIndex)
|
||||
{
|
||||
if (!IsOpen || _pendingPurchase != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_pendingPurchase = new GrandCompanyPurchaseRequest
|
||||
{
|
||||
ItemId = itemId,
|
||||
RankTabIndex = rankTabIndex,
|
||||
CategoryTabIndex = categoryTabIndex
|
||||
};
|
||||
_navigationStep = 0;
|
||||
_lastActionTime = DateTime.MinValue;
|
||||
_pluginLog.Information($"[GCShop] Starting purchase of item {itemId}");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CancelPurchase()
|
||||
{
|
||||
_pendingPurchase = null;
|
||||
IsAwaitingConfirmation = false;
|
||||
_navigationStep = 0;
|
||||
}
|
||||
|
||||
public unsafe bool ProcessPurchase()
|
||||
{
|
||||
if (_pendingPurchase == null || !IsOpen)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((DateTime.Now - _lastActionTime).TotalMilliseconds < 600.0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (InventoryManager.Instance()->GetInventoryItemCount(_pendingPurchase.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0)
|
||||
{
|
||||
_pluginLog.Information($"[GCShop] Item {_pendingPurchase.ItemId} already in inventory");
|
||||
uint itemId = _pendingPurchase.ItemId;
|
||||
_pendingPurchase = null;
|
||||
this.PurchaseCompleted?.Invoke(this, new PurchaseCompletedEventArgs(itemId, success: true));
|
||||
return true;
|
||||
}
|
||||
if (!_gameGui.TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonPtr) || !LAddon.IsAddonReady(addonPtr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
switch (_navigationStep)
|
||||
{
|
||||
case 0:
|
||||
FireCallback(addonPtr, 1, _pendingPurchase.RankTabIndex);
|
||||
_navigationStep++;
|
||||
_lastActionTime = DateTime.Now;
|
||||
break;
|
||||
case 1:
|
||||
FireCallback(addonPtr, 2, _pendingPurchase.CategoryTabIndex);
|
||||
_navigationStep++;
|
||||
_lastActionTime = DateTime.Now;
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
GrandCompanyItem grandCompanyItem = FindItemInShop(addonPtr, _pendingPurchase.ItemId);
|
||||
if (grandCompanyItem != null)
|
||||
{
|
||||
_pluginLog.Information($"[GCShop] Found item {grandCompanyItem.ItemId} at index {grandCompanyItem.Index}, purchasing...");
|
||||
FirePurchaseCallback(addonPtr, grandCompanyItem);
|
||||
IsAwaitingConfirmation = true;
|
||||
_navigationStep++;
|
||||
_lastActionTime = DateTime.Now;
|
||||
}
|
||||
else
|
||||
{
|
||||
_pluginLog.Warning($"[GCShop] Item {_pendingPurchase.ItemId} not found in shop");
|
||||
_lastActionTime = DateTime.Now;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
if (!IsAwaitingConfirmation && InventoryManager.Instance()->GetInventoryItemCount(_pendingPurchase.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0)
|
||||
{
|
||||
_pluginLog.Information($"[GCShop] Purchase of {_pendingPurchase.ItemId} completed");
|
||||
uint itemId2 = _pendingPurchase.ItemId;
|
||||
_pendingPurchase = null;
|
||||
_navigationStep = 0;
|
||||
this.PurchaseCompleted?.Invoke(this, new PurchaseCompletedEventArgs(itemId2, success: true));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public unsafe void CloseShop()
|
||||
{
|
||||
if (IsOpen && _gameGui.TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonPtr))
|
||||
{
|
||||
addonPtr->Close(fireCallback: true);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe static int GetCompanySeals()
|
||||
{
|
||||
return (int)InventoryManager.Instance()->GetCompanySeals(PlayerState.Instance()->GrandCompany);
|
||||
}
|
||||
|
||||
private unsafe static GrandCompanyItem? FindItemInShop(AtkUnitBase* addon, uint targetItemId)
|
||||
{
|
||||
int atkValueInt = GetAtkValueInt(addon, 1);
|
||||
if (atkValueInt <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
for (int i = 0; i < atkValueInt; i++)
|
||||
{
|
||||
int atkValueInt2 = GetAtkValueInt(addon, 317 + i);
|
||||
if (atkValueInt2 == (int)targetItemId)
|
||||
{
|
||||
return new GrandCompanyItem
|
||||
{
|
||||
Index = i,
|
||||
ItemId = (uint)atkValueInt2,
|
||||
IconId = (uint)GetAtkValueInt(addon, 167 + i),
|
||||
SealCost = (uint)GetAtkValueInt(addon, 67 + i)
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private unsafe void FireCallback(AtkUnitBase* addon, params int[] args)
|
||||
{
|
||||
AtkValue* ptr = stackalloc AtkValue[args.Length];
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
ptr[i].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
|
||||
ptr[i].Int = args[i];
|
||||
}
|
||||
addon->FireCallback((uint)args.Length, ptr);
|
||||
}
|
||||
|
||||
private unsafe void FirePurchaseCallback(AtkUnitBase* addon, GrandCompanyItem item)
|
||||
{
|
||||
AtkValue* ptr = stackalloc AtkValue[9];
|
||||
ptr->Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
|
||||
ptr->Int = 0;
|
||||
ptr[1].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
|
||||
ptr[1].Int = item.Index;
|
||||
ptr[2].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
|
||||
ptr[2].Int = 1;
|
||||
ptr[3].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
|
||||
ptr[3].Int = 0;
|
||||
ptr[4].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
|
||||
ptr[4].Int = 0;
|
||||
ptr[5].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
|
||||
ptr[5].Int = 0;
|
||||
ptr[6].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.UInt;
|
||||
ptr[6].UInt = item.ItemId;
|
||||
ptr[7].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.UInt;
|
||||
ptr[7].UInt = item.IconId;
|
||||
ptr[8].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.UInt;
|
||||
ptr[8].UInt = item.SealCost;
|
||||
addon->FireCallback(9u, ptr);
|
||||
}
|
||||
|
||||
private unsafe static void ClickYes(AtkUnitBase* addon)
|
||||
{
|
||||
AtkValue* ptr = stackalloc AtkValue[1];
|
||||
ptr->Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
|
||||
ptr->Int = 0;
|
||||
addon->FireCallback(1u, ptr);
|
||||
}
|
||||
|
||||
private unsafe static int GetAtkValueInt(AtkUnitBase* addon, int index)
|
||||
{
|
||||
if (addon == null || addon->AtkValues == null || index < 0 || index >= addon->AtkValuesCount)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
AtkValue atkValue = addon->AtkValues[index];
|
||||
return atkValue.Type switch
|
||||
{
|
||||
FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int => atkValue.Int,
|
||||
FFXIVClientStructs.FFXIV.Component.GUI.ValueType.UInt => (int)atkValue.UInt,
|
||||
FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Bool => (atkValue.Byte != 0) ? 1 : 0,
|
||||
_ => -1,
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "GrandCompanyExchange", OnShopPostSetup);
|
||||
_addonLifecycle.UnregisterListener(AddonEvent.PreFinalize, "GrandCompanyExchange", OnShopPreFinalize);
|
||||
_addonLifecycle.UnregisterListener(AddonEvent.PostUpdate, "SelectYesno", OnSelectYesNoPostUpdate);
|
||||
}
|
||||
}
|
||||
16
LLib/LLib.Shop/PurchaseCompletedEventArgs.cs
Normal file
16
LLib/LLib.Shop/PurchaseCompletedEventArgs.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
|
||||
namespace LLib.Shop;
|
||||
|
||||
public sealed class PurchaseCompletedEventArgs : EventArgs
|
||||
{
|
||||
public uint ItemId { get; }
|
||||
|
||||
public bool Success { get; }
|
||||
|
||||
public PurchaseCompletedEventArgs(uint itemId, bool success)
|
||||
{
|
||||
ItemId = itemId;
|
||||
Success = success;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue