vanilla-wow-addons – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | --[[ |
2 | Enchantrix Addon for World of Warcraft(tm). |
||
3 | Version: 3.6.1 (Platypus) |
||
4 | Revision: $Id: EnxUtil.lua 878 2006-05-28 16:50:50Z aradan $ |
||
5 | |||
6 | General utility functions |
||
7 | |||
8 | License: |
||
9 | This program is free software; you can redistribute it and/or |
||
10 | modify it under the terms of the GNU General Public License |
||
11 | as published by the Free Software Foundation; either version 2 |
||
12 | of the License, or (at your option) any later version. |
||
13 | |||
14 | This program is distributed in the hope that it will be useful, |
||
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
17 | GNU General Public License for more details. |
||
18 | |||
19 | You should have received a copy of the GNU General Public License |
||
20 | along with this program(see GLP.txt); if not, write to the Free Software |
||
21 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
22 | ]] |
||
23 | |||
24 | -- Global functions |
||
25 | local isDisenchantable |
||
26 | local getReagentInfo |
||
27 | local getLinkFromName |
||
28 | local getReagentPrice |
||
29 | local getItemType |
||
30 | local getItemIdFromSig |
||
31 | local getItemIdFromLink |
||
32 | local getSigFromLink |
||
33 | |||
34 | local getRevision |
||
35 | local split |
||
36 | local spliterator |
||
37 | local chatPrint |
||
38 | |||
39 | local gcd |
||
40 | local roundUp |
||
41 | local round |
||
42 | local confidenceInterval |
||
43 | |||
44 | local createProfiler |
||
45 | |||
46 | ------------------------ |
||
47 | -- Item functions -- |
||
48 | ------------------------ |
||
49 | |||
50 | -- Return false if item id can't be disenchanted |
||
51 | function isDisenchantable(id) |
||
52 | if (id) then |
||
53 | local _, _, quality, _, _, _, count, equip = GetItemInfo(id) |
||
54 | if (not quality) then |
||
55 | -- GetItemInfo() failed, item might be disenchantable |
||
56 | return true |
||
57 | end |
||
58 | if (not Enchantrix.Constants.InventoryTypes[equip]) then |
||
59 | -- Neither weapon nor armor |
||
60 | return false |
||
61 | end |
||
62 | if (quality and quality < 2) then |
||
63 | -- Low quality |
||
64 | return false |
||
65 | end |
||
66 | if (count and count > 1) then |
||
67 | -- Stackable item |
||
68 | return false |
||
69 | end |
||
70 | return true |
||
71 | end |
||
72 | return false |
||
73 | end |
||
74 | |||
75 | -- Frontend to GetItemInfo() |
||
76 | -- Information for disenchant reagents are kept in a saved variable cache |
||
77 | function getReagentInfo(id) |
||
78 | local cache = EnchantConfig.cache.reagentinfo |
||
79 | |||
80 | if type(id) == "string" then |
||
81 | local _, _, i = string.find(id, "item:(%d+):") |
||
82 | id = i |
||
83 | end |
||
84 | id = tonumber(id) |
||
85 | |||
86 | local sName, sLink, iQuality, iLevel, sType, sSubtype, iStack, sEquip, sTexture = GetItemInfo(id) |
||
87 | if id and Enchantrix.Constants.StaticPrices[id] then |
||
88 | if sName then |
||
89 | cache[id] = sName.."|"..iQuality.."|"..sTexture |
||
90 | cache["t"] = sType |
||
91 | elseif type(cache[id]) == "string" then |
||
92 | local cdata = split(cache[id], "|") |
||
93 | |||
94 | sName = cdata[1] |
||
95 | iQuality = tonumber(cdata[2]) |
||
96 | iLevel = 0 |
||
97 | sType = cache["t"] |
||
98 | sSubtype = cache["t"] |
||
99 | iStack = 10 |
||
100 | sEquip = "" |
||
101 | sTexture = cdata[3] |
||
102 | sLink = "item:"..id..":0:0:0" |
||
103 | end |
||
104 | end |
||
105 | |||
106 | if sName and id then |
||
107 | -- Might as well save this name while we have the data |
||
108 | EnchantConfig.cache.names[sName] = "item:"..id..":0:0:0" |
||
109 | end |
||
110 | |||
111 | return sName, sLink, iQuality, iLevel, sType, sSubtype, iStack, sEquip, sTexture |
||
112 | end |
||
113 | |||
114 | function getLinkFromName(name) |
||
115 | assert(type(name) == "string") |
||
116 | |||
117 | if not EnchantConfig.cache then |
||
118 | EnchantConfig.cache = {} |
||
119 | end |
||
120 | if not EnchantConfig.cache.names then |
||
121 | EnchantConfig.cache.names = {} |
||
122 | end |
||
123 | |||
124 | local link = EnchantConfig.cache.names[name] |
||
125 | if link then |
||
126 | local n = GetItemInfo(link) |
||
127 | if n ~= name then |
||
128 | EnchantConfig.cache.names[name] = nil |
||
129 | end |
||
130 | end |
||
131 | if not EnchantConfig.cache.names[name] then |
||
132 | for i = 1, Enchantrix.State.MAX_ITEM_ID + 4000 do |
||
133 | local n, link = GetItemInfo(i) |
||
134 | if n then |
||
135 | if n == name then |
||
136 | EnchantConfig.cache.names[name] = link |
||
137 | break |
||
138 | end |
||
139 | Enchantrix.State.MAX_ITEM_ID = math.max(Enchantrix.State.MAX_ITEM_ID, i) |
||
140 | end |
||
141 | end |
||
142 | end |
||
143 | return EnchantConfig.cache.names[name] |
||
144 | end |
||
145 | |||
146 | -- Returns HSP, median and static price for reagent |
||
147 | -- Auctioneer values are kept in cache for 48h in case Auctioneer isn't loaded |
||
148 | function getReagentPrice(reagentID) |
||
149 | -- reagentID ::= number | hyperlink |
||
150 | if type(reagentID) == "string" then |
||
151 | local _, _, i = string.find(reagentID, "item:(%d+):") |
||
152 | reagentID = i |
||
153 | end |
||
154 | reagentID = tonumber(reagentID) |
||
155 | if not reagentID then return nil end |
||
156 | |||
157 | local hsp, median, market |
||
158 | |||
159 | market = Enchantrix.Constants.StaticPrices[reagentID] |
||
160 | |||
161 | if Enchantrix.State.Auctioneer_Loaded then |
||
162 | local itemKey = string.format("%d:0:0", reagentID); |
||
163 | local realm = Auctioneer.Util.GetAuctionKey() |
||
164 | hsp = Auctioneer.Statistic.GetHSP(itemKey, realm) |
||
165 | median = Auctioneer.Statistic.GetUsableMedian(itemKey, realm) |
||
166 | end |
||
167 | |||
168 | if not EnchantConfig.cache then EnchantConfig.cache = {} end |
||
169 | if not EnchantConfig.cache.prices then EnchantConfig.cache.prices = {} end |
||
170 | if not EnchantConfig.cache.prices[reagentID] then EnchantConfig.cache.prices[reagentID] = {} end |
||
171 | local cache = EnchantConfig.cache.prices[reagentID] |
||
172 | if cache.timestamp and time() - cache.timestamp > 172800 then |
||
173 | cache = {} |
||
174 | end |
||
175 | |||
176 | cache.hsp = hsp or cache.hsp |
||
177 | cache.median = median or cache.median |
||
178 | cache.market = market or cache.market |
||
179 | cache.timestamp = time() |
||
180 | |||
181 | return cache.hsp, cache.median, cache.market |
||
182 | end |
||
183 | |||
184 | -- Return item level (rounded up to nearest 5 levels), quality and type as string, |
||
185 | -- e.g. "20:2:Armor" for uncommon level 20 armor |
||
186 | function getItemType(id) |
||
187 | if (id) then |
||
188 | local _, _, quality, level, _, _, _, equip = GetItemInfo(id) |
||
189 | if (quality and quality >= 2 and level > 0 and Enchantrix.Constants.InventoryTypes[equip]) then |
||
190 | return string.format("%d:%d:%s", Enchantrix.Util.RoundUp(level, 5), quality, Enchantrix.Constants.InventoryTypes[equip]) |
||
191 | end |
||
192 | end |
||
193 | end |
||
194 | |||
195 | -- Return item id as integer |
||
196 | function getItemIdFromSig(sig) |
||
197 | if type(sig) == "string" then |
||
198 | _, _, sig = string.find(sig, "(%d+)") |
||
199 | end |
||
200 | return tonumber(sig) |
||
201 | end |
||
202 | |||
203 | function getItemIdFromLink(link) |
||
204 | return (EnhTooltip.BreakLink(link)) |
||
205 | end |
||
206 | |||
207 | function getSigFromLink(link) |
||
208 | assert(type(link) == "string") |
||
209 | |||
210 | local _, _, id, rand = string.find(link, "item:(%d+):%d+:(%d+):%d+") |
||
211 | if id and rand then |
||
212 | return id..":0:"..rand |
||
213 | end |
||
214 | end |
||
215 | |||
216 | ----------------------------------- |
||
217 | -- General Utility Functions -- |
||
218 | ----------------------------------- |
||
219 | |||
220 | -- Extract the revision number from SVN keyword string |
||
221 | function getRevision(str) |
||
222 | if not str then return 0 end |
||
223 | local _, _, rev = string.find(str, "Revision: (%d+)") |
||
224 | return tonumber(rev) or 0 |
||
225 | end |
||
226 | |||
227 | function split(str, at) |
||
228 | local splut = {}; |
||
229 | |||
230 | if (type(str) ~= "string") then return nil end |
||
231 | if (not str) then str = "" end |
||
232 | |||
233 | if (not at) |
||
234 | then table.insert(splut, str) |
||
235 | |||
236 | else |
||
237 | for n, c in string.gfind(str, '([^%'..at..']*)(%'..at..'?)') do |
||
238 | table.insert(splut, n); |
||
239 | |||
240 | if (c == '') then break end |
||
241 | end |
||
242 | end |
||
243 | return splut; |
||
244 | end |
||
245 | |||
246 | -- Iterator version of split() |
||
247 | -- for i in spliterator(a, b) do |
||
248 | -- is equivalent to |
||
249 | -- for _, i in ipairs(split(a, b)) do |
||
250 | -- but puts less strain on the garbage collector |
||
251 | function spliterator(str, at) |
||
252 | local start |
||
253 | local found = 0 |
||
254 | local done = (type(str) ~= "string") |
||
255 | return function() |
||
256 | if done then return nil end |
||
257 | start = found + 1 |
||
258 | found = string.find(str, at, start, true) |
||
259 | if not found then |
||
260 | found = 0 |
||
261 | done = true |
||
262 | end |
||
263 | return string.sub(str, start, found - 1) |
||
264 | end |
||
265 | end |
||
266 | |||
267 | function chatPrint(text, cRed, cGreen, cBlue, cAlpha, holdTime) |
||
268 | local frameIndex = Enchantrix.Config.GetFrameIndex(); |
||
269 | |||
270 | if (cRed and cGreen and cBlue) then |
||
271 | if getglobal("ChatFrame"..frameIndex) then |
||
272 | getglobal("ChatFrame"..frameIndex):AddMessage(text, cRed, cGreen, cBlue, cAlpha, holdTime); |
||
273 | |||
274 | elseif (DEFAULT_CHAT_FRAME) then |
||
275 | DEFAULT_CHAT_FRAME:AddMessage(text, cRed, cGreen, cBlue, cAlpha, holdTime); |
||
276 | end |
||
277 | |||
278 | else |
||
279 | if getglobal("ChatFrame"..frameIndex) then |
||
280 | getglobal("ChatFrame"..frameIndex):AddMessage(text, 1.0, 0.5, 0.25); |
||
281 | elseif (DEFAULT_CHAT_FRAME) then |
||
282 | DEFAULT_CHAT_FRAME:AddMessage(text, 1.0, 0.5, 0.25); |
||
283 | end |
||
284 | end |
||
285 | end |
||
286 | |||
287 | |||
288 | ------------------------ |
||
289 | -- Math Functions -- |
||
290 | ------------------------ |
||
291 | |||
292 | function gcd(a, b) |
||
293 | -- Greatest Common Divisor, Euclidean algorithm |
||
294 | local m, n = tonumber(a), tonumber(b) or 0 |
||
295 | while (n ~= 0) do |
||
296 | m, n = n, math.mod(m, n) |
||
297 | end |
||
298 | return m |
||
299 | end |
||
300 | |||
301 | -- Round up m to nearest multiple of n |
||
302 | function roundUp(m, n) |
||
303 | return math.ceil(m / n) * n |
||
304 | end |
||
305 | |||
306 | -- Round m to n digits in given base |
||
307 | function round(m, n, base, offset) |
||
308 | base = base or 10 -- Default to base 10 |
||
309 | offset = offset or 0.5 |
||
310 | |||
311 | if (n or 0) == 0 then |
||
312 | return math.floor(m + offset) |
||
313 | end |
||
314 | |||
315 | if m == 0 then |
||
316 | return 0 |
||
317 | elseif m < 0 then |
||
318 | return -round(-m, n, base, offset) |
||
319 | end |
||
320 | |||
321 | -- Get integer and fractional part of n |
||
322 | local f = math.floor(n) |
||
323 | n, f = f, n - f |
||
324 | |||
325 | -- Pre-rounding multiplier is 1 / f |
||
326 | local mul = 1 |
||
327 | if f > 0.1 then |
||
328 | mul = math.floor(1 / f + 0.5) |
||
329 | end |
||
330 | |||
331 | local d |
||
332 | if n > 0 then |
||
333 | d = base^(n - math.floor(math.log(m) / math.log(base)) - 1) |
||
334 | else |
||
335 | d = 1 |
||
336 | end |
||
337 | if offset >= 1 then |
||
338 | return math.ceil(m * d * mul) / (d * mul) |
||
339 | else |
||
340 | return math.floor(m * d * mul + offset) / (d * mul) |
||
341 | end |
||
342 | end |
||
343 | |||
344 | -- Returns confidence interval for binomial distribution given observed |
||
345 | -- probability p, sample size n, and z-value |
||
346 | function confidenceInterval(p, n, z) |
||
347 | if not z then |
||
348 | --[[ |
||
349 | z conf |
||
350 | 1.282 80% |
||
351 | 1.645 90% |
||
352 | 1.960 95% |
||
353 | 2.326 98% |
||
354 | 2.576 99% |
||
355 | 3.090 99.8% |
||
356 | 3.291 99.9% |
||
357 | ]] |
||
358 | z = 1.645 |
||
359 | end |
||
360 | assert(p >= 0 and p <= 1) |
||
361 | assert(n > 0) |
||
362 | |||
363 | local a = p + z^2 / (2 * n) |
||
364 | local b = z * math.sqrt(p * (1 - p) / n + z^2 / (4 * n^2)) |
||
365 | local c = 1 + z^2 / n |
||
366 | |||
367 | return (a - b) / c, (a + b) / c |
||
368 | end |
||
369 | |||
370 | --------------------- |
||
371 | -- Debug functions -- |
||
372 | --------------------- |
||
373 | |||
374 | -- profiler:Start() |
||
375 | -- Record start time and memory, set state to running |
||
376 | local function _profilerStart(this) |
||
377 | this.t = GetTime() |
||
378 | this.m = gcinfo() |
||
379 | this.r = true |
||
380 | end |
||
381 | |||
382 | -- profiler:Stop() |
||
383 | -- Record time and memory change, set state to stopped |
||
384 | local function _profilerStop(this) |
||
385 | this.m = (gcinfo()) - this.m |
||
386 | this.t = GetTime() - this.t |
||
387 | this.r = false |
||
388 | end |
||
389 | |||
390 | -- profiler:DebugPrint() |
||
391 | local function _profilerDebugPrint(this) |
||
392 | if this.n then |
||
393 | EnhTooltip.DebugPrint("Profiler ["..this.n.."]") |
||
394 | else |
||
395 | EnhTooltip.DebugPrint("Profiler") |
||
396 | end |
||
397 | if this.r == nil then |
||
398 | EnhTooltip.DebugPrint(" Not started") |
||
399 | else |
||
400 | EnhTooltip.DebugPrint(string.format(" Time: %0.3f s", this:Time())) |
||
401 | EnhTooltip.DebugPrint(string.format(" Mem: %0.0f KiB", this:Mem())) |
||
402 | if this.r then |
||
403 | EnhTooltip.DebugPrint(" Running...") |
||
404 | end |
||
405 | end |
||
406 | end |
||
407 | |||
408 | -- time = profiler:Time() |
||
409 | -- Return time (in seconds) from Start() [until Stop(), if stopped] |
||
410 | local function _profilerTime(this) |
||
411 | if this.r == false then |
||
412 | return this.t |
||
413 | elseif this.r == true then |
||
414 | return GetTime() - this.t |
||
415 | end |
||
416 | end |
||
417 | |||
418 | -- mem = profiler:Mem() |
||
419 | -- Return memory change (in kilobytes) from Start() [until Stop(), if stopped] |
||
420 | local function _profilerMem(this) |
||
421 | if this.r == false then |
||
422 | return this.m |
||
423 | elseif this.r == true then |
||
424 | return (gcinfo()) - this.m |
||
425 | end |
||
426 | end |
||
427 | |||
428 | -- profiler = Enchantrix.Util.CreateProfiler("foobar") |
||
429 | function createProfiler(name) |
||
430 | return { |
||
431 | Start = _profilerStart, |
||
432 | Stop = _profilerStop, |
||
433 | DebugPrint = _profilerDebugPrint, |
||
434 | Time = _profilerTime, |
||
435 | Mem = _profilerMem, |
||
436 | n = name, |
||
437 | } |
||
438 | end |
||
439 | |||
440 | Enchantrix.Util = { |
||
441 | Revision = "$Revision: 878 $", |
||
442 | |||
443 | IsDisenchantable = isDisenchantable, |
||
444 | GetReagentInfo = getReagentInfo, |
||
445 | GetLinkFromName = getLinkFromName, |
||
446 | GetReagentPrice = getReagentPrice, |
||
447 | GetItemType = getItemType, |
||
448 | GetItemIdFromSig = getItemIdFromSig, |
||
449 | GetItemIdFromLink = getItemIdFromLink, |
||
450 | GetSigFromLink = getSigFromLink, |
||
451 | SigFromLink = sigFromLink, |
||
452 | |||
453 | GetRevision = getRevision, |
||
454 | Split = split, |
||
455 | Spliterator = spliterator, |
||
456 | ChatPrint = chatPrint, |
||
457 | |||
458 | GCD = gcd, |
||
459 | RoundUp = roundUp, |
||
460 | Round = round, |
||
461 | ConfidenceInterval = confidenceInterval, |
||
462 | |||
463 | CreateProfiler = createProfiler, |
||
464 | } |