-- ------------------------------------------------------------------------------ -- -- TradeSkillMaster -- -- https://tradeskillmaster.com -- -- All Rights Reserved - Detailed license information included with addon. -- -- ------------------------------------------------------------------------------ -- --- Item String functions -- @module ItemString local _, TSM = ... local ItemString = TSM.Init("Util.ItemString") local BonusIds = TSM.Include("Data.BonusIds") local SmartMap = TSM.Include("Util.SmartMap") local private = { filteredItemStringCache = {}, itemStringCache = {}, baseItemStringMap = nil, baseItemStringReader = nil, hasNonBaseItemStrings = {}, bonusIdsTemp = {}, modifiersTemp = {}, } local ITEM_MAX_ID = 999999 local UNKNOWN_ITEM_STRING = "i:0" local PLACEHOLDER_ITEM_STRING = "i:1" local PET_CAGE_ITEM_STRING = "i:82800" local MINIMUM_VARIANT_ITEM_ID = 152632 local IMPORTANT_MODIFIER_TYPES = { [9] = true, } -- ============================================================================ -- Module Loading -- ============================================================================ ItemString:OnModuleLoad(function() private.baseItemStringMap = SmartMap.New("string", "string", private.ToBaseItemString) private.baseItemStringReader = private.baseItemStringMap:CreateReader() end) -- ============================================================================ -- Module Functions -- ============================================================================ --- Gets the constant unknown item string for places where the itemString is not known. -- @treturn string The itemString function ItemString.GetUnknown() return UNKNOWN_ITEM_STRING end --- Gets the constant placeholder item string. -- @treturn string The itemString function ItemString.GetPlaceholder() return PLACEHOLDER_ITEM_STRING end --- Gets the battlepet cage item string. -- @treturn string The itemString function ItemString.GetPetCage() return PET_CAGE_ITEM_STRING end --- Gets the base itemString smart map. -- @treturn SmartMap The smart map function ItemString.GetBaseMap() return private.baseItemStringMap end --- Converts the parameter into an itemString. -- @tparam ?number|string item Either an itemId, itemLink, or itemString to be converted -- @treturn string The itemString function ItemString.Get(item) if not item then return nil end if not private.itemStringCache[item] then private.itemStringCache[item] = private.ToItemString(item) end return private.itemStringCache[item] end function ItemString.Filter(itemString) if not private.filteredItemStringCache[itemString] then private.filteredItemStringCache[itemString] = private.FilterBonusIdsAndModifiers(itemString, true, strsplit(":", itemString)) end return private.filteredItemStringCache[itemString] end --- Converts the parameter into an itemId. -- @tparam string item An item to get the id of -- @treturn number The itemId function ItemString.ToId(item) local itemString = ItemString.Get(item) if type(itemString) ~= "string" then return end return tonumber(strmatch(itemString, "^[ip]:(%d+)")) end --- Converts the parameter into a base itemString. -- @tparam string itemString An itemString to get the base itemString of -- @treturn string The base itemString function ItemString.GetBaseFast(itemString) if not itemString then return nil end return private.baseItemStringReader[itemString] end --- Converts the parameter into a base itemString. -- @tparam string item An item to get the base itemString of -- @treturn string The base itemString function ItemString.GetBase(item) -- make sure it's a valid itemString local itemString = ItemString.Get(item) if not itemString then return end -- quickly return if we're certain it's already a valid baseItemString if type(itemString) == "string" and strmatch(itemString, "^[ip]:[0-9]+$") then return itemString end return ItemString.GetBaseFast(itemString) end --- Converts an itemKey from WoW into a base itemString. -- @tparam table itemKey An itemKey to get the itemString of -- @treturn string The base itemString function ItemString.GetBaseFromItemKey(itemKey) if itemKey.battlePetSpeciesID > 0 then return "p:"..itemKey.battlePetSpeciesID else return "i:"..itemKey.itemID end end function ItemString.HasNonBase(baseItemString) return private.hasNonBaseItemStrings[baseItemString] or false end --- Converts the parameter into a WoW itemString. -- @tparam string itemString An itemString to get the WoW itemString of -- @treturn number The WoW itemString function ItemString.ToWow(itemString) local _, itemId, rand, extra = strsplit(":", itemString) local level = UnitLevel("player") local spec = not TSM.IsWowClassic() and GetSpecialization() or nil spec = spec and GetSpecializationInfo(spec) or "" local extraPart = extra and strmatch(itemString, "i:[0-9]+:[0-9%-]*:(.+)") or "" return "item:"..itemId.."::::::"..(rand or "").."::"..level..":"..spec..":::"..extraPart..":::" end function ItemString.IsItem(itemString) return strmatch(itemString, "^i:[%-:0-9]+$") and true or false end function ItemString.IsPet(itemString) return strmatch(itemString, "^p:[%-:0-9]+$") and true or false end -- ============================================================================ -- Helper Functions -- ============================================================================ function private.ToItemString(item) local paramType = type(item) if paramType == "string" then item = strtrim(item) local itemId = strmatch(item, "^[ip]:([0-9]+)$") if itemId then if tonumber(itemId) > ITEM_MAX_ID then return nil end -- this is already an itemString return item end itemId = strmatch(item, "item:(%d+)") if itemId and tonumber(itemId) > ITEM_MAX_ID then return nil end elseif paramType == "number" or tonumber(item) then local itemId = tonumber(item) if itemId > ITEM_MAX_ID then return nil end -- assume this is an itemId return "i:"..item else error("Invalid item parameter type: "..tostring(item)) end -- test if it's already (likely) an item string or battle pet string if strmatch(item, "^i:([0-9%-:]+)$") then return private.FixItemString(item) elseif strmatch(item, "^p:([0-9:]+)$") then return private.FixPet(item) end local result = strmatch(item, "^\124cff[0-9a-z]+\124[Hh](.+)\124h%[.+%]\124h\124r$") if result then -- it was a full item link which we've extracted the itemString from item = result end -- test if it's an old style item string result = strjoin(":", strmatch(item, "^(i)tem:([0-9%-]+):[0-9%-]+:[0-9%-]+:[0-9%-]+:[0-9%-]+:[0-9%-]+:([0-9%-]+)$")) if result then return private.FixItemString(result) end -- test if it's an old style battle pet string (or if it was a link) result = strjoin(":", strmatch(item, "^battle(p)et:(%d+:%d+:%d+)")) if result then return private.FixPet(result) end result = strjoin(":", strmatch(item, "^battle(p)et:(%d+)[:]*$")) if result then return result end result = strjoin(":", strmatch(item, "^(p):(%d+:%d+:%d+)")) if result then return private.FixPet(result) end -- test if it's a long item string result = strjoin(":", strmatch(item, "(i)tem:([0-9%-]+):[0-9%-]*:[0-9%-]*:[0-9%-]*:[0-9%-]*:[0-9%-]*:([0-9%-]*):[0-9%-]*:[0-9%-]*:[0-9%-]*:[0-9%-]*:[0-9%-]*:([0-9%-:]+)")) if result and result ~= "" then return private.FixItemString(result) end -- test if it's a shorter item string (without bonuses) result = strjoin(":", strmatch(item, "(i)tem:([0-9%-]+):[0-9%-]*:[0-9%-]*:[0-9%-]*:[0-9%-]*:[0-9%-]*:([0-9%-]*)")) if result and result ~= "" then return result end end function private.RemoveExtra(itemString) local num = 1 while num > 0 do itemString, num = gsub(itemString, ":0?$", "") end return itemString end function private.FixItemString(itemString) itemString = gsub(itemString, ":0:", "::") -- remove 0s which are in the middle itemString = private.RemoveExtra(itemString) return private.FilterBonusIdsAndModifiers(itemString, false, strsplit(":", itemString)) end function private.FixPet(itemString) itemString = private.RemoveExtra(itemString) local result = strmatch(itemString, "^(p:%d+:%d+:%d+)$") if result then return result end return strmatch(itemString, "^(p:%d+)") end function private.FilterBonusIdsAndModifiers(itemString, importantBonusIdsOnly, itemType, itemId, rand, numBonusIds, ...) numBonusIds = tonumber(numBonusIds) or 0 local numParts = select("#", ...) if numParts == 0 then return itemString end -- grab the modifiers and filter them local numModifiers = numParts - numBonusIds local modifiersStr = (numModifiers > 0 and numModifiers > 1 and numModifiers % 2 == 1) and strjoin(":", select(numBonusIds + 1, ...)) or "" if modifiersStr ~= "" then wipe(private.modifiersTemp) local num, modifierType = nil, nil for modifier in gmatch(modifiersStr, "[0-9]+") do modifier = tonumber(modifier) if not num then num = modifier elseif not modifierType then modifierType = modifier else if IMPORTANT_MODIFIER_TYPES[modifierType] then tinsert(private.modifiersTemp, modifierType) tinsert(private.modifiersTemp, modifier) end modifierType = nil end end if #private.modifiersTemp > 0 then assert(#private.modifiersTemp % 2 == 0) tinsert(private.modifiersTemp, 1, #private.modifiersTemp / 2) modifiersStr = table.concat(private.modifiersTemp, ":") end end -- filter the bonusIds local bonusIdsStr = "" if numBonusIds > 0 then -- get the list of bonusIds and filter them wipe(private.bonusIdsTemp) for i = 1, numBonusIds do private.bonusIdsTemp[i] = select(i, ...) end if importantBonusIdsOnly then -- Only track bonusIds if the itemId is above our minimum if tonumber(itemId) >= MINIMUM_VARIANT_ITEM_ID then bonusIdsStr = BonusIds.FilterImportant(table.concat(private.bonusIdsTemp, ":")) end else bonusIdsStr = BonusIds.FilterAll(table.concat(private.bonusIdsTemp, ":")) end end -- rebuild the itemString itemString = strjoin(":", itemType, itemId, rand, bonusIdsStr, modifiersStr) itemString = gsub(itemString, ":0:", "::") -- remove 0s which are in the middle return private.RemoveExtra(itemString) end function private.ToBaseItemString(itemString) local baseItemString = strmatch(itemString, "[ip]:%d+") if baseItemString ~= itemString then private.hasNonBaseItemStrings[baseItemString] = true end return baseItemString end