initial commit

This commit is contained in:
Gitea
2020-11-13 14:13:12 -05:00
commit 05df49ff60
368 changed files with 128754 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local Mailing = TSM:NewPackage("Mailing")
local Event = TSM.Include("Util.Event")
local private = {
mailOpen = false,
frameCallbacks = {},
}
-- ============================================================================
-- Module Functions
-- ============================================================================
function Mailing.OnInitialize()
Event.Register("MAIL_SHOW", private.MailShow)
Event.Register("MAIL_CLOSED", private.MailClosed)
end
function Mailing.RegisterFrameCallback(callback)
tinsert(private.frameCallbacks, callback)
end
function Mailing.IsOpen()
return private.mailOpen
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.MailShow()
private.mailOpen = true
for _, callback in ipairs(private.frameCallbacks) do
callback(true)
end
end
function private.MailClosed()
if not private.mailOpen then
return
end
private.mailOpen = false
for _, callback in ipairs(private.frameCallbacks) do
callback(false)
end
end

View File

@@ -0,0 +1,161 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local Groups = TSM.Mailing:NewPackage("Groups")
local L = TSM.Include("Locale").GetTable()
local Log = TSM.Include("Util.Log")
local Threading = TSM.Include("Service.Threading")
local Inventory = TSM.Include("Service.Inventory")
local PlayerInfo = TSM.Include("Service.PlayerInfo")
local BagTracking = TSM.Include("Service.BagTracking")
local private = {
thread = nil,
sendDone = false,
}
-- ============================================================================
-- Module Functions
-- ============================================================================
function Groups.OnInitialize()
private.thread = Threading.New("MAIL_GROUPS", private.GroupsMailThread)
end
function Groups.KillThread()
Threading.Kill(private.thread)
end
function Groups.StartSending(callback, groupList, sendRepeat, isDryRun)
Threading.Kill(private.thread)
Threading.SetCallback(private.thread, callback)
Threading.Start(private.thread, groupList, sendRepeat, isDryRun)
end
-- ============================================================================
-- Group Sending Thread
-- ============================================================================
function private.GroupsMailThread(groupList, sendRepeat, isDryRun)
while true do
local targets = Threading.AcquireSafeTempTable()
local numMailable = Threading.AcquireSafeTempTable()
for _, groupPath in ipairs(groupList) do
if groupPath ~= TSM.CONST.ROOT_GROUP_PATH then
local used = Threading.AcquireSafeTempTable()
local keep = Threading.AcquireSafeTempTable()
for _, _, operationSettings in TSM.Operations.GroupOperationIterator("Mailing", groupPath) do
local target = operationSettings.target
if target ~= "" then
local targetItems = targets[target] or Threading.AcquireSafeTempTable()
for _, itemString in TSM.Groups.ItemIterator(groupPath) do
itemString = TSM.Groups.TranslateItemString(itemString)
used[itemString] = used[itemString] or 0
keep[itemString] = max(keep[itemString] or 0, operationSettings.keepQty)
numMailable[itemString] = numMailable[itemString] or BagTracking.GetNumMailable(itemString)
local numAvailable = numMailable[itemString] - used[itemString] - keep[itemString]
local quantity = private.GetItemQuantity(itemString, numAvailable, operationSettings)
assert(quantity >= 0)
if PlayerInfo.IsPlayer(target) then
keep[itemString] = max(keep[itemString], quantity)
else
used[itemString] = used[itemString] + quantity
if quantity > 0 then
targetItems[itemString] = quantity
end
end
end
if next(targetItems) then
targets[target] = targetItems
else
Threading.ReleaseSafeTempTable(targetItems)
end
end
end
Threading.ReleaseSafeTempTable(used)
Threading.ReleaseSafeTempTable(keep)
end
end
Threading.ReleaseSafeTempTable(numMailable)
if not next(targets) then
Log.PrintUser(L["Nothing to send."])
end
for name, items in pairs(targets) do
private.SendItems(name, items, isDryRun)
Threading.ReleaseSafeTempTable(items)
Threading.Sleep(0.5)
end
Threading.ReleaseSafeTempTable(targets)
if sendRepeat then
Threading.Sleep(TSM.db.global.mailingOptions.resendDelay * 60)
else
break
end
end
end
function private.SendItems(target, items, isDryRun)
private.sendDone = false
TSM.Mailing.Send.StartSending(private.SendCallback, target, "", "", 0, items, true, isDryRun)
while not private.sendDone do
Threading.Yield(true)
end
end
function private.SendCallback()
private.sendDone = true
end
function private.GetItemQuantity(itemString, numAvailable, operationSettings)
if numAvailable <= 0 then
return 0
end
local numToSend = 0
local isTargetPlayer = PlayerInfo.IsPlayer(operationSettings.target)
if operationSettings.maxQtyEnabled then
if operationSettings.restock then
local targetQty = private.GetTargetQuantity(operationSettings.target, itemString, operationSettings.restockSources)
if isTargetPlayer and targetQty <= operationSettings.maxQty then
numToSend = numAvailable
else
numToSend = min(numAvailable, operationSettings.maxQty - targetQty)
end
if isTargetPlayer then
numToSend = numAvailable - (targetQty - operationSettings.maxQty)
end
else
numToSend = min(numAvailable, operationSettings.maxQty)
end
elseif not isTargetPlayer then
numToSend = numAvailable
end
return max(numToSend, 0)
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

View File

@@ -0,0 +1,53 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local Inbox = TSM.Mailing:NewPackage("Inbox")
local Database = TSM.Include("Util.Database")
local TempTable = TSM.Include("Util.TempTable")
local MailTracking = TSM.Include("Service.MailTracking")
local private = {
itemsQuery = nil,
}
-- ============================================================================
-- Module Functions
-- ============================================================================
function Inbox.OnInitialize()
private.itemsQuery = MailTracking.CreateMailItemQuery()
:Equal("index", Database.BoundQueryParam())
end
function Inbox.CreateQuery()
return MailTracking.CreateMailInboxQuery()
:VirtualField("itemList", "string", private.GetVirtualItemList)
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.GetVirtualItemList(row)
private.itemsQuery:BindParams(row:GetField("index"))
local items = TempTable.Acquire()
for _, itemsRow in private.itemsQuery:Iterator() do
local itemName = TSM.UI.GetColoredItemName(itemsRow:GetField("itemLink")) or ""
local qty = itemsRow:GetField("quantity")
tinsert(items, qty > 1 and (itemName.." (x"..qty..")") or itemName)
end
local result = table.concat(items, ", ")
TempTable.Release(items)
return result
end

View File

@@ -0,0 +1,233 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local Open = TSM.Mailing:NewPackage("Open")
local L = TSM.Include("Locale").GetTable()
local Delay = TSM.Include("Util.Delay")
local Event = TSM.Include("Util.Event")
local String = TSM.Include("Util.String")
local Money = TSM.Include("Util.Money")
local Log = TSM.Include("Util.Log")
local ItemString = TSM.Include("Util.ItemString")
local Theme = TSM.Include("Util.Theme")
local Threading = TSM.Include("Service.Threading")
local ItemInfo = TSM.Include("Service.ItemInfo")
local MailTracking = TSM.Include("Service.MailTracking")
local private = {
thread = nil,
isOpening = false,
lastCheck = nil,
moneyCollected = 0,
}
local INBOX_SIZE = TSM.IsWowClassic() and 50 or 100
local MAIL_REFRESH_TIME = TSM.IsWowClassic() and 60 or 15
-- ============================================================================
-- Module Functions
-- ============================================================================
function Open.OnInitialize()
private.thread = Threading.New("MAIL_OPENING", private.OpenMailThread)
Event.Register("MAIL_SHOW", private.ScheduleCheck)
Event.Register("MAIL_CLOSED", private.MailClosedHandler)
end
function Open.KillThread()
Threading.Kill(private.thread)
private.PrintMoneyCollected()
private.isOpening = false
end
function Open.StartOpening(callback, autoRefresh, keepMoney, filterText, filterType)
Threading.Kill(private.thread)
private.isOpening = true
private.moneyCollected = 0
Threading.SetCallback(private.thread, callback)
Threading.Start(private.thread, autoRefresh, keepMoney, filterText, filterType)
end
function Open.GetLastCheckTime()
return private.lastCheck
end
-- ============================================================================
-- Mail Opening Thread
-- ============================================================================
function private.OpenMailThread(autoRefresh, keepMoney, filterText, filterType)
local isLastLoop = false
while true do
local query = TSM.Mailing.Inbox.CreateQuery()
query:ResetOrderBy()
:OrderBy("index", false)
:Or()
:Matches("itemList", filterText)
:Matches("subject", filterText)
:End()
:Select("index")
if filterType then
query:Equal("icon", filterType)
end
local mails = Threading.AcquireSafeTempTable()
for _, index in query:Iterator() do
tinsert(mails, index)
end
query:Release()
private.OpenMails(mails, keepMoney, filterType)
Threading.ReleaseSafeTempTable(mails)
if not autoRefresh or isLastLoop then
break
end
local numLeftMail, totalLeftMail = GetInboxNumItems()
if totalLeftMail == numLeftMail or numLeftMail == INBOX_SIZE then
isLastLoop = true
end
CheckInbox()
Threading.Sleep(1)
end
private.PrintMoneyCollected()
private.isOpening = false
end
function private.CanOpenMail()
return not C_Mail.IsCommandPending()
end
function private.OpenMails(mails, keepMoney, filterType)
for i = 1, #mails do
local index = mails[i]
Threading.WaitForFunction(private.CanOpenMail)
local mailType = MailTracking.GetMailType(index)
local matchesFilter = (not filterType and mailType) or (filterType and filterType == mailType)
local hasBagSpace = not MailTracking.GetInboxItemLink(index) or CalculateTotalNumberOfFreeBagSlots() > TSM.db.global.mailingOptions.keepMailSpace
if matchesFilter and hasBagSpace then
local _, _, _, _, money = GetInboxHeaderInfo(index)
if not keepMoney or (keepMoney and money <= 0) then
-- marks the mail as read
GetInboxText(index)
AutoLootMailItem(index)
private.moneyCollected = private.moneyCollected + money
if Threading.WaitForEvent("CLOSE_INBOX_ITEM", "MAIL_FAILED") ~= "MAIL_FAILED" then
if TSM.db.global.mailingOptions.inboxMessages then
private.PrintOpenMailMessage(index)
end
end
end
end
end
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.CheckInbox()
if private.isOpening then
private.ScheduleCheck()
return
end
if not TSM.UI.MailingUI.Inbox.IsMailOpened() then
CheckInbox()
end
private.ScheduleCheck()
end
function private.PrintMoneyCollected()
if TSM.db.global.mailingOptions.inboxMessages and private.moneyCollected > 0 then
Log.PrintfUser(L["Total Gold Collected: %s"], Money.ToString(private.moneyCollected))
end
private.moneyCollected = 0
end
function private.PrintOpenMailMessage(index)
local _, _, sender, subject, money, cod, _, hasItem = GetInboxHeaderInfo(index)
sender = sender or "?"
local _, _, _, _, isInvoice = GetInboxText(index)
if isInvoice then
-- it's an invoice
local invoiceType, itemName, playerName, bid, _, _, ahcut, _, _, _, quantity = GetInboxInvoiceInfo(index)
playerName = playerName or (invoiceType == "buyer" and AUCTION_HOUSE_MAIL_MULTIPLE_SELLERS or AUCTION_HOUSE_MAIL_MULTIPLE_BUYERS)
if invoiceType == "buyer" then
local itemLink = MailTracking.GetInboxItemLink(index) or "["..itemName.."]"
Log.PrintfUser(L["Bought %sx%d for %s from %s"], itemLink, quantity, Money.ToString(bid, Theme.GetFeedbackColor("RED"):GetTextColorPrefix()), playerName)
elseif invoiceType == "seller" then
Log.PrintfUser(L["Sold [%s]x%d for %s to %s"], itemName, quantity, Money.ToString(bid - ahcut, Theme.GetFeedbackColor("GREEN"):GetTextColorPrefix()), playerName)
end
elseif hasItem then
local itemLink
local quantity = 0
for i = 1, hasItem do
local link = GetInboxItemLink(index, i)
itemLink = itemLink or link
quantity = quantity + (select(4, GetInboxItem(index, i)) or 0)
if ItemString.Get(itemLink) ~= ItemString.Get(link) then
itemLink = L["Multiple Items"]
quantity = -1
break
end
end
if hasItem == 1 then
itemLink = MailTracking.GetInboxItemLink(index) or itemLink
end
local itemName = ItemInfo.GetName(itemLink) or "?"
local itemDesc = (quantity > 0 and format("%sx%d", itemLink, quantity)) or (quantity == -1 and "Multiple Items") or "?"
if hasItem == 1 and itemLink and strfind(subject, "^" .. String.Escape(format(AUCTION_EXPIRED_MAIL_SUBJECT, itemName))) then
Log.PrintfUser(L["Your auction of %s expired"], itemDesc)
elseif hasItem == 1 and quantity > 0 and (subject == format(AUCTION_REMOVED_MAIL_SUBJECT.."x%d", itemName, quantity) or subject == format(AUCTION_REMOVED_MAIL_SUBJECT, itemName)) then
Log.PrintfUser(L["Cancelled auction of %sx%d"], itemLink, quantity)
elseif cod > 0 then
Log.PrintfUser(L["%s sent you a COD of %s for %s"], sender, Money.ToString(cod, Theme.GetFeedbackColor("RED"):GetTextColorPrefix()), itemDesc)
elseif money > 0 then
Log.PrintfUser(L["%s sent you %s and %s"], sender, itemDesc, Money.ToString(money, Theme.GetFeedbackColor("GREEN"):GetTextColorPrefix()))
else
Log.PrintfUser(L["%s sent you %s"], sender, itemDesc)
end
elseif money > 0 then
Log.PrintfUser(L["%s sent you %s"], sender, Money.ToString(money, Theme.GetFeedbackColor("GREEN"):GetTextColorPrefix()))
elseif subject then
Log.PrintfUser(L["%s sent you a message: %s"], sender, subject)
end
end
-- ============================================================================
-- Event Handlers
-- ============================================================================
function private.ScheduleCheck()
if not private.lastCheck or time() - private.lastCheck > (MAIL_REFRESH_TIME - 1) then
private.lastCheck = time()
Delay.AfterTime("mailInboxCheck", MAIL_REFRESH_TIME, private.CheckInbox)
else
local nextUpdate = MAIL_REFRESH_TIME - (time() - private.lastCheck)
Delay.AfterTime("mailInboxCheck", nextUpdate, private.CheckInbox)
end
end
function private.MailClosedHandler()
Delay.Cancel("mailInboxCheck")
end

View File

@@ -0,0 +1,287 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local Send = TSM.Mailing:NewPackage("Send")
local L = TSM.Include("Locale").GetTable()
local Table = TSM.Include("Util.Table")
local Money = TSM.Include("Util.Money")
local SlotId = TSM.Include("Util.SlotId")
local Log = TSM.Include("Util.Log")
local ItemString = TSM.Include("Util.ItemString")
local Theme = TSM.Include("Util.Theme")
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 private = {
thread = nil,
bagUpdate = nil,
}
local PLAYER_NAME = UnitName("player")
local PLAYER_NAME_REALM = string.gsub(PLAYER_NAME.."-"..GetRealmName(), "%s+", "")
-- ============================================================================
-- Module Functions
-- ============================================================================
function Send.OnInitialize()
private.thread = Threading.New("MAIL_SENDING", private.SendMailThread)
BagTracking.RegisterCallback(private.BagUpdate)
end
function Send.KillThread()
Threading.Kill(private.thread)
end
function Send.StartSending(callback, recipient, subject, body, money, items, isGroup, isDryRun)
Threading.Kill(private.thread)
Threading.SetCallback(private.thread, callback)
Threading.Start(private.thread, recipient, subject, body, money, items, isGroup, isDryRun)
end
-- ============================================================================
-- Mail Sending Thread
-- ============================================================================
function private.SendMailThread(recipient, subject, body, money, items, isGroup, isDryRun)
if recipient == "" or recipient == PLAYER_NAME or recipient == PLAYER_NAME_REALM then
return
end
private.PrintMailMessage(money, items, recipient, isGroup, isDryRun)
if isDryRun then
return
end
if not items then
private.SendMail(recipient, subject, body, money, true)
return
end
ClearSendMail()
local itemInfo = Threading.AcquireSafeTempTable()
local query = BagTracking.CreateQueryBags()
:OrderBy("slotId", true)
:Select("bag", "slot", "itemString", "quantity")
:Equal("isBoP", false)
for _, bag, slot, itemString, quantity in query:Iterator() do
if isGroup then
itemString = TSM.Groups.TranslateItemString(itemString)
end
if items[itemString] and not InventoryInfo.IsBagSlotLocked(bag, slot) then
if not itemInfo[itemString] then
itemInfo[itemString] = { locations = {} }
end
tinsert(itemInfo[itemString].locations, { bag = bag, slot = slot, quantity = quantity })
end
end
query:Release()
for itemString, quantity in pairs(items) do
if quantity > 0 and itemInfo[itemString] and #itemInfo[itemString].locations > 0 then
for i = 1, #itemInfo[itemString].locations do
local info = itemInfo[itemString].locations[i]
if info.quantity > 0 then
if quantity == info.quantity then
PickupContainerItem(info.bag, info.slot)
ClickSendMailItemButton()
if private.GetNumPendingAttachments() == ATTACHMENTS_MAX_SEND or (isGroup and TSM.db.global.mailingOptions.sendItemsIndividually) then
private.SendMail(recipient, subject, body, money)
end
items[itemString] = 0
info.quantity = 0
break
end
end
end
end
end
for itemString in pairs(items) do
if items[itemString] > 0 and itemInfo[itemString] and #itemInfo[itemString].locations > 0 then
local emptySlotIds = private.GetEmptyBagSlotsThreaded(ItemString.IsItem(itemString) and GetItemFamily(ItemString.ToId(itemString)) or 0)
for i = 1, #itemInfo[itemString].locations do
local info = itemInfo[itemString].locations[i]
if items[itemString] > 0 and info.quantity > 0 then
if items[itemString] < info.quantity then
if #emptySlotIds > 0 then
local splitBag, splitSlot = SlotId.Split(tremove(emptySlotIds, 1))
SplitContainerItem(info.bag, info.slot, items[itemString])
PickupContainerItem(splitBag, splitSlot)
Threading.WaitForFunction(private.BagSlotHasItem, splitBag, splitSlot)
PickupContainerItem(splitBag, splitSlot)
ClickSendMailItemButton()
if private.GetNumPendingAttachments() == ATTACHMENTS_MAX_SEND then
private.SendMail(recipient, subject, body, money)
end
items[itemString] = 0
info.quantity = 0
break
end
else
PickupContainerItem(info.bag, info.slot)
ClickSendMailItemButton()
if private.GetNumPendingAttachments() == ATTACHMENTS_MAX_SEND then
private.SendMail(recipient, subject, body, money)
end
items[itemString] = items[itemString] - info.quantity
info.quantity = 0
end
end
end
if isGroup and TSM.db.global.mailingOptions.sendItemsIndividually then
private.SendMail(recipient, subject, body, money)
end
Threading.ReleaseSafeTempTable(emptySlotIds)
end
end
if private.HasPendingAttachments() then
private.SendMail(recipient, subject, body, money)
end
Threading.ReleaseSafeTempTable(itemInfo)
end
function private.PrintMailMessage(money, items, target, isGroup, isDryRun)
if not TSM.db.global.mailingOptions.sendMessages and not isDryRun then
return
end
if money > 0 and not items then
Log.PrintfUser(L["Sending %s to %s"], Money.ToString(money), target)
return
end
if not items then
return
end
local itemList = ""
for k, v in pairs(items) do
local coloredItem = ItemInfo.GetLink(k)
itemList = itemList..coloredItem.."x"..v..", "
end
itemList = strtrim(itemList, ", ")
if next(items) and money < 0 then
if isDryRun then
Log.PrintfUser(L["Would send %s to %s with a COD of %s"], itemList, target, Money.ToString(money, Theme.GetFeedbackColor("RED"):GetTextColorPrefix()))
else
Log.PrintfUser(L["Sending %s to %s with a COD of %s"], itemList, target, Money.ToString(money, Theme.GetFeedbackColor("RED"):GetTextColorPrefix()))
end
elseif next(items) then
if isDryRun then
Log.PrintfUser(L["Would send %s to %s"], itemList, target)
else
Log.PrintfUser(L["Sending %s to %s"], itemList, target)
end
end
end
function private.SendMail(recipient, subject, body, money, noItem)
if subject == "" then
local text = SendMailSubjectEditBox:GetText()
subject = text ~= "" and text or "TSM Mailing"
end
if money > 0 then
SetSendMailMoney(money)
SetSendMailCOD(0)
elseif money < 0 then
SetSendMailCOD(abs(money))
SetSendMailMoney(0)
else
SetSendMailMoney(0)
SetSendMailCOD(0)
end
private.bagUpdate = false
SendMail(recipient, subject, body)
if Threading.WaitForEvent("MAIL_SUCCESS", "MAIL_FAILED") == "MAIL_SUCCESS" then
if noItem then
Threading.Sleep(0.5)
else
Threading.WaitForFunction(private.HasNewBagUpdate)
end
else
Threading.Sleep(0.5)
end
end
function private.BagUpdate()
private.bagUpdate = true
end
function private.HasNewBagUpdate()
return private.bagUpdate
end
function private.HasPendingAttachments()
for i = 1, ATTACHMENTS_MAX_SEND do
if GetSendMailItem(i) then
return true
end
end
return false
end
function private.GetNumPendingAttachments()
local totalAttached = 0
for i = 1, ATTACHMENTS_MAX_SEND do
if GetSendMailItem(i) then
totalAttached = totalAttached + 1
end
end
return totalAttached
end
function private.BagSlotHasItem(bag, slot)
return GetContainerItemInfo(bag, slot) and true or false
end
function private.GetEmptyBagSlotsThreaded(itemFamily)
local emptySlotIds = Threading.AcquireSafeTempTable()
local sortvalue = Threading.AcquireSafeTempTable()
for bag = 0, NUM_BAG_SLOTS do
-- make sure the item can go in this bag
local bagFamily = bag ~= 0 and GetItemFamily(GetInventoryItemLink("player", ContainerIDToInventoryID(bag))) or 0
if bagFamily == 0 or bit.band(itemFamily, bagFamily) > 0 then
for slot = 1, GetContainerNumSlots(bag) do
if not GetContainerItemInfo(bag, slot) then
local slotId = SlotId.Join(bag, slot)
tinsert(emptySlotIds, slotId)
-- use special bags first
sortvalue[slotId] = slotId + (bagFamily > 0 and 0 or 100000)
end
end
end
Threading.Yield()
end
Table.SortWithValueLookup(emptySlotIds, sortvalue)
Threading.ReleaseSafeTempTable(sortvalue)
return emptySlotIds
end