-- Ade Alts: scan local AA = AdeAlts ---------------------------------------------------------------------- -- scanning: trade skill window ---------------------------------------------------------------------- local tip = CreateFrame("GameTooltip", "AdeAltsTip", nil, "GameTooltipTemplate") local function TipCooldown(index) if GetTradeSkillCooldown then local c = GetTradeSkillCooldown(index) if c and c > 0 then return c end end tip:SetOwner(UIParent, "ANCHOR_NONE") tip:ClearLines() tip:SetTradeSkillItem(index) for i = 1, tip:NumLines() do local fs = getglobal("AdeAltsTipTextLeft" .. i) local t = fs and fs:GetText() if t then local l = string.lower(t) if string.find(l, "cooldown") and string.find(l, "remaining") then local d = AA.ParseDuration(t) if d then return d end end end end return nil end -- remember the known recipes of a profession (read from its trade skill / craft window) local function StoreRecipes(prof, recs, rank, maxrank) if not AA.me or not prof or prof == "" or table.getn(recs) == 0 then return end AA.me.prof = AA.me.prof or {} local p = AA.me.prof[prof] if not p then p = {} AA.me.prof[prof] = p end if rank and rank > 0 and (p.r ~= rank or p.m ~= maxrank) then p.r = rank p.m = maxrank p.ts = time() AA.fileDirty = true end table.sort(recs) local sig = table.concat(recs, "~") if sig ~= p.sig then p.rec = recs p.sig = sig p.rts = time() AA.fileDirty = true end end function AA.ScanTradeSkills() if AA.scanning or not AA.me then return end if GetTime() - AA.lastScan < 1 then return end local n = GetNumTradeSkills() if not n or n == 0 then return end AA.scanning = true -- expand collapsed headers temporarily so every recipe is visible local expanded = {} for i = n, 1, -1 do local name, ttype, _, isExp = GetTradeSkillInfo(i) if ttype == "header" and not isExp then expanded[name] = true ExpandTradeSkillSubClass(i) end end n = GetNumTradeSkills() local nowt = time() local recs = {} for i = 1, n do local name, ttype = GetTradeSkillInfo(i) if name and ttype ~= "header" then table.insert(recs, name) end if name and ttype ~= "header" and AA.IsTracked(name) then local rem = TipCooldown(i) if rem then AA.db.tipWorks = true AA.SetCD(name, nowt + rem) else local cur = AA.me.cds[name] if AA.prov[name] and nowt - AA.prov[name] < 10 then -- just crafted, keep provisional value elseif cur and cur > nowt and not AA.db.tipWorks then -- tooltips don't expose cooldowns on this client: keep the estimate else AA.SetCD(name, 0) end end end end local pn, pr, pm = GetTradeSkillLine() StoreRecipes(pn, recs, pr, pm) -- restore collapsed state for i = GetNumTradeSkills(), 1, -1 do local name, ttype, _, isExp = GetTradeSkillInfo(i) if ttype == "header" and expanded[name] and isExp then CollapseTradeSkillSubClass(i) end end AA.scanning = false AA.lastScan = GetTime() if AA.Refresh then AA.Refresh() end end -- craft window (Enchanting uses it instead of the trade skill window) local scanningCraft, lastCraftScan = false, 0 function AA.ScanCrafts() if scanningCraft or not AA.me or not GetNumCrafts then return end if GetTime() - lastCraftScan < 1 then return end local n = GetNumCrafts() if not n or n == 0 then return end local pn, pr, pm if GetCraftDisplaySkillLine then pn, pr, pm = GetCraftDisplaySkillLine() end if not pn or pn == "" then return end -- e.g. hunter pet training scanningCraft = true if ExpandCraftSkillLine then for i = n, 1, -1 do local name, sub, ctype, _, isExp = GetCraftInfo(i) if ctype == "header" and not isExp then ExpandCraftSkillLine(i) end end end n = GetNumCrafts() local recs = {} for i = 1, n do local name, sub, ctype = GetCraftInfo(i) if name and ctype ~= "header" then table.insert(recs, name) end end StoreRecipes(pn, recs, pr, pm) scanningCraft = false lastCraftScan = GetTime() if AA.Refresh then AA.Refresh() end end -- crafting hook: provisional cooldown the moment a tracked recipe is started local origDoTradeSkill = DoTradeSkill DoTradeSkill = function(index, count) if AA.me and index then local name, ttype, avail = GetTradeSkillInfo(index) if name and avail and avail > 0 and AA.IsTracked(name) then local d = AA.DEFAULTS[name] if d then AA.SetCD(name, time() + d) AA.prov[name] = time() end AA.After(5, function() if TradeSkillFrame and TradeSkillFrame:IsVisible() then AA.ScanTradeSkills() end end) AA.After(12, function() if TradeSkillFrame and TradeSkillFrame:IsVisible() then AA.ScanTradeSkills() end end) end end return origDoTradeSkill(index, count) end ---------------------------------------------------------------------- -- scanning: bags (Salt Shaker etc.) ---------------------------------------------------------------------- local BYNAME = {} for id, it in pairs(AA.ITEMS) do BYNAME[it.key] = it end -- match a tracked item by id, or by name as a fallback (in case the item id differs on this server) function AA.ItemFromLink(link) if not link then return nil end local _, _, id = string.find(link, "item:(%d+)") local it = id and AA.ITEMS[tonumber(id)] if not it then local _, _, nm = string.find(link, "%[(.-)%]") if nm then it = BYNAME[nm] end end return it end AA.bankOpen = false local function FindItemDef(link) local _, _, id = string.find(link, "item:(%d+)") local it = id and AA.ITEMS[tonumber(id)] if it then return it end local _, _, nm = string.find(link, "%[(.-)%]") if nm and AA.ITEMNAME[string.lower(nm)] then return AA.ITEMNAME[string.lower(nm)] end return nil end function AA.ScanBags(verbose) if not AA.me then return end local bags = { 0, 1, 2, 3, 4 } if AA.bankOpen then table.insert(bags, -1) for b = 5, 10 do table.insert(bags, b) end end local found = 0 for _, bag in ipairs(bags) do local slots = GetContainerNumSlots(bag) or 0 for slot = 1, slots do local link = GetContainerItemLink(bag, slot) if link then local it = FindItemDef(link) if it then found = found + 1 local start, dur = GetContainerItemCooldown(bag, slot) local exp = 0 if start and start > 0 and dur and dur > 30 then local rem = start + dur - GetTime() if rem > 0 then exp = time() + rem if exp > time() + it.cd + 60 then -- client reported a nonsense start time: keep our stored value if sane, else assume a fresh use local cur = AA.me.cds[it.key] if cur and cur > time() and cur <= time() + it.cd + 60 then exp = cur else exp = time() + it.cd end end end end if verbose then AA.Print(it.key .. " in bag " .. bag .. " slot " .. slot .. ": start=" .. tostring(start) .. " dur=" .. tostring(dur) .. " -> " .. (exp > 0 and AA.FormatTime(exp - time()) or "Ready")) end AA.SetCD(it.key, exp) end end end end if verbose and found == 0 then AA.Print("no tracked items found in bags" .. (AA.bankOpen and " / bank" or "") .. ". (Open the bank to scan it too.)") end return found end ---------------------------------------------------------------------- -- scanning: character data, inventory and bank (for the alts manager) ---------------------------------------------------------------------- local QHEX = { ["9d9d9d"] = 0, ["ffffff"] = 1, ["1eff00"] = 2, ["0070dd"] = 3, ["a335ee"] = 4, ["ff8000"] = 5 } local function LearnItem(id, link, tex) AA.db.items = AA.db.items or {} local it = AA.db.items[id] if it and it.n and it.t and it.t ~= "" then return end local _, _, nm = string.find(link, "%[(.-)%]") local _, _, col = string.find(link, "|c%x%x(%x%x%x%x%x%x)|H") local t = tex if t then t = (string.gsub(t, "^Interface\\Icons\\", "")) end AA.db.items[id] = { n = nm or ("item:" .. id), t = t or "", q = (col and QHEX[col]) or 1 } end -- returns aggregated list {{id,count},...} and a signature string, or nil if the containers are not available local function ScanContainers(bags) local order, agg = {}, {} local slotsTotal = 0 for _, bag in ipairs(bags) do local slots = GetContainerNumSlots(bag) or 0 slotsTotal = slotsTotal + slots for slot = 1, slots do local link = GetContainerItemLink(bag, slot) if link then local _, _, sid = string.find(link, "item:(%d+)") local id = tonumber(sid) if id then local tex, count = GetContainerItemInfo(bag, slot) count = count or 1 if count < 1 then count = 1 end if not agg[id] then agg[id] = 0 table.insert(order, id) end agg[id] = agg[id] + count LearnItem(id, link, tex) end end end end if slotsTotal == 0 then return nil end local list, parts = {}, {} for _, id in ipairs(order) do table.insert(list, { id, agg[id] }) table.insert(parts, id .. ":" .. agg[id]) end return list, table.concat(parts, "~") end -- equipped gear: slot -> "id:enchant:suffix:unique" local function ScanEquip() if not AA.me then return false end local eq, parts, any = {}, {}, false for slot = 0, 19 do local link = GetInventoryItemLink("player", slot) if link then local _, _, ref = string.find(link, "item:(%-?%d+:%-?%d+:%-?%d+:%-?%d+)") local _, _, sid = string.find(link, "item:(%d+)") if ref and sid then eq[slot] = ref table.insert(parts, slot .. "=" .. ref) LearnItem(tonumber(sid), link, GetInventoryItemTexture("player", slot)) any = true end end end if not any then return false end -- gear not loaded yet: keep what we have local sig = table.concat(parts, "~") if sig ~= AA.me.eqSig then AA.me.equip = eq AA.me.eqSig = sig AA.me.eqTs = time() return true end return false end -- profession skill levels from the skill window local PROFN = { ["Alchemy"] = 1, ["Blacksmithing"] = 1, ["Enchanting"] = 1, ["Engineering"] = 1, ["Herbalism"] = 1, ["Leatherworking"] = 1, ["Mining"] = 1, ["Skinning"] = 1, ["Tailoring"] = 1, ["Cooking"] = 1, ["First Aid"] = 1, ["Fishing"] = 1, ["Survival"] = 1, } local function ScanSkills() if not AA.me or not GetNumSkillLines then return false end local n = GetNumSkillLines() if not n or n == 0 then return false end -- collapsed headers hide their skills: expand them (this only happens once) for i = n, 1, -1 do local name, header, isExp = GetSkillLineInfo(i) if header and not isExp then ExpandSkillHeader(i) end end n = GetNumSkillLines() AA.me.prof = AA.me.prof or {} local seen, changed = {}, false local section for i = 1, n do local name, header, _, rank, _, _, maxRank = GetSkillLineInfo(i) if header then section = name end if not header and name and PROFN[name] and not AA.BadProf(name, maxRank) and not (name == "Survival" and section == "Class Skills") then seen[name] = true local p = AA.me.prof[name] if not p then p = {} AA.me.prof[name] = p end if p.r ~= rank or p.m ~= maxRank then p.r = rank p.m = maxRank p.ts = time() changed = true end end end for name, p in pairs(AA.me.prof) do if not seen[name] and PROFN[name] then AA.me.prof[name] = nil -- profession was unlearned changed = true end end return changed end AA.enterTime = 0 function AA.ScanSelf() if not AA.me or not AA.db then return end local changed = false local lvl, gold = UnitLevel("player"), GetMoney() local early = GetTime() - AA.enterTime < 12 -- right after login the client may still report 0 local metaChanged = false if lvl and lvl > 0 and AA.me.level ~= lvl then AA.me.level = lvl metaChanged = true end if gold and not (gold == 0 and early) and AA.me.gold ~= gold then AA.me.gold = gold metaChanged = true end if metaChanged then AA.me.dataTs = time() changed = true end if ScanEquip() then changed = true end if ScanSkills() then changed = true end local list, sig = ScanContainers({ 0, 1, 2, 3, 4 }) if list and sig ~= AA.me.invSig then AA.me.inv = list AA.me.invSig = sig AA.me.invTs = time() changed = true end if AA.bankOpen then local bags = { -1 } for b = 5, 10 do table.insert(bags, b) end local bl, bs = ScanContainers(bags) if bl and bs ~= AA.me.bankSig then AA.me.bank = bl AA.me.bankSig = bs AA.me.bankTs = time() changed = true end end if changed then AA.fileDirty = true if AA.RefreshManager then AA.RefreshManager() end end end ---------------------------------------------------------------------- -- data -> display ---------------------------------------------------------------------- function AA.SortedChars() local list = {} for k, c in pairs(AA.db.chars) do table.insert(list, k) end table.sort(list, function(a, b) if a == AA.myKey then return true end if b == AA.myKey then return false end return a < b end) return list end function AA.Entries(onlyCd) local out = {} local nowt = time() for _, k in ipairs(AA.SortedChars()) do local c = AA.db.chars[k] local keys = {} for key, exp in pairs(c.cds or {}) do table.insert(keys, key) end table.sort(keys) if table.getn(keys) > 0 or not onlyCd then table.insert(out, { l = "|cff" .. AA.ClassHex(c.class) .. c.name .. "|r |cff808080" .. c.realm .. "|r", r = "" }) if table.getn(keys) == 0 then table.insert(out, { l = " |cff808080(no cooldowns recorded)|r", r = "" }) end for _, key in ipairs(keys) do local exp = c.cds[key] local rem = exp - nowt local r if exp <= 0 or rem <= 0 then r = "|cff40ff40Ready|r" elseif rem >= AA.DAY then r = "|cffff6060" .. AA.FormatTime(rem) .. "|r" else r = "|cffffd100" .. AA.FormatTime(rem) .. "|r" end table.insert(out, { l = " " .. key, r = r }) end end end return out end