TradeSkillMaster/Core/Service/Crafting/ProfessionUtil.lua

481 lines
16 KiB
Lua

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local ProfessionUtil = TSM.Crafting:NewPackage("ProfessionUtil")
local ProfessionInfo = TSM.Include("Data.ProfessionInfo")
local Event = TSM.Include("Util.Event")
local TempTable = TSM.Include("Util.TempTable")
local Log = TSM.Include("Util.Log")
local Delay = TSM.Include("Util.Delay")
local ItemString = TSM.Include("Util.ItemString")
local ItemInfo = TSM.Include("Service.ItemInfo")
local BagTracking = TSM.Include("Service.BagTracking")
local Inventory = TSM.Include("Service.Inventory")
local CustomPrice = TSM.Include("Service.CustomPrice")
local private = {
craftQuantity = nil,
craftSpellId = nil,
craftCallback = nil,
craftName = nil,
castingTimeout = nil,
craftTimeout = nil,
preparedSpellId = nil,
preparedTime = 0,
categoryInfoTemp = {},
}
local PROFESSION_LOOKUP = {
["Costura"] = "Sastrería",
["Marroquinería"] = "Peletería",
["Ingénierie"] = "Ingénieur",
["Secourisme"] = "Premiers soins",
}
-- ============================================================================
-- Module Functions
-- ============================================================================
function ProfessionUtil.OnInitialize()
Event.Register("UNIT_SPELLCAST_SUCCEEDED", function(_, unit, _, spellId)
if unit ~= "player" then
return
end
if (TSM.IsWowClassic() and GetSpellInfo(spellId) ~= private.craftName) or (not TSM.IsWowClassic() and spellId ~= private.craftSpellId) then
return
end
-- check if we need to update bank quantity manually
for _, itemString, quantity in TSM.Crafting.MatIterator(private.craftSpellId) do
local bankUsed = quantity - (Inventory.GetBagQuantity(itemString) + Inventory.GetReagentBankQuantity(itemString))
if bankUsed > 0 and bankUsed <= Inventory.GetBankQuantity(itemString) then
Log.Info("Used %d from bank", bankUsed)
BagTracking.ForceBankQuantityDeduction(itemString, bankUsed)
end
end
local callback = private.craftCallback
assert(callback)
private.craftQuantity = private.craftQuantity - 1
private.DoCraftCallback(true, private.craftQuantity == 0)
-- ignore profession updates from crafting something
TSM.Crafting.ProfessionScanner.IgnoreNextProfessionUpdates()
-- restart the timeout
end)
local function SpellcastFailedEventHandler(_, unit, _, spellId)
if unit ~= "player" then
return
end
if (TSM.IsWowClassic() and GetSpellInfo(spellId) ~= private.craftName) or (not TSM.IsWowClassic() and spellId ~= private.craftSpellId) then
return
end
private.DoCraftCallback(false, true)
end
local function ClearCraftCast()
private.craftQuantity = nil
private.craftSpellId = nil
private.craftName = nil
private.castingTimeout = nil
private.craftTimeout = nil
end
Event.Register("UNIT_SPELLCAST_INTERRUPTED", SpellcastFailedEventHandler)
Event.Register("UNIT_SPELLCAST_FAILED", SpellcastFailedEventHandler)
Event.Register("UNIT_SPELLCAST_FAILED_QUIET", SpellcastFailedEventHandler)
Event.Register("TRADE_SKILL_CLOSE", ClearCraftCast)
if TSM.IsWowClassic() then
Event.Register("CRAFT_CLOSE", ClearCraftCast)
end
end
function ProfessionUtil.GetCurrentProfessionName()
if TSM.IsWowClassic() then
local name = TSM.Crafting.ProfessionState.IsClassicCrafting() and GetCraftSkillLine(1) or GetTradeSkillLine()
return name
else
local _, name, _, _, _, _, parentName = C_TradeSkillUI.GetTradeSkillLine()
return parentName or name
end
end
function ProfessionUtil.GetResultInfo(spellId)
-- get the links
local itemLink = ProfessionUtil.GetRecipeInfo(spellId)
assert(itemLink, "Invalid craft: "..tostring(spellId))
if strfind(itemLink, "enchant:") then
-- result of craft is not an item
local itemString = ProfessionInfo.GetIndirectCraftResult(spellId)
if itemString and not TSM.IsWowClassic() then
return TSM.UI.GetColoredItemName(itemString), itemString, ItemInfo.GetTexture(itemString)
elseif ProfessionInfo.IsEngineeringTinker(spellId) then
local name, _, icon = GetSpellInfo(spellId)
return name, nil, icon
else
local name, _, icon = GetSpellInfo(TSM.Crafting.ProfessionState.IsClassicCrafting() and GetCraftInfo(TSM.IsWowClassic() and TSM.Crafting.ProfessionScanner.GetIndexBySpellId(spellId) or spellId) or spellId)
return name, nil, icon
end
elseif strfind(itemLink, "item:") then
-- result of craft is an item
return TSM.UI.GetColoredItemName(itemLink), ItemString.Get(itemLink), ItemInfo.GetTexture(itemLink)
else
error("Invalid craft: "..tostring(spellId))
end
end
function ProfessionUtil.GetNumCraftable(spellId)
local num, numAll = math.huge, math.huge
for i = 1, ProfessionUtil.GetNumMats(spellId) do
local matItemLink, _, _, quantity = ProfessionUtil.GetMatInfo(spellId, i)
local itemString = ItemString.Get(matItemLink)
local totalQuantity = CustomPrice.GetItemPrice(itemString, "NumInventory") or 0
if not itemString or not quantity or totalQuantity == 0 then
return 0, 0
end
local bagQuantity = Inventory.GetBagQuantity(itemString)
if not TSM.IsWowClassic() then
bagQuantity = bagQuantity + Inventory.GetReagentBankQuantity(itemString) + Inventory.GetBankQuantity(itemString)
end
num = min(num, floor(bagQuantity / quantity))
numAll = min(numAll, floor(totalQuantity / quantity))
end
if num == math.huge or numAll == math.huge then
return 0, 0
end
return num, numAll
end
function ProfessionUtil.IsCraftable(spellId)
for i = 1, ProfessionUtil.GetNumMats(spellId) do
local matItemLink, _, _, quantity = ProfessionUtil.GetMatInfo(spellId, i)
local itemString = ItemString.Get(matItemLink)
if not itemString or not quantity then
return false
end
local bagQuantity = Inventory.GetBagQuantity(itemString)
if not TSM.IsWowClassic() then
bagQuantity = bagQuantity + Inventory.GetReagentBankQuantity(itemString) + Inventory.GetBankQuantity(itemString)
end
if floor(bagQuantity / quantity) == 0 then
return false
end
end
return true
end
function ProfessionUtil.GetNumCraftableFromDB(spellId)
local num = math.huge
for _, itemString, quantity in TSM.Crafting.MatIterator(spellId) do
local bagQuantity = Inventory.GetBagQuantity(itemString)
if not TSM.IsWowClassic() then
bagQuantity = bagQuantity + Inventory.GetReagentBankQuantity(itemString) + Inventory.GetBankQuantity(itemString)
end
num = min(num, floor(bagQuantity / quantity))
end
if num == math.huge then
return 0
end
return num
end
function ProfessionUtil.IsEnchant(spellId)
local name = ProfessionUtil.GetCurrentProfessionName()
if name ~= GetSpellInfo(7411) or TSM.IsWowClassic() then
return false
end
if not strfind(C_TradeSkillUI.GetRecipeItemLink(spellId), "enchant:") then
return false
end
local recipeInfo = nil
if not TSM.IsShadowlands() then
recipeInfo = TempTable.Acquire()
assert(C_TradeSkillUI.GetRecipeInfo(spellId, recipeInfo) == recipeInfo)
else
recipeInfo = C_TradeSkillUI.GetRecipeInfo(spellId)
end
local altVerb = recipeInfo.alternateVerb
if not TSM.IsShadowlands() then
TempTable.Release(recipeInfo)
end
return altVerb and true or false
end
function ProfessionUtil.OpenProfession(profession, skillId)
if TSM.IsWowClassic() then
if profession == ProfessionInfo.GetName("Mining") then
-- mining needs to be opened as smelting
profession = ProfessionInfo.GetName("Smelting")
end
if PROFESSION_LOOKUP[profession] then
profession = PROFESSION_LOOKUP[profession]
end
CastSpellByName(profession)
else
C_TradeSkillUI.OpenTradeSkill(skillId)
end
end
function ProfessionUtil.PrepareToCraft(spellId, quantity)
quantity = min(quantity, ProfessionUtil.GetNumCraftable(spellId))
if quantity == 0 then
return
end
if ProfessionUtil.IsEnchant(spellId) then
quantity = 1
end
if not TSM.IsWowClassic() then
C_TradeSkillUI.SetRecipeRepeatCount(spellId, quantity)
end
private.preparedSpellId = spellId
private.preparedTime = GetTime()
end
function ProfessionUtil.Craft(spellId, quantity, useVellum, callback)
assert(TSM.Crafting.ProfessionScanner.HasSpellId(spellId))
if private.craftSpellId then
private.craftCallback = callback
private.DoCraftCallback(false, true)
return 0
end
quantity = min(quantity, ProfessionUtil.GetNumCraftable(spellId))
if quantity == 0 then
return 0
end
local isEnchant = ProfessionUtil.IsEnchant(spellId)
if isEnchant then
quantity = 1
elseif spellId ~= private.preparedSpellId or private.preparedTime == GetTime() then
-- We can only craft one of this item due to a bug on Blizzard's end
quantity = 1
end
private.craftQuantity = quantity
private.craftSpellId = spellId
private.craftCallback = callback
if TSM.IsWowClassic() then
spellId = TSM.Crafting.ProfessionScanner.GetIndexBySpellId(spellId)
if TSM.Crafting.ProfessionState.IsClassicCrafting() then
private.craftName = GetCraftInfo(spellId)
else
private.craftName = GetTradeSkillInfo(spellId)
DoTradeSkill(spellId, quantity)
end
else
C_TradeSkillUI.CraftRecipe(spellId, quantity)
end
if useVellum and isEnchant then
UseItemByName(ItemInfo.GetName(ProfessionInfo.GetVellumItemString()))
end
private.castingTimeout = nil
private.craftTimeout = nil
Delay.AfterTime("PROFESSION_CRAFT_TIMEOUT_MONITOR", 0.5, private.CraftTimeoutMonitor, 0.5)
return quantity
end
function ProfessionUtil.IsDataStable()
return TSM.IsWowClassic() or (C_TradeSkillUI.IsTradeSkillReady() and not C_TradeSkillUI.IsDataSourceChanging())
end
function ProfessionUtil.HasCooldown(spellId)
if TSM.IsWowClassic() then
return GetTradeSkillCooldown(spellId) and true or false
else
return select(2, C_TradeSkillUI.GetRecipeCooldown(spellId)) and true or false
end
end
function ProfessionUtil.GetRemainingCooldown(spellId)
if TSM.IsWowClassic() then
return GetTradeSkillCooldown(spellId)
else
return C_TradeSkillUI.GetRecipeCooldown(spellId)
end
end
function ProfessionUtil.GetRecipeInfo(spellId)
local itemLink, lNum, hNum, toolsStr, hasTools = nil, nil, nil, nil, nil
if TSM.IsWowClassic() then
spellId = TSM.Crafting.ProfessionScanner.GetIndexBySpellId(spellId) or spellId
itemLink = TSM.Crafting.ProfessionState.IsClassicCrafting() and GetCraftItemLink(spellId) or GetTradeSkillItemLink(spellId)
if TSM.Crafting.ProfessionState.IsClassicCrafting() then
lNum, hNum = 1, 1
toolsStr, hasTools = GetCraftSpellFocus(spellId)
else
lNum, hNum = GetTradeSkillNumMade(spellId)
toolsStr, hasTools = GetTradeSkillTools(spellId)
end
else
itemLink = C_TradeSkillUI.GetRecipeItemLink(spellId)
lNum, hNum = C_TradeSkillUI.GetRecipeNumItemsProduced(spellId)
toolsStr, hasTools = C_TradeSkillUI.GetRecipeTools(spellId)
end
return itemLink, lNum, hNum, toolsStr, hasTools
end
function ProfessionUtil.GetNumMats(spellId)
local numMats = nil
if TSM.IsWowClassic() then
spellId = TSM.Crafting.ProfessionScanner.GetIndexBySpellId(spellId) or spellId
numMats = TSM.Crafting.ProfessionState.IsClassicCrafting() and GetCraftNumReagents(spellId) or GetTradeSkillNumReagents(spellId)
else
numMats = C_TradeSkillUI.GetRecipeNumReagents(spellId)
end
return numMats
end
function ProfessionUtil.GetMatInfo(spellId, index)
local itemLink, name, texture, quantity = nil, nil, nil, nil
if TSM.IsWowClassic() then
spellId = TSM.Crafting.ProfessionScanner.GetIndexBySpellId(spellId) or spellId
itemLink = TSM.Crafting.ProfessionState.IsClassicCrafting() and GetCraftReagentItemLink(spellId, index) or GetTradeSkillReagentItemLink(spellId, index)
if TSM.Crafting.ProfessionState.IsClassicCrafting() then
name, texture, quantity = GetCraftReagentInfo(spellId, index)
else
name, texture, quantity = GetTradeSkillReagentInfo(spellId, index)
end
else
itemLink = C_TradeSkillUI.GetRecipeReagentItemLink(spellId, index)
name, texture, quantity = C_TradeSkillUI.GetRecipeReagentInfo(spellId, index)
if itemLink then
name = name or ItemInfo.GetName(itemLink)
texture = texture or ItemInfo.GetTexture(itemLink)
end
end
return itemLink, name, texture, quantity
end
function ProfessionUtil.CloseTradeSkill(closeBoth)
if TSM.IsWowClassic() then
if closeBoth then
CloseCraft()
CloseTradeSkill()
else
if TSM.Crafting.ProfessionState.IsClassicCrafting() then
CloseCraft()
else
CloseTradeSkill()
end
end
else
C_TradeSkillUI.CloseTradeSkill()
C_Garrison.CloseGarrisonTradeskillNPC()
end
end
function ProfessionUtil.IsNPCProfession()
return not TSM.IsWowClassic() and C_TradeSkillUI.IsNPCCrafting()
end
function ProfessionUtil.IsLinkedProfession()
if TSM.IsWowClassic() then
return nil, nil
else
return C_TradeSkillUI.IsTradeSkillLinked()
end
end
function ProfessionUtil.IsGuildProfession()
return not TSM.IsWowClassic() and C_TradeSkillUI.IsTradeSkillGuild()
end
function ProfessionUtil.GetCategoryInfo(categoryId)
local name, numIndents, parentCategoryId, currentSkillLevel, maxSkillLevel = nil, nil, nil, nil, nil
if TSM.IsWowClassic() then
name = TSM.Crafting.ProfessionState.IsClassicCrafting() and GetCraftDisplaySkillLine() or (categoryId and GetTradeSkillInfo(categoryId) or nil)
numIndents = 0
parentCategoryId = nil
else
C_TradeSkillUI.GetCategoryInfo(categoryId, private.categoryInfoTemp)
assert(private.categoryInfoTemp.numIndents)
name = private.categoryInfoTemp.name
numIndents = private.categoryInfoTemp.numIndents
parentCategoryId = private.categoryInfoTemp.numIndents ~= 0 and private.categoryInfoTemp.parentCategoryID or nil
currentSkillLevel = private.categoryInfoTemp.skillLineCurrentLevel
maxSkillLevel = private.categoryInfoTemp.skillLineMaxLevel
wipe(private.categoryInfoTemp)
end
return name, numIndents, parentCategoryId, currentSkillLevel, maxSkillLevel
end
function ProfessionUtil.StoreOptionalMatText(matList, text)
TSM.db.global.internalData.optionalMatTextLookup[matList] = TSM.db.global.internalData.optionalMatTextLookup[matList] or text
end
function ProfessionUtil.GetOptionalMatText(matList)
return TSM.db.global.internalData.optionalMatTextLookup[matList]
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.DoCraftCallback(result, isDone)
local callback = private.craftCallback
assert(callback)
-- reset timeouts
private.castingTimeout = nil
private.craftTimeout = nil
if isDone then
private.craftQuantity = nil
private.craftSpellId = nil
private.craftCallback = nil
private.craftName = nil
Delay.Cancel("PROFESSION_CRAFT_TIMEOUT_MONITOR")
end
callback(result, isDone)
end
function private.CraftTimeoutMonitor()
if not private.craftSpellId then
Log.Info("No longer crafting")
private.castingTimeout = nil
private.craftTimeout = nil
Delay.Cancel("PROFESSION_CRAFT_TIMEOUT_MONITOR")
return
end
local _, _, _, _, castEndTimeMs, _, _, _, spellId = private.GetPlayerCastingInfo()
if spellId then
private.castingTimeout = nil
else
private.craftTimeout = nil
end
if not spellId then
-- no active cast
if GetTime() > (private.castingTimeout or math.huge) then
Log.Err("Craft timed out (%s)", private.craftSpellId)
private.DoCraftCallback(false, true)
return
end
-- set the casting timeout to 1 second from now
private.castingTimeout = GetTime() + 1
return
elseif private.craftSpellId ~= spellId then
Log.Err("Crafting something else (%s, %s)", private.craftSpellId, spellId)
private.castingTimeout = nil
private.craftTimeout = nil
Delay.Cancel("PROFESSION_CRAFT_TIMEOUT_MONITOR")
return
end
if GetTime() > (private.craftTimeout or math.huge) then
Log.Err("Craft timed out (%s)", private.craftSpellId)
private.DoCraftCallback(false, true)
return
end
-- set the timeout to 1 second after the end time
private.craftTimeout = castEndTimeMs / 1000 + 1
end
function private.GetPlayerCastingInfo()
if TSM.IsWowClassic() then
return CastingInfo()
else
return UnitCastingInfo("player")
end
end