avoid a crash
[QuestHelper.git] / utility.lua
blob0cbe57d8c90cc18def6995c98368038719fc9fdb
1 QuestHelper_File["utility.lua"] = "Development Version"
2 QuestHelper_Loadtime["utility.lua"] = GetTime()
4 QuestHelper = CreateFrame("Frame", "QuestHelper", nil)
6 local default_colour_theme =
7 {message_prefix={0.4, 0.78, 1},
8 message={1, 0.6, 0.2},
9 tooltip={1, 0.8, 0.5},
10 message_highlight={0.73, 1, 0.84},
11 menu_text={1, 1, 1},
12 menu_text_highlight={0, 0, 0},
13 menu={0, 0, 0},
14 menu_highlight={0.3, 0.5, 0.7},
15 menu_title_text={1, 1, 1},
16 menu_title_text_highlight={1, 1, 1},
17 menu_title={0, 0.2, 0.6},
18 menu_title_highlight={0.1, 0.4, 0.8}}
20 local xmas_colour_theme =
21 {message_prefix={0.0, 0.7, 0.0},
22 message={0.2, 1, 0.2},
23 tooltip={0.4, 1, 0.4},
24 message_highlight={1, 0.3, 0.1},
25 menu_text={1, 1, 1},
26 menu_text_highlight={0, 0, 0},
27 menu={0.2, 0, 0},
28 menu_highlight={1, 0.3, 0.3},
29 menu_title_text={0.8, 1, 0.8},
30 menu_title_text_highlight={1, 1, 1},
31 menu_title={0.2, 0.6, 0.2},
32 menu_title_highlight={0.4, 0.7, 0.4}}
34 function QuestHelper:GetColourTheme()
35 if date("%b%d") == "Dec25" then
36 return xmas_colour_theme
37 end
39 return default_colour_theme
40 end
42 QuestHelper.nop = function () end -- Who wouldn't want a function that does nothing?
44 function QuestHelper:HashString(text)
45 -- Computes an Adler-32 checksum.
46 local a, b = 1, 0
47 for i=1,string.len(text) do
48 a = (a+string.byte(text,i))%65521
49 b = (b+a)%65521
50 end
51 return b*65536+a
52 end
54 function QuestHelper:CreateUID(length)
55 local result = ""
56 local characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
57 for k = 1, (math.floor(GetTime() % 1000) + 5) do math.random() end -- it's sort of like seeding. only worse.
58 local base = GetUnitName("player")..":"..GetRealmName()..":"..math.random(0, 2147483647)..":"..GetTime()..":"..time()
60 for c = 1,(length or 32) do
61 local pos = 1+math.floor(self:HashString(result..base..math.random(0, 2147483647))%string.len(characters))
62 result = result .. string.sub(characters, pos, pos)
63 end
65 return result
66 end
68 function QuestHelper:ZoneSanity()
69 local sane = true
71 for c in pairs(self.Astrolabe:GetMapVirtualContinents()) do
72 local pz = self.Astrolabe:GetMapVirtualZones(c)
73 pz[0] = true
74 for z in pairs(pz) do
75 local name
77 if z == 0 then
78 name = self.Astrolabe:GetMapVirtualContinents()[c]
79 else
80 name = self.Astrolabe:GetMapVirtualZones(c)[z]
81 end
83 assert(name)
85 if QuestHelper_Zones[c][z] ~= name then
86 sane = false
87 QuestHelper:TextOut(string.format("'%s' has the wrong ID (should be %d,%d).", name, c, z))
88 end
90 local pair = QuestHelper_ZoneLookup[name]
92 if not pair or c ~= pair[1] or z ~= pair[2] then
93 sane = false
94 QuestHelper:TextOut("ZoneLookup['"..name.."'] maps to wrong pair.")
95 end
97 local index = QuestHelper_IndexLookup[name]
98 if QuestHelper_ZoneLookup[index] ~= pair then
99 sane = false
100 QuestHelper:TextOut("ZoneLookup['"..name.."'] isn't equal to ZoneLookup["..index.."]")
103 if not index or QuestHelper_NameLookup[index] ~= name then
104 sane = false
105 QuestHelper:TextOut("NameLookup["..(index or "???").."'] doesn't equal '"..name.."'")
110 return sane
113 function QuestHelper:TextOut(text)
114 local theme = self:GetColourTheme()
115 DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff%2x%2x%2xQuestHelper: |r%s", theme.message_prefix[1]*255,
116 theme.message_prefix[2]*255,
117 theme.message_prefix[3]*255, text),
118 theme.message[1],
119 theme.message[2],
120 theme.message[3])
123 function QuestHelper:Error(what)
124 --DEFAULT_CHAT_FRAME:AddMessage("QuestHelper Error: "..(what or "Unknown").."\n"..debugstack(2), 1,.5,0)
125 QuestHelper_ErrorCatcher_ExplicitError(true, what or "Unknown", nil, nil)
126 error("Abort!")
129 function QuestHelper:HighlightText(text)
130 local theme = self:GetColourTheme()
131 return string.format("|cff%2x%2x%2x%s|r", theme.message_highlight[1]*255,
132 theme.message_highlight[2]*255,
133 theme.message_highlight[3]*255, text)
136 function QuestHelper:GetUnitID(unit)
137 local id = UnitGUID(unit)
139 if id then
140 return (string.sub(id, 5, 5) == "3") and tonumber(string.sub(id, 6, 12), 16) or nil
143 return nil
146 function QuestHelper:GetQuestID(index)
147 return tonumber(select(3, string.find(GetQuestLink(index), "|Hquest:(%d+):")))
150 -- For future reference:
151 -- Hearthstone = 6948
152 -- Rune of Teleportation = 17031
153 -- Rune of Portals = 17032
155 function QuestHelper:CountItem(item_id)
156 local count = 0
158 for bag = 0,NUM_BAG_SLOTS do
159 for slot = 1,GetContainerNumSlots(bag) do
160 local link = GetContainerItemLink(bag, slot)
161 if link and string.find(link, string.format("|Hitem:%d:", item_id)) then
162 count = count + (select(2, GetContainerItemInfo(bag, slot)) or 0)
167 return count
170 function QuestHelper:ItemCooldown(item_id)
171 local now = GetTime()
172 local cooldown = nil
174 for bag = 0,NUM_BAG_SLOTS do
175 for slot = 1,GetContainerNumSlots(bag) do
176 local link = GetContainerItemLink(bag, slot)
177 if link and string.find(link, string.format("|Hitem:%d:", item_id)) then
178 local s, d, e = GetContainerItemCooldown(bag, slot)
179 if e then
180 if cooldown then
181 cooldown = math.min(cooldown, math.max(0, d-now+s))
182 else
183 cooldown = math.max(0, d-now+s)
185 else
186 return 0
192 return cooldown
195 function QuestHelper:TimeString(seconds)
196 if not seconds then
197 --self:AppendNotificationError("2008-10-8 nil-timestring") -- we're just going to do away with this entirely, the fact is that a lot of this is going to be ripped to shreds soon anyway
198 return "(unknown)"
201 local seconds = math.ceil(seconds)
202 local h, m, s = math.floor(seconds/(60*60)), math.floor(seconds/60)%60, seconds%60
203 if h > 0 then
204 return string.format("|cffffffff%d|r:|cffffffff%02d|r:|cffffffff%02d|r", h, m, s)
205 else
206 return string.format("|cffffffff%d|r:|cffffffff%02d|r", m, s)
210 function QuestHelper:ProgressString(str, pct)
211 if pct > 1 then
212 return string.format("|cff00ff00%s|r", str)
213 elseif pct < 0 then
214 return string.format("|cffff0000%s|r", str)
215 elseif pct > 0.5 then
216 return string.format("|cff%2xff00%s|r", 510-pct*510, str)
217 else
218 return string.format("|cffff%2x00%s|r", pct*510, str)
222 function QuestHelper:PercentString(pct)
223 if pct > 1 then
224 return string.format("|cff00ff00%.1f%%|r", pct*100)
225 elseif pct < 0 then
226 return string.format("|cffff0000%.1f%%|r", pct*100)
227 elseif pct > 0.5 then
228 return string.format("|cff%2xff00%.1f%%|r", 510-pct*510, pct*100)
229 else
230 return string.format("|cffff%2x00%.1f%%|r", pct*510, pct*100)
234 function QuestHelper:PlayerPosition()
235 return self.i, self.x, self.y
238 function QuestHelper:UnitPosition(unit)
239 local c, z, x, y = self.Astrolabe:GetUnitPosition(unit,true)
240 if c then
241 if z == 0 then
242 SetMapToCurrentZone()
243 z = GetCurrentMapZone()
244 if z ~= 0 then
245 x, y = self.Astrolabe:TranslateWorldMapPosition(c, 0, x, y, c, z)
248 return QuestHelper_IndexLookup[c][z], x, y
249 else
250 return self:PlayerPosition()
254 function QuestHelper:PlayerFaction()
255 return UnitFactionGroup("player") == "Alliance" and 1 or 2
258 function QuestHelper:LocationString(i, x, y)
259 return ("[|cffffffff%s|r:|cffffffff%d,%.3f,%.3f|r]"):format(QuestHelper_NameLookup[i] or "nil", i or -7777, x or -7777, y or -7777)
261 function QuestHelper:Location_RawString(delayed, c, z, x, y)
262 return ("[|cffffffff%s/%s,%s,%s,%s|r]"):format(delayed and "D" or "c", c and string.format("%d", c) or tostring(c), z and string.format("%d", z) or tostring(z), x and string.format("%.3f", x) or tostring(x), y and string.format("%.3f", y) or tostring(y))
264 function QuestHelper:Location_AbsoluteString(delayed, c, x, y)
265 return ("[|cffffffff%s/%s,%s,%s|r]"):format(delayed and "D" or "c", c and string.format("%d", c) or tostring(c), x and string.format("%.3f", x) or tostring(x), y and string.format("%.3f", y) or tostring(y))
268 function QuestHelper:Distance(i1, x1, y1, i2, x2, y2)
269 local p1, p2 = QuestHelper_ZoneLookup[i1], QuestHelper_ZoneLookup[i2]
270 return self.Astrolabe:ComputeDistance(p1[1], p1[2], x1, y1, p2[1], p2[2], x2, y2) or 10000
273 function QuestHelper:AppendPosition(list, index, x, y, w, min_dist)
274 if not x or not y or (x == 0 and y == 0) or x <= -0.1 or y <= -0.1 or x >= 1.1 or y >= 1.1 then
275 local nc, nz, nx, ny = self.Astrolabe:GetCurrentPlayerPosition()
276 --self:AppendNotificationError("2008-10-6 nil-position", string.format("nilposition, %s %s %s %s vs %s %s", tostring(nc), tostring(nz), tostring(nx), tostring(ny), tostring(x), tostring(y))) -- We're just not worrying about this too much anymore. Slash and burn.
277 return list -- This isn't a real position.
280 local closest, distance = nil, 0
281 w = w or 1
282 min_dist = min_dist or 200
284 for i, p in ipairs(list) do
285 if index == p[1] then
286 local d = self:Distance(index, x, y, p[1], p[2], p[3])
287 if not closest or d < distance then
288 closest, distance = i, d
293 if closest and distance < min_dist then
294 local p = list[closest]
295 p[2] = (p[2]*p[4]+x*w)/(p[4]+w)
296 p[3] = (p[3]*p[4]+y*w)/(p[4]+w)
297 p[4] = p[4]+w
298 else
299 table.insert(list, {index, x, y, w})
302 return list
305 function QuestHelper:PositionListDistance(list, index, x, y)
306 local closest, distance = nil, 0
307 for i, p in ipairs(list) do
308 local d = self:Distance(index, x, y, p[1], p[2], p[3])
309 if not closest or d < distance then
310 closest, distance = p, d
313 if closest then
314 return distance, closest[1], closest[2], closest[3]
318 function QuestHelper:PositionListDistance2(list, i1, x1, y1, i2, x2, y2)
319 local closest, bd1, bd2, bdt = nil, 0, 0, 0
320 for i, p in ipairs(list) do
321 local d1 = self:Distance(i1, x1, y1, p[1], p[2], p[3])
322 local d2 = self:Distance(i2, x2, y2, p[1], p[2], p[3])
323 local t = d1+d2
324 if not closest or t < bdt then
325 closest, bd1, bd2, bdt = p, d1, d2, t
328 if closest then
329 return d1, d2, closest[1], closest[2], closest[3]
333 function QuestHelper:MergePositions(list1, list2)
334 for i, p in ipairs(list2) do
335 self:AppendPosition(list1, unpack(p))
339 function QuestHelper:MergeDrops(list1, list2)
340 for element, count in pairs(list2) do
341 list1[element] = (list1[element] or 0) + count
345 function QuestHelper: Assert(a, b) -- the space exists so the anti-assert script doesn't find it :D
346 if not a then
347 QuestHelper:Error(b or "Assertion Failed")
351 function QuestHelper:StringizeTable(a)
352 if not a then return "nil" end
353 acu = tostring(self.recycle_tabletyping[a])..": "
354 for i,v in pairs(a) do acu = acu.."["..tostring(i)..","..tostring(v).."] " end
355 return acu
358 function QuestHelper:StringizeTableDouble(a)
359 if not a then return "nil" end
360 acu = tostring(self.recycle_tabletyping[a])..": "
361 for i,v in pairs(a) do acu = acu.."["..self:StringizeTable(i)..","..self:StringizeTable(v).."] " end
362 return acu
365 function QuestHelper:StringizeRecursive(a, d)
366 if not a then return "nil" end
367 if d <= 0 or type(a) ~= "table" then return tostring(a) end
368 acu = tostring(self.recycle_tabletyping[a])..": "
369 for i,v in pairs(a) do acu = acu.."["..self:StringizeRecursive(i, d - 1)..","..self:StringizeRecursive(v, d - 1).."] " end
370 return acu
373 function QuestHelper:TableSize(tbl)
374 local count = 0
375 for k, v in pairs(tbl) do
376 count = count + 1
378 return count
381 function QuestHelper:IsWrath()
382 --return GetBuildInfo():sub(1,1) == '3' or GetBuildInfo() == "0.0.2" -- come on
383 return true -- this had better be true :D
386 function QuestHelper:AppendNotificationError(type, data)
387 local terror = QuestHelper_ErrorPackage(2)
388 terror.data = data
389 QuestHelper_ErrorCatcher_RegisterError(type, terror)
394 function QuestHelper.CreateLoadingCounter()
395 return {
396 MakeSubcategory = function(self, weight)
397 QuestHelper: Assert(not self.percentage)
398 if not self.weighting then self.weighting = {} end
399 local subcat = QuestHelper:CreateLoadingCounter()
400 table.insert(self.weighting, {weight = weight, item = subcat})
401 return subcat
402 end,
403 SetPercentage = function(self, percent)
404 QuestHelper: Assert(not self.weighting)
405 self.percentage = percent
406 end,
407 GetPercentage = function(self)
408 if self.percentage then return self.percentage end
409 if not self.weighting then return 0 end
410 local total_weight = 0
411 local total_value = 0
412 for _, v in ipairs(self.weighting) do
413 total_weight = total_weight + v.weight
414 total_value = total_value + v.weight * v.item:GetPercentage()
416 return total_value / total_weight