777 lines
26 KiB
Lua
777 lines
26 KiB
Lua
-- ------------------------------------------------------------------------------ --
|
|
-- 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
|