local GREY = "|cff999999"; local RED = "|cffff0000"; local WHITE = "|cffFFFFFF"; local GREEN = "|cff1eff00"; local PURPLE = "|cff9F3FFF"; local BLUE = "|cff0070dd"; local ORANGE = "|cffFF8400"; local AL = LibStub("AceLocale-3.0"):GetLocale("AtlasLoot"); local modules = { "AtlasLoot_BurningCrusade", "AtlasLoot_Crafting", "AtlasLoot_OriginalWoW", "AtlasLoot_WorldEvents", "AtlasLoot_WrathoftheLichKing" }; local currentPage = 1; local SearchResult = nil; -- Supported Operators local OP_AND = "&" local BINARYOPERATORS = { OP_AND }; -- multi-character patterns must come before single-character patterns local RELATIONAL_OPERATORS = { ">=", "<=", "<>", "<", ">", "=", }; -- Supported Stat Filters local STAT_FILTERS = { -- Base Stats ["stamina"] = "ITEM_MOD_STAMINA_SHORT", ["stam"] = "ITEM_MOD_STAMINA_SHORT", ["sta"] = "ITEM_MOD_STAMINA_SHORT", ["strength"] = "ITEM_MOD_STRENGTH_SHORT", ["str"] = "ITEM_MOD_STRENGTH_SHORT", ["agility"] = "ITEM_MOD_AGILITY_SHORT", ["agi"] = "ITEM_MOD_AGILITY_SHORT", ["intellect"] = "ITEM_MOD_INTELLECT_SHORT", ["int"] = "ITEM_MOD_INTELLECT_SHORT", ["spirit"] = "ITEM_MOD_SPIRIT_SHORT", ["spir"] = "ITEM_MOD_SPIRIT_SHORT", ["spi"] = "ITEM_MOD_SPIRIT_SHORT", ["health"] = "ITEM_MOD_HEALTH_SHORT", ["mana"] = "ITEM_MOD_MANA_SHORT", ["mp5"] = "ITEM_MOD_POWER_REGEN0_SHORT", ["mpr"] = "ITEM_MOD_POWER_REGEN0_SHORT", ["hp5"] = "ITEM_MOD_HEALTH_REGEN_SHORT", ["hpr"] = "ITEM_MOD_HEALTH_REGEN_SHORT", --Sockets ["socketblue"] = "EMPTY_SOCKET_BLUE", ["socketred"] = "EMPTY_SOCKET_RED", ["socketyellow"] = "EMPTY_SOCKET_YELLOW", ["socketnocolor"] = "EMPTY_SOCKET_NO_COLOR", ["socketwhite"] = "EMPTY_SOCKET_NO_COLOR", ["socketmeta"] = "EMPTY_SOCKET_META", ["meta"] = "EMPTY_SOCKET_META", --Secondary Stats ["attackpowerferal"] = "ITEM_MOD_FERAL_ATTACK_POWER_SHORT", ["attackpowferal"] = "ITEM_MOD_FERAL_ATTACK_POWER_SHORT", ["apferal"] = "ITEM_MOD_FERAL_ATTACK_POWER_SHORT", ["attackpower"] = "ITEM_MOD_ATTACK_POWER_SHORT", ["attackpow"] = "ITEM_MOD_ATTACK_POWER_SHORT", ["ap"] = "ITEM_MOD_ATTACK_POWER_SHORT", ["spellpower"] = "ITEM_MOD_SPELL_POWER_SHORT", ["spellpow"] = "ITEM_MOD_SPELL_POWER_SHORT", ["sp"] = "ITEM_MOD_SPELL_POWER_SHORT", ["spellpenetration"] = "ITEM_MOD_SPELL_PENETRATION_SHORT", ["spellpen"] = "ITEM_MOD_SPELL_PENETRATION_SHORT", ["spp"] = "ITEM_MOD_SPELL_PENETRATION_SHORT", ["crit"] = "ITEM_MOD_CRIT_RATING_SHORT", ["haste"] = "ITEM_MOD_HASTE_RATING_SHORT", ["hit"] = "ITEM_MOD_HIT_RATING_SHORT", ["armorpenetration"] = "ITEM_MOD_ARMOR_PENETRATION_RATING_SHORT", ["armourpenetration"] = "ITEM_MOD_ARMOR_PENETRATION_RATING_SHORT", ["armorpen"] = "ITEM_MOD_ARMOR_PENETRATION_RATING_SHORT", ["armourpen"] = "ITEM_MOD_ARMOR_PENETRATION_RATING_SHORT", ["arp"] = "ITEM_MOD_ARMOR_PENETRATION_RATING_SHORT", ["expertise"] = "ITEM_MOD_EXPERTISE_RATING_SHORT", ["exp"] = "ITEM_MOD_EXPERTISE_RATING_SHORT", ["dps"] = "ITEM_MOD_DAMAGE_PER_SECOND_SHORT", ["resilience"] = "ITEM_MOD_RESILIENCE_RATING", ["resil"] = "ITEM_MOD_RESILIENCE_RATING", ["res"] = "ITEM_MOD_RESILIENCE_RATING", ["defense"] = "ITEM_MOD_DEFENSE_SKILL_RATING_SHORT", ["def"] = "ITEM_MOD_DEFENSE_SKILL_RATING_SHORT", ["dodge"] = "ITEM_MOD_DODGE_RATING_SHORT", ["dod"] = "ITEM_MOD_DODGE_RATING_SHORT", ["block"] = "ITEM_MOD_BLOCK_RATING_SHORT", ["blockvalue"] = "ITEM_MOD_BLOCK_VALUE_SHORT", ["blockval"] = "ITEM_MOD_BLOCK_VALUE_SHORT", ["bv"] = "ITEM_MOD_BLOCK_VALUE_SHORT", ["parry"] = "ITEM_MOD_PARRY_RATING_SHORT", --Resistances ["armor"] = "RESISTANCE0_NAME", ["armour"] = "RESISTANCE0_NAME", ["arm"] = "RESISTANCE0_NAME", ["resistancephysical"] = "RESISTANCE0_NAME", ["resistancephys"] = "RESISTANCE0_NAME", ["resphys"] = "RESISTANCE0_NAME", ["resistanceholy"] = "RESISTANCE1_NAME", ["resholy"] = "RESISTANCE1_NAME", ["resistancefire"] = "RESISTANCE2_NAME", ["resfire"] = "RESISTANCE2_NAME", ["resistancenature"] = "RESISTANCE3_NAME", ["resnature"] = "RESISTANCE3_NAME", ["resnat"] = "RESISTANCE3_NAME", ["resistanceforst"] = "RESISTANCE4_NAME", ["resfrost"] = "RESISTANCE4_NAME", ["resistanceshadow"] = "RESISTANCE5_NAME", ["resshadow"] = "RESISTANCE5_NAME", ["resshad"] = "RESISTANCE5_NAME", ["resistancearcane"] = "RESISTANCE6_NAME", ["resarcane"] = "RESISTANCE6_NAME", ["resarc"] = "RESISTANCE6_NAME" }; local SOCKET_FILTERS = { ["socket"] = true, ["sockets"] = true, ["gem"] = true, ["gems"] = true, }; local SOCKET_TYPES = { "EMPTY_SOCKET_BLUE", "EMPTY_SOCKET_RED", "EMPTY_SOCKET_YELLOW", "EMPTY_SOCKET_META", "EMPTY_SOCKET_NO_COLOR" }; local INFO_FILTERS = { ["ilvl"] = true, ["minlvl"] = true, --["type"] = true, --["subtype"] = true, }; local QUALITY_FILTERS = { ["poor"] = 0, ["common"] = 1, ["uncommon"] = 2, ["rare"] = 3, ["epic"] = 4, ["legendary"] = 5, ["artifact"] = 6, ["heirloom"] = 7 }; local SLOT_FILTERS = { ["none"] = "INVTYPE_NON_EQUIP", ["head"] = "INVTYPE_HEAD", ["neck"] = "INVTYPE_NECK", ["shoulder"] = "INVTYPE_SHOULDER", ["body"] = "INVTYPE_BODY", ["chest"] = "INVTYPE_CHEST", ["waist"] = "INVTYPE_WAIST", ["legs"] = "INVTYPE_LEGS", ["feet"] = "INVTYPE_FEET", ["wrist"] = "INVTYPE_WRIST", ["hand"] = "INVTYPE_HAND", ["finger"] = "INVTYPE_FINGER", ["trinket"] = "INVTYPE_TRINKET", ["weapon"] = "INVTYPE_WEAPON", ["shield"] = "INVTYPE_SHIELD", ["ranged"] = "INVTYPE_RANGED", ["cloak"] = "INVTYPE_CLOAK", ["2hweapon"] = "INVTYPE_2HWEAPON", ["2h"] = "INVTYPE_2HWEAPON", ["bag"] = "INVTYPE_BAG", ["tabard"] = "INVTYPE_TABARD", ["robe"] = "INVTYPE_ROBE", ["weaponmainhand"] = "INVTYPE_WEAPONMAINHAND", ["mainhand"] = "INVTYPE_WEAPONMAINHAND", ["weaponoffhand"] = "INVTYPE_WEAPONOFFHAND", ["offhand"] = "INVTYPE_WEAPONOFFHAND", ["holdable"] = "INVTYPE_HOLDABLE", ["ammo"] = "INVTYPE_AMMO", ["thrown"] = "INVTYPE_THROWN", ["rangedright"] = "INVTYPE_RANGEDRIGHT", ["quiver"] = "INVTYPE_QUIVER", ["relic"] = "INVTYPE_RELIC", }; local TYPE_FILTERS = { ["cloth"] = "Cloth", ["leather"] = "Leather", ["mail"] = "Mail", ["plate"] = "Plate", ["shield"] = "Shields", ["libram"] = "Librams", ["idol"] = "Idols", ["totem"] = "Totems", ["sigil"] = "Sigils", ["axe1h"] = "One-Handed Axes", ["axe2h"] = "Two-Handed Axes", ["bows"] = "Bows", ["bow"] = "Bows", ["guns"] = "Guns", ["gun"] = "Guns", ["mace1h"] = "One-Handed Maces", ["mace2h"] = "Two-Handed Maces", ["polearm"] = "Polearms", ["sword1h"] = "One-Handed Swords", ["sword2h"] = "Two-Handed Swords", ["staff"] = "Staves", ["fist"] = "Fist Weapons", ["generic"] = "", ["dagger"] = "Daggers", ["thrown"] = "Thrown", ["crossbows"] = "Crossbows", ["crossbow"] = "Crossbows", ["wand"] = "Wands", ["fishingpole"] = "Fishing Poles", } local NON_EQUIPABLE_SLOTS = { ["INVTYPE_NON_EQUIP"] = true, ["INVTYPE_BODY"] = true, ["INVTYPE_BAG"] = true, ["INVTYPE_AMMO"] = true, ["INVTYPE_QUIVER"] = true, }; SLASH_ATLASLOOTHELP1 = "/atlasloothelp"; SlashCmdList["ATLASLOOTHELP"] = function(msg, editBox) local function show(caption, t) local keys = {} for key in pairs(t) do table.insert(keys, key) end table.sort(keys, function(a,b) return a < b end) print(caption..": ".. table.concat(keys, ", ")) end show("stat", STAT_FILTERS) show("socket", SOCKET_FILTERS) show("slot", SLOT_FILTERS) show("quality", QUALITY_FILTERS) show("info", INFO_FILTERS) print("\nExamples:") print("gladiator") print("str>40") print("gladiator&str>40") print("str>40&ilvl>=120&ilvl<140&int>0&socket>2"); print("sp>20&quality>=rare&quality20&minlvl<=50") end SLASH_ATLASLOOTSEARCH1 = "/atlaslootsearch"; SLASH_ATLASLOOTSEARCH2 = "/als"; SlashCmdList["ATLASLOOTSEARCH"] = function(search, editBox) AtlasLootDefaultFrame:Show() if search and search ~= '' then AtlasLootDefaultFrameSearchBox:SetText(search) AtlasLootDefaultFrameSearchButton:Click() end end -- split one string on another (delimiter can be more than one character) local function SplitString(str, delimiter) local result = {} local start = 1 local len = #str while start <= len do local delimPos = str:find(delimiter, start, true) if delimPos then result[#result+1] = str:sub(start, delimPos-1) -- grab up to the delimeter start = delimPos + #delimiter -- advance past the delimiter else result[#result+1] = str:sub(start) -- grab the rest of the string break end end return result end local function CompareNumbersByOperator(operator, left, right) return left and right and ( (operator == "<>" and left ~= right) or (operator == "<=" and left <= right) or (operator == ">=" and left >= right) or (operator == "=" and left == right) or (operator == "<" and left < right) or (operator == ">" and left > right) ) end local function ThrowQueryError(...) error("Error: "..string.format(...)) end local function IsItemStatMatch(term, stats) local filterKey = STAT_FILTERS[term.left] if not filterKey then return end local statValue = tonumber(stats[filterKey]) if not statValue then return end local searchedValue = tonumber(term.right) if not searchedValue then ThrowQueryError("'%s' requires a numeric argument", term.left) end return CompareNumbersByOperator(term.relational, statValue, searchedValue) end local function IsItemLevelFilterMatch(term, itemLvl, itemEquipLoc) if term.left ~= "ilvl" then return end local searchedValue = tonumber(term.right); if not searchedValue then ThrowQueryError("ilvl search requires a numeric argument") end return itemLvl ~= nil and itemLvl > 0 and CompareNumbersByOperator(term.relational, itemLvl, searchedValue) end local function IsItemQualityMatch(term, itemQuality) if term.left ~= "quality" then return end local searchedValue = QUALITY_FILTERS[term.right] if not searchedValue then ThrowQueryError("unrecognized quality value \"%s\"", term.right) end return CompareNumbersByOperator(term.relational, itemQuality, searchedValue) end local function IsItemSocketMatch(term, stats) if not SOCKET_FILTERS[term.left] then return end local searchedValue = tonumber(term.right) if not searchedValue then ThrowQueryError("'%s' requires a numeric argument", term.left) end local socketCount = 0; for _, socketType in pairs(SOCKET_TYPES) do local statValue = tonumber(stats[socketType]); if statValue then socketCount = socketCount + statValue; end end return CompareNumbersByOperator(term.relational, socketCount, searchedValue) end local function IsMinLevelFilterMatch(term, minLvl) if term.left ~= "minlvl" then return end local searchedValue = tonumber(term.right); if not searchedValue then ThrowQueryError("minlvl search requires a numeric argument") end return minLvl ~= nil and minLvl > 0 and CompareNumbersByOperator(term.relational, minLvl, searchedValue) end local function IsItemSlotMatch(term, itemEquipLoc) if term.left ~= "slot" then return end if term.relational ~= "=" then ThrowQueryError("slot searches should be in the form \"slot=[slotname]\"") end local slot = SLOT_FILTERS[term.right] if not slot then ThrowQueryError("unrecognized slot name: \"%s\"", term.right) end return slot == itemEquipLoc end local function IsItemTypeMatch(term, itemEquipType) if term.left ~= "type" then return end if term.relational ~= "=" then ThrowQueryError("type searches should be in the form \"type=[typename]\"") end local type = TYPE_FILTERS[term.right] if not type then ThrowQueryError("unrecognized type name: \"%s\"", term.right) end return type == itemEquipType; end local function IsItemDifficulty(term, itemName) if term.left ~= "dif" then return end if term.relational ~= "=" then ThrowQueryError("difficulty searches should be in the form \"dif=[difficulty]\"") end if tonumber(term.right) > (#AtlasLoot_Difficulty.MythicPlus + 4) then ThrowQueryError("difficulty should be a number: 1 - %i", #AtlasLoot_Difficulty.MythicPlus + 4) end local _, hasDifficulty = AL_FindId(itemName, tonumber(term.right)); return hasDifficulty; end local function nameMatches(name, searchText) if AtlasLoot.db.profile.PartialMatching then return string.find(string.lower(name), searchText); else return string.lower(name) == searchText; end end local function ItemMatchesTerm(term, itemName, stats, itemLvl, minLvl, itemQuality, itemEquipLoc, itemType) if term.relational then return IsItemStatMatch(term, stats) or IsItemSocketMatch(term, stats) or IsItemLevelFilterMatch(term, itemLvl, itemEquipLoc) or IsMinLevelFilterMatch(term, minLvl) or IsItemSlotMatch(term, itemEquipLoc) or IsItemQualityMatch(term, itemQuality) or IsItemTypeMatch(term, itemType) or IsItemDifficulty(term, itemName) else return nameMatches(itemName, term.name) end end local function ItemMatchesAllTerms(searchTerms, itemName, stats, itemLvl, minLvl, itemQuality, itemEquipLoc, itemType) for _, term in ipairs(searchTerms) do if not ItemMatchesTerm(term, itemName, stats, itemLvl, minLvl, itemQuality, itemEquipLoc, itemType) then return false end end return true end local function TermsContainDifficulty(searchTerms) for _, term in ipairs(searchTerms) do if term.relational then if term.left == "dif" then return tonumber(term.right) end end end return AtlasLoot_Difficulty.Normal; end local function ParseTerm(termText) for _, relational in ipairs(RELATIONAL_OPERATORS) do local operands = SplitString(termText, relational) if #operands == 2 then return { left = operands[1], right = operands[2], relational = relational } end end return { name = termText } -- simple name search end -- Parse search text into '&'-delimited search terms, -- then parse each term on its relational operator, if present. local function ParseQuery(searchText) local terms = {} for i, term in pairs(SplitString(searchText, OP_AND)) do table.insert(terms, ParseTerm(term)) end return terms end local function GetItemDetails(itemId, atlasName) -- Name, Link, Quality(num), iLvl(num), minLvl(num), itemType(localized string), itemSubType(localized string), stackCount(num), itemEquipLoc(enum), texture(link to a local file), displayId(num) local itemName, _, itemQuality, itemLvl, minLvl, _, itemSubType, _, itemEquipLoc = GetItemInfo(itemId); if not itemName then itemName = gsub(atlasName, "=q%d=", "") end return itemName, itemQuality, itemLvl, minLvl, itemEquipLoc, itemSubType, GetItemStats("item:"..itemId) end local function GetSpellName(itemId, atlasName) local spellName = GetSpellInfo(string.sub(itemId, 2)); if spellName then return spellName elseif (string.sub(atlasName, 1, 2) == "=d") then return gsub(atlasName, "=ds=", ""); else return gsub(atlasName, "=q%d=", ""); end end local function DoSearch(searchText) AtlasLootCharDB["SearchResult"] = {}; AtlasLootCharDB.LastSearchedText = searchText; local function AddItemToSearchResult(itemId, itemType, itemName, dataID, difficulty) local lootPage = AtlasLoot_TableNames[dataID] and AtlasLoot_TableNames[dataID][1] or "Argh!"; table.insert(AtlasLootCharDB["SearchResult"], { 0, itemId, itemType, itemName, lootPage, "", "", dataID.."|".."\"\""}); end local searchTerms = ParseQuery(searchText); local equipableFilterOn = AtlasLoot.db.profile.EquipableFilter; local difficulty = TermsContainDifficulty(searchTerms); local function IsItemEquipableMatch(itemEquipLoc) return not equipableFilterOn or ((itemEquipLoc and itemEquipLoc ~= '' and not NON_EQUIPABLE_SLOTS[itemEquipLoc])) end for dataID, data in pairs(AtlasLoot_Data) do for _, v in ipairs(data) do local _, itemId, itemType, atlasName = unpack(v) if (string.sub(itemId, 1, 1) ~= "s") then itemId = AL_FindId(string.sub(atlasName, 5), difficulty) or itemId; itemId = tonumber(itemId); end if type(itemId) == "number" and itemId > 0 then local itemName, itemQuality, itemLvl, minLvl, itemEquipLoc, itemSubType, stats = GetItemDetails(itemId, atlasName); if IsItemEquipableMatch(itemEquipLoc) and ItemMatchesAllTerms(searchTerms, itemName, stats, itemLvl, minLvl, itemQuality, itemEquipLoc, itemSubType) then AddItemToSearchResult(itemId, itemType, itemName, dataID, difficulty); end elseif not equipableFilterOn and itemId and itemId ~= "" and string.sub(itemId, 1, 1) == "s" then local spellName = GetSpellName(itemId, atlasName) if nameMatches(spellName, searchText) then spellName = string.sub(atlasName, 1, 4)..spellName; AddItemToSearchResult(itemId, itemType, spellName, dataID) end end end end end function AtlasLoot:ShowSearchResult() AtlasLoot_ShowItemsFrame("SearchResult", "SearchResultPage"..currentPage, (AL["Search Result: %s"]):format(AtlasLootCharDB.LastSearchedText or ""), pFrame); end function AtlasLoot:Search(Text) if not Text then return end Text = strtrim(Text); if Text == "" then return end -- Decide if we need load all modules or just specified ones local allDisabled = not self.db.profile.SearchOn.All; if allDisabled then for _, module in ipairs(modules) do if self.db.profile.SearchOn[module] == true then allDisabled = false; break; end end end if allDisabled then DEFAULT_CHAT_FRAME:AddMessage(RED..AL["AtlasLoot"]..": "..WHITE..AL["You don't have any module selected to search on."]); return; end if self.db.profile.SearchOn.All then AtlasLoot_LoadAllModules(); else for k, v in pairs(self.db.profile.SearchOn) do if k ~= "All" and v == true and not IsAddOnLoaded(k) and LoadAddOn(k) and self.db.profile.LoDNotify then DEFAULT_CHAT_FRAME:AddMessage(GREEN..AL["AtlasLoot"]..": "..ORANGE..k..WHITE.." "..AL["sucessfully loaded."]); end end end local success, message = pcall(DoSearch, Text) if not success then message = message:match("[^:]+: (.*)") or message -- strip stack location DEFAULT_CHAT_FRAME:AddMessage(RED..AL["AtlasLoot"]..": "..WHITE..message); elseif #AtlasLootCharDB["SearchResult"] == 0 then local itemFilterErrorMessage = ""; if operator then itemFilterErrorMessage = [[ Please check if you have a typo in the filter. For help, type "/atlasloothelp". You might also have to query the server for item informations to load them into your client's Cache.]]; end DEFAULT_CHAT_FRAME:AddMessage(RED..AL["AtlasLoot"]..": "..WHITE..AL["No match found for"].." \""..Text.."\"."..itemFilterErrorMessage); else currentPage = 1; SearchResult = AtlasLoot_CategorizeWishList(AtlasLootCharDB["SearchResult"]); AtlasLoot_ShowItemsFrame("SearchResult", "SearchResultPage1", (AL["Search Result: %s"]):format(AtlasLootCharDB.LastSearchedText or ""), pFrame); end end function AtlasLoot:ShowSearchOptions(button) local dewdrop = AceLibrary("Dewdrop-2.0"); if dewdrop:IsOpen(button) then dewdrop:Close(1); else local setOptions = function() dewdrop:AddLine( "text", AL["Search on"], "isTitle", true, "notCheckable", true ); dewdrop:AddLine( "text", AL["All modules"], "checked", self.db.profile.SearchOn.All, "tooltipTitle", AL["All modules"], "tooltipText", AL["If checked, AtlasLoot will load and search across all the modules."], "func", function() self.db.profile.SearchOn.All = not self.db.profile.SearchOn.All; end ); for _, module in ipairs(modules) do if IsAddOnLoadOnDemand(module) then local title = GetAddOnMetadata(module, "title"); local notes = GetAddOnMetadata(module, "notes"); dewdrop:AddLine( "text", title, "checked", self.db.profile.SearchOn.All or self.db.profile.SearchOn[module], "disabled", self.db.profile.SearchOn.All, "tooltipTitle", title, "tooltipText", notes, "func", function() if self.db.profile.SearchOn[module] == nil then self.db.profile.SearchOn[module] = true; else self.db.profile.SearchOn[module] = nil; end end ); end end dewdrop:AddLine( "text", AL["Search options"], "isTitle", true, "notCheckable", true ); dewdrop:AddLine( "text", AL["Partial matching"], "checked", self.db.profile.PartialMatching, "tooltipTitle", AL["Partial matching"], "tooltipText", AL["If checked, AtlasLoot search item names for a partial match."], "func", function() self.db.profile.PartialMatching = not self.db.profile.PartialMatching end ); dewdrop:AddLine( "text", "Only search equipable", -- TODO: put in AL "checked", self.db.profile.EquipableFilter, "tooltipTitle", "Equipable filter", -- TODO: put in AL "tooltipText", "If checked, AtlasLoot only includes items that are euipable.", -- TODO: put in AL "func", function() self.db.profile.EquipableFilter = not self.db.profile.EquipableFilter end ); end; dewdrop:Open(button, 'point', function(parent) return "BOTTOMLEFT", "BOTTOMRIGHT"; end, "children", setOptions ); end end function AtlasLoot:GetOriginalDataFromSearchResult(itemID) for i, v in ipairs(AtlasLootCharDB["SearchResult"]) do if v[2] == itemID then AtlasLoot_ShowWishListDropDown(v[2], v[3], v[4], v[5], v[8], this, nil, v[AtlasLoot_Difficulty.DIF_SEARCH]); end end end -- Copied and modified from AtlasLoot_GetWishListPage function AtlasLoot:GetSearchResultPage(page) if not SearchResult then SearchResult = AtlasLoot_CategorizeWishList(AtlasLootCharDB["SearchResult"]) end -- Calc for maximal pages local pageMax = math.ceil(#SearchResult / 30); if page < 1 then page = 1 end if page > pageMax then page = pageMax end currentPage = page; -- Table copy local k=1; local result = {}; local start = (page - 1) * 30 + 1; for i = start, start + 29 do if not SearchResult[i] then break end SearchResult[i][1] = k; table.insert(result, SearchResult[i]); k=k+1; end return result, pageMax; end