initial commit
This commit is contained in:
300
Core/Service/Vendoring/Buy.lua
Normal file
300
Core/Service/Vendoring/Buy.lua
Normal file
@@ -0,0 +1,300 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Buy = TSM.Vendoring:NewPackage("Buy")
|
||||
local Database = TSM.Include("Util.Database")
|
||||
local Delay = TSM.Include("Util.Delay")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local Inventory = TSM.Include("Service.Inventory")
|
||||
local private = {
|
||||
merchantDB = nil,
|
||||
pendingIndex = nil,
|
||||
pendingQuantity = 0,
|
||||
}
|
||||
local FIRST_BUY_TIMEOUT = 5
|
||||
local FIRST_BUY_TIMEOUT_PER_STACK = 1
|
||||
local CONSECUTIVE_BUY_TIMEOUT = 5
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Buy.OnInitialize()
|
||||
private.merchantDB = Database.NewSchema("MERCHANT")
|
||||
:AddUniqueNumberField("index")
|
||||
:AddStringField("itemString")
|
||||
:AddSmartMapField("baseItemString", ItemString.GetBaseMap(), "itemString")
|
||||
:AddNumberField("price")
|
||||
:AddStringField("costItemsText")
|
||||
:AddStringField("firstCostItemString")
|
||||
:AddNumberField("stackSize")
|
||||
:AddNumberField("numAvailable")
|
||||
:Commit()
|
||||
Event.Register("MERCHANT_SHOW", private.MerchantShowEventHandler)
|
||||
Event.Register("MERCHANT_CLOSED", private.MerchantClosedEventHandler)
|
||||
Event.Register("MERCHANT_UPDATE", private.MerchantUpdateEventHandler)
|
||||
Event.Register("CHAT_MSG_LOOT", private.ChatMsgLootEventHandler)
|
||||
end
|
||||
|
||||
function Buy.CreateMerchantQuery()
|
||||
return private.merchantDB:NewQuery()
|
||||
end
|
||||
|
||||
function Buy.NeedsRepair()
|
||||
local _, needsRepair = GetRepairAllCost()
|
||||
return needsRepair
|
||||
end
|
||||
|
||||
function Buy.CanGuildRepair()
|
||||
return Buy.NeedsRepair() and not TSM.IsWowClassic() and CanGuildBankRepair()
|
||||
end
|
||||
|
||||
function Buy.DoGuildRepair()
|
||||
RepairAllItems(true)
|
||||
end
|
||||
|
||||
function Buy.DoRepair()
|
||||
RepairAllItems()
|
||||
end
|
||||
|
||||
function Buy.GetMaxCanAfford(index)
|
||||
local maxCanAfford = math.huge
|
||||
local _, _, price, stackSize, _, _, _, extendedCost = GetMerchantItemInfo(index)
|
||||
local numAltCurrencies = GetMerchantItemCostInfo(index)
|
||||
-- bug with big keech vendor returning extendedCost = true for gold only items
|
||||
if numAltCurrencies == 0 then
|
||||
extendedCost = false
|
||||
end
|
||||
|
||||
-- check the price
|
||||
if price > 0 then
|
||||
maxCanAfford = min(floor(GetMoney() / price), maxCanAfford)
|
||||
end
|
||||
|
||||
-- check the extended cost
|
||||
if extendedCost then
|
||||
assert(numAltCurrencies > 0)
|
||||
for i = 1, numAltCurrencies do
|
||||
local _, costNum, costItemLink, currencyName = GetMerchantItemCostItem(index, i)
|
||||
local costItemString = ItemString.Get(costItemLink)
|
||||
local costNumHave = nil
|
||||
if costItemString then
|
||||
costNumHave = Inventory.GetBagQuantity(costItemString) + Inventory.GetBankQuantity(costItemString) + Inventory.GetReagentBankQuantity(costItemString)
|
||||
elseif currencyName then
|
||||
if TSM.IsShadowlands() then
|
||||
for j = 1, C_CurrencyInfo.GetCurrencyListSize() do
|
||||
local info = C_CurrencyInfo.GetCurrencyListInfo(j)
|
||||
if not info.isHeader and info.name == currencyName then
|
||||
costNumHave = info.quantity
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
for j = 1, GetCurrencyListSize() do
|
||||
local name, isHeader, _, _, _, count = GetCurrencyListInfo(j)
|
||||
if not isHeader and name == currencyName then
|
||||
costNumHave = count
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if costNumHave then
|
||||
maxCanAfford = min(floor(costNumHave / costNum), maxCanAfford)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return maxCanAfford * stackSize
|
||||
end
|
||||
|
||||
function Buy.BuyItem(itemString, quantity)
|
||||
local index = private.GetFirstIndex(itemString)
|
||||
if not index then
|
||||
return
|
||||
end
|
||||
private.BuyIndex(index, quantity)
|
||||
end
|
||||
|
||||
function Buy.BuyItemIndex(index, quantity)
|
||||
private.BuyIndex(index, quantity)
|
||||
end
|
||||
|
||||
function Buy.CanBuyItem(itemString)
|
||||
local index = private.GetFirstIndex(itemString)
|
||||
return index and true or false
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.MerchantShowEventHandler()
|
||||
Delay.AfterFrame("UPDATE_MERCHANT_DB", 1, private.UpdateMerchantDB)
|
||||
end
|
||||
|
||||
function private.MerchantClosedEventHandler()
|
||||
private.ClearPendingContext()
|
||||
Delay.Cancel("UPDATE_MERCHANT_DB")
|
||||
Delay.Cancel("RESCAN_MERCHANT_DB")
|
||||
private.merchantDB:Truncate()
|
||||
end
|
||||
|
||||
function private.MerchantUpdateEventHandler()
|
||||
Delay.AfterFrame("UPDATE_MERCHANT_DB", 1, private.UpdateMerchantDB)
|
||||
end
|
||||
|
||||
function private.UpdateMerchantDB()
|
||||
local needsRetry = false
|
||||
private.merchantDB:TruncateAndBulkInsertStart()
|
||||
for i = 1, GetMerchantNumItems() do
|
||||
local itemLink = GetMerchantItemLink(i)
|
||||
local itemString = ItemString.Get(itemLink)
|
||||
if itemString then
|
||||
ItemInfo.StoreItemInfoByLink(itemLink)
|
||||
local _, _, price, stackSize, numAvailable, _, _, extendedCost = GetMerchantItemInfo(i)
|
||||
local numAltCurrencies = GetMerchantItemCostInfo(i)
|
||||
-- bug with big keech vendor returning extendedCost = true for gold only items
|
||||
if numAltCurrencies == 0 then
|
||||
extendedCost = false
|
||||
end
|
||||
local costItemsText, firstCostItemString = "", ""
|
||||
if extendedCost then
|
||||
assert(numAltCurrencies > 0)
|
||||
local costItems = TempTable.Acquire()
|
||||
for j = 1, numAltCurrencies do
|
||||
local _, costNum, costItemLink = GetMerchantItemCostItem(i, j)
|
||||
local costItemString = ItemString.Get(costItemLink)
|
||||
local texture = nil
|
||||
if not costItemLink then
|
||||
needsRetry = true
|
||||
elseif costItemString then
|
||||
firstCostItemString = firstCostItemString ~= "" and firstCostItemString or costItemString
|
||||
texture = ItemInfo.GetTexture(costItemString)
|
||||
elseif strmatch(costItemLink, "currency:") then
|
||||
if TSM.IsShadowlands() then
|
||||
texture = C_CurrencyInfo.GetCurrencyInfoFromLink(costItemLink).iconFileID
|
||||
else
|
||||
_, _, texture = GetCurrencyInfo(costItemLink)
|
||||
end
|
||||
firstCostItemString = strmatch(costItemLink, "(currency:%d+)")
|
||||
else
|
||||
error(format("Unknown item cost (%d, %d, %s)", i, costNum, tostring(costItemLink)))
|
||||
end
|
||||
if TSM.Vendoring.Buy.GetMaxCanAfford(i) < stackSize then
|
||||
costNum = Theme.GetFeedbackColor("RED"):ColorText(costNum)
|
||||
end
|
||||
tinsert(costItems, costNum.." |T"..(texture or "")..":12|t")
|
||||
end
|
||||
costItemsText = table.concat(costItems, " ")
|
||||
TempTable.Release(costItems)
|
||||
end
|
||||
private.merchantDB:BulkInsertNewRow(i, itemString, price, costItemsText, firstCostItemString, stackSize, numAvailable)
|
||||
end
|
||||
end
|
||||
private.merchantDB:BulkInsertEnd()
|
||||
|
||||
if needsRetry then
|
||||
Log.Err("Failed to scan merchant")
|
||||
Delay.AfterTime("RESCAN_MERCHANT_DB", 0.2, private.UpdateMerchantDB)
|
||||
else
|
||||
Delay.Cancel("RESCAN_MERCHANT_DB")
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetFirstIndex(itemString)
|
||||
local index = Buy.CreateMerchantQuery()
|
||||
:Equal("itemString", itemString)
|
||||
:OrderBy("index", true)
|
||||
:Select("index")
|
||||
:GetFirstResultAndRelease()
|
||||
if not index and ItemString.GetBaseFast(itemString) == itemString then
|
||||
index = Buy.CreateMerchantQuery()
|
||||
:Equal("baseItemString", itemString)
|
||||
:OrderBy("index", true)
|
||||
:Select("index")
|
||||
:GetFirstResultAndRelease()
|
||||
end
|
||||
return index
|
||||
end
|
||||
|
||||
function private.BuyIndex(index, quantity)
|
||||
local maxStack = GetMerchantItemMaxStack(index)
|
||||
quantity = min(quantity, Buy.GetMaxCanAfford(index))
|
||||
if quantity == 0 then
|
||||
return
|
||||
end
|
||||
private.ClearPendingContext()
|
||||
private.pendingIndex = index
|
||||
local numStacks = 0
|
||||
while quantity > 0 do
|
||||
local buyQuantity = min(quantity, maxStack)
|
||||
BuyMerchantItem(index, buyQuantity)
|
||||
private.pendingQuantity = private.pendingQuantity + buyQuantity
|
||||
quantity = quantity - buyQuantity
|
||||
numStacks = numStacks + 1
|
||||
end
|
||||
Log.Info("Buying %d of %d (%d stacks)", private.pendingQuantity, index, numStacks)
|
||||
Delay.AfterTime("VENDORING_BUY_TIMEOUT", numStacks * FIRST_BUY_TIMEOUT_PER_STACK + FIRST_BUY_TIMEOUT, private.BuyTimeout)
|
||||
end
|
||||
|
||||
function private.ChatMsgLootEventHandler(_, msg)
|
||||
if not private.pendingIndex then
|
||||
return
|
||||
end
|
||||
local link = GetMerchantItemLink(private.pendingIndex)
|
||||
if not link then
|
||||
Log.Err("Failed to get link (%s)", private.pendingIndex)
|
||||
private.ClearPendingContext()
|
||||
return
|
||||
end
|
||||
local quantity = nil
|
||||
if msg == format(LOOT_ITEM_PUSHED_SELF, link) then
|
||||
quantity = 1
|
||||
else
|
||||
for i = 1, GetMerchantItemMaxStack(private.pendingIndex) do
|
||||
if msg == format(LOOT_ITEM_PUSHED_SELF_MULTIPLE, link, i) then
|
||||
quantity = i
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
Log.Info("Got CHAT_MSG_LOOT(%s) with a quantity of %s (%d pending)", msg, tostring(quantity), private.pendingQuantity)
|
||||
if not quantity then
|
||||
return
|
||||
end
|
||||
private.pendingQuantity = private.pendingQuantity - quantity
|
||||
if private.pendingQuantity <= 0 then
|
||||
-- we're done
|
||||
private.ClearPendingContext()
|
||||
return
|
||||
end
|
||||
|
||||
-- reset the timeout
|
||||
Delay.Cancel("VENDORING_BUY_TIMEOUT")
|
||||
Delay.AfterTime("VENDORING_BUY_TIMEOUT", CONSECUTIVE_BUY_TIMEOUT, private.BuyTimeout)
|
||||
end
|
||||
|
||||
function private.BuyTimeout()
|
||||
Log.Warn("Retrying buying (%d, %d)", private.pendingIndex, private.pendingQuantity)
|
||||
Buy.BuyItemIndex(private.pendingIndex, private.pendingQuantity)
|
||||
end
|
||||
|
||||
function private.ClearPendingContext()
|
||||
private.pendingIndex = nil
|
||||
private.pendingQuantity = 0
|
||||
Delay.Cancel("VENDORING_BUY_TIMEOUT")
|
||||
end
|
||||
74
Core/Service/Vendoring/Buyback.lua
Normal file
74
Core/Service/Vendoring/Buyback.lua
Normal file
@@ -0,0 +1,74 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Buyback = TSM.Vendoring:NewPackage("Buyback")
|
||||
local Database = TSM.Include("Util.Database")
|
||||
local Delay = TSM.Include("Util.Delay")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local private = {
|
||||
buybackDB = nil,
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Buyback.OnInitialize()
|
||||
private.buybackDB = Database.NewSchema("BUYBACK")
|
||||
:AddUniqueNumberField("index")
|
||||
:AddStringField("itemString")
|
||||
:AddNumberField("price")
|
||||
:AddNumberField("quantity")
|
||||
:Commit()
|
||||
Event.Register("MERCHANT_SHOW", private.MerchantShowEventHandler)
|
||||
Event.Register("MERCHANT_CLOSED", private.MerchantClosedEventHandler)
|
||||
Event.Register("MERCHANT_UPDATE", private.MerchantUpdateEventHandler)
|
||||
end
|
||||
|
||||
function Buyback.CreateQuery()
|
||||
return private.buybackDB:NewQuery()
|
||||
:InnerJoin(ItemInfo.GetDBForJoin(), "itemString")
|
||||
end
|
||||
|
||||
function Buyback.BuybackItem(index)
|
||||
BuybackItem(index)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.MerchantShowEventHandler()
|
||||
Delay.AfterFrame("UPDATE_BUYBACK_DB", 1, private.UpdateBuybackDB)
|
||||
end
|
||||
|
||||
function private.MerchantClosedEventHandler()
|
||||
Delay.Cancel("UPDATE_BUYBACK_DB")
|
||||
private.buybackDB:Truncate()
|
||||
end
|
||||
|
||||
function private.MerchantUpdateEventHandler()
|
||||
Delay.AfterFrame("UPDATE_BUYBACK_DB", 1, private.UpdateBuybackDB)
|
||||
end
|
||||
|
||||
function private.UpdateBuybackDB()
|
||||
private.buybackDB:TruncateAndBulkInsertStart()
|
||||
for i = 1, GetNumBuybackItems() do
|
||||
local itemString = ItemString.Get(GetBuybackItemLink(i))
|
||||
if itemString then
|
||||
local _, _, price, quantity = GetBuybackItemInfo(i)
|
||||
private.buybackDB:BulkInsertNewRow(i, itemString, price, quantity)
|
||||
end
|
||||
end
|
||||
private.buybackDB:BulkInsertEnd()
|
||||
end
|
||||
8
Core/Service/Vendoring/Core.lua
Normal file
8
Core/Service/Vendoring/Core.lua
Normal file
@@ -0,0 +1,8 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
TSM:NewPackage("Vendoring")
|
||||
278
Core/Service/Vendoring/Groups.lua
Normal file
278
Core/Service/Vendoring/Groups.lua
Normal file
@@ -0,0 +1,278 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Groups = TSM.Vendoring:NewPackage("Groups")
|
||||
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 Threading = TSM.Include("Service.Threading")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local CustomPrice = TSM.Include("Service.CustomPrice")
|
||||
local BagTracking = TSM.Include("Service.BagTracking")
|
||||
local Inventory = TSM.Include("Service.Inventory")
|
||||
local private = {
|
||||
buyThreadId = nil,
|
||||
sellThreadId = nil,
|
||||
tempGroups = {},
|
||||
printedBagsFullMsg = false,
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Groups.OnInitialize()
|
||||
private.buyThreadId = Threading.New("VENDORING_GROUP_BUY", private.BuyThread)
|
||||
private.sellThreadId = Threading.New("VENDORING_GROUP_SELL", private.SellThread)
|
||||
end
|
||||
|
||||
function Groups.BuyGroups(groups, callback)
|
||||
Groups.StopBuySell()
|
||||
|
||||
wipe(private.tempGroups)
|
||||
for _, groupPath in ipairs(groups) do
|
||||
tinsert(private.tempGroups, groupPath)
|
||||
end
|
||||
Threading.SetCallback(private.buyThreadId, callback)
|
||||
Threading.Start(private.buyThreadId, private.tempGroups)
|
||||
end
|
||||
|
||||
function Groups.SellGroups(groups, callback)
|
||||
Groups.StopBuySell()
|
||||
|
||||
wipe(private.tempGroups)
|
||||
for _, groupPath in ipairs(groups) do
|
||||
tinsert(private.tempGroups, groupPath)
|
||||
end
|
||||
Threading.SetCallback(private.sellThreadId, callback)
|
||||
Threading.Start(private.sellThreadId, private.tempGroups)
|
||||
end
|
||||
|
||||
function Groups.StopBuySell()
|
||||
Threading.Kill(private.buyThreadId)
|
||||
Threading.Kill(private.sellThreadId)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Buy Thread
|
||||
-- ============================================================================
|
||||
|
||||
function private.BuyThread(groups)
|
||||
for _, groupPath in ipairs(groups) do
|
||||
groups[groupPath] = true
|
||||
end
|
||||
|
||||
local itemsToBuy = Threading.AcquireSafeTempTable()
|
||||
local itemBuyQuantity = Threading.AcquireSafeTempTable()
|
||||
local query = TSM.Vendoring.Buy.CreateMerchantQuery()
|
||||
:InnerJoin(ItemInfo.GetDBForJoin(), "itemString")
|
||||
:InnerJoin(TSM.Groups.GetItemDBForJoin(), "itemString")
|
||||
:Select("itemString", "groupPath", "numAvailable")
|
||||
for _, itemString, groupPath, numAvailable in query:Iterator() do
|
||||
if groups[groupPath] then
|
||||
local _, operationSettings = TSM.Operations.GetFirstOperationByItem("Vendoring", itemString)
|
||||
if operationSettings.enableBuy then
|
||||
local numToBuy = private.GetNumToBuy(itemString, operationSettings)
|
||||
if numAvailable ~= -1 then
|
||||
numToBuy = min(numToBuy, numAvailable)
|
||||
end
|
||||
if numToBuy > 0 then
|
||||
assert(not itemBuyQuantity[itemString])
|
||||
tinsert(itemsToBuy, itemString)
|
||||
itemBuyQuantity[itemString] = numToBuy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
query:Release()
|
||||
|
||||
for _, itemString in ipairs(itemsToBuy) do
|
||||
local numToBuy = itemBuyQuantity[itemString]
|
||||
TSM.Vendoring.Buy.BuyItem(itemString, numToBuy)
|
||||
Threading.Yield(true)
|
||||
end
|
||||
|
||||
Threading.ReleaseSafeTempTable(itemsToBuy)
|
||||
Threading.ReleaseSafeTempTable(itemBuyQuantity)
|
||||
end
|
||||
|
||||
function private.GetNumToBuy(itemString, operationSettings)
|
||||
local numHave = BagTracking.CreateQueryBagsItem(itemString)
|
||||
:VirtualField("autoBaseItemString", "string", TSM.Groups.TranslateItemString, "itemString")
|
||||
:Equal("autoBaseItemString", itemString)
|
||||
:Equal("isBoA", false)
|
||||
:SumAndRelease("quantity") or 0
|
||||
if operationSettings.restockSources.bank then
|
||||
numHave = numHave + Inventory.GetBankQuantity(itemString) + Inventory.GetReagentBankQuantity(itemString)
|
||||
end
|
||||
if operationSettings.restockSources.guild then
|
||||
numHave = numHave + Inventory.GetGuildQuantity(itemString)
|
||||
end
|
||||
if operationSettings.restockSources.ah then
|
||||
numHave = numHave + Inventory.GetAuctionQuantity(itemString)
|
||||
end
|
||||
if operationSettings.restockSources.mail then
|
||||
numHave = numHave + Inventory.GetMailQuantity(itemString)
|
||||
end
|
||||
if operationSettings.restockSources.alts or operationSettings.restockSources.alts_ah then
|
||||
local _, alts, _, altsAH = Inventory.GetPlayerTotals(itemString)
|
||||
numHave = numHave + (operationSettings.restockSources.alts and alts or 0) + (operationSettings.restockSources.alts_ah and altsAH or 0)
|
||||
end
|
||||
return max(operationSettings.restockQty - numHave, 0)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Sell Thread
|
||||
-- ============================================================================
|
||||
|
||||
function private.SellThread(groups)
|
||||
private.printedBagsFullMsg = false
|
||||
local totalValue = 0
|
||||
local operationsTemp = Threading.AcquireSafeTempTable()
|
||||
for _, groupPath in ipairs(groups) do
|
||||
if groupPath ~= TSM.CONST.ROOT_GROUP_PATH then
|
||||
wipe(operationsTemp)
|
||||
for _, operationName, operationSettings in TSM.Operations.GroupOperationIterator("Vendoring", groupPath) do
|
||||
if operationSettings.enableSell then
|
||||
tinsert(operationsTemp, operationName)
|
||||
end
|
||||
end
|
||||
for _, operationName in ipairs(operationsTemp) do
|
||||
for _, itemString in TSM.Groups.ItemIterator(groupPath) do
|
||||
totalValue = totalValue + private.SellItemThreaded(itemString, TSM.Operations.GetSettings("Vendoring", operationName))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Threading.ReleaseSafeTempTable(operationsTemp)
|
||||
|
||||
if TSM.db.global.vendoringOptions.displayMoneyCollected then
|
||||
Log.PrintfUser(L["Sold %s worth of items."], Money.ToString(totalValue))
|
||||
end
|
||||
end
|
||||
|
||||
function private.SellItemThreaded(itemString, operationSettings)
|
||||
-- calculate the number to sell
|
||||
local numHave = BagTracking.CreateQueryBagsItem(itemString)
|
||||
:VirtualField("autoBaseItemString", "string", TSM.Groups.TranslateItemString, "itemString")
|
||||
:Equal("autoBaseItemString", itemString)
|
||||
:Equal("isBoA", false)
|
||||
:SumAndRelease("quantity") or 0
|
||||
local numToSell = numHave - operationSettings.keepQty
|
||||
if numToSell <= 0 then
|
||||
return 0
|
||||
end
|
||||
|
||||
-- check the expires
|
||||
if operationSettings.sellAfterExpired > 0 and TSM.Accounting.Auctions.GetNumExpiresSinceSale(itemString) < operationSettings.sellAfterExpired then
|
||||
return 0
|
||||
end
|
||||
|
||||
-- check the destroy value
|
||||
local destroyValue = CustomPrice.GetValue(operationSettings.vsDestroyValue, itemString) or 0
|
||||
local maxDestroyValue = CustomPrice.GetValue(operationSettings.vsMaxDestroyValue, itemString) or 0
|
||||
if maxDestroyValue > 0 and destroyValue >= maxDestroyValue then
|
||||
return 0
|
||||
end
|
||||
|
||||
-- check the market value
|
||||
local marketValue = CustomPrice.GetValue(operationSettings.vsMarketValue, itemString) or 0
|
||||
local maxMarketValue = CustomPrice.GetValue(operationSettings.vsMaxMarketValue, itemString) or 0
|
||||
if maxMarketValue > 0 and marketValue >= maxMarketValue then
|
||||
return 0
|
||||
end
|
||||
|
||||
-- get a list of empty slots which we can use to split items into
|
||||
local emptySlotIds = private.GetEmptyBagSlotsThreaded(ItemString.IsItem(itemString) and GetItemFamily(ItemString.ToId(itemString)) or 0)
|
||||
|
||||
-- get a list of slots containing the item we want to sell
|
||||
local slotIds = Threading.AcquireSafeTempTable()
|
||||
local bagQuery = BagTracking.CreateQueryBagsItem(itemString)
|
||||
:VirtualField("autoBaseItemString", "string", TSM.Groups.TranslateItemString, "itemString")
|
||||
:Equal("autoBaseItemString", itemString)
|
||||
:Select("slotId", "quantity")
|
||||
:Equal("isBoA", false)
|
||||
:OrderBy("quantity", true)
|
||||
if not operationSettings.sellSoulbound then
|
||||
bagQuery:Equal("isBoP", false)
|
||||
end
|
||||
for _, slotId in bagQuery:Iterator() do
|
||||
tinsert(slotIds, slotId)
|
||||
end
|
||||
bagQuery:Release()
|
||||
|
||||
local totalValue = 0
|
||||
for _, slotId in ipairs(slotIds) do
|
||||
local bag, slot = SlotId.Split(slotId)
|
||||
local quantity = BagTracking.GetQuantityBySlotId(slotId)
|
||||
if quantity <= numToSell then
|
||||
UseContainerItem(bag, slot)
|
||||
totalValue = totalValue + ((ItemInfo.GetVendorSell(itemString) or 0) * quantity)
|
||||
numToSell = numToSell - quantity
|
||||
else
|
||||
if #emptySlotIds > 0 then
|
||||
local splitBag, splitSlot = SlotId.Split(tremove(emptySlotIds, 1))
|
||||
SplitContainerItem(bag, slot, numToSell)
|
||||
PickupContainerItem(splitBag, splitSlot)
|
||||
-- wait for the stack to be split
|
||||
Threading.WaitForFunction(private.BagSlotHasItem, splitBag, splitSlot)
|
||||
PickupContainerItem(splitBag, splitSlot)
|
||||
UseContainerItem(splitBag, splitSlot)
|
||||
totalValue = totalValue + ((ItemInfo.GetVendorSell(itemString) or 0) * quantity)
|
||||
elseif not private.printedBagsFullMsg then
|
||||
Log.PrintUser(L["Could not sell items due to not having free bag space available to split a stack of items."])
|
||||
private.printedBagsFullMsg = true
|
||||
end
|
||||
-- we're done
|
||||
numToSell = 0
|
||||
end
|
||||
if numToSell == 0 then
|
||||
break
|
||||
end
|
||||
Threading.Yield(true)
|
||||
end
|
||||
|
||||
Threading.ReleaseSafeTempTable(slotIds)
|
||||
Threading.ReleaseSafeTempTable(emptySlotIds)
|
||||
return totalValue
|
||||
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
|
||||
|
||||
function private.BagSlotHasItem(bag, slot)
|
||||
return GetContainerItemInfo(bag, slot) and true or false
|
||||
end
|
||||
168
Core/Service/Vendoring/Sell.lua
Normal file
168
Core/Service/Vendoring/Sell.lua
Normal file
@@ -0,0 +1,168 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Sell = TSM.Vendoring:NewPackage("Sell")
|
||||
local Database = TSM.Include("Util.Database")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local CustomPrice = TSM.Include("Service.CustomPrice")
|
||||
local BagTracking = TSM.Include("Service.BagTracking")
|
||||
local private = {
|
||||
ignoreDB = nil,
|
||||
potentialValueDB = nil,
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Sell.OnInitialize()
|
||||
local used = TempTable.Acquire()
|
||||
private.ignoreDB = Database.NewSchema("VENDORING_IGNORE")
|
||||
:AddUniqueStringField("itemString")
|
||||
:AddBooleanField("ignoreSession")
|
||||
:AddBooleanField("ignorePermanent")
|
||||
:Commit()
|
||||
private.ignoreDB:BulkInsertStart()
|
||||
for itemString in pairs(TSM.db.global.userData.vendoringIgnore) do
|
||||
itemString = ItemString.Get(itemString)
|
||||
if not used[itemString] then
|
||||
used[itemString] = true
|
||||
private.ignoreDB:BulkInsertNewRow(itemString, false, true)
|
||||
end
|
||||
end
|
||||
private.ignoreDB:BulkInsertEnd()
|
||||
TempTable.Release(used)
|
||||
|
||||
private.potentialValueDB = Database.NewSchema("VENDORING_POTENTIAL_VALUE")
|
||||
:AddUniqueStringField("itemString")
|
||||
:AddNumberField("potentialValue")
|
||||
:Commit()
|
||||
BagTracking.RegisterCallback(private.UpdatePotentialValueDB)
|
||||
end
|
||||
|
||||
function Sell.IgnoreItemSession(itemString)
|
||||
local row = private.ignoreDB:GetUniqueRow("itemString", itemString)
|
||||
if row then
|
||||
assert(not row:GetField("ignoreSession"))
|
||||
row:SetField("ignoreSession", true)
|
||||
row:Update()
|
||||
row:Release()
|
||||
else
|
||||
private.ignoreDB:NewRow()
|
||||
:SetField("itemString", itemString)
|
||||
:SetField("ignoreSession", true)
|
||||
:SetField("ignorePermanent", false)
|
||||
:Create()
|
||||
end
|
||||
end
|
||||
|
||||
function Sell.IgnoreItemPermanent(itemString)
|
||||
assert(not TSM.db.global.userData.vendoringIgnore[itemString])
|
||||
TSM.db.global.userData.vendoringIgnore[itemString] = true
|
||||
|
||||
local row = private.ignoreDB:GetUniqueRow("itemString", itemString)
|
||||
if row then
|
||||
assert(not row:GetField("ignorePermanent"))
|
||||
row:SetField("ignorePermanent", true)
|
||||
row:Update()
|
||||
row:Release()
|
||||
else
|
||||
private.ignoreDB:NewRow()
|
||||
:SetField("itemString", itemString)
|
||||
:SetField("ignoreSession", false)
|
||||
:SetField("ignorePermanent", true)
|
||||
:Create()
|
||||
end
|
||||
end
|
||||
|
||||
function Sell.ForgetIgnoreItemPermanent(itemString)
|
||||
assert(TSM.db.global.userData.vendoringIgnore[itemString])
|
||||
TSM.db.global.userData.vendoringIgnore[itemString] = nil
|
||||
|
||||
local row = private.ignoreDB:GetUniqueRow("itemString", itemString)
|
||||
assert(row and row:GetField("ignorePermanent"))
|
||||
if row:GetField("ignoreSession") then
|
||||
row:SetField("ignorePermanent")
|
||||
row:Update()
|
||||
else
|
||||
private.ignoreDB:DeleteRow(row)
|
||||
end
|
||||
row:Release()
|
||||
end
|
||||
|
||||
function Sell.CreateIgnoreQuery()
|
||||
return private.ignoreDB:NewQuery()
|
||||
:Equal("ignorePermanent", true)
|
||||
:InnerJoin(ItemInfo.GetDBForJoin(), "itemString")
|
||||
:OrderBy("name", true)
|
||||
end
|
||||
|
||||
function Sell.CreateBagsQuery()
|
||||
local query = BagTracking.CreateQueryBags()
|
||||
:Distinct("itemString")
|
||||
:LeftJoin(private.ignoreDB, "itemString")
|
||||
:InnerJoin(ItemInfo.GetDBForJoin(), "itemString")
|
||||
:LeftJoin(private.potentialValueDB, "itemString")
|
||||
:Equal("isBoP", false)
|
||||
:Equal("isBoA", false)
|
||||
Sell.ResetBagsQuery(query)
|
||||
return query
|
||||
end
|
||||
|
||||
function Sell.ResetBagsQuery(query)
|
||||
query:ResetOrderBy()
|
||||
query:ResetFilters()
|
||||
BagTracking.FilterQueryBags(query)
|
||||
query:NotEqual("ignoreSession", true)
|
||||
:NotEqual("ignorePermanent", true)
|
||||
:Equal("isBoP", false)
|
||||
:Equal("isBoA", false)
|
||||
:GreaterThan("vendorSell", 0)
|
||||
:OrderBy("name", true)
|
||||
end
|
||||
|
||||
function Sell.SellItem(itemString, includeSoulbound)
|
||||
local query = BagTracking.CreateQueryBags()
|
||||
:OrderBy("slotId", true)
|
||||
:Select("bag", "slot", "itemString")
|
||||
:Equal("isBoP", false)
|
||||
:Equal("isBoA", false)
|
||||
for _, bag, slot, bagItemString in query:Iterator() do
|
||||
if itemString == bagItemString and ItemString.Get(GetContainerItemLink(bag, slot)) == itemString then
|
||||
UseContainerItem(bag, slot)
|
||||
end
|
||||
end
|
||||
query:Release()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.UpdatePotentialValueDB()
|
||||
private.potentialValueDB:TruncateAndBulkInsertStart()
|
||||
local query = BagTracking.CreateQueryBags()
|
||||
:OrderBy("slotId", true)
|
||||
:Select("itemString")
|
||||
:Distinct("itemString")
|
||||
:Equal("isBoP", false)
|
||||
:Equal("isBoA", false)
|
||||
for _, itemString in query:Iterator() do
|
||||
local value = CustomPrice.GetValue(TSM.db.global.vendoringOptions.qsMarketValue, itemString)
|
||||
if value then
|
||||
private.potentialValueDB:BulkInsertNewRow(itemString, value)
|
||||
end
|
||||
end
|
||||
query:Release()
|
||||
private.potentialValueDB:BulkInsertEnd()
|
||||
end
|
||||
Reference in New Issue
Block a user