Mercurial > wow > ouroloot
comparison core.lua @ 1:822b6ca3ef89
Import of 2.15, moving to wowace svn.
author | Farmbuyer of US-Kilrogg <farmbuyer@gmail.com> |
---|---|
date | Sat, 16 Apr 2011 06:03:29 +0000 |
parents | |
children | fe437e761ef8 |
comparison
equal
deleted
inserted
replaced
0:0f14a1e5364d | 1:822b6ca3ef89 |
---|---|
1 local addon = select(2,...) | |
2 | |
3 --[==[ | |
4 g_loot's numeric indices are loot entries (including titles, separators, | |
5 etc); its named indices are: | |
6 - forum: saved text from forum markup window, default nil | |
7 - attend: saved text from raid attendence window, default nil | |
8 - printed.FOO: last index formatted into text window FOO, default 0 | |
9 - saved: table of copies of saved texts, default nil; keys are numeric | |
10 indices of tables, subkeys of those are name/forum/attend/date | |
11 - autoshard: optional name of disenchanting player, default nil | |
12 - threshold: optional loot threshold, default nil | |
13 | |
14 Functions arranged like this, with these lables (for jumping to). As a | |
15 rule, member functions with UpperCamelCase names are called directly by | |
16 user-facing code, ones with lowercase names are "one step removed", and | |
17 names with leading underscores are strictly internal helper functions. | |
18 ------ Saved variables | |
19 ------ Constants | |
20 ------ Addon member data | |
21 ------ Locals | |
22 ------ Expiring caches | |
23 ------ Ace3 framework stuff | |
24 ------ Event handlers | |
25 ------ Slash command handler | |
26 ------ On/off | |
27 ------ Behind the scenes routines | |
28 ------ Saved texts | |
29 ------ Loot histories | |
30 ------ Player communication | |
31 | |
32 This started off as part of a raid addon package written by somebody else. | |
33 After he retired, I began modifying the code. Eventually I set aside the | |
34 entire package and rewrote the loot tracker module from scratch. Many of the | |
35 variable/function naming conventions (sv_*, g_*, and family) stayed across the | |
36 rewrite. Some variables are needlessly initialized to nil just to look uniform. | |
37 | |
38 ]==] | |
39 | |
40 ------ Saved variables | |
41 OuroLootSV = nil -- possible copy of g_loot | |
42 OuroLootSV_opts = nil -- same as option_defaults until changed | |
43 OuroLootSV_hist = nil | |
44 | |
45 | |
46 ------ Constants | |
47 local option_defaults = { | |
48 ['popup_on_join'] = true, | |
49 ['register_slashloot'] = true, | |
50 ['scroll_to_bottom'] = true, | |
51 ['chatty_on_kill'] = false, | |
52 ['no_tracking_wipes'] = false, | |
53 ['snarky_boss'] = true, | |
54 ['keybinding'] = false, | |
55 ['keybinding_text'] = 'CTRL-SHIFT-O', | |
56 ['forum'] = { | |
57 ['[url]'] = '[url=http://www.wowhead.com/?item=$I]$N[/url]$X - $T', | |
58 ['[item] by name'] = '[item]$N[/item]$X - $T', | |
59 ['[item] by ID'] = '[item]$I[/item]$X - $T', | |
60 ['Custom...'] = '', | |
61 }, | |
62 ['forum_current'] = '[item] by name', | |
63 } | |
64 local virgin = "First time loaded? Hi! Use the /ouroloot or /loot command" | |
65 .." to show the main display. You should probably browse the instructions" | |
66 .." if you've never used this before; %s to display the help window. This" | |
67 .." welcome message will not intrude again." | |
68 local qualnames = { | |
69 ['gray'] = 0, ['grey'] = 0, ['poor'] = 0, ['trash'] = 0, | |
70 ['white'] = 1, ['common'] = 1, | |
71 ['green'] = 2, ['uncommon'] = 2, | |
72 ['blue'] = 3, ['rare'] = 3, | |
73 ['epic'] = 4, ['purple'] = 4, | |
74 ['legendary'] = 5, ['orange'] = 5, | |
75 ['artifact'] = 6, | |
76 --['heirloom'] = 7, | |
77 } | |
78 local my_name = UnitName('player') | |
79 local comm_cleanup_ttl = 5 -- seconds in the cache | |
80 | |
81 | |
82 ------ Addon member data | |
83 local flib = LibStub("LibFarmbuyer") | |
84 addon.author_debug = flib.author_debug | |
85 | |
86 -- Play cute games with namespaces here just to save typing. | |
87 do local _G = _G setfenv (1, addon) | |
88 | |
89 revision = 15 | |
90 ident = "OuroLoot2" | |
91 identTg = "OuroLoot2Tg" | |
92 status_text = nil | |
93 | |
94 DEBUG_PRINT = false | |
95 debug = { | |
96 comm = false, | |
97 loot = false, | |
98 flow = false, | |
99 notraid = false, | |
100 cache = false, | |
101 } | |
102 function dprint (t,...) | |
103 if DEBUG_PRINT and debug[t] then return _G.print("<"..t.."> ",...) end | |
104 end | |
105 | |
106 if author_debug then | |
107 function pprint(t,...) | |
108 return _G.print("<<"..t..">> ",...) | |
109 end | |
110 else | |
111 pprint = flib.nullfunc | |
112 end | |
113 | |
114 enabled = false | |
115 rebroadcast = false | |
116 display = nil -- display frame, when visible | |
117 loot_clean = nil -- index of last GUI entry with known-current visual data | |
118 sender_list = {active={},names={}} -- this should be reworked | |
119 threshold = debug.loot and 0 or 3 -- rare by default | |
120 sharder = nil -- name of person whose loot is marked as shards | |
121 | |
122 -- The rest is also used in the GUI: | |
123 | |
124 popped = nil -- non-nil when reminder has been shown, actual value unimportant | |
125 | |
126 -- This is an amalgamation of all four LOOT_ITEM_* patterns. | |
127 -- Captures: 1 person/You, 2 itemstring, 3 rest of string after final |r until '.' | |
128 -- Can change 'loot' to 'item' to trigger on, e.g., extracting stuff from mail. | |
129 loot_pattern = "(%S+) receives? loot:.*|cff%x+|H(.-)|h.*|r(.*)%.$" | |
130 | |
131 dbm_registered = nil | |
132 requesting = nil -- for prompting for additional rebroadcasters | |
133 | |
134 thresholds, quality_hexes = {}, {} | |
135 for i = 0,6 do | |
136 local hex = _G.select(4,_G.GetItemQualityColor(i)) | |
137 local desc = _G["ITEM_QUALITY"..i.."_DESC"] | |
138 quality_hexes[i] = hex | |
139 thresholds[i] = hex .. desc .. "|r" | |
140 end | |
141 | |
142 _G.setfenv (1, _G) | |
143 end | |
144 | |
145 addon = LibStub("AceAddon-3.0"):NewAddon(addon, "Ouro Loot", | |
146 "AceTimer-3.0", "AceComm-3.0", "AceConsole-3.0", "AceEvent-3.0") | |
147 | |
148 | |
149 ------ Locals | |
150 local g_loot = nil | |
151 local g_restore_p = nil | |
152 local g_saved_tmp = nil -- restoring across a clear | |
153 local g_wafer_thin = nil -- for prompting for additional rebroadcasters | |
154 local g_today = nil -- "today" entry in g_loot | |
155 local opts = nil | |
156 | |
157 local pairs, ipairs, tinsert, tremove, tonumber = pairs, ipairs, table.insert, table.remove, tonumber | |
158 | |
159 local pprint, tabledump = addon.pprint, flib.tabledump | |
160 | |
161 -- En masse forward decls of symbols defined inside local blocks | |
162 local _registerDBM -- break out into separate file | |
163 local makedate, create_new_cache, _init | |
164 | |
165 -- Hypertext support, inspired by DBM broadcast pizza timers | |
166 do | |
167 local hypertext_format_str = "|HOuroRaid:%s|h%s[%s]|r|h" | |
168 | |
169 function addon.format_hypertext (code, text, color) | |
170 return hypertext_format_str:format (code, | |
171 type(color)=='number' and addon.quality_hexes[color] or color, | |
172 text) | |
173 end | |
174 | |
175 DEFAULT_CHAT_FRAME:HookScript("OnHyperlinkClick", function(self, link, string, mousebutton) | |
176 local ltype, arg = strsplit(":",link) | |
177 if ltype ~= "OuroRaid" then return end | |
178 if arg == 'openloot' then | |
179 addon:BuildMainDisplay() | |
180 elseif arg == 'help' then | |
181 addon:BuildMainDisplay('help') | |
182 elseif arg == 'bcaston' then | |
183 if not addon.rebroadcast then | |
184 addon:Activate(nil,true) | |
185 end | |
186 addon:broadcast('bcast_responder') | |
187 elseif arg == 'waferthin' then -- mint? it's wafer thin! | |
188 g_wafer_thin = true -- fuck off, I'm full | |
189 addon:broadcast('bcast_denied') -- remove once tested | |
190 end | |
191 end) | |
192 | |
193 local old = ItemRefTooltip.SetHyperlink | |
194 function ItemRefTooltip:SetHyperlink (link, ...) | |
195 if link:match("^OuroRaid") then return end | |
196 return old (self, link, ...) | |
197 end | |
198 end | |
199 | |
200 do | |
201 -- copied here because it's declared local to the calendar ui, thanks blizz >< | |
202 local CALENDAR_FULLDATE_MONTH_NAMES = { | |
203 FULLDATE_MONTH_JANUARY, FULLDATE_MONTH_FEBRUARY, FULLDATE_MONTH_MARCH, | |
204 FULLDATE_MONTH_APRIL, FULLDATE_MONTH_MAY, FULLDATE_MONTH_JUNE, | |
205 FULLDATE_MONTH_JULY, FULLDATE_MONTH_AUGUST, FULLDATE_MONTH_SEPTEMBER, | |
206 FULLDATE_MONTH_OCTOBER, FULLDATE_MONTH_NOVEMBER, FULLDATE_MONTH_DECEMBER, | |
207 } | |
208 -- returns "dd Month yyyy", mm, dd, yyyy | |
209 function makedate() | |
210 Calendar_LoadUI() | |
211 local _, M, D, Y = CalendarGetDate() | |
212 local text = ("%d %s %d"):format(D, CALENDAR_FULLDATE_MONTH_NAMES[M], Y) | |
213 return text, M, D, Y | |
214 end | |
215 end | |
216 | |
217 -- Returns an instance name or abbreviation | |
218 local function instance_tag() | |
219 local name, typeof, diffcode, diffstr, _, perbossheroic, isdynamic = GetInstanceInfo() | |
220 local t | |
221 name = addon.instance_abbrev[name] or name | |
222 if typeof == "none" then return name end | |
223 -- diffstr is "5 Player", "10 Player (Heroic)", etc. ugh. | |
224 if diffcode == 1 then | |
225 t = ((GetNumRaidMembers()>0) and "10" or "5") | |
226 elseif diffcode == 2 then | |
227 t = ((GetNumRaidMembers()>0) and "25" or "5h") | |
228 elseif diffcode == 3 then | |
229 t = "10h" | |
230 elseif diffcode == 4 then | |
231 t = "25h" | |
232 end | |
233 -- dynamic difficulties always return normal "codes" | |
234 if isdynamic and perbossheroic == 1 then | |
235 t = t .. "h" | |
236 end | |
237 return name .. "(" .. t .. ")" | |
238 end | |
239 addon.instance_tag = instance_tag -- grumble | |
240 | |
241 | |
242 ------ Expiring caches | |
243 --[[ | |
244 foo = create_new_cache("myfoo",15[,cleanup]) -- ttl | |
245 foo:add("blah") | |
246 foo:test("blah") -- returns true | |
247 ]] | |
248 do | |
249 local caches = {} | |
250 local cleanup_group = AnimTimerFrame:CreateAnimationGroup() | |
251 cleanup_group:SetLooping("REPEAT") | |
252 cleanup_group:SetScript("OnLoop", function(cg) | |
253 addon.dprint('cache',"OnLoop firing") | |
254 local now = GetTime() | |
255 local alldone = true | |
256 -- this is ass-ugly | |
257 for _,c in ipairs(caches) do | |
258 while (#c > 0) and (now - c[1].t > c.ttl) do | |
259 addon.dprint('cache', c.name, "cache removing",c[1].t, c[1].m) | |
260 tremove(c,1) | |
261 end | |
262 alldone = alldone and (#c == 0) | |
263 end | |
264 if alldone then | |
265 addon.dprint('cache',"OnLoop finishing animation group") | |
266 cleanup_group:Finish() | |
267 for _,c in ipairs(caches) do | |
268 if c.func then c:func() end | |
269 end | |
270 end | |
271 addon.dprint('cache',"OnLoop done") | |
272 end) | |
273 | |
274 local function _add (cache, x) | |
275 tinsert(cache, {t=GetTime(),m=x}) | |
276 if not cleanup_group:IsPlaying() then | |
277 addon.dprint('cache', cache.name, "STARTING animation group") | |
278 cache.cleanup:SetDuration(2) -- hmmm | |
279 cleanup_group:Play() | |
280 end | |
281 end | |
282 local function _test (cache, x) | |
283 for _,v in ipairs(cache) do | |
284 if v.m == x then return true end | |
285 end | |
286 end | |
287 function create_new_cache (name, ttl, on_alldone) | |
288 local c = { | |
289 ttl = ttl, | |
290 name = name, | |
291 add = _add, | |
292 test = _test, | |
293 cleanup = cleanup_group:CreateAnimation("Animation"), | |
294 func = on_alldone, | |
295 } | |
296 c.cleanup:SetOrder(1) | |
297 -- setting OnFinished for cleanup fires at the end of each inner loop, | |
298 -- with no 'requested' argument to distinguish cases. thus, on_alldone. | |
299 tinsert (caches, c) | |
300 return c | |
301 end | |
302 end | |
303 | |
304 | |
305 ------ Ace3 framework stuff | |
306 function addon:OnInitialize() | |
307 -- VARIABLES_LOADED has fired by this point; test if we're doing something like | |
308 -- relogging during a raid and already have collected loot data | |
309 g_restore_p = OuroLootSV ~= nil | |
310 self.dprint('flow', "oninit sets restore as", g_restore_p) | |
311 | |
312 if OuroLootSV_opts == nil then | |
313 OuroLootSV_opts = {} | |
314 self:ScheduleTimer(function(s) | |
315 s:Print(virgin, s.format_hypertext('help',"click here",ITEM_QUALITY_UNCOMMON)) | |
316 virgin = nil | |
317 end,10,self) | |
318 end | |
319 opts = OuroLootSV_opts | |
320 for opt,default in pairs(option_defaults) do | |
321 if opts[opt] == nil then | |
322 opts[opt] = default | |
323 end | |
324 end | |
325 option_defaults = nil | |
326 -- transition/remove old options | |
327 opts["forum_use_itemid"] = nil | |
328 if opts["forum_format"] then | |
329 opts.forum["Custom..."] = opts["forum_format"] | |
330 opts["forum_format"] = nil | |
331 end | |
332 -- get item filter table if needed | |
333 if opts.itemfilter == nil then | |
334 opts.itemfilter = addon.default_itemfilter | |
335 end | |
336 addon.default_itemfilter = nil | |
337 | |
338 self:RegisterChatCommand("ouroloot", "OnSlash") | |
339 -- maybe try to detect if this command is already in use... | |
340 if opts.register_slashloot then | |
341 SLASH_ACECONSOLE_OUROLOOT2 = "/loot" | |
342 end | |
343 | |
344 self.history_all = self.history_all or OuroLootSV_hist or {} | |
345 local r = GetRealmName() | |
346 self.history_all[r] = self:_prep_new_history_category (self.history_all[r], r) | |
347 self.history = self.history_all[r] | |
348 | |
349 _init(self) | |
350 self.OnInitialize = nil | |
351 end | |
352 | |
353 function addon:OnEnable() | |
354 self:RegisterEvent "PLAYER_LOGOUT" | |
355 self:RegisterEvent "RAID_ROSTER_UPDATE" | |
356 | |
357 -- Cribbed from Talented. I like the way jerry thinks: the first argument | |
358 -- can be a format spec for the remainder of the arguments. (The new | |
359 -- AceConsole:Printf isn't used because we can't specify a prefix without | |
360 -- jumping through ridonkulous hoops.) The part about overriding :Print | |
361 -- with a version using prefix hyperlinks is my fault. | |
362 do | |
363 local AC = LibStub("AceConsole-3.0") | |
364 local chat_prefix = self.format_hypertext('openloot',"Ouro Loot",--[[legendary]]5) | |
365 function addon:Print (str, ...) | |
366 if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then | |
367 return AC:Print (chat_prefix, str:format(...)) | |
368 else | |
369 return AC:Print (chat_prefix, str, ...) | |
370 end | |
371 end | |
372 end | |
373 | |
374 if opts.keybinding then | |
375 local btn = CreateFrame("Button", "OuroLootBindingOpen", nil, "SecureActionButtonTemplate") | |
376 btn:SetAttribute("type", "macro") | |
377 btn:SetAttribute("macrotext", "/ouroloot toggle") | |
378 if SetBindingClick(opts.keybinding_text, "OuroLootBindingOpen") then | |
379 SaveBindings(GetCurrentBindingSet()) | |
380 else | |
381 self:Print("Error registering '%s' as a keybinding, check spelling!", | |
382 opts.keybinding_text) | |
383 end | |
384 end | |
385 | |
386 if self.debug.flow then self:Print"is in control-flow debug mode." end | |
387 end | |
388 --function addon:OnDisable() end | |
389 | |
390 | |
391 ------ Event handlers | |
392 function addon:_clear_SVs() | |
393 g_loot = {} -- not saved, just fooling PLAYER_LOGOUT tests | |
394 OuroLootSV = nil | |
395 OuroLootSV_opts = nil | |
396 OuroLootSV_hist = nil | |
397 end | |
398 function addon:PLAYER_LOGOUT() | |
399 if (#g_loot > 0) or g_loot.saved | |
400 or (g_loot.forum and g_loot.forum ~= "") | |
401 or (g_loot.attend and g_loot.attend ~= "") | |
402 then | |
403 g_loot.autoshard = self.sharder | |
404 g_loot.threshold = self.threshold | |
405 --OuroLootSV = g_loot | |
406 --for i,e in ipairs(OuroLootSV) do | |
407 for i,e in ipairs(g_loot) do | |
408 e.cols = nil | |
409 end | |
410 OuroLootSV = g_loot | |
411 end | |
412 self.history.kind = nil | |
413 self.history.st = nil | |
414 self.history.byname = nil | |
415 OuroLootSV_hist = self.history_all | |
416 end | |
417 | |
418 function addon:RAID_ROSTER_UPDATE (event) | |
419 if GetNumRaidMembers() > 0 then | |
420 local inside,whatkind = IsInInstance() | |
421 if inside and (whatkind == "pvp" or whatkind == "arena") then | |
422 return self.dprint('flow', "got RRU event but in pvp zone, bailing") | |
423 end | |
424 if event == "Activate" then | |
425 -- dispatched manually from Activate | |
426 self:RegisterEvent "CHAT_MSG_LOOT" | |
427 _registerDBM(self) | |
428 elseif event == "RAID_ROSTER_UPDATE" then | |
429 -- event registration from onload, joined a raid, maybe show popup | |
430 if opts.popup_on_join and not self.popped then | |
431 self.popped = StaticPopup_Show "OUROL_REMIND" | |
432 self.popped.data = self | |
433 end | |
434 end | |
435 else | |
436 self:UnregisterEvent "CHAT_MSG_LOOT" | |
437 self.popped = nil | |
438 end | |
439 end | |
440 | |
441 -- helper for CHAT_MSG_LOOT handler | |
442 do | |
443 -- Recent loot cache | |
444 addon.recent_loot = create_new_cache ('loot', comm_cleanup_ttl) | |
445 | |
446 local GetItemInfo = GetItemInfo | |
447 | |
448 -- 'from' and onwards only present if this is triggered by a broadcast | |
449 function addon:_do_loot (local_override, recipient, itemid, count, from, extratext) | |
450 local iname, ilink, iquality, _,_,_,_,_,_, itexture = GetItemInfo(itemid) | |
451 if not iname then return end -- sigh | |
452 self.dprint('loot',">>_do_loot, R:", recipient, "I:", itemid, "C:", count, "frm:", from, "ex:", extratext) | |
453 | |
454 local i | |
455 itemid = tonumber(ilink:match("item:(%d+)")) | |
456 if local_override or ((iquality >= self.threshold) and not opts.itemfilter[itemid]) then | |
457 if (self.rebroadcast and (not from)) and not local_override then | |
458 self:broadcast('loot', recipient, itemid, count) | |
459 end | |
460 if self.enabled or local_override then | |
461 local signature = recipient .. iname .. (count or "") | |
462 if self.recent_loot:test(signature) then | |
463 self.dprint('cache', "loot <",signature,"> already in cache, skipping") | |
464 else | |
465 self.recent_loot:add(signature) | |
466 i = self._addLootEntry{ -- There is some redundancy here... | |
467 kind = 'loot', | |
468 person = recipient, | |
469 person_class= select(2,UnitClass(recipient)), | |
470 quality = iquality, | |
471 itemname = iname, | |
472 id = itemid, | |
473 itemlink = ilink, | |
474 itexture = itexture, | |
475 disposition = (recipient == self.sharder) and 'shard' or nil, | |
476 count = count, | |
477 bcast_from = from, | |
478 extratext = extratext, | |
479 is_heroic = self:is_heroic_item(ilink), | |
480 } | |
481 self.dprint('loot', "added entry", i) | |
482 self:_addHistoryEntry(i) | |
483 if self.display then | |
484 self:redisplay() | |
485 --[[ | |
486 local st = self.display:GetUserData("eoiST") | |
487 if st and st.frame:IsVisible() then | |
488 st:OuroLoot_Refresh() | |
489 end | |
490 ]] | |
491 end | |
492 end | |
493 end | |
494 end | |
495 self.dprint('loot',"<<_do_loot out") | |
496 return i | |
497 end | |
498 | |
499 function addon:CHAT_MSG_LOOT (event, ...) | |
500 if (not self.rebroadcast) and (not self.enabled) and (event ~= "manual") then return end | |
501 | |
502 --[[ | |
503 iname: Hearthstone | |
504 iquality: integer | |
505 ilink: clickable formatted link | |
506 itemstring: item:6948:.... | |
507 itexture: inventory icon texture | |
508 ]] | |
509 | |
510 if event == "CHAT_MSG_LOOT" then | |
511 local msg = ... | |
512 --ChatFrame2:AddMessage("original string: >"..(msg:gsub("\124","\124\124")).."<") | |
513 local person, itemstring, remainder = msg:match(self.loot_pattern) | |
514 self.dprint('loot', "CHAT_MSG_LOOT, person is", person, ", itemstring is", itemstring, ", rest is", remainder) | |
515 if not person then return end -- "So-and-So selected Greed", etc, not actual looting | |
516 local count = remainder and remainder:match(".*(x%d+)$") | |
517 | |
518 -- Name might be colorized, remove the highlighting | |
519 local p = person:match("|c%x%x%x%x%x%x%x%x(%S+)") | |
520 person = p or person | |
521 person = (person == UNIT_YOU) and my_name or person | |
522 | |
523 local id = tonumber((select(2, strsplit(":", itemstring)))) | |
524 | |
525 return self:_do_loot (false, person, id, count) | |
526 | |
527 elseif event == "broadcast" then | |
528 return self:_do_loot(false, ...) | |
529 | |
530 elseif event == "manual" then | |
531 local r,i,n = ... | |
532 return self:_do_loot(true, r,i,nil,nil,n) | |
533 end | |
534 end | |
535 end | |
536 | |
537 | |
538 ------ Slash command handler | |
539 -- Thought about breaking this up into a table-driven dispatcher. But | |
540 -- that would result in a pile of teensy functions, most of which would | |
541 -- never be called. Too much overhead. (2.0: Most of these removed now | |
542 -- that GUI is in place.) | |
543 function addon:OnSlash (txt) --, editbox) | |
544 txt = strtrim(txt:lower()) | |
545 local cmd, arg = "" | |
546 do | |
547 local s,e = txt:find("^%a+") | |
548 if s then | |
549 cmd = txt:sub(s,e) | |
550 s = txt:find("%S", e+2) | |
551 if s then arg = txt:sub(s,-1) end | |
552 end | |
553 end | |
554 | |
555 if cmd == "" then | |
556 if InCombatLockdown() then | |
557 return self:Print("Can't display window in combat.") | |
558 else | |
559 return self:BuildMainDisplay() | |
560 end | |
561 | |
562 elseif cmd:find("^thre") then | |
563 self:SetThreshold(arg) | |
564 | |
565 elseif cmd == "on" then self:Activate(arg) | |
566 elseif cmd == "off" then self:Deactivate() | |
567 elseif cmd == "broadcast" or cmd == "bcast" then self:Activate(nil,true) | |
568 | |
569 elseif cmd == "fake" then -- maybe comment this out for real users | |
570 self:_mark_boss_kill (self._addLootEntry{ | |
571 kind='boss',reason='kill',bosskill="Baron Steamroller",instance=instance_tag(),duration=0 | |
572 }) | |
573 self:CHAT_MSG_LOOT ('manual', my_name, 54797) | |
574 if self.display then | |
575 self:redisplay() | |
576 end | |
577 self:Print "Baron Steamroller has been slain. Congratulations on your rug." | |
578 | |
579 elseif cmd == "debug" then | |
580 if arg then | |
581 self.debug[arg] = not self.debug[arg] | |
582 _G.print(arg,self.debug[arg]) | |
583 if self.debug[arg] then self.DEBUG_PRINT = true end | |
584 else | |
585 self.DEBUG_PRINT = not self.DEBUG_PRINT | |
586 end | |
587 | |
588 elseif cmd == "save" and arg and arg:len() > 0 then | |
589 self:save_saveas(arg) | |
590 elseif cmd == "list" then | |
591 self:save_list() | |
592 elseif cmd == "restore" and arg and arg:len() > 0 then | |
593 self:save_restore(tonumber(arg)) | |
594 elseif cmd == "delete" and arg and arg:len() > 0 then | |
595 self:save_delete(tonumber(arg)) | |
596 | |
597 elseif cmd == "help" then | |
598 self:BuildMainDisplay('help') | |
599 elseif cmd == "toggle" then | |
600 if self.display then | |
601 self.display:Hide() | |
602 else | |
603 return self:BuildMainDisplay() | |
604 end | |
605 | |
606 else | |
607 if self:OpenMainDisplayToTab(cmd) then | |
608 return | |
609 end | |
610 self:Print("Unknown command '%s'. %s to see the help window.", | |
611 cmd, self.format_hypertext('help',"Click here",ITEM_QUALITY_UNCOMMON)) | |
612 end | |
613 end | |
614 | |
615 function addon:SetThreshold (arg, quiet_p) | |
616 local q = tonumber(arg) | |
617 if q then | |
618 q = math.floor(q+0.001) | |
619 if q<0 or q>6 then | |
620 return self:Print("Threshold must be 0-6.") | |
621 end | |
622 else | |
623 q = qualnames[arg] | |
624 if not q then | |
625 return self:Print("Unrecognized item quality argument.") | |
626 end | |
627 end | |
628 self.threshold = q | |
629 if not quiet_p then self:Print("Threshold now set to %s.", self.thresholds[q]) end | |
630 end | |
631 | |
632 | |
633 ------ On/off | |
634 function addon:Activate (opt_threshold, opt_bcast_only) | |
635 self:RegisterEvent "RAID_ROSTER_UPDATE" | |
636 self.popped = true | |
637 if GetNumRaidMembers() > 0 then | |
638 self:RAID_ROSTER_UPDATE("Activate") | |
639 elseif self.debug.notraid then | |
640 self:RegisterEvent "CHAT_MSG_LOOT" | |
641 _registerDBM(self) | |
642 elseif g_restore_p then | |
643 g_restore_p = nil | |
644 if #g_loot == 0 then return end -- only saved texts, not worth verbage | |
645 self:Print("Ouro Raid Loot restored previous data, but not in a raid", | |
646 "and 5-person mode not active. |cffff0505NOT tracking loot|r;", | |
647 "use 'enable' to activate loot tracking, or 'clear' to erase", | |
648 "previous data, or 'help' to read about saved-texts commands.") | |
649 self.popped = nil -- get the reminder if later joining a raid | |
650 return | |
651 end | |
652 self.rebroadcast = true -- hardcode to true; this used to be more complicated | |
653 self.enabled = not opt_bcast_only | |
654 if opt_threshold then | |
655 self:SetThreshold (opt_threshold, --[[quiet_p=]]true) | |
656 end | |
657 self:Print("Ouro Raid Loot is %s. Threshold currently %s.", | |
658 self.enabled and "tracking" or "only broadcasting", | |
659 self.thresholds[self.threshold]) | |
660 end | |
661 | |
662 -- Note: running '/loot off' will also avoid the popup reminder when | |
663 -- joining a raid, but will not change the saved option setting. | |
664 function addon:Deactivate() | |
665 self.enabled = false | |
666 self.rebroadcast = false | |
667 self:UnregisterEvent "RAID_ROSTER_UPDATE" | |
668 self:UnregisterEvent "CHAT_MSG_LOOT" | |
669 self:Print("Ouro Raid Loot deactivated.") | |
670 end | |
671 | |
672 function addon:Clear(verbose_p) | |
673 local repopup, st | |
674 if self.display then | |
675 -- in the new version, this is likely to always be the case | |
676 repopup = true | |
677 st = self.display:GetUserData("eoiST") | |
678 if not st then | |
679 self.dprint('flow', "Clear: display visible but eoiST not set??") | |
680 end | |
681 self.display:Hide() | |
682 end | |
683 g_restore_p = nil | |
684 OuroLootSV = nil | |
685 self:_reset_timestamps() | |
686 g_saved_tmp = g_loot.saved | |
687 if verbose_p then | |
688 if (g_saved_tmp and #g_saved_tmp>0) then | |
689 self:Print("Current loot data cleared, %d saved sets remaining.", #g_saved_tmp) | |
690 else | |
691 self:Print("Current loot data cleared.") | |
692 end | |
693 end | |
694 _init(self,st) | |
695 if repopup then | |
696 addon:BuildMainDisplay() | |
697 end | |
698 end | |
699 | |
700 | |
701 ------ Behind the scenes routines | |
702 -- Adds indices to traverse the tables in a nice sorted order. | |
703 do | |
704 local byindex, temp = {}, {} | |
705 local function sort (src, dest) | |
706 for k in pairs(src) do | |
707 temp[#temp+1] = k | |
708 end | |
709 table.sort(temp) | |
710 table.wipe(dest) | |
711 for i = 1, #temp do | |
712 dest[i] = src[temp[i]] | |
713 end | |
714 end | |
715 | |
716 function addon.sender_list.sort() | |
717 sort (addon.sender_list.active, byindex) | |
718 table.wipe(temp) | |
719 addon.sender_list.activeI = #byindex | |
720 sort (addon.sender_list.names, byindex) | |
721 table.wipe(temp) | |
722 end | |
723 addon.sender_list.namesI = byindex | |
724 end | |
725 | |
726 -- Message sending. | |
727 -- See OCR_funcs.tag at the end of this file for incoming message treatment. | |
728 do | |
729 local function assemble(...) | |
730 local msg = ... | |
731 for i = 2, select('#',...) do | |
732 msg = msg .. '\a' .. (select(i,...) or "") | |
733 end | |
734 return msg | |
735 end | |
736 | |
737 -- broadcast('tag', <stuff>) | |
738 function addon:broadcast(...) | |
739 local msg = assemble(...) | |
740 self.dprint('comm', "<broadcast>:", msg) | |
741 -- the "GUILD" here is just so that we can also pick up on it | |
742 self:SendCommMessage(self.ident, msg, self.debug.comm and "GUILD" or "RAID") | |
743 end | |
744 -- whispercast(<to>, 'tag', <stuff>) | |
745 function addon:whispercast(to,...) | |
746 local msg = assemble(...) | |
747 self.dprint('comm', "<whispercast>@", to, ":", msg) | |
748 self:SendCommMessage(self.identTg, msg, "WHISPER", to) | |
749 end | |
750 end | |
751 | |
752 -- Generic helpers | |
753 function addon._find_next_after (kind, index) | |
754 index = index + 1 | |
755 while index <= #g_loot do | |
756 if g_loot[index].kind == kind then | |
757 return index, g_loot[index] | |
758 end | |
759 index = index + 1 | |
760 end | |
761 end | |
762 | |
763 -- Iterate through g_loot entries according to the KIND field. Loop variables | |
764 -- are g_loot indices and the corresponding entries (essentially ipairs + some | |
765 -- conditionals). | |
766 function addon:filtered_loot_iter (filter_kind) | |
767 return self._find_next_after, filter_kind, 0 | |
768 end | |
769 | |
770 do | |
771 local itt | |
772 local function create() | |
773 local tip, lefts = CreateFrame("GameTooltip"), {} | |
774 for i = 1, 2 do -- scanning idea here also snagged from Talented | |
775 local L,R = tip:CreateFontString(), tip:CreateFontString() | |
776 L:SetFontObject(GameFontNormal) | |
777 R:SetFontObject(GameFontNormal) | |
778 tip:AddFontStrings(L,R) | |
779 lefts[i] = L | |
780 end | |
781 tip.lefts = lefts | |
782 return tip | |
783 end | |
784 function addon:is_heroic_item(item) -- returns true or *nil* | |
785 itt = itt or create() | |
786 itt:SetOwner(UIParent,"ANCHOR_NONE") | |
787 itt:ClearLines() | |
788 itt:SetHyperlink(item) | |
789 local t = itt.lefts[2]:GetText() | |
790 itt:Hide() | |
791 return (t == ITEM_HEROIC) or nil | |
792 end | |
793 end | |
794 | |
795 -- Called when first loading up, and then also when a 'clear' is being | |
796 -- performed. If SV's are present then restore_p will be true. | |
797 function _init (self, possible_st) | |
798 self.dprint('flow',"_init running") | |
799 self.loot_clean = nil | |
800 self.hist_clean = nil | |
801 if g_restore_p then | |
802 g_loot = OuroLootSV | |
803 self.popped = true | |
804 self.dprint('flow', "restoring", #g_loot, "entries") | |
805 self:ScheduleTimer("Activate", 8, g_loot.threshold) | |
806 -- FIXME printed could be too large if entries were deleted, how much do we care? | |
807 self.sharder = g_loot.autoshard | |
808 else | |
809 g_loot = { printed = {} } | |
810 g_loot.saved = g_saved_tmp; g_saved_tmp = nil -- potentially restore across a clear | |
811 end | |
812 | |
813 self.threshold = g_loot.threshold or self.threshold -- in the case of restoring but not tracking | |
814 self:gui_init(g_loot) | |
815 | |
816 if g_restore_p then | |
817 self:zero_printed_fenceposts() -- g_loot.printed.* = previous/safe values | |
818 else | |
819 self:zero_printed_fenceposts(0) -- g_loot.printed.* = 0 | |
820 end | |
821 if possible_st then | |
822 possible_st:SetData(g_loot) | |
823 end | |
824 | |
825 self.status_text = ("v2r%d communicating as ident %s"):format(self.revision,self.ident) | |
826 self:RegisterComm(self.ident) | |
827 self:RegisterComm(self.identTg, "OnCommReceivedNocache") | |
828 | |
829 if self.author_debug then | |
830 _G.OL = self | |
831 _G.Oloot = g_loot | |
832 end | |
833 end | |
834 | |
835 -- Tie-ins with Deadly Boss Mods | |
836 do | |
837 local candidates, location | |
838 local function fixup_durations (cache) | |
839 if candidates == nil then return end -- this is called for *all* cache expirations, including non-boss | |
840 local boss, bossi | |
841 boss = candidates[1] | |
842 if #candidates == 1 then | |
843 -- (1) or (2) | |
844 boss.duration = boss.duration or 0 | |
845 addon.dprint('loot', "only one candidate") | |
846 else | |
847 -- (3), should only be one 'cast entry and our local entry | |
848 if #candidates ~= 2 then | |
849 -- could get a bunch of 'cast entries on the heels of one another | |
850 -- before the local one ever fires, apparently... sigh | |
851 --addon:Print("<warning> s3 cache has %d entries, does that seem right to you?", #candidates) | |
852 end | |
853 if candidates[2].duration == nil then | |
854 --addon:Print("<warning> s3's second entry is not the local trigger, does that seem right to you?") | |
855 end | |
856 -- try and be generic anyhow | |
857 for i,c in ipairs(candidates) do | |
858 if c.duration then | |
859 boss = c | |
860 addon.dprint('loot', "fixup found candidate", i, "duration", c.duration) | |
861 break | |
862 end | |
863 end | |
864 end | |
865 bossi = addon._addLootEntry(boss) | |
866 addon.dprint('loot', "added entry", bossi) | |
867 if boss.reason == 'kill' then | |
868 addon:_mark_boss_kill (bossi) | |
869 if opts.chatty_on_kill then | |
870 addon:Print("Registered kill for '%s' in %s!", boss.bosskill, boss.instance) | |
871 end | |
872 end | |
873 candidates = nil | |
874 end | |
875 addon.recent_boss = create_new_cache ('boss', 10, fixup_durations) | |
876 | |
877 -- Similar to _do_loot, but duration+ parms only present when locally generated. | |
878 local function _do_boss (self, reason, bossname, intag, duration, raiders) | |
879 self.dprint('loot',">>_do_boss, R:", reason, "B:", bossname, "T:", intag, | |
880 "D:", duration, "RL:", (raiders and #raiders or 'nil')) | |
881 if self.rebroadcast and duration then | |
882 self:broadcast('boss', reason, bossname, intag) | |
883 end | |
884 -- This is only a loop to make jumping out of it easy, and still do cleanup below. | |
885 while self.enabled do | |
886 if reason == 'wipe' and opts.no_tracking_wipes then break end | |
887 bossname = (opts.snarky_boss and self.boss_abbrev[bossname] or bossname) or bossname | |
888 local not_from_local = duration == nil | |
889 local signature = bossname .. reason | |
890 if not_from_local and self.recent_boss:test(signature) then | |
891 self.dprint('cache', "boss <",signature,"> already in cache, skipping") | |
892 else | |
893 self.recent_boss:add(signature) | |
894 -- Possible scenarios: (1) we don't see a boss event at all (e.g., we're | |
895 -- outside the instance) and so this only happens once as a non-local event, | |
896 -- (2) we see a local event first and all non-local events are filtered | |
897 -- by the cache, (3) we happen to get some non-local events before doing | |
898 -- our local event (not because of network weirdness but because our local | |
899 -- DBM might not trigger for a while). | |
900 local c = { | |
901 kind = 'boss', | |
902 bosskill = bossname, -- minor misnomer, might not actually be a kill | |
903 reason = reason, | |
904 instance = intag, | |
905 duration = duration, -- these two deliberately may be nil | |
906 raiderlist = raiders and table.concat(raiders, ", ") | |
907 } | |
908 candidates = candidates or {} | |
909 tinsert(candidates,c) | |
910 break | |
911 end | |
912 end | |
913 self.dprint('loot',"<<_do_boss out") | |
914 end | |
915 -- No wrapping layer for now | |
916 addon.on_boss_broadcast = _do_boss | |
917 | |
918 function addon:_mark_boss_kill (index) | |
919 local e = g_loot[index] | |
920 if not e.bosskill then | |
921 return self:Print("Something horribly wrong;", index, "is not a boss entry!") | |
922 end | |
923 if e.reason ~= 'wipe' then | |
924 -- enh, bail | |
925 self.loot_clean = index-1 | |
926 end | |
927 local attempts = 1 | |
928 local first | |
929 | |
930 local i,d = 1,g_loot[1] | |
931 while d ~= e do | |
932 if d.bosskill and | |
933 d.bosskill == e.bosskill and | |
934 d.reason == 'wipe' | |
935 then | |
936 first = first or i | |
937 attempts = attempts + 1 | |
938 assert(tremove(g_loot,i)==d,"_mark_boss_kill screwed up data badly") | |
939 else | |
940 i = i + 1 | |
941 end | |
942 d = g_loot[i] | |
943 end | |
944 e.reason = 'kill' | |
945 e.attempts = attempts | |
946 self.loot_clean = first or index-1 | |
947 end | |
948 | |
949 local GetRaidRosterInfo = GetRaidRosterInfo | |
950 function addon:DBMBossCallback (reason, mod, ...) | |
951 if (not self.rebroadcast) and (not self.enabled) then return end | |
952 | |
953 local name | |
954 if mod.combatInfo and mod.combatInfo.name then | |
955 name = mod.combatInfo.name | |
956 elseif mod.id then | |
957 name = mod.id | |
958 else | |
959 name = "Unknown Boss" | |
960 end | |
961 | |
962 local it = location or instance_tag() | |
963 location = nil | |
964 | |
965 local duration = 0 | |
966 if mod.combatInfo and mod.combatInfo.pull then | |
967 duration = math.floor (GetTime() - mod.combatInfo.pull) | |
968 end | |
969 | |
970 -- attendance: maybe put people in groups 6,7,8 into a "backup/standby" | |
971 -- list? probably too specific to guild practices. | |
972 local raiders = {} | |
973 for i = 1, GetNumRaidMembers() do | |
974 tinsert(raiders, (GetRaidRosterInfo(i))) | |
975 end | |
976 table.sort(raiders) | |
977 | |
978 return _do_boss (self, reason, name, it, duration, raiders) | |
979 end | |
980 | |
981 local callback = function(...) addon:DBMBossCallback(...) end | |
982 function _registerDBM(self) | |
983 if DBM then | |
984 if not self.dbm_registered then | |
985 local rev = tonumber(DBM.Revision) or 0 | |
986 if rev < 1503 then | |
987 self.status_text = "|cffff1010Deadly Boss Mods must be version 1.26 or newer to work with Ouro Loot.|r" | |
988 return | |
989 end | |
990 local r = DBM:RegisterCallback("kill", callback) | |
991 DBM:RegisterCallback("wipe", callback) | |
992 DBM:RegisterCallback("pull", function() location = instance_tag() end) | |
993 self.dbm_registered = r > 0 | |
994 end | |
995 else | |
996 self.status_text = "|cffff1010Ouro Loot cannot find Deadly Boss Mods, loot will not be grouped by boss.|r" | |
997 end | |
998 end | |
999 end -- DBM tie-ins | |
1000 | |
1001 -- Adding entries to the loot record, and tracking the corresponding timestamp. | |
1002 do | |
1003 -- This shouldn't be required. /sadface | |
1004 local loot_entry_mt = { | |
1005 __index = function (e,key) | |
1006 if key == 'cols' then | |
1007 pprint('mt', e.kind) | |
1008 --tabledump(e) -- not actually that useful | |
1009 addon:_fill_out_eoi_data(1) | |
1010 end | |
1011 return rawget(e,key) | |
1012 end | |
1013 } | |
1014 | |
1015 -- Given a loot index, searches backwards for a timestamp. Returns that | |
1016 -- index and the time entry, or nil if it falls off the beginning. Pass an | |
1017 -- optional second index to search no earlier than it. | |
1018 -- May also be able to make good use of this in forum-generation routine. | |
1019 function addon:find_previous_time_entry(i,stop) | |
1020 local stop = stop or 0 | |
1021 while i > stop do | |
1022 if g_loot[i].kind == 'time' then | |
1023 return i, g_loot[i] | |
1024 end | |
1025 i = i - 1 | |
1026 end | |
1027 end | |
1028 | |
1029 -- format_timestamp (["format_string"], Day, [Loot]) | |
1030 -- DAY is a loot entry with kind=='time', and controls the date printed. | |
1031 -- LOOT may be any kind of entry in the g_loot table. If present, it | |
1032 -- overrides the hour and minute printed; if absent, those values are | |
1033 -- taken from the DAY entry. | |
1034 -- FORMAT_STRING may contain $x (x in Y/M/D/h/m) tokens. | |
1035 local format_timestamp_values, point2dee = {}, "%.2d" | |
1036 function addon:format_timestamp (fmt_opt, day_entry, time_entry_opt) | |
1037 if not time_entry_opt then | |
1038 if type(fmt_opt) == 'table' then -- Two entries, default format | |
1039 time_entry_opt, day_entry = day_entry, fmt_opt | |
1040 fmt_opt = "$Y/$M/$D $h:$m" | |
1041 --elseif type(fmt_opt) == "string" then -- Day entry only, specified format | |
1042 end | |
1043 end | |
1044 --format_timestamp_values.Y = point2dee:format (day_entry.startday.year % 100) | |
1045 format_timestamp_values.Y = ("%.4d"):format (day_entry.startday.year) | |
1046 format_timestamp_values.M = point2dee:format (day_entry.startday.month) | |
1047 format_timestamp_values.D = point2dee:format (day_entry.startday.day) | |
1048 format_timestamp_values.h = point2dee:format ((time_entry_opt or day_entry).hour) | |
1049 format_timestamp_values.m = point2dee:format ((time_entry_opt or day_entry).minute) | |
1050 return fmt_opt:gsub ('%$([YMDhm])', format_timestamp_values) | |
1051 end | |
1052 | |
1053 local done_todays_date | |
1054 function addon:_reset_timestamps() | |
1055 done_todays_date = nil | |
1056 end | |
1057 local function do_todays_date() | |
1058 local text, M, D, Y = makedate() | |
1059 local found,ts = #g_loot+1 | |
1060 repeat | |
1061 found,ts = addon:find_previous_time_entry(found-1) | |
1062 if found and ts.startday.text == text then | |
1063 done_todays_date = true | |
1064 end | |
1065 until done_todays_date or (not found) | |
1066 if done_todays_date then | |
1067 g_today = ts | |
1068 else | |
1069 done_todays_date = true | |
1070 g_today = g_loot[addon._addLootEntry{ | |
1071 kind = 'time', | |
1072 startday = { | |
1073 text = text, month = M, day = D, year = Y | |
1074 } | |
1075 }] | |
1076 end | |
1077 addon:_fill_out_eoi_data(1) | |
1078 end | |
1079 | |
1080 -- Adding anything original to g_loot goes through this routine. | |
1081 function addon._addLootEntry (e) | |
1082 setmetatable(e,loot_entry_mt) | |
1083 | |
1084 if not done_todays_date then do_todays_date() end | |
1085 | |
1086 local h, m = GetGameTime() | |
1087 local localuptime = math.floor(GetTime()) | |
1088 e.hour = h | |
1089 e.minute = m | |
1090 e.stamp = localuptime | |
1091 local index = #g_loot + 1 | |
1092 g_loot[index] = e | |
1093 return index | |
1094 end | |
1095 end | |
1096 | |
1097 | |
1098 ------ Saved texts | |
1099 function addon:check_saved_table(silent_p) | |
1100 local s = g_loot.saved | |
1101 if s and (#s > 0) then return s end | |
1102 g_loot.saved = nil | |
1103 if not silent_p then self:Print("There are no saved loot texts.") end | |
1104 end | |
1105 | |
1106 function addon:save_list() | |
1107 local s = self:check_saved_table(); if not s then return end; | |
1108 for i,t in ipairs(s) do | |
1109 self:Print("#%d %s %d entries %s", i, t.date, t.count, t.name) | |
1110 end | |
1111 end | |
1112 | |
1113 function addon:save_saveas(name) | |
1114 g_loot.saved = g_loot.saved or {} | |
1115 local n = #(g_loot.saved) + 1 | |
1116 local save = { | |
1117 name = name, | |
1118 date = makedate(), | |
1119 count = #g_loot, | |
1120 forum = g_loot.forum, | |
1121 attend = g_loot.attend, | |
1122 } | |
1123 self:Print("Saving current loot texts to #%d '%s'", n, name) | |
1124 g_loot.saved[n] = save | |
1125 return self:save_list() | |
1126 end | |
1127 | |
1128 function addon:save_restore(num) | |
1129 local s = self:check_saved_table(); if not s then return end; | |
1130 if (not num) or (num > #s) then | |
1131 return self:Print("Saved text number must be 1 - "..#s) | |
1132 end | |
1133 local save = s[num] | |
1134 self:Print("Overwriting current loot data with saved text #%d '%s'", num, save.name) | |
1135 self:Clear(--[[verbose_p=]]false) | |
1136 -- Clear will already have displayed the window, and re-selected the first | |
1137 -- tab. Set these up for when the text tabs are clicked. | |
1138 g_loot.forum = save.forum | |
1139 g_loot.attend = save.attend | |
1140 end | |
1141 | |
1142 function addon:save_delete(num) | |
1143 local s = self:check_saved_table(); if not s then return end; | |
1144 if (not num) or (num > #s) then | |
1145 return self:Print("Saved text number must be 1 - "..#s) | |
1146 end | |
1147 self:Print("Deleting saved text #"..num) | |
1148 tremove(s,num) | |
1149 return self:save_list() | |
1150 end | |
1151 | |
1152 | |
1153 ------ Loot histories | |
1154 -- history_all = { | |
1155 -- ["Kilrogg"] = { | |
1156 -- ["realm"] = "Kilrogg", -- not saved | |
1157 -- ["st"] = { lib-st display table }, -- not saved | |
1158 -- ["byname"] = { -- not saved | |
1159 -- ["OtherPlayer"] = 2, | |
1160 -- ["Farmbuyer"] = 1, | |
1161 -- } | |
1162 -- [1] = { | |
1163 -- ["name"] = "Farmbuyer", | |
1164 -- [1] = { id = nnnnn, when = "formatted timestamp for displaying" } -- most recent loot | |
1165 -- [2] = { ......., [count = "x3"] } -- previous loot | |
1166 -- }, | |
1167 -- [2] = { | |
1168 -- ["name"] = "OtherPlayer", | |
1169 -- ...... | |
1170 -- }, ...... | |
1171 -- }, | |
1172 -- ["OtherRealm"] = ...... | |
1173 -- } | |
1174 do | |
1175 -- Builds the map of names to array indices. | |
1176 function addon:_build_history_names (opt_hist) | |
1177 local hist = opt_hist or self.history | |
1178 local m = {} | |
1179 for i = 1, #hist do | |
1180 m[hist[i].name] = i | |
1181 end | |
1182 hist.byname = m | |
1183 end | |
1184 | |
1185 -- Maps a name to an array index, creating new tables if needed. Returns | |
1186 function addon:get_loot_history (name) | |
1187 local i | |
1188 i = self.history.byname[name] | |
1189 if not i then | |
1190 i = #self.history + 1 | |
1191 self.history[i] = { name=name } | |
1192 self.history.byname[name] = i | |
1193 end | |
1194 return self.history[i] | |
1195 end | |
1196 | |
1197 -- Prepares and returns table to be used as self.history. | |
1198 function addon:_prep_new_history_category (prev_table, realmname) | |
1199 local t = prev_table or { | |
1200 --kind = 'realm', | |
1201 realm = realmname, | |
1202 } | |
1203 | |
1204 --[[ | |
1205 t.cols = setmetatable({ | |
1206 { value = realmname }, | |
1207 }, self.time_column1_used_mt) | |
1208 ]] | |
1209 | |
1210 if not t.byname then | |
1211 self:_build_history_names (t) | |
1212 end | |
1213 | |
1214 return t | |
1215 end | |
1216 | |
1217 function addon:_addHistoryEntry (lootindex) | |
1218 local e = g_loot[lootindex] | |
1219 local h = self:get_loot_history(e.person) | |
1220 local n = { | |
1221 id = e.id, | |
1222 when = self:format_timestamp (g_today, e), | |
1223 count = e.count, | |
1224 } | |
1225 h[#h+1] = n | |
1226 end | |
1227 end | |
1228 | |
1229 | |
1230 ------ Player communication | |
1231 do | |
1232 local function adduser (name, status, active) | |
1233 if status then addon.sender_list.names[name] = status end | |
1234 if active then addon.sender_list.active[name] = active end | |
1235 end | |
1236 | |
1237 -- Incoming handler functions. All take the sender name and the incoming | |
1238 -- tag as the first two arguments. All of these are active even when the | |
1239 -- player is not tracking loot, so test for that when appropriate. | |
1240 local OCR_funcs = {} | |
1241 | |
1242 OCR_funcs.ping = function (sender) | |
1243 pprint('comm', "incoming ping from", sender) | |
1244 addon:whispercast (sender, 'pong', addon.revision, | |
1245 addon.enabled and "tracking" or (addon.rebroadcast and "broadcasting" or "disabled")) | |
1246 end | |
1247 OCR_funcs.pong = function (sender, _, rev, status) | |
1248 local s = ("|cff00ff00%s|r v2r%s is |cff00ffff%s|r"):format(sender,rev,status) | |
1249 addon:Print("Echo: ", s) | |
1250 adduser (sender, s, status=="tracking" or status=="broadcasting" or nil) | |
1251 end | |
1252 | |
1253 OCR_funcs.loot = function (sender, _, recip, item, count, extratext) | |
1254 addon.dprint('comm', "DOTloot, sender", sender, "recip", recip, "item", item, "count", count) | |
1255 if not addon.enabled then return end | |
1256 adduser (sender, nil, true) | |
1257 addon:CHAT_MSG_LOOT ("broadcast", recip, item, count, sender, extratext) | |
1258 end | |
1259 | |
1260 OCR_funcs.boss = function (sender, _, reason, bossname, instancetag) | |
1261 addon.dprint('comm', "DOTboss, sender", sender, "reason", reason, "name", bossname, "it", instancetag) | |
1262 if not addon.enabled then return end | |
1263 adduser (sender, nil, true) | |
1264 addon:on_boss_broadcast (reason, bossname, instancetag) | |
1265 end | |
1266 | |
1267 OCR_funcs.bcast_req = function (sender) | |
1268 if addon.debug.comm or ((not g_wafer_thin) and (not addon.rebroadcast)) | |
1269 then | |
1270 addon:Print("%s has requested additional broadcasters! Choose %s to enable rebroadcasting, or %s to remain off and also ignore rebroadcast requests for as long as you're logged in. Or do nothing for now to see if other requests arrive.", | |
1271 sender, | |
1272 addon.format_hypertext('bcaston',"the red pill",'|cffff4040'), | |
1273 addon.format_hypertext('waferthin',"the blue pill",'|cff0070dd')) | |
1274 end | |
1275 self.popped = true | |
1276 end | |
1277 | |
1278 OCR_funcs.bcast_responder = function (sender) | |
1279 if addon.debug.comm or addon.requesting or | |
1280 ((not g_wafer_thin) and (not addon.rebroadcast)) | |
1281 then | |
1282 addon:Print(sender, "has answered the call and is now broadcasting loot.") | |
1283 end | |
1284 end | |
1285 -- remove this tag once it's all tested | |
1286 OCR_funcs.bcast_denied = function (sender) | |
1287 if addon.requesting then addon:Print(sender, "declines futher broadcast requests.") end | |
1288 end | |
1289 | |
1290 -- Incoming message dispatcher | |
1291 local function dotdotdot (sender, tag, ...) | |
1292 local f = OCR_funcs[tag] | |
1293 addon.dprint('comm', ":... processing",tag,"from",sender) | |
1294 if f then return f(sender,tag,...) end | |
1295 addon.dprint('comm', "unknown comm message",tag",from", sender) | |
1296 end | |
1297 -- Recent message cache | |
1298 addon.recent_messages = create_new_cache ('comm', comm_cleanup_ttl) | |
1299 | |
1300 function addon:OnCommReceived (prefix, msg, distribution, sender) | |
1301 if prefix ~= self.ident then return end | |
1302 if not self.debug.comm then | |
1303 if distribution ~= "RAID" and distribution ~= "WHISPER" then return end | |
1304 if sender == my_name then return end | |
1305 end | |
1306 self.dprint('comm', ":OCR from", sender, "message is", msg) | |
1307 | |
1308 if self.recent_messages:test(msg) then | |
1309 return self.dprint('cache', "message <",msg,"> already in cache, skipping") | |
1310 end | |
1311 self.recent_messages:add(msg) | |
1312 | |
1313 -- Nothing is actually returned, just (ab)using tail calls. | |
1314 return dotdotdot(sender,strsplit('\a',msg)) | |
1315 end | |
1316 | |
1317 function addon:OnCommReceivedNocache (prefix, msg, distribution, sender) | |
1318 if prefix ~= self.identTg then return end | |
1319 if not self.debug.comm then | |
1320 if distribution ~= "WHISPER" then return end | |
1321 if sender == my_name then return end | |
1322 end | |
1323 self.dprint('comm', ":OCRN from", sender, "message is", msg) | |
1324 return dotdotdot(sender,strsplit('\a',msg)) | |
1325 end | |
1326 end | |
1327 | |
1328 -- vim:noet |