-- ------------------------------------------------------------------------------ -- -- TradeSkillMaster -- -- https://tradeskillmaster.com -- -- All Rights Reserved - Detailed license information included with addon. -- -- ------------------------------------------------------------------------------ -- local _, TSM = ... local Crafting = TSM:NewPackage("Crafting") local L = TSM.Include("Locale").GetTable() local ProfessionInfo = TSM.Include("Data.ProfessionInfo") local Database = TSM.Include("Util.Database") local TempTable = TSM.Include("Util.TempTable") local Table = TSM.Include("Util.Table") local Math = TSM.Include("Util.Math") local Money = TSM.Include("Util.Money") local String = TSM.Include("Util.String") local Vararg = TSM.Include("Util.Vararg") local Log = TSM.Include("Util.Log") local ItemString = TSM.Include("Util.ItemString") local ItemInfo = TSM.Include("Service.ItemInfo") local CustomPrice = TSM.Include("Service.CustomPrice") local Conversions = TSM.Include("Service.Conversions") local Inventory = TSM.Include("Service.Inventory") local private = { spellDB = nil, matDB = nil, matItemDB = nil, matDBSpellIdQuery = nil, matDBMatsInTableQuery = nil, matDBMatNamesQuery = nil, ignoredCooldownDB = nil, } local CHARACTER_KEY = UnitName("player").." - "..GetRealmName() local IGNORED_COOLDOWN_SEP = "\001" local PROFESSION_SEP = "," local PLAYER_SEP = "," local BAD_CRAFTING_PRICE_SOURCES = { crafting = true, } -- ============================================================================ -- Module Functions -- ============================================================================ function Crafting.OnInitialize() local used = TempTable.Acquire() for _, craftInfo in pairs(TSM.db.factionrealm.internalData.crafts) do for itemString in pairs(craftInfo.mats) do if strmatch(itemString, "^o:") then local _, _, matList = strsplit(":", itemString) for matItemId in String.SplitIterator(matList, ",") do used["i:"..matItemId] = true end else used[itemString] = true end end end for itemString in pairs(used) do TSM.db.factionrealm.internalData.mats[itemString] = TSM.db.factionrealm.internalData.mats[itemString] or {} end for itemString in pairs(TSM.db.factionrealm.internalData.mats) do if not used[itemString] then TSM.db.factionrealm.internalData.mats[itemString] = nil end end TempTable.Release(used) local professionItems = TempTable.Acquire() local matSpellCount = TempTable.Acquire() local matFirstItemString = TempTable.Acquire() local matFirstQuantity = TempTable.Acquire() private.matDB = Database.NewSchema("CRAFTING_MATS") :AddNumberField("spellId") :AddStringField("itemString") :AddNumberField("quantity") :AddIndex("spellId") :AddIndex("itemString") :Commit() private.matDB:BulkInsertStart() private.spellDB = Database.NewSchema("CRAFTING_SPELLS") :AddUniqueNumberField("spellId") :AddStringField("itemString") :AddStringField("itemName") :AddStringField("name") :AddStringField("profession") :AddNumberField("numResult") :AddStringField("players") :AddBooleanField("hasCD") :AddIndex("itemString") :Commit() private.spellDB:BulkInsertStart() local playersTemp = TempTable.Acquire() for spellId, craftInfo in pairs(TSM.db.factionrealm.internalData.crafts) do wipe(playersTemp) for player in pairs(craftInfo.players) do tinsert(playersTemp, player) end sort(playersTemp) local playersStr = table.concat(playersTemp, PLAYER_SEP) local itemName = ItemInfo.GetName(craftInfo.itemString) or "" private.spellDB:BulkInsertNewRow(spellId, craftInfo.itemString, itemName, craftInfo.name or "", craftInfo.profession, craftInfo.numResult, playersStr, craftInfo.hasCD and true or false) for matItemString, matQuantity in pairs(craftInfo.mats) do private.matDB:BulkInsertNewRow(spellId, matItemString, matQuantity) professionItems[craftInfo.profession] = professionItems[craftInfo.profession] or TempTable.Acquire() matSpellCount[spellId] = (matSpellCount[spellId] or 0) + 1 if matQuantity > 0 then matFirstItemString[spellId] = matItemString matFirstQuantity[spellId] = matQuantity end if strmatch(matItemString, "^o:") then local _, _, matList = strsplit(":", matItemString) for matItemId in String.SplitIterator(matList, ",") do local optionalMatItemString = "i:"..matItemId professionItems[craftInfo.profession][optionalMatItemString] = true end else professionItems[craftInfo.profession][matItemString] = true end end end TempTable.Release(playersTemp) private.spellDB:BulkInsertEnd() private.matDB:BulkInsertEnd() private.matDBMatsInTableQuery = private.matDB:NewQuery() :Select("itemString", "quantity") :Equal("spellId", Database.BoundQueryParam()) :GreaterThan("quantity", 0) private.matDBMatNamesQuery = private.matDB:NewQuery() :Select("name") :InnerJoin(ItemInfo.GetDBForJoin(), "itemString") :Equal("spellId", Database.BoundQueryParam()) :GreaterThan("quantity", 0) private.matItemDB = Database.NewSchema("CRAFTING_MAT_ITEMS") :AddUniqueStringField("itemString") :AddStringField("professions") :AddStringField("customValue") :Commit() private.matItemDB:BulkInsertStart() local professionsTemp = TempTable.Acquire() for itemString, info in pairs(TSM.db.factionrealm.internalData.mats) do wipe(professionsTemp) for profession, items in pairs(professionItems) do if items[itemString] then tinsert(professionsTemp, profession) end end sort(professionsTemp) local professionsStr = table.concat(professionsTemp) private.matItemDB:BulkInsertNewRow(itemString, professionsStr, info.customValue or "") end TempTable.Release(professionsTemp) private.matItemDB:BulkInsertEnd() for _, tbl in pairs(professionItems) do TempTable.Release(tbl) end TempTable.Release(professionItems) private.matDBSpellIdQuery = private.matDB:NewQuery() :Equal("spellId", Database.BoundQueryParam()) -- register 1:1 crafting conversions local addedConversion = false local query = private.spellDB:NewQuery() :Select("spellId", "itemString", "numResult") :Equal("hasCD", false) for _, spellId, itemString, numResult in query:Iterator() do if not ProfessionInfo.IsMassMill(spellId) and matSpellCount[spellId] == 1 then Conversions.AddCraft(itemString, matFirstItemString[spellId], numResult / matFirstQuantity[spellId]) addedConversion = true end end query:Release() TempTable.Release(matSpellCount) TempTable.Release(matFirstItemString) TempTable.Release(matFirstQuantity) if addedConversion then CustomPrice.OnSourceChange("Destroy") end local isValid, err = CustomPrice.Validate(TSM.db.global.craftingOptions.defaultCraftPriceMethod, BAD_CRAFTING_PRICE_SOURCES) if not isValid then Log.PrintfUser(L["Your default craft value method was invalid so it has been returned to the default. Details: %s"], err) TSM.db.global.craftingOptions.defaultCraftPriceMethod = TSM.db:GetDefault("global", "craftingOptions", "defaultCraftPriceMethod") end private.ignoredCooldownDB = Database.NewSchema("IGNORED_COOLDOWNS") :AddStringField("characterKey") :AddNumberField("spellId") :Commit() private.ignoredCooldownDB:BulkInsertStart() for entry in pairs(TSM.db.factionrealm.userData.craftingCooldownIgnore) do local characterKey, spellId = strsplit(IGNORED_COOLDOWN_SEP, entry) spellId = tonumber(spellId) if Crafting.HasSpellId(spellId) then private.ignoredCooldownDB:BulkInsertNewRow(characterKey, spellId) else TSM.db.factionrealm.userData.craftingCooldownIgnore[entry] = nil end end private.ignoredCooldownDB:BulkInsertEnd() end function Crafting.HasSpellId(spellId) return private.spellDB:HasUniqueRow("spellId", spellId) end function Crafting.CreateRawCraftsQuery() return private.spellDB:NewQuery() end function Crafting.CreateCraftsQuery() return private.spellDB:NewQuery() :LeftJoin(TSM.Crafting.Queue.GetDBForJoin(), "spellId") :VirtualField("bagQuantity", "number", Inventory.GetBagQuantity, "itemString") :VirtualField("auctionQuantity", "number", Inventory.GetAuctionQuantity, "itemString") :VirtualField("craftingCost", "number", private.CraftingCostVirtualField, "spellId") :VirtualField("itemValue", "number", private.ItemValueVirtualField, "itemString") :VirtualField("profit", "number", private.ProfitVirtualField, "spellId") :VirtualField("profitPct", "number", private.ProfitPctVirtualField, "spellId") :VirtualField("saleRate", "number", private.SaleRateVirtualField, "itemString") end function Crafting.CreateQueuedCraftsQuery() return private.spellDB:NewQuery() :InnerJoin(TSM.Crafting.Queue.GetDBForJoin(), "spellId") end function Crafting.CreateCooldownSpellsQuery() return private.spellDB:NewQuery() :Equal("hasCD", true) end function Crafting.CreateRawMatItemQuery() return private.matItemDB:NewQuery() end function Crafting.CreateMatItemQuery() return private.matItemDB:NewQuery() :InnerJoin(ItemInfo.GetDBForJoin(), "itemString") :VirtualField("matCost", "number", private.MatCostVirtualField, "itemString") :VirtualField("totalQuantity", "number", private.GetTotalQuantity, "itemString") end function Crafting.SpellIterator() return private.spellDB:NewQuery() :Select("spellId") :IteratorAndRelease() end function Crafting.GetSpellIdsByItem(itemString) local query = private.spellDB:NewQuery() :Equal("itemString", itemString) :Select("spellId", "hasCD") return query:IteratorAndRelease() end function Crafting.GetMostProfitableSpellIdByItem(itemString, playerFilter, noCD) local maxProfit, bestSpellId = nil, nil local maxProfitCD, bestSpellIdCD = nil, nil for _, spellId, hasCD in Crafting.GetSpellIdsByItem(itemString) do if not playerFilter or playerFilter == "" or Vararg.In(playerFilter, Crafting.GetPlayers(spellId)) then local profit = TSM.Crafting.Cost.GetProfitBySpellId(spellId) if hasCD then if profit and profit > (maxProfitCD or -math.huge) then maxProfitCD = profit bestSpellIdCD = spellId elseif not maxProfitCD then bestSpellIdCD = spellId end else if profit and profit > (maxProfit or -math.huge) then maxProfit = profit bestSpellId = spellId elseif not maxProfit then bestSpellId = spellId end end end end if noCD then maxProfitCD = nil bestSpellIdCD = nil end if maxProfit then return bestSpellId, maxProfit elseif maxProfitCD then return bestSpellIdCD, maxProfitCD else return bestSpellId or bestSpellIdCD or nil, nil end end function Crafting.GetItemString(spellId) return private.spellDB:GetUniqueRowField("spellId", spellId, "itemString") end function Crafting.GetProfession(spellId) return private.spellDB:GetUniqueRowField("spellId", spellId, "profession") end function Crafting.GetNumResult(spellId) return private.spellDB:GetUniqueRowField("spellId", spellId, "numResult") end function Crafting.GetPlayers(spellId) local players = private.spellDB:GetUniqueRowField("spellId", spellId, "players") if not players then return end return strsplit(PLAYER_SEP, players) end function Crafting.GetName(spellId) return private.spellDB:GetUniqueRowField("spellId", spellId, "name") end function Crafting.MatIterator(spellId) return private.matDB:NewQuery() :Select("itemString", "quantity") :Equal("spellId", spellId) :GreaterThan("quantity", 0) :IteratorAndRelease() end function Crafting.GetOptionalMatIterator(spellId) return private.matDB:NewQuery() :Select("itemString", "slotId", "text") :VirtualField("slotId", "number", private.OptionalMatSlotIdVirtualField, "itemString") :VirtualField("text", "string", private.OptionalMatTextVirtualField, "itemString") :Equal("spellId", spellId) :LessThan("quantity", 0) :OrderBy("slotId", true) :IteratorAndRelease() end function Crafting.GetMatsAsTable(spellId, tbl) private.matDBMatsInTableQuery :BindParams(spellId) :AsTable(tbl) end function Crafting.RemovePlayers(spellId, playersToRemove) local shouldRemove = TempTable.Acquire() for _, player in ipairs(playersToRemove) do shouldRemove[player] = true end local players = TempTable.Acquire(Crafting.GetPlayers(spellId)) for i = #players, 1, -1 do local player = players[i] if shouldRemove[player] then TSM.db.factionrealm.internalData.crafts[spellId].players[player] = nil tremove(players, i) end end TempTable.Release(shouldRemove) local query = private.spellDB:NewQuery() :Equal("spellId", spellId) local row = query:GetFirstResult() local playersStr = strjoin(PLAYER_SEP, TempTable.UnpackAndRelease(players)) if playersStr ~= "" then row:SetField("players", playersStr) :Update() query:Release() return true end -- no more players so remove this spell and all its mats private.spellDB:DeleteRow(row) query:Release() TSM.db.factionrealm.internalData.crafts[spellId] = nil local removedMats = TempTable.Acquire() private.matDB:SetQueryUpdatesPaused(true) query = private.matDB:NewQuery() :Equal("spellId", spellId) for _, matRow in query:Iterator() do removedMats[matRow:GetField("itemString")] = true private.matDB:DeleteRow(matRow) end query:Release() private.matDB:SetQueryUpdatesPaused(false) private.ProcessRemovedMats(removedMats) TempTable.Release(removedMats) return false end function Crafting.RemovePlayerSpells(inactiveSpellIds) local playerName = UnitName("player") local query = private.spellDB:NewQuery() :InTable("spellId", inactiveSpellIds) :Custom(private.QueryPlayerFilter, playerName) local removedSpellIds = TempTable.Acquire() local toRemove = TempTable.Acquire() private.spellDB:SetQueryUpdatesPaused(true) if query:Count() > 0 then Log.Info("Removing %d inactive spellds", query:Count()) end for _, row in query:Iterator() do local players = row:GetField("players") if row:GetField("players") == playerName then -- the current player was the only player, so we'll delete the entire row and all its mats local spellId = row:GetField("spellId") removedSpellIds[spellId] = true TSM.db.factionrealm.internalData.crafts[spellId] = nil tinsert(toRemove, row) else -- remove this player form the row local playersTemp = TempTable.Acquire(strsplit(PLAYER_SEP, players)) assert(Table.RemoveByValue(playersTemp, playerName) == 1) row:SetField("players", strjoin(PLAYER_SEP, TempTable.UnpackAndRelease(playersTemp))) :Update() end end for _, row in ipairs(toRemove) do private.spellDB:DeleteRow(row) end TempTable.Release(toRemove) query:Release() private.spellDB:SetQueryUpdatesPaused(false) local removedMats = TempTable.Acquire() private.matDB:SetQueryUpdatesPaused(true) local matQuery = private.matDB:NewQuery() :InTable("spellId", removedSpellIds) for _, matRow in matQuery:Iterator() do removedMats[matRow:GetField("itemString")] = true private.matDB:DeleteRow(matRow) end TempTable.Release(removedSpellIds) matQuery:Release() private.matDB:SetQueryUpdatesPaused(false) private.ProcessRemovedMats(removedMats) TempTable.Release(removedMats) end function Crafting.SetSpellDBQueryUpdatesPaused(paused) private.spellDB:SetQueryUpdatesPaused(paused) end function Crafting.CreateOrUpdate(spellId, itemString, profession, name, numResult, player, hasCD) local row = private.spellDB:GetUniqueRow("spellId", spellId) if row then local playersStr = row:GetField("players") local foundPlayer = String.SeparatedContains(playersStr, PLAYER_SEP, player) if not foundPlayer then assert(playersStr ~= "") playersStr = playersStr .. PLAYER_SEP .. player end row:SetField("itemString", itemString) :SetField("profession", profession) :SetField("itemName", ItemInfo.GetName(itemString) or "") :SetField("name", name) :SetField("numResult", numResult) :SetField("players", playersStr) :SetField("hasCD", hasCD) :Update() row:Release() local craftInfo = TSM.db.factionrealm.internalData.crafts[spellId] craftInfo.itemString = itemString craftInfo.profession = profession craftInfo.name = name craftInfo.numResult = numResult craftInfo.players[player] = true craftInfo.hasCD = hasCD or nil else TSM.db.factionrealm.internalData.crafts[spellId] = { mats = {}, players = { [player] = true }, queued = 0, itemString = itemString, name = name, profession = profession, numResult = numResult, hasCD = hasCD, } private.spellDB:NewRow() :SetField("spellId", spellId) :SetField("itemString", itemString) :SetField("profession", profession) :SetField("itemName", ItemInfo.GetName(itemString) or "") :SetField("name", name) :SetField("numResult", numResult) :SetField("players", player) :SetField("hasCD", hasCD) :Create() end end function Crafting.AddPlayer(spellId, player) if TSM.db.factionrealm.internalData.crafts[spellId].players[player] then return end local row = private.spellDB:GetUniqueRow("spellId", spellId) local playersStr = row:GetField("players") assert(playersStr ~= "") playersStr = playersStr .. PLAYER_SEP .. player row:SetField("players", playersStr) row:Update() row:Release() TSM.db.factionrealm.internalData.crafts[spellId].players[player] = true end function Crafting.SetMats(spellId, matQuantities) if Table.Equal(TSM.db.factionrealm.internalData.crafts[spellId].mats, matQuantities) then -- nothing changed return end wipe(TSM.db.factionrealm.internalData.crafts[spellId].mats) for itemString, quantity in pairs(matQuantities) do TSM.db.factionrealm.internalData.crafts[spellId].mats[itemString] = quantity end private.matDB:SetQueryUpdatesPaused(true) local removedMats = TempTable.Acquire() local usedMats = TempTable.Acquire() private.matDBSpellIdQuery:BindParams(spellId) for _, row in private.matDBSpellIdQuery:Iterator() do local itemString = row:GetField("itemString") local quantity = matQuantities[itemString] if not quantity then -- remove this row private.matDB:DeleteRow(row) removedMats[itemString] = true else usedMats[itemString] = true row:SetField("quantity", quantity) :Update() end end local profession = Crafting.GetProfession(spellId) for itemString, quantity in pairs(matQuantities) do if not usedMats[itemString] then private.matDB:NewRow() :SetField("spellId", spellId) :SetField("itemString", itemString) :SetField("quantity", quantity) :Create() if quantity > 0 then private.MatItemDBUpdateOrInsert(itemString, profession) else local _, _, matList = strsplit(":", itemString) for matItemId in String.SplitIterator(matList, ",") do private.MatItemDBUpdateOrInsert("i:"..matItemId, profession) end end end end TempTable.Release(usedMats) private.matDB:SetQueryUpdatesPaused(false) private.ProcessRemovedMats(removedMats) TempTable.Release(removedMats) end function Crafting.SetMatCustomValue(itemString, value) TSM.db.factionrealm.internalData.mats[itemString].customValue = value private.matItemDB:GetUniqueRow("itemString", itemString) :SetField("customValue", value or "") :Update() end function Crafting.CanCraftItem(itemString) local count = private.spellDB:NewQuery() :Equal("itemString", itemString) :CountAndRelease() return count > 0 end function Crafting.RestockHelp(link) local itemString = ItemString.Get(link) if not itemString then Log.PrintUser(L["No item specified. Usage: /tsm restock_help [ITEM_LINK]"]) return end local msg = private.GetRestockHelpMessage(itemString) Log.PrintfUser(L["Restock help for %s: %s"], link, msg) end function Crafting.IgnoreCooldown(spellId) assert(not TSM.db.factionrealm.userData.craftingCooldownIgnore[CHARACTER_KEY..IGNORED_COOLDOWN_SEP..spellId]) TSM.db.factionrealm.userData.craftingCooldownIgnore[CHARACTER_KEY..IGNORED_COOLDOWN_SEP..spellId] = true private.ignoredCooldownDB:NewRow() :SetField("characterKey", CHARACTER_KEY) :SetField("spellId", spellId) :Create() end function Crafting.IsCooldownIgnored(spellId) return TSM.db.factionrealm.userData.craftingCooldownIgnore[CHARACTER_KEY..IGNORED_COOLDOWN_SEP..spellId] end function Crafting.CreateIgnoredCooldownQuery() return private.ignoredCooldownDB:NewQuery() end function Crafting.RemoveIgnoredCooldown(characterKey, spellId) assert(TSM.db.factionrealm.userData.craftingCooldownIgnore[characterKey..IGNORED_COOLDOWN_SEP..spellId]) TSM.db.factionrealm.userData.craftingCooldownIgnore[characterKey..IGNORED_COOLDOWN_SEP..spellId] = nil local row = private.ignoredCooldownDB:NewQuery() :Equal("characterKey", characterKey) :Equal("spellId", spellId) :GetFirstResultAndRelease() assert(row) private.ignoredCooldownDB:DeleteRow(row) row:Release() end function Crafting.GetMatNames(spellId) return private.matDBMatNamesQuery:BindParams(spellId) :JoinedString("name", "") end -- ============================================================================ -- Private Helper Functions -- ============================================================================ function private.ProcessRemovedMats(removedMats) private.matItemDB:SetQueryUpdatesPaused(true) for itemString in pairs(removedMats) do local numSpells = private.matDB:NewQuery() :Equal("itemString", itemString) :CountAndRelease() if numSpells == 0 then local matItemRow = private.matItemDB:GetUniqueRow("itemString", itemString) private.matItemDB:DeleteRow(matItemRow) matItemRow:Release() end end private.matItemDB:SetQueryUpdatesPaused(false) end function private.CraftingCostVirtualField(spellId) return TSM.Crafting.Cost.GetCraftingCostBySpellId(spellId) or Math.GetNan() end function private.ItemValueVirtualField(itemString) return TSM.Crafting.Cost.GetCraftedItemValue(itemString) or Math.GetNan() end function private.ProfitVirtualField(spellId) return TSM.Crafting.Cost.GetProfitBySpellId(spellId) or Math.GetNan() end function private.ProfitPctVirtualField(spellId) local craftingCost, _, profit = TSM.Crafting.Cost.GetCostsBySpellId(spellId) return (craftingCost and profit) and floor(profit * 100 / craftingCost) or Math.GetNan() end function private.SaleRateVirtualField(itemString) local saleRate = TSM.AuctionDB.GetRegionItemData(itemString, "regionSalePercent") return saleRate and (saleRate / 100) or Math.GetNan() end function private.MatCostVirtualField(itemString) return TSM.Crafting.Cost.GetMatCost(itemString) or Math.GetNan() end function private.OptionalMatSlotIdVirtualField(matStr) local _, slotId = strsplit(":", matStr) return tonumber(slotId) end function private.OptionalMatTextVirtualField(matStr) local _, _, matList = strsplit(":", matStr) return TSM.Crafting.ProfessionUtil.GetOptionalMatText(matList) or OPTIONAL_REAGENT_POSTFIX end function private.GetRestockHelpMessage(itemString) -- check if the item is in a group local groupPath = TSM.Groups.GetPathByItem(itemString) if not groupPath then return L["This item is not in a TSM group."] end -- check that there's a crafting operation applied if not TSM.Operations.Crafting.HasOperation(itemString) then return format(L["There is no Crafting operation applied to this item's TSM group (%s)."], TSM.Groups.Path.Format(groupPath)) end -- check if it's an invalid operation local isValid, err = TSM.Operations.Crafting.IsValid(itemString) if not isValid then return err end -- check that this item is craftable if not TSM.Crafting.CanCraftItem(itemString) then return L["You don't know how to craft this item."] end -- check the restock quantity local neededQuantity = TSM.Operations.Crafting.GetRestockQuantity(itemString, private.GetTotalQuantity(itemString)) if neededQuantity == 0 then return L["You either already have at least your max restock quantity of this item or the number which would be queued is less than the min restock quantity."] end -- check if we would actually queue any local cost, spellId = TSM.Crafting.Cost.GetLowestCostByItem(itemString) local numResult = spellId and TSM.Crafting.GetNumResult(spellId) if neededQuantity < numResult then return format(L["A single craft makes %d and you only need to restock %d."], numResult, neededQuantity) end -- check the prices on the item and the min profit local hasMinProfit, minProfit = TSM.Operations.Crafting.GetMinProfit(itemString) if hasMinProfit then local craftedValue = TSM.Crafting.Cost.GetCraftedItemValue(itemString) local profit = cost and craftedValue and (craftedValue - cost) or nil -- check that there's a crafted value if not craftedValue then return L["The 'Craft Value Method' did not return a value for this item."] end -- check that there's a crafted cost if not cost then return L["This item does not have a crafting cost. Check that all of its mats have mat prices."] end -- check that there's a profit assert(profit) if not minProfit then return L["The min profit did not evalulate to a valid value for this item."] end if profit < minProfit then return format(L["The profit of this item (%s) is below the min profit (%s)."], Money.ToString(profit), Money.ToString(minProfit)) end end return L["This item will be added to the queue when you restock its group. If this isn't happening, please visit http://support.tradeskillmaster.com for further assistance."] end function private.QueryPlayerFilter(row, player) return String.SeparatedContains(row:GetField("players"), ",", player) end function private.GetTotalQuantity(itemString) return CustomPrice.GetItemPrice(itemString, "NumInventory") or 0 end function private.MatItemDBUpdateOrInsert(itemString, profession) local matItemRow = private.matItemDB:GetUniqueRow("itemString", itemString) if matItemRow then -- update the professions if necessary local professions = TempTable.Acquire(strsplit(PROFESSION_SEP, matItemRow:GetField("professions"))) if not Table.KeyByValue(professions, profession) then tinsert(professions, profession) sort(professions) matItemRow:SetField("professions", table.concat(professions, PROFESSION_SEP)) :Update() end TempTable.Release(professions) else private.matItemDB:NewRow() :SetField("itemString", itemString) :SetField("professions", profession) :SetField("customValue", TSM.db.factionrealm.internalData.mats[itemString].customValue or "") :Create() end end