You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
515 lines
16 KiB
515 lines
16 KiB
-- ------------------------------------------------------------------------------ --
|
|
-- TradeSkillMaster --
|
|
-- https://tradeskillmaster.com --
|
|
-- All Rights Reserved - Detailed license information included with addon. --
|
|
-- ------------------------------------------------------------------------------ --
|
|
|
|
local TSM = select(2, ...) ---@type TSM
|
|
local ItemString = TSM.Init("Util.ItemString") ---@class Util.ItemString
|
|
local Environment = TSM.Include("Environment")
|
|
local BonusIds = TSM.Include("Data.BonusIds")
|
|
local SmartMap = TSM.Include("Util.SmartMap")
|
|
local private = {
|
|
filteredItemStringCache = {},
|
|
itemStringCache = {},
|
|
baseItemStringMap = nil,
|
|
baseItemStringReader = nil,
|
|
levelItemStringMap = nil,
|
|
hasNonBaseItemStrings = {},
|
|
bonusIdsTemp = {},
|
|
modifiersTemp = {},
|
|
modifiersValueTemp = {},
|
|
extraStatModifiersTemp = {},
|
|
}
|
|
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 = 187518
|
|
local IMPORTANT_MODIFIER_TYPES = {
|
|
[9] = true,
|
|
}
|
|
local EXTRA_STAT_MODIFIER_TYPES = {
|
|
[29] = true,
|
|
[30] = true,
|
|
}
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Module Loading
|
|
-- ============================================================================
|
|
|
|
ItemString:OnModuleLoad(function()
|
|
private.baseItemStringMap = SmartMap.New("string", "string", private.ToBaseItemString)
|
|
private.baseItemStringReader = private.baseItemStringMap:CreateReader()
|
|
private.levelItemStringMap = SmartMap.New("string", "string", ItemString.ToLevel)
|
|
end)
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Module Functions
|
|
-- ============================================================================
|
|
|
|
---Gets the constant unknown item string for places where the itemString is not known.
|
|
---@return string
|
|
function ItemString.GetUnknown()
|
|
return UNKNOWN_ITEM_STRING
|
|
end
|
|
|
|
---Gets the constant placeholder item string.
|
|
---@return string
|
|
function ItemString.GetPlaceholder()
|
|
return PLACEHOLDER_ITEM_STRING
|
|
end
|
|
|
|
---Gets the battlepet cage item string.
|
|
---@return string
|
|
function ItemString.GetPetCage()
|
|
return PET_CAGE_ITEM_STRING
|
|
end
|
|
|
|
---Gets the base itemString smart map.
|
|
---@return SmartMapObject
|
|
function ItemString.GetBaseMap()
|
|
return private.baseItemStringMap
|
|
end
|
|
|
|
---Gets the level itemString smart map.
|
|
---@return SmartMapObject
|
|
function ItemString.GetLevelMap()
|
|
return private.levelItemStringMap
|
|
end
|
|
|
|
---Converts the parameter into an itemString.
|
|
---@param item number|string Either an itemId, itemLink, or itemString to be converted
|
|
---@return string
|
|
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.
|
|
---@param item string An item to get the id of
|
|
---@return number
|
|
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.
|
|
---@param itemString string An itemString to get the base itemString of
|
|
---@return string
|
|
function ItemString.GetBaseFast(itemString)
|
|
if not itemString then
|
|
return nil
|
|
end
|
|
return private.baseItemStringReader[itemString]
|
|
end
|
|
|
|
---Converts the parameter into a base itemString.
|
|
---@param item string An item to get the base itemString of
|
|
---@return string
|
|
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.
|
|
---@param itemKey table An itemKey to get the itemString of
|
|
---@return string
|
|
function ItemString.GetBaseFromItemKey(itemKey)
|
|
if itemKey.battlePetSpeciesID > 0 then
|
|
return "p:"..itemKey.battlePetSpeciesID
|
|
else
|
|
return "i:"..itemKey.itemID
|
|
end
|
|
end
|
|
|
|
---Returns whether or not a non-base version of an item has been seen.
|
|
---@param baseItemString string A base itemString to check
|
|
---@return boolean
|
|
function ItemString.HasNonBase(baseItemString)
|
|
return private.hasNonBaseItemStrings[baseItemString] or false
|
|
end
|
|
|
|
---Converts an itemString to a level itemString
|
|
---@param itemString string An itemString to get the level itemString of
|
|
---@return string
|
|
function ItemString.ToLevel(itemString)
|
|
if ItemString.IsLevel(itemString) then
|
|
-- Already a level itemString
|
|
return itemString
|
|
end
|
|
local baseItemString = ItemString.GetBaseFast(itemString)
|
|
if itemString == baseItemString then
|
|
-- Already a base itemString
|
|
return itemString
|
|
end
|
|
if ItemString.IsPet(itemString) then
|
|
local petLevel = strmatch(itemString, "^p:%d+:(%d+):.+$")
|
|
return petLevel and baseItemString..":i"..petLevel or baseItemString
|
|
end
|
|
local level, isAbs = BonusIds.GetItemLevel(itemString)
|
|
if not level then
|
|
return baseItemString
|
|
end
|
|
if isAbs then
|
|
return baseItemString.."::".."i"..level
|
|
else
|
|
if level >= 0 then
|
|
level = "+"..level
|
|
end
|
|
return baseItemString.."::"..level
|
|
end
|
|
end
|
|
|
|
---Parse the level modifier from a (potential) level itemString
|
|
---@param itemString string An itemString to parse
|
|
---@return number @The level modifier
|
|
---@return boolean @Whether or not it is an absolute level
|
|
function ItemString.ParseLevel(itemString)
|
|
local petLevel = strmatch(itemString, "^p:[0-9]+:i([0-9]+)$")
|
|
if petLevel then
|
|
return tonumber(petLevel), true
|
|
end
|
|
local prefix, level = strmatch(itemString, "^i:[0-9]+:[0-9%-]*:([i%+%-])([0-9]+)")
|
|
level = level and tonumber(level) or nil
|
|
if not prefix or not level then
|
|
return nil, nil
|
|
elseif prefix == "i" then
|
|
return level, true
|
|
elseif prefix == "+" then
|
|
return level, false
|
|
elseif prefix == "-" then
|
|
return level * -1, false
|
|
else
|
|
error("Invalid prefix: "..tostring(prefix))
|
|
end
|
|
end
|
|
|
|
---Attempts to determine the itemLevel by parsing the itemString
|
|
---@param itemString string An itemString to get the itemLevel of
|
|
---@return number|nil
|
|
function ItemString.GetItemLevel(itemString)
|
|
-- Check if this is a level itemString first
|
|
local itemLevel, isAbs = ItemString.ParseLevel(itemString)
|
|
if itemLevel then
|
|
return isAbs and itemLevel or nil
|
|
end
|
|
if ItemString.IsPet(itemString) then
|
|
local petLevel = strmatch(itemString, "^p:%d+:(%d+):.+$")
|
|
return petLevel and tonumber(petLevel) or nil
|
|
else
|
|
-- Try to get the level from the bonusIds
|
|
itemLevel, isAbs = BonusIds.GetItemLevel(itemString)
|
|
return isAbs and itemLevel or nil
|
|
end
|
|
end
|
|
|
|
---Gets a list of stat modifier values which are present in an itemString
|
|
---@param itemString string An itemString to get the stat modifiers of
|
|
---@param fromBonusIdsOnly boolean Only get equivalent modifiers from bonusIds
|
|
---@param resultTbl table The table to store the results in
|
|
function ItemString.GetStatModifiers(itemString, fromBonusIdsOnly, resultTbl)
|
|
if not ItemString.IsItem(itemString) then
|
|
return
|
|
end
|
|
return private.GetStatModifiersHelper(resultTbl, fromBonusIdsOnly, select(4, strsplit(":", itemString)))
|
|
end
|
|
|
|
---Converts the parameter into a WoW itemString.
|
|
---@param itemString string An itemString to get the WoW itemString of
|
|
---@return number
|
|
function ItemString.ToWow(itemString)
|
|
local itemStringLevel, isAbsItemStringLevel = ItemString.ParseLevel(itemString)
|
|
local itemId, rand, extraPart = nil, nil, nil
|
|
if itemStringLevel then
|
|
itemId, rand = select(2, strsplit(":", itemString))
|
|
extraPart = BonusIds.GetBonusStringForLevel(itemStringLevel, isAbsItemStringLevel)
|
|
else
|
|
local _, extra = nil, nil
|
|
itemId, rand, extra = select(2, strsplit(":", itemString))
|
|
extraPart = extra and strmatch(itemString, "i:[0-9]+:[0-9%-]*:(.+)") or ""
|
|
end
|
|
local level = UnitLevel("player")
|
|
local spec = Environment.IsRetail() and GetSpecialization() or nil
|
|
spec = spec and GetSpecializationInfo(spec) or ""
|
|
return "item:"..itemId.."::::::"..(rand or "").."::"..level..":"..spec..":::"..extraPart..":::"
|
|
end
|
|
|
|
---Returns whether or not the itemString is for an item
|
|
---@param itemString string The itemString to check
|
|
---@return boolean
|
|
function ItemString.IsItem(itemString)
|
|
return strmatch(itemString, "^i:[%-:0-9%+i]+$") and true or false
|
|
end
|
|
|
|
---Returns whether or not the itemString is a level itemString
|
|
---@param itemString string The itemString to check
|
|
---@return boolean
|
|
function ItemString.IsLevel(itemString)
|
|
return strmatch(itemString, "^i:[0-9]+:[0-9%-]*:[i%+%-][0-9]+$") or strmatch(itemString, "^p:[0-9]+:i[0-9]+$") and true or false
|
|
end
|
|
|
|
---Returns whether or not the itemString is for a pet
|
|
---@param itemString string The itemString to check
|
|
---@return boolean
|
|
function ItemString.IsPet(itemString)
|
|
return strmatch(itemString, "^p:[%-:0-9i]+$") 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, "^item:(%d+)$")
|
|
if itemId then
|
|
item = "i:"..itemId
|
|
else
|
|
itemId = strmatch(item, "^[ip]:(%d+)$")
|
|
end
|
|
if itemId then
|
|
if tonumber(itemId) > ITEM_MAX_ID then
|
|
return nil
|
|
end
|
|
-- This is already an itemString
|
|
return item
|
|
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%-:i%+]+)$") then
|
|
return private.FixItemString(item)
|
|
elseif strmatch(item, "^p:([i0-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)
|
|
return strmatch(itemString, "^(p:%d+:%d+:%d+)$") or strmatch(itemString, "^(p:%d+:i%d+)$") or 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)
|
|
wipe(private.modifiersValueTemp)
|
|
wipe(private.extraStatModifiersTemp)
|
|
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)
|
|
assert(not private.modifiersValueTemp[modifierType])
|
|
private.modifiersValueTemp[modifierType] = modifier
|
|
elseif not importantBonusIdsOnly and EXTRA_STAT_MODIFIER_TYPES[modifierType] then
|
|
tinsert(private.modifiersTemp, modifierType)
|
|
tinsert(private.extraStatModifiersTemp, modifier)
|
|
end
|
|
modifierType = nil
|
|
end
|
|
end
|
|
if #private.modifiersTemp > 0 then
|
|
sort(private.modifiersTemp)
|
|
sort(private.extraStatModifiersTemp)
|
|
-- insert the values into modifiersTemp
|
|
for i = #private.modifiersTemp, 1, -1 do
|
|
local tempModifierType = private.modifiersTemp[i]
|
|
local modifier = nil
|
|
if EXTRA_STAT_MODIFIER_TYPES[tempModifierType] then
|
|
assert(not importantBonusIdsOnly)
|
|
modifier = tremove(private.extraStatModifiersTemp)
|
|
else
|
|
modifier = private.modifiersValueTemp[tempModifierType]
|
|
end
|
|
assert(modifier)
|
|
tinsert(private.modifiersTemp, i + 1, modifier)
|
|
end
|
|
tinsert(private.modifiersTemp, 1, #private.modifiersTemp / 2)
|
|
modifiersStr = table.concat(private.modifiersTemp, ":")
|
|
else
|
|
modifiersStr = ""
|
|
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
|
|
|
|
function private.GetStatModifiersHelper(resultTbl, fromBonusIds, numBonusIds, ...)
|
|
numBonusIds = tonumber(numBonusIds) or 0
|
|
local numParts = select("#", ...)
|
|
if numParts == 0 then
|
|
return
|
|
end
|
|
|
|
-- check the bonusIds
|
|
for i = 1, numBonusIds do
|
|
local bonusId = select(i, ...)
|
|
bonusId = tonumber(bonusId)
|
|
assert(bonusId)
|
|
local modifier = BonusIds.GetCraftingStatModifier(bonusId)
|
|
if modifier then
|
|
tinsert(resultTbl, modifier)
|
|
end
|
|
end
|
|
if fromBonusIds then
|
|
sort(resultTbl)
|
|
return
|
|
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
|
|
return
|
|
end
|
|
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 EXTRA_STAT_MODIFIER_TYPES[modifierType] then
|
|
if not tContains(resultTbl, modifier) then
|
|
tinsert(resultTbl, modifier)
|
|
end
|
|
end
|
|
modifierType = nil
|
|
end
|
|
end
|
|
sort(resultTbl)
|
|
end
|
|
|