--[[ Enchantrix Addon for World of Warcraft(tm). Version: 3.6.1 (Platypus) Revision: $Id: EnxUtil.lua 878 2006-05-28 16:50:50Z aradan $ General utility functions License: This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program(see GLP.txt); if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ]] -- Global functions local isDisenchantable local getReagentInfo local getLinkFromName local getReagentPrice local getItemType local getItemIdFromSig local getItemIdFromLink local getSigFromLink local getRevision local split local spliterator local chatPrint local gcd local roundUp local round local confidenceInterval local createProfiler ------------------------ -- Item functions -- ------------------------ -- Return false if item id can't be disenchanted function isDisenchantable(id) if (id) then local _, _, quality, _, _, _, count, equip = GetItemInfo(id) if (not quality) then -- GetItemInfo() failed, item might be disenchantable return true end if (not Enchantrix.Constants.InventoryTypes[equip]) then -- Neither weapon nor armor return false end if (quality and quality < 2) then -- Low quality return false end if (count and count > 1) then -- Stackable item return false end return true end return false end -- Frontend to GetItemInfo() -- Information for disenchant reagents are kept in a saved variable cache function getReagentInfo(id) local cache = EnchantConfig.cache.reagentinfo if type(id) == "string" then local _, _, i = string.find(id, "item:(%d+):") id = i end id = tonumber(id) local sName, sLink, iQuality, iLevel, sType, sSubtype, iStack, sEquip, sTexture = GetItemInfo(id) if id and Enchantrix.Constants.StaticPrices[id] then if sName then cache[id] = sName.."|"..iQuality.."|"..sTexture cache["t"] = sType elseif type(cache[id]) == "string" then local cdata = split(cache[id], "|") sName = cdata[1] iQuality = tonumber(cdata[2]) iLevel = 0 sType = cache["t"] sSubtype = cache["t"] iStack = 10 sEquip = "" sTexture = cdata[3] sLink = "item:"..id..":0:0:0" end end if sName and id then -- Might as well save this name while we have the data EnchantConfig.cache.names[sName] = "item:"..id..":0:0:0" end return sName, sLink, iQuality, iLevel, sType, sSubtype, iStack, sEquip, sTexture end function getLinkFromName(name) assert(type(name) == "string") if not EnchantConfig.cache then EnchantConfig.cache = {} end if not EnchantConfig.cache.names then EnchantConfig.cache.names = {} end local link = EnchantConfig.cache.names[name] if link then local n = GetItemInfo(link) if n ~= name then EnchantConfig.cache.names[name] = nil end end if not EnchantConfig.cache.names[name] then for i = 1, Enchantrix.State.MAX_ITEM_ID + 4000 do local n, link = GetItemInfo(i) if n then if n == name then EnchantConfig.cache.names[name] = link break end Enchantrix.State.MAX_ITEM_ID = math.max(Enchantrix.State.MAX_ITEM_ID, i) end end end return EnchantConfig.cache.names[name] end -- Returns HSP, median and static price for reagent -- Auctioneer values are kept in cache for 48h in case Auctioneer isn't loaded function getReagentPrice(reagentID) -- reagentID ::= number | hyperlink if type(reagentID) == "string" then local _, _, i = string.find(reagentID, "item:(%d+):") reagentID = i end reagentID = tonumber(reagentID) if not reagentID then return nil end local hsp, median, market market = Enchantrix.Constants.StaticPrices[reagentID] if Enchantrix.State.Auctioneer_Loaded then local itemKey = string.format("%d:0:0", reagentID); local realm = Auctioneer.Util.GetAuctionKey() hsp = Auctioneer.Statistic.GetHSP(itemKey, realm) median = Auctioneer.Statistic.GetUsableMedian(itemKey, realm) end if not EnchantConfig.cache then EnchantConfig.cache = {} end if not EnchantConfig.cache.prices then EnchantConfig.cache.prices = {} end if not EnchantConfig.cache.prices[reagentID] then EnchantConfig.cache.prices[reagentID] = {} end local cache = EnchantConfig.cache.prices[reagentID] if cache.timestamp and time() - cache.timestamp > 172800 then cache = {} end cache.hsp = hsp or cache.hsp cache.median = median or cache.median cache.market = market or cache.market cache.timestamp = time() return cache.hsp, cache.median, cache.market end -- Return item level (rounded up to nearest 5 levels), quality and type as string, -- e.g. "20:2:Armor" for uncommon level 20 armor function getItemType(id) if (id) then local _, _, quality, level, _, _, _, equip = GetItemInfo(id) if (quality and quality >= 2 and level > 0 and Enchantrix.Constants.InventoryTypes[equip]) then return string.format("%d:%d:%s", Enchantrix.Util.RoundUp(level, 5), quality, Enchantrix.Constants.InventoryTypes[equip]) end end end -- Return item id as integer function getItemIdFromSig(sig) if type(sig) == "string" then _, _, sig = string.find(sig, "(%d+)") end return tonumber(sig) end function getItemIdFromLink(link) return (EnhTooltip.BreakLink(link)) end function getSigFromLink(link) assert(type(link) == "string") local _, _, id, rand = string.find(link, "item:(%d+):%d+:(%d+):%d+") if id and rand then return id..":0:"..rand end end ----------------------------------- -- General Utility Functions -- ----------------------------------- -- Extract the revision number from SVN keyword string function getRevision(str) if not str then return 0 end local _, _, rev = string.find(str, "Revision: (%d+)") return tonumber(rev) or 0 end function split(str, at) local splut = {}; if (type(str) ~= "string") then return nil end if (not str) then str = "" end if (not at) then table.insert(splut, str) else for n, c in string.gfind(str, '([^%'..at..']*)(%'..at..'?)') do table.insert(splut, n); if (c == '') then break end end end return splut; end -- Iterator version of split() -- for i in spliterator(a, b) do -- is equivalent to -- for _, i in ipairs(split(a, b)) do -- but puts less strain on the garbage collector function spliterator(str, at) local start local found = 0 local done = (type(str) ~= "string") return function() if done then return nil end start = found + 1 found = string.find(str, at, start, true) if not found then found = 0 done = true end return string.sub(str, start, found - 1) end end function chatPrint(text, cRed, cGreen, cBlue, cAlpha, holdTime) local frameIndex = Enchantrix.Config.GetFrameIndex(); if (cRed and cGreen and cBlue) then if getglobal("ChatFrame"..frameIndex) then getglobal("ChatFrame"..frameIndex):AddMessage(text, cRed, cGreen, cBlue, cAlpha, holdTime); elseif (DEFAULT_CHAT_FRAME) then DEFAULT_CHAT_FRAME:AddMessage(text, cRed, cGreen, cBlue, cAlpha, holdTime); end else if getglobal("ChatFrame"..frameIndex) then getglobal("ChatFrame"..frameIndex):AddMessage(text, 1.0, 0.5, 0.25); elseif (DEFAULT_CHAT_FRAME) then DEFAULT_CHAT_FRAME:AddMessage(text, 1.0, 0.5, 0.25); end end end ------------------------ -- Math Functions -- ------------------------ function gcd(a, b) -- Greatest Common Divisor, Euclidean algorithm local m, n = tonumber(a), tonumber(b) or 0 while (n ~= 0) do m, n = n, math.mod(m, n) end return m end -- Round up m to nearest multiple of n function roundUp(m, n) return math.ceil(m / n) * n end -- Round m to n digits in given base function round(m, n, base, offset) base = base or 10 -- Default to base 10 offset = offset or 0.5 if (n or 0) == 0 then return math.floor(m + offset) end if m == 0 then return 0 elseif m < 0 then return -round(-m, n, base, offset) end -- Get integer and fractional part of n local f = math.floor(n) n, f = f, n - f -- Pre-rounding multiplier is 1 / f local mul = 1 if f > 0.1 then mul = math.floor(1 / f + 0.5) end local d if n > 0 then d = base^(n - math.floor(math.log(m) / math.log(base)) - 1) else d = 1 end if offset >= 1 then return math.ceil(m * d * mul) / (d * mul) else return math.floor(m * d * mul + offset) / (d * mul) end end -- Returns confidence interval for binomial distribution given observed -- probability p, sample size n, and z-value function confidenceInterval(p, n, z) if not z then --[[ z conf 1.282 80% 1.645 90% 1.960 95% 2.326 98% 2.576 99% 3.090 99.8% 3.291 99.9% ]] z = 1.645 end assert(p >= 0 and p <= 1) assert(n > 0) local a = p + z^2 / (2 * n) local b = z * math.sqrt(p * (1 - p) / n + z^2 / (4 * n^2)) local c = 1 + z^2 / n return (a - b) / c, (a + b) / c end --------------------- -- Debug functions -- --------------------- -- profiler:Start() -- Record start time and memory, set state to running local function _profilerStart(this) this.t = GetTime() this.m = gcinfo() this.r = true end -- profiler:Stop() -- Record time and memory change, set state to stopped local function _profilerStop(this) this.m = (gcinfo()) - this.m this.t = GetTime() - this.t this.r = false end -- profiler:DebugPrint() local function _profilerDebugPrint(this) if this.n then EnhTooltip.DebugPrint("Profiler ["..this.n.."]") else EnhTooltip.DebugPrint("Profiler") end if this.r == nil then EnhTooltip.DebugPrint(" Not started") else EnhTooltip.DebugPrint(string.format(" Time: %0.3f s", this:Time())) EnhTooltip.DebugPrint(string.format(" Mem: %0.0f KiB", this:Mem())) if this.r then EnhTooltip.DebugPrint(" Running...") end end end -- time = profiler:Time() -- Return time (in seconds) from Start() [until Stop(), if stopped] local function _profilerTime(this) if this.r == false then return this.t elseif this.r == true then return GetTime() - this.t end end -- mem = profiler:Mem() -- Return memory change (in kilobytes) from Start() [until Stop(), if stopped] local function _profilerMem(this) if this.r == false then return this.m elseif this.r == true then return (gcinfo()) - this.m end end -- profiler = Enchantrix.Util.CreateProfiler("foobar") function createProfiler(name) return { Start = _profilerStart, Stop = _profilerStop, DebugPrint = _profilerDebugPrint, Time = _profilerTime, Mem = _profilerMem, n = name, } end Enchantrix.Util = { Revision = "$Revision: 878 $", IsDisenchantable = isDisenchantable, GetReagentInfo = getReagentInfo, GetLinkFromName = getLinkFromName, GetReagentPrice = getReagentPrice, GetItemType = getItemType, GetItemIdFromSig = getItemIdFromSig, GetItemIdFromLink = getItemIdFromLink, GetSigFromLink = getSigFromLink, SigFromLink = sigFromLink, GetRevision = getRevision, Split = split, Spliterator = spliterator, ChatPrint = chatPrint, GCD = gcd, RoundUp = roundUp, Round = round, ConfidenceInterval = confidenceInterval, CreateProfiler = createProfiler, }