TradeSkillMaster/Core/Service/Banking/Core.lua

322 lines
9.5 KiB
Lua

-- ------------------------------------------------------------------------------ --
-- 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