initial commit
This commit is contained in:
123
Core/Service/Banking/Auctioning.lua
Normal file
123
Core/Service/Banking/Auctioning.lua
Normal file
@@ -0,0 +1,123 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Auctioning = TSM.Banking:NewPackage("Auctioning")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local BagTracking = TSM.Include("Service.BagTracking")
|
||||
local Inventory = TSM.Include("Service.Inventory")
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Auctioning.MoveGroupsToBank(callback, groups)
|
||||
local items = TempTable.Acquire()
|
||||
TSM.Banking.Util.PopulateGroupItemsFromBags(items, groups, private.GroupsGetNumToMoveToBank)
|
||||
TSM.Banking.MoveToBank(items, callback)
|
||||
TempTable.Release(items)
|
||||
end
|
||||
|
||||
function Auctioning.PostCapToBags(callback, groups)
|
||||
local items = TempTable.Acquire()
|
||||
TSM.Banking.Util.PopulateGroupItemsFromOpenBank(items, groups, private.GetNumToMoveToBags)
|
||||
TSM.Banking.MoveToBag(items, callback)
|
||||
TempTable.Release(items)
|
||||
end
|
||||
|
||||
function Auctioning.ShortfallToBags(callback, groups)
|
||||
local items = TempTable.Acquire()
|
||||
TSM.Banking.Util.PopulateGroupItemsFromOpenBank(items, groups, private.GetNumToMoveToBags, true)
|
||||
TSM.Banking.MoveToBag(items, callback)
|
||||
TempTable.Release(items)
|
||||
end
|
||||
|
||||
function Auctioning.MaxExpiresToBank(callback, groups)
|
||||
local items = TempTable.Acquire()
|
||||
TSM.Banking.Util.PopulateGroupItemsFromBags(items, groups, private.MaxExpiresGetNumToMoveToBank)
|
||||
TSM.Banking.MoveToBank(items, callback)
|
||||
TempTable.Release(items)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GroupsGetNumToMoveToBank(itemString, numHave)
|
||||
-- move everything
|
||||
return numHave
|
||||
end
|
||||
|
||||
function private.GetNumToMoveToBags(itemString, numHave, includeAH)
|
||||
local totalNumToMove = 0
|
||||
local numAvailable = numHave
|
||||
local numInBags = BagTracking.CreateQueryBagsItem(itemString)
|
||||
:VirtualField("autoBaseItemString", "string", TSM.Groups.TranslateItemString, "itemString")
|
||||
:Equal("autoBaseItemString", itemString)
|
||||
:SumAndRelease("quantity") or 0
|
||||
if includeAH then
|
||||
numInBags = numInBags + select(3, Inventory.GetPlayerTotals(itemString)) + Inventory.GetMailQuantity(itemString)
|
||||
end
|
||||
|
||||
for _, _, operationSettings in TSM.Operations.GroupOperationIterator("Auctioning", TSM.Groups.GetPathByItem(itemString)) do
|
||||
local maxExpires = TSM.Auctioning.Util.GetPrice("maxExpires", operationSettings, itemString)
|
||||
local operationHasExpired = false
|
||||
if maxExpires and maxExpires > 0 then
|
||||
local numExpires = TSM.Accounting.Auctions.GetNumExpiresSinceSale(itemString)
|
||||
if numExpires and numExpires > maxExpires then
|
||||
operationHasExpired = true
|
||||
end
|
||||
end
|
||||
|
||||
local postCap = TSM.Auctioning.Util.GetPrice("postCap", operationSettings, itemString)
|
||||
local stackSize = (TSM.IsWowClassic() and TSM.Auctioning.Util.GetPrice("stackSize", operationSettings, itemString)) or (not TSM.IsWowClassic() and 1)
|
||||
if not operationHasExpired and postCap and stackSize then
|
||||
local numNeeded = stackSize * postCap
|
||||
if numInBags > numNeeded then
|
||||
-- we can satisfy this operation from the bags
|
||||
numInBags = numInBags - numNeeded
|
||||
numNeeded = 0
|
||||
elseif numInBags > 0 then
|
||||
-- we can partially satisfy this operation from the bags
|
||||
numNeeded = numNeeded - numInBags
|
||||
numInBags = 0
|
||||
end
|
||||
|
||||
local numToMove = min(numAvailable, numNeeded)
|
||||
if numToMove > 0 then
|
||||
numAvailable = numAvailable - numToMove
|
||||
totalNumToMove = totalNumToMove + numToMove
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return totalNumToMove
|
||||
end
|
||||
|
||||
function private.MaxExpiresGetNumToMoveToBank(itemString, numHave)
|
||||
local numToKeepInBags = 0
|
||||
for _, _, operationSettings in TSM.Operations.GroupOperationIterator("Auctioning", TSM.Groups.GetPathByItem(itemString)) do
|
||||
local maxExpires = TSM.Auctioning.Util.GetPrice("maxExpires", operationSettings, itemString)
|
||||
local operationHasExpired = false
|
||||
if maxExpires and maxExpires > 0 then
|
||||
local numExpires = TSM.Accounting.Auctions.GetNumExpiresSinceSale(itemString)
|
||||
if numExpires and numExpires > maxExpires then
|
||||
operationHasExpired = true
|
||||
end
|
||||
end
|
||||
local postCap = TSM.Auctioning.Util.GetPrice("postCap", operationSettings, itemString)
|
||||
local stackSize = (TSM.IsWowClassic() and TSM.Auctioning.Util.GetPrice("stackSize", operationSettings, itemString)) or (not TSM.IsWowClassic() and 1)
|
||||
if not operationHasExpired and postCap and stackSize then
|
||||
numToKeepInBags = numToKeepInBags + stackSize * postCap
|
||||
end
|
||||
end
|
||||
return max(numHave - numToKeepInBags, 0)
|
||||
end
|
||||
321
Core/Service/Banking/Core.lua
Normal file
321
Core/Service/Banking/Core.lua
Normal file
@@ -0,0 +1,321 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Banking = TSM:NewPackage("Banking")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local String = TSM.Include("Util.String")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local Threading = TSM.Include("Service.Threading")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local private = {
|
||||
moveThread = nil,
|
||||
moveItems = {},
|
||||
restoreItems = {},
|
||||
restoreFrame = nil,
|
||||
callback = nil,
|
||||
openFrame = nil,
|
||||
frameCallbacks = {},
|
||||
}
|
||||
local MOVE_WAIT_TIMEOUT = 2
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Banking.OnInitialize()
|
||||
private.moveThread = Threading.New("BANKING_MOVE", private.MoveThread)
|
||||
|
||||
Event.Register("BANKFRAME_OPENED", private.BankOpened)
|
||||
Event.Register("BANKFRAME_CLOSED", private.BankClosed)
|
||||
if not TSM.IsWowClassic() then
|
||||
Event.Register("GUILDBANKFRAME_OPENED", private.GuildBankOpened)
|
||||
Event.Register("GUILDBANKFRAME_CLOSED", private.GuildBankClosed)
|
||||
end
|
||||
end
|
||||
|
||||
function Banking.RegisterFrameCallback(callback)
|
||||
tinsert(private.frameCallbacks, callback)
|
||||
end
|
||||
|
||||
function Banking.IsGuildBankOpen()
|
||||
return private.openFrame == "GUILD_BANK"
|
||||
end
|
||||
|
||||
function Banking.IsBankOpen()
|
||||
return private.openFrame == "BANK"
|
||||
end
|
||||
|
||||
function Banking.MoveToBag(items, callback)
|
||||
assert(private.openFrame)
|
||||
local context = Banking.IsGuildBankOpen() and Banking.MoveContext.GetGuildBankToBag() or Banking.MoveContext.GetBankToBag()
|
||||
private.StartMove(items, context, callback)
|
||||
end
|
||||
|
||||
function Banking.MoveToBank(items, callback)
|
||||
assert(private.openFrame)
|
||||
local context = Banking.IsGuildBankOpen() and Banking.MoveContext.GetBagToGuildBank() or Banking.MoveContext.GetBagToBank()
|
||||
private.StartMove(items, context, callback)
|
||||
end
|
||||
|
||||
function Banking.EmptyBags(callback)
|
||||
assert(private.openFrame)
|
||||
local items = TempTable.Acquire()
|
||||
for _, _, _, itemString, quantity in Banking.Util.BagIterator(false) do
|
||||
items[itemString] = (items[itemString] or 0) + quantity
|
||||
end
|
||||
wipe(private.restoreItems)
|
||||
private.restoreFrame = private.openFrame
|
||||
private.callback = callback
|
||||
local context = Banking.IsGuildBankOpen() and Banking.MoveContext.GetBagToGuildBank() or Banking.MoveContext.GetBagToBank()
|
||||
private.StartMove(items, context, private.EmptyBagsThreadCallbackWrapper)
|
||||
TempTable.Release(items)
|
||||
end
|
||||
|
||||
function Banking.RestoreBags(callback)
|
||||
assert(private.openFrame)
|
||||
assert(Banking.CanRestoreBags())
|
||||
private.callback = callback
|
||||
local context = Banking.IsGuildBankOpen() and Banking.MoveContext.GetGuildBankToBag() or Banking.MoveContext.GetBankToBag()
|
||||
private.StartMove(private.restoreItems, context, private.RestoreBagsThreadCallbackWrapper)
|
||||
end
|
||||
|
||||
function Banking.CanRestoreBags()
|
||||
assert(private.openFrame)
|
||||
return private.openFrame == private.restoreFrame
|
||||
end
|
||||
|
||||
function Banking.PutByFilter(filterStr)
|
||||
if not private.openFrame then
|
||||
return
|
||||
end
|
||||
local filterItemString = ItemString.Get(filterStr)
|
||||
filterStr = String.Escape(strlower(filterStr))
|
||||
|
||||
local items = TempTable.Acquire()
|
||||
for _, _, _, itemString, quantity in Banking.Util.BagIterator(false) do
|
||||
items[itemString] = (items[itemString] or 0) + quantity
|
||||
end
|
||||
|
||||
for itemString in pairs(items) do
|
||||
if not private.MatchesFilter(itemString, filterStr, filterItemString) then
|
||||
-- remove this item
|
||||
items[itemString] = nil
|
||||
end
|
||||
end
|
||||
|
||||
Banking.MoveToBank(items, private.GetPutCallback)
|
||||
TempTable.Release(items)
|
||||
end
|
||||
|
||||
function Banking.GetByFilter(filterStr)
|
||||
if not private.openFrame then
|
||||
return
|
||||
end
|
||||
local filterItemString = ItemString.Get(filterStr)
|
||||
filterStr = String.Escape(strlower(filterStr))
|
||||
|
||||
local items = TempTable.Acquire()
|
||||
for _, _, _, itemString, quantity in Banking.Util.OpenBankIterator(false) do
|
||||
items[itemString] = (items[itemString] or 0) + quantity
|
||||
end
|
||||
|
||||
for itemString in pairs(items) do
|
||||
if not private.MatchesFilter(itemString, filterStr, filterItemString) then
|
||||
-- remove this item
|
||||
items[itemString] = nil
|
||||
end
|
||||
end
|
||||
|
||||
Banking.MoveToBag(items, private.GetPutCallback)
|
||||
TempTable.Release(items)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Threads
|
||||
-- ============================================================================
|
||||
|
||||
function private.MoveThread(context, callback)
|
||||
local numMoves = 0
|
||||
local emptySlotIds = Threading.AcquireSafeTempTable()
|
||||
context:GetEmptySlotsThreaded(emptySlotIds)
|
||||
local slotIds = Threading.AcquireSafeTempTable()
|
||||
local slotItemString = Threading.AcquireSafeTempTable()
|
||||
local slotMoveQuantity = Threading.AcquireSafeTempTable()
|
||||
local slotEndQuantity = Threading.AcquireSafeTempTable()
|
||||
for itemString, numQueued in pairs(private.moveItems) do
|
||||
for _, slotId, quantity in context:SlotIdIterator(itemString) do
|
||||
if numQueued > 0 then
|
||||
-- find a suitable empty slot
|
||||
local targetSlotId = context:GetTargetSlotId(itemString, emptySlotIds)
|
||||
if targetSlotId then
|
||||
assert(not slotIds[slotId])
|
||||
slotIds[slotId] = targetSlotId
|
||||
slotItemString[slotId] = itemString
|
||||
slotMoveQuantity[slotId] = min(quantity, numQueued)
|
||||
slotEndQuantity[slotId] = max(quantity - numQueued, 0)
|
||||
numQueued = numQueued - slotMoveQuantity[slotId]
|
||||
numMoves = numMoves + 1
|
||||
else
|
||||
Log.Err("No target slot")
|
||||
end
|
||||
end
|
||||
end
|
||||
if numQueued > 0 then
|
||||
Log.Err("No slots with item (%s)", itemString)
|
||||
end
|
||||
end
|
||||
|
||||
local numDone = 0
|
||||
while next(slotIds) do
|
||||
local movedSlotId = nil
|
||||
-- do all the pending moves
|
||||
for slotId, targetSlotId in pairs(slotIds) do
|
||||
context:MoveSlot(slotId, targetSlotId, slotMoveQuantity[slotId])
|
||||
Threading.Yield()
|
||||
if private.openFrame == "GUILD_BANK" then
|
||||
movedSlotId = slotId
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- wait for at least one to finish or the timeout to elapse
|
||||
local didMove = false
|
||||
local timeout = GetTime() + MOVE_WAIT_TIMEOUT
|
||||
while not didMove and GetTime() < timeout do
|
||||
-- check which moves are done
|
||||
for slotId in pairs(slotIds) do
|
||||
if private.openFrame ~= "GUILD_BANK" or slotId == movedSlotId then
|
||||
if context:GetSlotQuantity(slotId) <= slotEndQuantity[slotId] then
|
||||
didMove = true
|
||||
slotIds[slotId] = nil
|
||||
numDone = numDone + 1
|
||||
callback("MOVED", slotItemString[slotId], slotMoveQuantity[slotId])
|
||||
end
|
||||
if didMove and slotId == movedSlotId then
|
||||
break
|
||||
end
|
||||
Threading.Yield()
|
||||
end
|
||||
end
|
||||
if didMove then
|
||||
callback("PROGRESS", numDone / numMoves)
|
||||
end
|
||||
Threading.Yield(true)
|
||||
end
|
||||
end
|
||||
|
||||
if private.openFrame == "GUILD_BANK" then
|
||||
QueryGuildBankTab(GetCurrentGuildBankTab())
|
||||
end
|
||||
|
||||
Threading.ReleaseSafeTempTable(slotIds)
|
||||
Threading.ReleaseSafeTempTable(slotItemString)
|
||||
Threading.ReleaseSafeTempTable(slotMoveQuantity)
|
||||
Threading.ReleaseSafeTempTable(slotEndQuantity)
|
||||
Threading.ReleaseSafeTempTable(emptySlotIds)
|
||||
callback("DONE")
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.BankOpened()
|
||||
if private.openFrame == "BANK" then
|
||||
return
|
||||
end
|
||||
assert(not private.openFrame)
|
||||
private.openFrame = "BANK"
|
||||
for _, callback in ipairs(private.frameCallbacks) do
|
||||
callback(private.openFrame)
|
||||
end
|
||||
end
|
||||
|
||||
function private.GuildBankOpened()
|
||||
if private.openFrame == "GUILD_BANK" then
|
||||
return
|
||||
end
|
||||
assert(not private.openFrame)
|
||||
private.openFrame = "GUILD_BANK"
|
||||
for _, callback in ipairs(private.frameCallbacks) do
|
||||
callback(private.openFrame)
|
||||
end
|
||||
end
|
||||
|
||||
function private.BankClosed()
|
||||
if not private.openFrame then
|
||||
return
|
||||
end
|
||||
private.openFrame = nil
|
||||
private.StopMove()
|
||||
for _, callback in ipairs(private.frameCallbacks) do
|
||||
callback(private.openFrame)
|
||||
end
|
||||
end
|
||||
|
||||
function private.GuildBankClosed()
|
||||
if not private.openFrame then
|
||||
return
|
||||
end
|
||||
private.openFrame = nil
|
||||
private.StopMove()
|
||||
for _, callback in ipairs(private.frameCallbacks) do
|
||||
callback(private.openFrame)
|
||||
end
|
||||
end
|
||||
|
||||
function private.StartMove(items, context, callback)
|
||||
private.StopMove()
|
||||
wipe(private.moveItems)
|
||||
for itemString, quantity in pairs(items) do
|
||||
private.moveItems[itemString] = quantity
|
||||
end
|
||||
Threading.Start(private.moveThread, context, callback)
|
||||
end
|
||||
|
||||
function private.StopMove()
|
||||
Threading.Kill(private.moveThread)
|
||||
end
|
||||
|
||||
function private.EmptyBagsThreadCallbackWrapper(event, ...)
|
||||
if event == "MOVED" then
|
||||
local itemString, numMoved = ...
|
||||
private.restoreItems[itemString] = (private.restoreItems[itemString] or 0) + numMoved
|
||||
elseif event == "DONE" then
|
||||
if not next(private.restoreItems) then
|
||||
private.restoreFrame = private.openFrame
|
||||
end
|
||||
end
|
||||
private.callback(event, ...)
|
||||
end
|
||||
|
||||
function private.RestoreBagsThreadCallbackWrapper(event, ...)
|
||||
if event == "DONE" then
|
||||
wipe(private.restoreItems)
|
||||
private.restoreFrame = nil
|
||||
end
|
||||
private.callback(event, ...)
|
||||
end
|
||||
|
||||
function private.GetPutCallback(event)
|
||||
if event == "DONE" then
|
||||
Log.PrintUser(DONE)
|
||||
end
|
||||
end
|
||||
|
||||
function private.MatchesFilter(itemString, filterStr, filterItemString)
|
||||
local name = strlower(ItemInfo.GetName(itemString) or "")
|
||||
return strmatch(ItemString.GetBase(itemString), filterStr) or strmatch(name, filterStr) or (filterItemString and itemString == filterItemString)
|
||||
end
|
||||
105
Core/Service/Banking/Mailing.lua
Normal file
105
Core/Service/Banking/Mailing.lua
Normal file
@@ -0,0 +1,105 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Mailing = TSM.Banking:NewPackage("Mailing")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Inventory = TSM.Include("Service.Inventory")
|
||||
local PlayerInfo = TSM.Include("Service.PlayerInfo")
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Mailing.MoveGroupsToBank(callback, groups)
|
||||
local items = TempTable.Acquire()
|
||||
TSM.Banking.Util.PopulateGroupItemsFromBags(items, groups, private.GroupsGetNumToMoveToBank)
|
||||
TSM.Banking.MoveToBank(items, callback)
|
||||
TempTable.Release(items)
|
||||
end
|
||||
|
||||
function Mailing.NongroupToBank(callback)
|
||||
local items = TempTable.Acquire()
|
||||
TSM.Banking.Util.PopulateItemsFromBags(items, private.NongroupGetNumToBank)
|
||||
TSM.Banking.MoveToBank(items, callback)
|
||||
TempTable.Release(items)
|
||||
end
|
||||
|
||||
function Mailing.TargetShortfallToBags(callback, groups)
|
||||
local items = TempTable.Acquire()
|
||||
TSM.Banking.Util.PopulateGroupItemsFromOpenBank(items, groups, private.TargetShortfallGetNumToBags)
|
||||
TSM.Banking.MoveToBag(items, callback)
|
||||
TempTable.Release(items)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GroupsGetNumToMoveToBank(itemString, numHave)
|
||||
-- move everything
|
||||
return numHave
|
||||
end
|
||||
|
||||
function private.NongroupGetNumToBank(itemString, numHave)
|
||||
local hasOperations = false
|
||||
for _ in TSM.Operations.GroupOperationIterator("Mailing", TSM.Groups.GetPathByItem(itemString)) do
|
||||
hasOperations = true
|
||||
end
|
||||
return not hasOperations and numHave or 0
|
||||
end
|
||||
|
||||
function private.TargetShortfallGetNumToBags(itemString, numHave)
|
||||
local totalNumToSend = 0
|
||||
for _, _, operationSettings in TSM.Operations.GroupOperationIterator("Mailing", TSM.Groups.GetPathByItem(itemString)) do
|
||||
local numAvailable = numHave - operationSettings.keepQty
|
||||
local numToSend = 0
|
||||
if numAvailable > 0 then
|
||||
if operationSettings.maxQtyEnabled then
|
||||
if operationSettings.restock then
|
||||
local targetQty = private.GetTargetQuantity(operationSettings.target, itemString, operationSettings.restockSources)
|
||||
if PlayerInfo.IsPlayer(operationSettings.target) and targetQty <= operationSettings.maxQty then
|
||||
numToSend = numAvailable
|
||||
else
|
||||
numToSend = min(numAvailable, operationSettings.maxQty - targetQty)
|
||||
end
|
||||
if PlayerInfo.IsPlayer(operationSettings.target) then
|
||||
-- if using restock and target == player ensure that subsequent operations don't take reserved bag inventory
|
||||
numHave = numHave - max((numAvailable - (targetQty - operationSettings.maxQty)), 0)
|
||||
end
|
||||
else
|
||||
numToSend = min(numAvailable, operationSettings.maxQty)
|
||||
end
|
||||
else
|
||||
numToSend = numAvailable
|
||||
end
|
||||
end
|
||||
totalNumToSend = totalNumToSend + numToSend
|
||||
numHave = numHave - numToSend
|
||||
end
|
||||
return totalNumToSend
|
||||
end
|
||||
|
||||
function private.GetTargetQuantity(player, itemString, sources)
|
||||
if player then
|
||||
player = strtrim(strmatch(player, "^[^-]+"))
|
||||
end
|
||||
local num = Inventory.GetBagQuantity(itemString, player) + Inventory.GetMailQuantity(itemString, player) + Inventory.GetAuctionQuantity(itemString, player)
|
||||
if sources then
|
||||
if sources.guild then
|
||||
num = num + Inventory.GetGuildQuantity(itemString, PlayerInfo.GetPlayerGuild(player))
|
||||
end
|
||||
if sources.bank then
|
||||
num = num + Inventory.GetBankQuantity(itemString, player) + Inventory.GetReagentBankQuantity(itemString, player)
|
||||
end
|
||||
end
|
||||
return num
|
||||
end
|
||||
292
Core/Service/Banking/MoveContext.lua
Normal file
292
Core/Service/Banking/MoveContext.lua
Normal file
@@ -0,0 +1,292 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local MoveContext = TSM.Banking:NewPackage("MoveContext")
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local SlotId = TSM.Include("Util.SlotId")
|
||||
local Threading = TSM.Include("Service.Threading")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local InventoryInfo = TSM.Include("Service.InventoryInfo")
|
||||
local BagTracking = TSM.Include("Service.BagTracking")
|
||||
local GuildTracking = TSM.Include("Service.GuildTracking")
|
||||
local private = {
|
||||
bagToBank = nil,
|
||||
bankToBag = nil,
|
||||
bagToGuildBank = nil,
|
||||
guildBankToBag = nil,
|
||||
}
|
||||
-- don't use MAX_GUILDBANK_SLOTS_PER_TAB since it isn't available right away
|
||||
local GUILD_BANK_TAB_SLOTS = 98
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- BaseMoveContext Class
|
||||
-- ============================================================================
|
||||
|
||||
local BaseMoveContext = TSM.Include("LibTSMClass").DefineClass("BaseMoveContext", nil, "ABSTRACT")
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- BagToBankMoveContext Class
|
||||
-- ============================================================================
|
||||
|
||||
local BagToBankMoveContext = TSM.Include("LibTSMClass").DefineClass("BagToBankMoveContext", BaseMoveContext)
|
||||
|
||||
function BagToBankMoveContext.MoveSlot(self, fromSlotId, toSlotId, quantity)
|
||||
local fromBag, fromSlot = SlotId.Split(fromSlotId)
|
||||
SplitContainerItem(fromBag, fromSlot, quantity)
|
||||
if GetCursorInfo() == "item" then
|
||||
PickupContainerItem(SlotId.Split(toSlotId))
|
||||
end
|
||||
ClearCursor()
|
||||
end
|
||||
|
||||
function BagToBankMoveContext.GetSlotQuantity(self, slotId)
|
||||
return private.BagBankGetSlotQuantity(slotId)
|
||||
end
|
||||
|
||||
function BagToBankMoveContext.SlotIdIterator(self, itemString)
|
||||
return private.BagSlotIdIterator(itemString)
|
||||
end
|
||||
|
||||
function BagToBankMoveContext.GetEmptySlotsThreaded(self, emptySlotIds)
|
||||
local sortValue = Threading.AcquireSafeTempTable()
|
||||
if not TSM.IsWowClassic() then
|
||||
private.GetEmptySlotsHelper(REAGENTBANK_CONTAINER, emptySlotIds, sortValue)
|
||||
end
|
||||
private.GetEmptySlotsHelper(BANK_CONTAINER, emptySlotIds, sortValue)
|
||||
for bag = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do
|
||||
private.GetEmptySlotsHelper(bag, emptySlotIds, sortValue)
|
||||
end
|
||||
Table.SortWithValueLookup(emptySlotIds, sortValue)
|
||||
Threading.ReleaseSafeTempTable(sortValue)
|
||||
end
|
||||
|
||||
function BagToBankMoveContext.GetTargetSlotId(self, itemString, emptySlotIds)
|
||||
return private.BagBankGetTargetSlotId(itemString, emptySlotIds)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- BankToBagMoveContext Class
|
||||
-- ============================================================================
|
||||
|
||||
local BankToBagMoveContext = TSM.Include("LibTSMClass").DefineClass("BankToBagMoveContext", BaseMoveContext)
|
||||
|
||||
function BankToBagMoveContext.MoveSlot(self, fromSlotId, toSlotId, quantity)
|
||||
local fromBag, fromSlot = SlotId.Split(fromSlotId)
|
||||
SplitContainerItem(fromBag, fromSlot, quantity)
|
||||
if GetCursorInfo() == "item" then
|
||||
PickupContainerItem(SlotId.Split(toSlotId))
|
||||
end
|
||||
ClearCursor()
|
||||
end
|
||||
|
||||
function BankToBagMoveContext.GetSlotQuantity(self, slotId)
|
||||
return private.BagBankGetSlotQuantity(slotId)
|
||||
end
|
||||
|
||||
function BankToBagMoveContext.SlotIdIterator(self, itemString)
|
||||
itemString = TSM.Groups.TranslateItemString(itemString)
|
||||
return BagTracking.CreateQueryBankItem(itemString)
|
||||
:VirtualField("autoBaseItemString", "string", TSM.Groups.TranslateItemString, "itemString")
|
||||
:Equal("autoBaseItemString", itemString)
|
||||
:Select("slotId", "quantity")
|
||||
:IteratorAndRelease()
|
||||
end
|
||||
|
||||
function BankToBagMoveContext.GetEmptySlotsThreaded(self, emptySlotIds)
|
||||
private.BagGetEmptySlotsThreaded(emptySlotIds)
|
||||
end
|
||||
|
||||
function BankToBagMoveContext.GetTargetSlotId(self, itemString, emptySlotIds)
|
||||
return private.BagBankGetTargetSlotId(itemString, emptySlotIds)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- BagToGuildBankMoveContext Class
|
||||
-- ============================================================================
|
||||
|
||||
local BagToGuildBankMoveContext = TSM.Include("LibTSMClass").DefineClass("BagToGuildBankMoveContext", BaseMoveContext)
|
||||
|
||||
function BagToGuildBankMoveContext.MoveSlot(self, fromSlotId, toSlotId, quantity)
|
||||
local fromBag, fromSlot = SlotId.Split(fromSlotId)
|
||||
SplitContainerItem(fromBag, fromSlot, quantity)
|
||||
if GetCursorInfo() == "item" then
|
||||
PickupGuildBankItem(SlotId.Split(toSlotId))
|
||||
end
|
||||
ClearCursor()
|
||||
end
|
||||
|
||||
function BagToGuildBankMoveContext.GetSlotQuantity(self, slotId)
|
||||
return private.BagBankGetSlotQuantity(slotId)
|
||||
end
|
||||
|
||||
function BagToGuildBankMoveContext.SlotIdIterator(self, itemString)
|
||||
return private.BagSlotIdIterator(itemString)
|
||||
end
|
||||
|
||||
function BagToGuildBankMoveContext.GetEmptySlotsThreaded(self, emptySlotIds)
|
||||
local currentTab = GetCurrentGuildBankTab()
|
||||
local _, _, _, _, numWithdrawals = GetGuildBankTabInfo(currentTab)
|
||||
if numWithdrawals == -1 or numWithdrawals >= GUILD_BANK_TAB_SLOTS then
|
||||
for slot = 1, GUILD_BANK_TAB_SLOTS do
|
||||
if not GetGuildBankItemInfo(currentTab, slot) then
|
||||
tinsert(emptySlotIds, SlotId.Join(currentTab, slot))
|
||||
end
|
||||
end
|
||||
end
|
||||
for tab = 1, GetNumGuildBankTabs() do
|
||||
if tab ~= currentTab then
|
||||
-- only use tabs which we have at least enough withdrawals to withdraw every slot
|
||||
_, _, _, _, numWithdrawals = GetGuildBankTabInfo(tab)
|
||||
if numWithdrawals == -1 or numWithdrawals >= GUILD_BANK_TAB_SLOTS then
|
||||
for slot = 1, GUILD_BANK_TAB_SLOTS do
|
||||
if not GetGuildBankItemInfo(tab, slot) then
|
||||
tinsert(emptySlotIds, SlotId.Join(tab, slot))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function BagToGuildBankMoveContext.GetTargetSlotId(self, itemString, emptySlotIds)
|
||||
return tremove(emptySlotIds, 1)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- GuildBankToBagMoveContext Class
|
||||
-- ============================================================================
|
||||
|
||||
local GuildBankToBagMoveContext = TSM.Include("LibTSMClass").DefineClass("GuildBankToBagMoveContext", BaseMoveContext)
|
||||
|
||||
function GuildBankToBagMoveContext.MoveSlot(self, fromSlotId, toSlotId, quantity)
|
||||
local fromTab, fromSlot = SlotId.Split(fromSlotId)
|
||||
SplitGuildBankItem(fromTab, fromSlot, quantity)
|
||||
if GetCursorInfo() == "item" then
|
||||
PickupContainerItem(SlotId.Split(toSlotId))
|
||||
end
|
||||
ClearCursor()
|
||||
end
|
||||
|
||||
function GuildBankToBagMoveContext.GetSlotQuantity(self, slotId)
|
||||
local tab, slot = SlotId.Split(slotId)
|
||||
QueryGuildBankTab(tab)
|
||||
local _, quantity = GetGuildBankItemInfo(tab, slot)
|
||||
return quantity or 0
|
||||
end
|
||||
|
||||
function GuildBankToBagMoveContext.SlotIdIterator(self, itemString)
|
||||
itemString = TSM.Groups.TranslateItemString(itemString)
|
||||
return GuildTracking.CreateQueryItem(itemString)
|
||||
:VirtualField("autoBaseItemString", "string", TSM.Groups.TranslateItemString, "itemString")
|
||||
:Equal("autoBaseItemString", itemString)
|
||||
:Select("slotId", "quantity")
|
||||
:IteratorAndRelease()
|
||||
end
|
||||
|
||||
function GuildBankToBagMoveContext.GetEmptySlotsThreaded(self, emptySlotIds)
|
||||
private.BagGetEmptySlotsThreaded(emptySlotIds)
|
||||
end
|
||||
|
||||
function GuildBankToBagMoveContext.GetTargetSlotId(self, itemString, emptySlotIds)
|
||||
return private.BagBankGetTargetSlotId(itemString, emptySlotIds)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function MoveContext.GetBagToBank()
|
||||
private.bagToBank = private.bagToBank or BagToBankMoveContext()
|
||||
return private.bagToBank
|
||||
end
|
||||
|
||||
function MoveContext.GetBankToBag()
|
||||
private.bankToBag = private.bankToBag or BankToBagMoveContext()
|
||||
return private.bankToBag
|
||||
end
|
||||
|
||||
function MoveContext.GetBagToGuildBank()
|
||||
private.bagToGuildBank = private.bagToGuildBank or BagToGuildBankMoveContext()
|
||||
return private.bagToGuildBank
|
||||
end
|
||||
|
||||
function MoveContext.GetGuildBankToBag()
|
||||
private.guildBankToBag = private.guildBankToBag or GuildBankToBagMoveContext()
|
||||
return private.guildBankToBag
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.BagBankGetSlotQuantity(slotId)
|
||||
local _, quantity = GetContainerItemInfo(SlotId.Split(slotId))
|
||||
return quantity or 0
|
||||
end
|
||||
|
||||
function private.BagSlotIdIterator(itemString)
|
||||
itemString = TSM.Groups.TranslateItemString(itemString)
|
||||
local query = BagTracking.CreateQueryBagsItem(itemString)
|
||||
:Select("slotId", "quantity")
|
||||
:VirtualField("autoBaseItemString", "string", TSM.Groups.TranslateItemString, "itemString")
|
||||
:Equal("autoBaseItemString", itemString)
|
||||
if TSM.Banking.IsGuildBankOpen() then
|
||||
query:Equal("isBoA", false)
|
||||
query:Equal("isBoP", false)
|
||||
end
|
||||
return query:IteratorAndRelease()
|
||||
end
|
||||
|
||||
function private.BagGetEmptySlotsThreaded(emptySlotIds)
|
||||
local sortValue = Threading.AcquireSafeTempTable()
|
||||
for bag = BACKPACK_CONTAINER, NUM_BAG_SLOTS do
|
||||
private.GetEmptySlotsHelper(bag, emptySlotIds, sortValue)
|
||||
end
|
||||
Table.SortWithValueLookup(emptySlotIds, sortValue)
|
||||
Threading.ReleaseSafeTempTable(sortValue)
|
||||
end
|
||||
|
||||
function private.GetEmptySlotsHelper(bag, emptySlotIds, sortValue)
|
||||
local isSpecial = nil
|
||||
if bag == REAGENTBANK_CONTAINER then
|
||||
isSpecial = true
|
||||
elseif bag == BACKPACK_CONTAINER or bag == BANK_CONTAINER then
|
||||
isSpecial = false
|
||||
else
|
||||
isSpecial = (GetItemFamily(GetInventoryItemLink("player", ContainerIDToInventoryID(bag))) or 0) ~= 0
|
||||
end
|
||||
for slot = 1, GetContainerNumSlots(bag) do
|
||||
if not GetContainerItemInfo(bag, slot) then
|
||||
local slotId = SlotId.Join(bag, slot)
|
||||
tinsert(emptySlotIds, slotId)
|
||||
sortValue[slotId] = slotId + (isSpecial and 0 or 100000)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function private.BagBankGetTargetSlotId(itemString, emptySlotIds)
|
||||
for i, slotId in ipairs(emptySlotIds) do
|
||||
local bag = SlotId.Split(slotId)
|
||||
if InventoryInfo.ItemWillGoInBag(ItemInfo.GetLink(itemString), bag) then
|
||||
return tremove(emptySlotIds, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
115
Core/Service/Banking/Util.lua
Normal file
115
Core/Service/Banking/Util.lua
Normal file
@@ -0,0 +1,115 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Util = TSM.Banking:NewPackage("Util")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local BagTracking = TSM.Include("Service.BagTracking")
|
||||
local GuildTracking = TSM.Include("Service.GuildTracking")
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Util.BagIterator(autoBaseItems)
|
||||
local query = BagTracking.CreateQueryBags()
|
||||
:OrderBy("slotId", true)
|
||||
if autoBaseItems then
|
||||
query:VirtualField("autoBaseItemString", "string", TSM.Groups.TranslateItemString, "itemString")
|
||||
:Select("bag", "slot", "autoBaseItemString", "quantity")
|
||||
else
|
||||
query:Select("bag", "slot", "itemString", "quantity")
|
||||
end
|
||||
if TSM.Banking.IsGuildBankOpen() then
|
||||
query:Equal("isBoP", false)
|
||||
:Equal("isBoA", false)
|
||||
end
|
||||
return query:IteratorAndRelease()
|
||||
end
|
||||
|
||||
function Util.OpenBankIterator(autoBaseItems)
|
||||
if TSM.Banking.IsGuildBankOpen() then
|
||||
local query = GuildTracking.CreateQuery()
|
||||
if autoBaseItems then
|
||||
query:VirtualField("autoBaseItemString", "string", TSM.Groups.TranslateItemString, "itemString")
|
||||
:Select("tab", "slot", "autoBaseItemString", "quantity")
|
||||
else
|
||||
query:Select("tab", "slot", "itemString", "quantity")
|
||||
end
|
||||
return query:IteratorAndRelease()
|
||||
else
|
||||
local query = BagTracking.CreateQueryBank()
|
||||
:OrderBy("slotId", true)
|
||||
if autoBaseItems then
|
||||
query:VirtualField("autoBaseItemString", "string", TSM.Groups.TranslateItemString, "itemString")
|
||||
:Select("bag", "slot", "autoBaseItemString", "quantity")
|
||||
else
|
||||
query:Select("bag", "slot", "itemString", "quantity")
|
||||
end
|
||||
return query:IteratorAndRelease()
|
||||
end
|
||||
end
|
||||
|
||||
function Util.PopulateGroupItemsFromBags(items, groups, getNumFunc, ...)
|
||||
local itemQuantity = TempTable.Acquire()
|
||||
for _, _, _, itemString, quantity in Util.BagIterator(true) do
|
||||
if private.InGroups(itemString, groups) then
|
||||
itemQuantity[itemString] = (itemQuantity[itemString] or 0) + quantity
|
||||
end
|
||||
end
|
||||
for itemString, numHave in pairs(itemQuantity) do
|
||||
local numToMove = getNumFunc(itemString, numHave, ...)
|
||||
if numToMove > 0 then
|
||||
items[itemString] = numToMove
|
||||
end
|
||||
end
|
||||
TempTable.Release(itemQuantity)
|
||||
end
|
||||
|
||||
function Util.PopulateGroupItemsFromOpenBank(items, groups, getNumFunc, ...)
|
||||
local itemQuantity = TempTable.Acquire()
|
||||
for _, _, _, itemString, quantity in Util.OpenBankIterator(true) do
|
||||
if private.InGroups(itemString, groups) then
|
||||
itemQuantity[itemString] = (itemQuantity[itemString] or 0) + quantity
|
||||
end
|
||||
end
|
||||
for itemString, numHave in pairs(itemQuantity) do
|
||||
local numToMove = getNumFunc(itemString, numHave, ...)
|
||||
if numToMove > 0 then
|
||||
items[itemString] = numToMove
|
||||
end
|
||||
end
|
||||
TempTable.Release(itemQuantity)
|
||||
end
|
||||
|
||||
function Util.PopulateItemsFromBags(items, getNumFunc, ...)
|
||||
local itemQuantity = TempTable.Acquire()
|
||||
for _, _, _, itemString, quantity in Util.BagIterator(true) do
|
||||
itemQuantity[itemString] = (itemQuantity[itemString] or 0) + quantity
|
||||
end
|
||||
for itemString, numHave in pairs(itemQuantity) do
|
||||
local numToMove = getNumFunc(itemString, numHave, ...)
|
||||
if numToMove > 0 then
|
||||
items[itemString] = numToMove
|
||||
end
|
||||
end
|
||||
TempTable.Release(itemQuantity)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.InGroups(itemString, groups)
|
||||
local groupPath = TSM.Groups.GetPathByItem(itemString)
|
||||
-- TODO: support the base group
|
||||
return groupPath and groupPath ~= TSM.CONST.ROOT_GROUP_PATH and groups[groupPath]
|
||||
end
|
||||
92
Core/Service/Banking/Warehousing.lua
Normal file
92
Core/Service/Banking/Warehousing.lua
Normal file
@@ -0,0 +1,92 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Warehousing = TSM.Banking:NewPackage("Warehousing")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local BagTracking = TSM.Include("Service.BagTracking")
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Warehousing.MoveGroupsToBank(callback, groups)
|
||||
local items = TempTable.Acquire()
|
||||
TSM.Banking.Util.PopulateGroupItemsFromBags(items, groups, private.GetNumToMoveToBank)
|
||||
TSM.Banking.MoveToBank(items, callback)
|
||||
TempTable.Release(items)
|
||||
end
|
||||
|
||||
function Warehousing.MoveGroupsToBags(callback, groups)
|
||||
local items = TempTable.Acquire()
|
||||
TSM.Banking.Util.PopulateGroupItemsFromOpenBank(items, groups, private.GetNumToMoveToBags)
|
||||
TSM.Banking.MoveToBag(items, callback)
|
||||
TempTable.Release(items)
|
||||
end
|
||||
|
||||
function Warehousing.RestockBags(callback, groups)
|
||||
local items = TempTable.Acquire()
|
||||
TSM.Banking.Util.PopulateGroupItemsFromOpenBank(items, groups, private.GetNumToMoveRestock)
|
||||
TSM.Banking.MoveToBag(items, callback)
|
||||
TempTable.Release(items)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetNumToMoveToBank(itemString, numToMove)
|
||||
local _, operationSettings = TSM.Operations.GetFirstOperationByItem("Warehousing", itemString)
|
||||
if not operationSettings then
|
||||
return 0
|
||||
end
|
||||
if operationSettings.keepBagQuantity ~= 0 then
|
||||
numToMove = max(numToMove - operationSettings.keepBagQuantity, 0)
|
||||
end
|
||||
if operationSettings.moveQuantity ~= 0 then
|
||||
numToMove = min(numToMove, operationSettings.moveQuantity)
|
||||
end
|
||||
return numToMove
|
||||
end
|
||||
|
||||
function private.GetNumToMoveToBags(itemString, numToMove)
|
||||
local _, operationSettings = TSM.Operations.GetFirstOperationByItem("Warehousing", itemString)
|
||||
if not operationSettings then
|
||||
return 0
|
||||
end
|
||||
if operationSettings.keepBankQuantity ~= 0 then
|
||||
numToMove = max(numToMove - operationSettings.keepBankQuantity, 0)
|
||||
end
|
||||
if operationSettings.moveQuantity ~= 0 then
|
||||
numToMove = min(numToMove, operationSettings.moveQuantity)
|
||||
end
|
||||
return Math.Floor(numToMove, operationSettings.stackSize ~= 0 and operationSettings.stackSize or 1)
|
||||
end
|
||||
|
||||
function private.GetNumToMoveRestock(itemString, numToMove)
|
||||
local _, operationSettings = TSM.Operations.GetFirstOperationByItem("Warehousing", itemString)
|
||||
if not operationSettings then
|
||||
return 0
|
||||
end
|
||||
local numInBags = BagTracking.CreateQueryBagsItem(itemString)
|
||||
:VirtualField("autoBaseItemString", "string", TSM.Groups.TranslateItemString, "itemString")
|
||||
:Equal("autoBaseItemString", itemString)
|
||||
:SumAndRelease("quantity") or 0
|
||||
if operationSettings.restockQuantity == 0 or numInBags >= operationSettings.restockQuantity then
|
||||
return 0
|
||||
end
|
||||
if operationSettings.restockKeepBankQuantity ~= 0 then
|
||||
numToMove = max(numToMove - operationSettings.restockKeepBankQuantity, 0)
|
||||
end
|
||||
numToMove = min(numToMove, operationSettings.restockQuantity - numInBags)
|
||||
return Math.Floor(numToMove, operationSettings.restockStackSize ~= 0 and operationSettings.restockStackSize or 1)
|
||||
end
|
||||
Reference in New Issue
Block a user