-------------------------------------------------- -- BonusScanner v1.1 -- by Crowley -- performance improvements by Archarodim -- -- get the latest version here: -- http://ui.worldofwar.net/ui.php?id=1461 -- http://www.curse-gaming.com/mod.php?addid=2384 -------------------------------------------------- BONUSSCANNER_VERSION = "v1.0"; BONUSSCANNER_PATTERN_SETNAME = "^(.*) %(%d/%d%)$"; BONUSSCANNER_PATTERN_GENERIC_PREFIX = "^%+(%d+)%%?(.*)$"; BONUSSCANNER_PATTERN_GENERIC_SUFFIX = "^(.*)%+(%d+)%%?$"; BonusScanner = { bonuses = {}; bonuses_details = {}; IsUpdating = false; -- not sure if this check is needed but who knows with multithreading... MinCheckInterval = 1; -- Minimum time to wait between each scan CheckIntervalCounter = 0; -- counter, do not change CheckForBonusPlease = 0; -- The flag that when set makes BonusScanner scan the equipment and call the update function ShowDebug = false; -- tells when the equipment is scanned active = nil; temp = { sets = {}, set = "", slot = "", bonuses = {}, details = {} }; types = { "STR", -- strength "AGI", -- agility "STA", -- stamina "INT", -- intellect "SPI", -- spirit "ARMOR", -- reinforced armor (not base armor) "ARCANERES", -- arcane resistance "FIRERES", -- fire resistance "NATURERES", -- nature resistance "FROSTRES", -- frost resistance "SHADOWRES", -- shadow resistance "FISHING", -- fishing skill "MINING", -- mining skill "HERBALISM", -- herbalism skill "SKINNING", -- skinning skill "DEFENSE", -- defense skill "BLOCK", -- chance to block "DODGE", -- chance to dodge "PARRY", -- chance to parry "ATTACKPOWER", -- attack power "ATTACKPOWERUNDEAD", -- attack power against undead "CRIT", -- chance to get a critical strike "RANGEDATTACKPOWER", -- ranged attack power "RANGEDCRIT", -- chance to get a crit with ranged weapons "TOHIT", -- chance to hit "DMG", -- spell damage "DMGUNDEAD", -- spell damage against undead "ARCANEDMG", -- arcane spell damage "FIREDMG", -- fire spell damage "FROSTDMG", -- frost spell damage "HOLYDMG", -- holy spell damage "NATUREDMG", -- nature spell damage "SHADOWDMG", -- shadow spell damage "SPELLCRIT", -- chance to crit with spells "HEAL", -- healing "HOLYCRIT", -- chance to crit with holy spells "SPELLTOHIT", -- Chance to Hit with spells "SPELLPEN", -- amount of spell resist reduction "HEALTHREG", -- health regeneration per 5 sec. "MANAREG", -- mana regeneration per 5 sec. "HEALTH", -- health points "MANA", -- mana points }; slots = { "Head", "Neck", "Shoulder", "Shirt", "Chest", "Waist", "Legs", "Feet", "Wrist", "Hands", "Finger0", "Finger1", "Trinket0", "Trinket1", "Back", "MainHand", "SecondaryHand", "Ranged", "Tabard", }; } -- Update function to hook into. -- Gets called, when Equipment changes (after UNIT_INVENTORY_CHANGED) function BonusScanner_Update() end function BonusScanner:GetBonus(bonus) if(BonusScanner.bonuses[bonus]) then return BonusScanner.bonuses[bonus]; end; return 0; end function BonusScanner:GetSlotBonuses(slotname) local i, bonus, details; local bonuses = {}; for bonus, details in BonusScanner.bonuses_details do if(details[slotname]) then bonuses[bonus] = details[slotname]; end end return bonuses; end function BonusScanner:GetBonusDetails(bonus) if(BonusScanner.bonuses_details[bonus]) then return BonusScanner.bonuses_details[bonus]; end; return {}; end function BonusScanner:GetSlotBonus(bonus, slotname) if(BonusScanner.bonuses_details[bonus]) then if(BonusScanner.bonuses_details[bonus][slotname]) then return BonusScanner.bonuses_details[bonus][slotname]; end; end; return 0; end function BonusScanner:OnLoad() this:RegisterEvent("PLAYER_ENTERING_WORLD"); this:RegisterEvent("PLAYER_LEAVING_WORLD"); end function BonusScanner:OnEvent() BonusScanner:Debug(event); if ((event == "UNIT_INVENTORY_CHANGED") and BonusScanner.active) then BonusScanner.CheckForBonusPlease = 1; return; end if (event == "PLAYER_ENTERING_WORLD") then BonusScanner.active = 1; BonusScanner.CheckForBonusPlease = 1; this:RegisterEvent("UNIT_INVENTORY_CHANGED"); return; end if (event == "PLAYER_LEAVING_WORLD") then this:UnregisterEvent("UNIT_INVENTORY_CHANGED"); return; end end -- A little debug function function BonusScanner:Debug( Message ) if (BonusScanner.ShowDebug) then DEFAULT_CHAT_FRAME:AddMessage("Bonnus-Scanner: " .. Message, 0.5, 0.8, 1); end end -- The use of the *feature* avoid freezes and lags caused by the useless repeated call of BonusScanner:ScanEquipment()... function BonusScanner:OnUpdate (elapsed) -- BonusScanner:Debug(elapsed); if (BonusScanner.IsUpdating) then return; end BonusScanner.IsUpdating = true; -- if the equipment has changed then check if we are allowed to test for bonuses if (BonusScanner.CheckForBonusPlease == 1) then BonusScanner.CheckIntervalCounter = BonusScanner.CheckIntervalCounter + elapsed; -- if we have wait long enough then proceed... if (BonusScanner.CheckIntervalCounter > BonusScanner.MinCheckInterval) then BonusScanner.CheckForBonusPlease = 2; -- means we are currently checking BonusScanner:ScanEquipment(); -- scan the equiped items BonusScanner_Update(); -- call the update function (for the mods using this library) if (BonusScanner.CheckForBonusPlease ~= 1) then -- if no other update has been requested BonusScanner.CheckForBonusPlease = 0; end BonusScanner.CheckIntervalCounter = 0; end end BonusScanner.IsUpdating = false; end function BonusScanner:ScanEquipment() local slotid, slotname, hasItem, i; BonusScannerTooltip:SetOwner(UIParent, "ANCHOR_NONE"); BonusScanner:Debug("Scanning Equipment has requested"); BonusScanner.temp.bonuses = {}; BonusScanner.temp.details = {}; BonusScanner.temp.sets = {}; BonusScanner.temp.set = ""; for i, slotname in BonusScanner.slots do slotid, _ = GetInventorySlotInfo(slotname.. "Slot"); hasItem = BonusScannerTooltip:SetInventoryItem("player", slotid); if ( hasItem ) then BonusScanner.temp.slot = slotname; BonusScanner:ScanTooltip(); -- if set item, mark set as already scanned if(BonusScanner.temp.set ~= "") then BonusScanner.temp.sets[BonusScanner.temp.set] = 1; end; end end BonusScanner.bonuses = BonusScanner.temp.bonuses; BonusScanner.bonuses_details = BonusScanner.temp.details; end function BonusScanner:ScanItem(itemlink) local name = GetItemInfo(itemlink); if(name) then BonusScanner.temp.bonuses = {}; BonusScanner.temp.sets = {}; BonusScanner.temp.set = ""; BonusScanner.temp.slot = ""; BonusScannerTooltip:SetHyperlink(itemlink); BonusScanner:ScanTooltip(); return BonusScanner.temp.bonuses; end return false; end function BonusScanner:ScanTooltip() local tmpTxt, line; local lines = BonusScannerTooltip:NumLines(); for i=2, lines, 1 do tmpText = getglobal("BonusScannerTooltipTextLeft"..i); val = nil; if (tmpText:GetText()) then line = tmpText:GetText(); BonusScanner:ScanLine(line); end end end function BonusScanner:AddValue(effect, value) local i,e; if(type(effect) == "string") then if(BonusScanner.temp.bonuses[effect]) then BonusScanner.temp.bonuses[effect] = BonusScanner.temp.bonuses[effect] + value; else BonusScanner.temp.bonuses[effect] = value; end if(BonusScanner.temp.slot) then if(BonusScanner.temp.details[effect]) then if(BonusScanner.temp.details[effect][BonusScanner.temp.slot]) then BonusScanner.temp.details[effect][BonusScanner.temp.slot] = BonusScanner.temp.details[effect][BonusScanner.temp.slot] + value; else BonusScanner.temp.details[effect][BonusScanner.temp.slot] = value; end else BonusScanner.temp.details[effect] = {}; BonusScanner.temp.details[effect][BonusScanner.temp.slot] = value; end end; else -- list of effects if(type(value) == "table") then for i,e in effect do BonusScanner:AddValue(e, value[i]); end else for i,e in effect do BonusScanner:AddValue(e, value); end end end end; function BonusScanner:ScanLine(line) local tmpStr, found; -- Check for "Equip: " if(string.sub(line,0,string.len(BONUSSCANNER_PREFIX_EQUIP)) == BONUSSCANNER_PREFIX_EQUIP) then tmpStr = string.sub(line,string.len(BONUSSCANNER_PREFIX_EQUIP)+1); BonusScanner:CheckPassive(tmpStr); -- Check for "Set: " elseif(string.sub(line,0,string.len(BONUSSCANNER_PREFIX_SET)) == BONUSSCANNER_PREFIX_SET and BonusScanner.temp.set ~= "" and not BonusScanner.temp.sets[BonusScanner.temp.set]) then tmpStr = string.sub(line,string.len(BONUSSCANNER_PREFIX_SET)+1); BonusScanner.temp.slot = "Set"; BonusScanner:CheckPassive(tmpStr); -- any other line (standard stats, enchantment, set name, etc.) else -- Check for set name _, _, tmpStr = string.find(line, BONUSSCANNER_PATTERN_SETNAME); if(tmpStr) then BonusScanner.temp.set = tmpStr; else found = BonusScanner:CheckGeneric(line); if(not found) then BonusScanner:CheckOther(line); end; end end end; -- Scans passive bonuses like "Set: " and "Equip: " function BonusScanner:CheckPassive(line) local i, p, value, found; found = nil; for i,p in BONUSSCANNER_PATTERNS_PASSIVE do _, _, value = string.find(line, "^" .. p.pattern); if(value) then BonusScanner:AddValue(p.effect, value) found = 1; break; -- prevent duplicated patterns to cause bonuses to be counted several times end end if(not found) then BonusScanner:Debug("\"" .. line .. "\""); BonusScanner:CheckGeneric(line); end end -- Scans generic bonuses like "+3 Intellect" or "Arcane Resistance +4" function BonusScanner:CheckGeneric(line) local value, token, pos, tmpStr, found; -- split line at "/" for enchants with multiple effects found = false; while(string.len(line) > 0) do pos = string.find(line, "/", 1, true); if(pos) then tmpStr = string.sub(line,1,pos-1); line = string.sub(line,pos+1); else tmpStr = line; line = ""; end -- trim line tmpStr = string.gsub( tmpStr, "^%s+", "" ); tmpStr = string.gsub( tmpStr, "%s+$", "" ); tmpStr = string.gsub( tmpStr, "%.$", "" ); _, _, value, token = string.find(tmpStr, BONUSSCANNER_PATTERN_GENERIC_PREFIX); if(not value) then _, _, token, value = string.find(tmpStr, BONUSSCANNER_PATTERN_GENERIC_SUFFIX); end if(token and value) then -- trim token token = string.gsub( token, "^%s+", "" ); token = string.gsub( token, "%s+$", "" ); token = string.gsub( token, "%.$", "" ); if(BonusScanner:CheckToken(token,value)) then found = true; end end end return found; end -- Identifies simple tokens like "Intellect" and composite tokens like "Fire damage" and -- add the value to the respective bonus. -- returns true if some bonus is found function BonusScanner:CheckToken(token, value) local i, p, s1, s2; if(BONUSSCANNER_PATTERNS_GENERIC_LOOKUP[token]) then BonusScanner:AddValue(BONUSSCANNER_PATTERNS_GENERIC_LOOKUP[token], value); return true; else s1 = nil; s2 = nil; for i,p in BONUSSCANNER_PATTERNS_GENERIC_STAGE1 do if(string.find(token,p.pattern,1,1)) then s1 = p.effect; end end for i,p in BONUSSCANNER_PATTERNS_GENERIC_STAGE2 do if(string.find(token,p.pattern,1,1)) then s2 = p.effect; end end if(s1 and s2) then BonusScanner:AddValue(s1..s2, value); return true; end end return false; end -- Last fallback for non generic enchants, like "Mana Regen x per 5 sec." function BonusScanner:CheckOther(line) local i, p, value, start, found; for i,p in BONUSSCANNER_PATTERNS_OTHER do start, _, value = string.find(line, "^" .. p.pattern); if(start) then if(p.value) then BonusScanner:AddValue(p.effect, p.value) elseif(value) then BonusScanner:AddValue(p.effect, value) end return true; end end return false; end -- Slash Command functions function BonusScanner_Cmd(cmd) local _, _, itemlink, itemid = string.find(cmd, "|c%x+|H(item:(%d+):%d+:%d+:%d+)|h%[.-%]|h|r"); if(itemid) then local name = GetItemInfo(itemid); if(name) then DEFAULT_CHAT_FRAME:AddMessage(GREEN_FONT_COLOR_CODE .. "Item bonuses of: " .. HIGHLIGHT_FONT_COLOR_CODE .. name); local bonuses = BonusScanner:ScanItem(itemlink); if(not bonuses) then DEFAULT_CHAT_FRAME:AddMessage(GREEN_FONT_COLOR_CODE .. "error scanning item (probably not cached)"); else BonusScanner:PrintInfo(bonuses); end end return; end if(string.lower(cmd) == "show") then DEFAULT_CHAT_FRAME:AddMessage(GREEN_FONT_COLOR_CODE .. "Current equipment bonuses:"); BonusScanner:PrintInfo(BonusScanner.bonuses); return; end if(string.lower(cmd) == "details") then DEFAULT_CHAT_FRAME:AddMessage(GREEN_FONT_COLOR_CODE .. "Current equipment bonus details:"); BonusScanner:PrintInfoDetailed(); return; end for i, slotname in BonusScanner.slots do if(string.lower(cmd) == string.lower(slotname)) then DEFAULT_CHAT_FRAME:AddMessage(GREEN_FONT_COLOR_CODE .. "Bonuses of '"..LIGHTYELLOW_FONT_COLOR_CODE .. slotname .. GREEN_FONT_COLOR_CODE .. "' slot:"); local bonuses = BonusScanner:GetSlotBonuses(slotname); BonusScanner:PrintInfo(bonuses); return end; end DEFAULT_CHAT_FRAME:AddMessage(GREEN_FONT_COLOR_CODE .. "BonusScanner " .. BONUSSCANNER_VERSION .. " by Crowley"); DEFAULT_CHAT_FRAME:AddMessage(GREEN_FONT_COLOR_CODE .. "/bscan show - shows all bonus of the current equipment"); DEFAULT_CHAT_FRAME:AddMessage(GREEN_FONT_COLOR_CODE .. "/bscan details - shows bonuses with slot distribution"); DEFAULT_CHAT_FRAME:AddMessage(GREEN_FONT_COLOR_CODE .. "/bscan - shows bonuses of linked item (insert link with Shift-Click)"); DEFAULT_CHAT_FRAME:AddMessage(GREEN_FONT_COLOR_CODE .. "/bscan - shows bonuses of given equipment slot"); end SLASH_BONUSSCANNER1 = "/bonusscanner"; SLASH_BONUSSCANNER2 = "/bscan"; SlashCmdList["BONUSSCANNER"] = BonusScanner_Cmd; function BonusScanner:PrintInfoDetailed() local bonus, name, i, j, slot, first, s; for i, bonus in BonusScanner.types do if(BonusScanner.bonuses[bonus]) then first = true; s = "("; for j, slot in BonusScanner.slots do if(BonusScanner.bonuses_details[bonus][slot]) then if(not first) then s = s .. ", "; else first = false; end s = s .. LIGHTYELLOW_FONT_COLOR_CODE .. slot .. HIGHLIGHT_FONT_COLOR_CODE .. ": " .. BonusScanner.bonuses_details[bonus][slot]; end end; if(BonusScanner.bonuses_details[bonus]["Set"]) then if(not first) then s = s .. ", "; end s = s .. LIGHTYELLOW_FONT_COLOR_CODE .. "Set" .. HIGHLIGHT_FONT_COLOR_CODE .. ": " .. BonusScanner.bonuses_details[bonus]["Set"]; end s = s .. ")"; DEFAULT_CHAT_FRAME:AddMessage(GREEN_FONT_COLOR_CODE .. BONUSSCANNER_NAMES[bonus] .. ": " .. HIGHLIGHT_FONT_COLOR_CODE .. BonusScanner.bonuses[bonus] .. " " .. s); end end end function BonusScanner:PrintInfo(bonuses) local bonus, i; for i, bonus in BonusScanner.types do if(bonuses[bonus]) then DEFAULT_CHAT_FRAME:AddMessage(GREEN_FONT_COLOR_CODE .. BONUSSCANNER_NAMES[bonus] .. ": " .. HIGHLIGHT_FONT_COLOR_CODE .. bonuses[bonus]); end end end