local minItem, maxItem, chunkSize = 1, 99999, 20000 local CONSECUTIVE_INVALID_TO_IGNORE = 5 local VALID_DELAY = 0.025 local INVALID_DELAY = 5 local function scanItemLink(link) local textL, textR -- Populate hidden tooltip ItemScannerHiddenTooltip:ClearLines() ItemScannerHiddenTooltip:SetHyperlink(link) local startTime = GetTime() while ItemScannerHiddenTooltipTextLeft1:GetText():find(RETRIEVING_ITEM_INFO) do coroutine.yield() if GetTime() - startTime >= INVALID_DELAY then -- If we got anything useful, keep it if numLines > 1 then break end return false end end if IS_item_info[link] and #(IS_item_info[link]) >= ItemScannerHiddenTooltip:NumLines() then return true end IS_item_info[link] = { itemInfo = {GetItemInfo(link)}, itemSpell = {GetItemSpell(link)}, statTable = GetItemStats(link), itemUniqueness = {GetItemUniqueness(link)}, consumableItem = IsConsumableItem(link), equippable = IsEquippableItem(link), helpful = IsHelpfulItem(link), harmful = IsHarmfulItem(link), } for i = 1, ItemScannerHiddenTooltip:NumLines() do IS_item_info[link][i] = { left = _G["ItemScannerHiddenTooltipTextLeft" .. i]:GetText(), right = _G["ItemScannerHiddenTooltipTextRight" .. i]:GetText(), } end return true end local frame = CreateFrame("Frame") local function OnUpdate(self) local status, err if self.itemco then status, err = pcall(self.itemco) if status == false then print("ItemScanner: item scan error, aborting.") self.itemco = nil IS_status.items.scan_active = false error(err) return elseif err == true then self.itemco = nil end else frame:SetScript("OnUpdate", nil) end end local function itemParseCoroutine(start) local chunkStart, chunkEnd = chunkSize * math.floor(start / chunkSize), chunkSize * math.floor((start + chunkSize) / chunkSize) - 1 IS_status.items.scan_active = true coroutine.yield() local startTime = GetTime() local roundStartTime = startTime local numProcessed, roundNumProcessed, valid, roundValid = 0, 0, 0, 0 print(string.format("ItemScanner: starting scan at item %d", start)) for i = start, math.min(chunkEnd, maxItem) do if not IS_status.items.invalid[i] or (type(IS_status.items.invalid[i]) == "number" and IS_status.items.invalid[i] < CONSECUTIVE_INVALID_TO_IGNORE) then IS_status.items.last_scanned = i local itemStartTime = GetTime() if scanItemLink("item:" .. i .. ":0:0:0:0:0:0:0:85") then IS_status.items.last_valid = i -- Reset invalid count IS_status.items.invalid[i] = false valid, roundValid = valid + 1, roundValid + 1 else -- start with 3 if no previous value IS_status.items.invalid[i] = (IS_status.items.invalid[i] or 2) + 1 end if not IS_status.items.scan_active then break end numProcessed = numProcessed + 1 roundNumProcessed = roundNumProcessed + 1 while GetTime() - itemStartTime < VALID_DELAY do coroutine.yield() end local currentTime = GetTime() if currentTime - roundStartTime >= 120 then local elapsedTime = currentTime - startTime print(string.format("ItemScanner: at item %d, %d in %.1f sec. (%.2f/min.) (%d valid), %d this round (%d valid)", i, numProcessed, elapsedTime, numProcessed / elapsedTime * 60, valid, roundNumProcessed, roundValid)) roundStartTime = roundStartTime + 120 roundNumProcessed, roundValid = 0, 0 end end end local elapsedTime = GetTime() - startTime if elapsedTime == 0 then elapsedTime = 0.001 end print(string.format("ItemScanner: scan stopped at item %d, %d in %.1f sec. (%.2f/min.) (%d valid)", IS_status.items.last_scanned, numProcessed, elapsedTime, numProcessed / elapsedTime * 60, valid)) IS_status.items.scan_active = false return true end local function commandHandler(msg) if string.match(msg, "^items") then local start if msg == "items" or msg == "items start" then start = IS_status.items.last_valid or IS_status.items.last_scanned or minItem elseif msg == "items clear" then IS_item_info = {} IS_status.items.last_scanned = nil IS_status.items.last_valid = nil return elseif msg == "items next-chunk" then start = IS_status.items.last_valid or IS_status.items.last_scanned or minItem start = chunkSize * math.ceil(start / chunkSize) if start > maxItem then commandHandler("items stop") return end commandHandler("items clear") elseif msg == "items reset" then commandHandler("items clear") commandHandler("items restart") return elseif msg == "items restart" then start = minItem IS_status.items.last_scanned = nil IS_status.items.last_valid = nil elseif msg == "items restart-chunk" then start = (IS_status.items.last_valid or IS_status.items.last_scanned or minItem) start = math.max(chunkSize * math.floor(start / chunkSize), minItem) elseif msg == "items stop" then IS_status.items.scan_active = false return else print("Usage: /is items <arg> (or /itemscanner items <arg>") print(" clear clears item data but does not stop a running scan") print(" reset clears item data and starts over") print(" restart restarts the current scan but leaves existing data intact") print(" start (default) starts where you last left off") print(" stop stops scanning but leaves existing data intact") return end frame.itemco = coroutine.wrap(itemParseCoroutine) frame.itemco(start) frame:SetScript("OnUpdate", OnUpdate) else print("Usage: /is <arg> (or /itemscanner <arg>") print(" items scans all items") end end if not IS_item_info then IS_item_info = {} end if not IS_status then IS_status = {} end if not IS_status.items then IS_status.items = {} end if not IS_status.items.invalid then IS_status.items.invalid = {} end SLASH_ITEMSCANNER1="/is" SLASH_ITEMSCANNER2="/itemscanner" SlashCmdList["ITEMSCANNER"] = commandHandler