Mercurial > wow > ouroloot
comparison core.lua @ 76:124da015c4a2
- Some more debugging aids (logging error/assert, auto-enable of testing
panel, reminder of GOP history mode)
- Move (finally!) hypertext handling code out to each call site.
- Fix some bugs in previous alpha code.
- Initial-but-mostly-tested code to handle items that have a "unique" field
which are in fact always the same (for example, elementium gem cluster).
Still need to test the case in which a remote tracker sees them first.
- The rest of the variable-cutoff history cleanup.
author | Farmbuyer of US-Kilrogg <farmbuyer@gmail.com> |
---|---|
date | Fri, 08 Jun 2012 08:05:37 +0000 |
parents | 32eb24fb2ebf |
children | a07c9dd79f3a |
comparison
equal
deleted
inserted
replaced
75:676fb79a4ae2 | 76:124da015c4a2 |
---|---|
91 entire package and rewrote the loot tracker module from scratch. Many of the | 91 entire package and rewrote the loot tracker module from scratch. Many of the |
92 variable/function naming conventions (sv_*, g_*, and family) stayed across the | 92 variable/function naming conventions (sv_*, g_*, and family) stayed across the |
93 rewrite. | 93 rewrite. |
94 | 94 |
95 Some variables are needlessly initialized to nil just to look uniform and | 95 Some variables are needlessly initialized to nil just to look uniform and |
96 serve as a reminder. | 96 serve as a spelling reminder. |
97 | 97 |
98 ]==] | 98 ]==] |
99 | 99 |
100 ------ Saved variables | 100 ------ Saved variables |
101 OuroLootSV = nil -- possible copy of g_loot | 101 OuroLootSV = nil -- possible copy of g_loot |
141 .." welcome message will not intrude again." | 141 .." welcome message will not intrude again." |
142 local newer_warning = "A newer version has been released. You can %s to display" | 142 local newer_warning = "A newer version has been released. You can %s to display" |
143 .." a download URL for copy-and-pasting. You can %s to ping other raiders" | 143 .." a download URL for copy-and-pasting. You can %s to ping other raiders" |
144 .." for their installed versions (same as '/ouroloot ping' or clicking the" | 144 .." for their installed versions (same as '/ouroloot ping' or clicking the" |
145 .." 'Ping!' button on the options panel)." | 145 .." 'Ping!' button on the options panel)." |
146 local unique_collision = "|cffff1010%s:|r Item '%s' was carrying unique tag <" | 146 local unique_collision = "|cffff1010%s:|r Item '%s' was carrying unique tag " |
147 ..">, but that was already in use! (New sender was '%s', previous cache " | 147 .."<%s>, but that was already in use! (New sender was '%s', previous cache " |
148 .."entry was <%s/%s>.) This may require a live human to figure out; the " | 148 .."entry was <%s/%s>.) This may require a live human to figure out; the " |
149 .."loot in question has not been stored." | 149 .."loot in question has not been stored." |
150 local remote_chatty = "|cff00ff00%s|r changed %d/%s from %s%s|r to %s%s|r" | 150 local remote_chatty = "|cff00ff00%s|r changed %d/%s from %s%s|r to %s%s|r" |
151 local qualnames = { | 151 local qualnames = { |
152 ['gray'] = 0, ['grey'] = 0, ['poor'] = 0, ['trash'] = 0, | 152 ['gray'] = 0, ['grey'] = 0, ['poor'] = 0, ['trash'] = 0, |
157 ['legendary'] = 5, ['orange'] = 5, | 157 ['legendary'] = 5, ['orange'] = 5, |
158 ['artifact'] = 6, | 158 ['artifact'] = 6, |
159 --['heirloom'] = 7, | 159 --['heirloom'] = 7, |
160 } | 160 } |
161 local my_name = UnitName('player') | 161 local my_name = UnitName('player') |
162 local comm_cleanup_ttl = 4 -- seconds in the cache | 162 local comm_cleanup_ttl = 4 -- seconds in the communications cache |
163 local revision_large = nil -- defaults to 1, possibly changed by revision | 163 local revision_large = nil -- defaults to 1, possibly changed by revision |
164 local g_LOOT_ITEM_ss, g_LOOT_ITEM_MULTIPLE_sss, g_LOOT_ITEM_SELF_s, g_LOOT_ITEM_SELF_MULTIPLE_ss | 164 local g_LOOT_ITEM_ss, g_LOOT_ITEM_MULTIPLE_sss, g_LOOT_ITEM_SELF_s, g_LOOT_ITEM_SELF_MULTIPLE_ss |
165 | 165 |
166 | 166 |
167 ------ Addon member data | 167 ------ Addon member data |
186 end | 186 end |
187 end | 187 end |
188 | 188 |
189 DEBUG_PRINT = false | 189 DEBUG_PRINT = false |
190 debug = { | 190 debug = { |
191 comm = false, | 191 comm = false, |
192 loot = false, | 192 loot = false, |
193 flow = false, | 193 flow = false, |
194 notraid = false, | 194 notraid = false, |
195 cache = false, | 195 cache = false, |
196 alsolog = false, | 196 alsolog = false, |
197 } | 197 } |
198 -- This looks ugly, but it factors out the load-time decisions from | 198 -- This looks ugly, but it factors out the load-time decisions from |
199 -- the run-time ones. Args to [dp]print are concatenated with spaces. | 199 -- the run-time ones. Args to [dp]print are concatenated with spaces. |
200 if tekdebug then | 200 if tekdebug then |
201 function dprint (t,...) | 201 function dprint (t,...) |
226 end | 226 end |
227 else | 227 else |
228 pprint = flib.nullfunc | 228 pprint = flib.nullfunc |
229 end | 229 end |
230 | 230 |
231 -- The same observable behavior as the Lua builtins, but with slightly | |
232 -- different hardcoded strings and, more importantly, implicit logging. | |
233 function error(txt,lvl) | |
234 pprint('ERROR()', txt) | |
235 pprint('DEBUGSTACK()', _G.debugstack()) | |
236 _G.error(txt,lvl) | |
237 end | |
238 function assert(cond,msg,...) | |
239 if cond then | |
240 return cond,msg,... | |
241 else | |
242 error('ASSERT() FAILED: '..tostring(msg or 'nil')) | |
243 end | |
244 end | |
245 | |
231 enabled = false | 246 enabled = false |
232 rebroadcast = false | 247 rebroadcast = false |
233 display = nil -- display frame, when visible | 248 display = nil -- reference to display frame iff visible |
234 loot_clean = nil -- index of last GUI entry with known-current visual data | 249 loot_clean = nil -- index of last GUI entry with known-current visual data |
235 sender_list = {active={},names={}} -- this should be reworked | |
236 threshold = debug.loot and 0 or 3 -- rare by default | 250 threshold = debug.loot and 0 or 3 -- rare by default |
237 sharder = nil -- name of person whose loot is marked as shards | 251 sharder = nil -- name of person whose loot is marked as shards |
238 | 252 |
239 -- The rest is also used in the GUI: | 253 -- The rest is also used in the GUI: |
240 | 254 |
255 sender_list = {active={},names={}} -- this should be reworked | |
241 popped = nil -- non-nil when reminder has been shown, actual value unimportant | 256 popped = nil -- non-nil when reminder has been shown, actual value unimportant |
242 | 257 |
243 bossmod_registered = nil | 258 bossmod_registered = nil |
244 bossmods = {} | 259 bossmods = {} |
245 | 260 |
246 requesting = nil -- for prompting for additional rebroadcasters | 261 requesting = nil -- prompting for additional rebroadcasters |
247 | 262 |
248 -- don't use NUM_ITEM_QUALITIES as the upper bound unless we expect heirlooms to show up | 263 -- don't use NUM_ITEM_QUALITIES as the upper loop bound unless we expect |
249 thresholds = {} | 264 -- heirlooms to show up |
265 thresholds = {} | |
250 for i = 0,6 do | 266 for i = 0,6 do |
251 thresholds[i] = _G.ITEM_QUALITY_COLORS[i].hex .. _G["ITEM_QUALITY"..i.."_DESC"] .. "|r" | 267 thresholds[i] = _G.ITEM_QUALITY_COLORS[i].hex .. _G["ITEM_QUALITY"..i.."_DESC"] .. "|r" |
252 end | 268 end |
253 | 269 |
254 _G.setfenv (1, _G) | 270 _G.setfenv (1, _G) |
265 msg = msg or "load-time assertion failed!" | 281 msg = msg or "load-time assertion failed!" |
266 self.NOLOAD = msg | 282 self.NOLOAD = msg |
267 self:Printf([[|cffff1010ERROR:|r <|cff00ff00%s|r> Ouro Loot cannot finish loading. You will need to type |cff30adff%s|r once these problems are resolved, and try again.]], msg, _G.SLASH_RELOAD1) | 283 self:Printf([[|cffff1010ERROR:|r <|cff00ff00%s|r> Ouro Loot cannot finish loading. You will need to type |cff30adff%s|r once these problems are resolved, and try again.]], msg, _G.SLASH_RELOAD1) |
268 SLASH_ACECONSOLE_OUROLOOT1 = nil | 284 SLASH_ACECONSOLE_OUROLOOT1 = nil |
269 SLASH_ACECONSOLE_OUROLOOT2 = nil | 285 SLASH_ACECONSOLE_OUROLOOT2 = nil |
270 _G.error (msg, --[[level=]]2) | 286 self.error (msg, --[[level=]]2) |
271 end | 287 end |
272 | 288 |
273 -- Seriously? ORLY? | 289 -- Seriously? ORLY? |
274 -- YARLY. Go ahead and guess what was involved in tracking this down. If | 290 -- YARLY. Go ahead and guess what was involved in tracking this down. If |
275 -- more such effects are added in the future, the "id==xxxxx" will need to | 291 -- more such effects are added in the future, the "id==xxxxx" will need to |
291 | 307 |
292 | 308 |
293 ------ Globals | 309 ------ Globals |
294 local g_loot = nil | 310 local g_loot = nil |
295 local g_restore_p = nil | 311 local g_restore_p = nil |
296 local g_wafer_thin = nil -- for prompting for additional rebroadcasters | 312 local g_wafer_thin = nil -- prompting for additional rebroadcasters |
297 local g_today = nil -- "today" entry in g_loot | 313 local g_today = nil -- "today" entry in g_loot |
298 local g_boss_signpost = nil | 314 local g_boss_signpost = nil |
299 local g_seeing_oldsigs = nil | 315 local g_seeing_oldsigs = nil |
300 local g_uniques = nil -- memoization of unique loot events | 316 local g_uniques = nil -- memoization of unique loot events |
317 local g_unique_replace = nil | |
301 local opts = nil | 318 local opts = nil |
319 | |
320 local error = addon.error | |
321 local assert = addon.assert | |
302 | 322 |
303 -- for speeding up local loads, not because I think _G will change | 323 -- for speeding up local loads, not because I think _G will change |
304 local _G = _G | 324 local _G = _G |
305 local type = _G.type | 325 local type = _G.type |
306 local select = _G.select | 326 local select = _G.select |
314 | 334 |
315 local pprint, tabledump = addon.pprint, flib.tabledump | 335 local pprint, tabledump = addon.pprint, flib.tabledump |
316 local CopyTable, GetNumRaidMembers = _G.CopyTable, _G.GetNumRaidMembers | 336 local CopyTable, GetNumRaidMembers = _G.CopyTable, _G.GetNumRaidMembers |
317 -- En masse forward decls of symbols defined inside local blocks | 337 -- En masse forward decls of symbols defined inside local blocks |
318 local _register_bossmod, makedate, create_new_cache, _init, _log | 338 local _register_bossmod, makedate, create_new_cache, _init, _log |
319 local _history_by_loot_id, _notify_about_remote | 339 local _history_by_loot_id, _notify_about_remote, _setup_unique_replace |
320 | 340 |
321 -- Try to extract numbers from the .toc "Version" and munge them into an | 341 -- Try to extract numbers from the .toc "Version" and munge them into an |
322 -- integral form for comparison. The result doesn't need to be meaningful as | 342 -- integral form for comparison. The result doesn't need to be meaningful as |
323 -- long as we can reliably feed two of them to "<" and get useful answers. | 343 -- long as we can reliably feed two of them to "<" and get useful answers. |
324 -- | 344 -- |
340 revision_large = math.max(r,1) | 360 revision_large = math.max(r,1) |
341 end | 361 end |
342 | 362 |
343 -- Hypertext support, inspired by DBM broadcast pizza timers | 363 -- Hypertext support, inspired by DBM broadcast pizza timers |
344 do | 364 do |
345 local hypertext_format_str = "|HOuroRaid:%s|h%s[%s]|r|h" | 365 local hypertext_format_str = "|HOuroLoot:%d|h%s[%s]|r|h" |
366 local func_map = {} --_G.setmetatable({}, {__mode = 'k'}) | |
367 local text_map = {} --_G.setmetatable({}, {__mode = 'kv'}) | |
368 local base = _G.newproxy(true) | |
369 _G.getmetatable(base).__tostring = function(ud) return text_map[ud] end | |
370 --@debug@ | |
371 -- collecting these tokens is an interesting micro-optimization but not yet | |
372 _G.getmetatable(base).__gc = function(ud) | |
373 print("Collecting hyperlink object <",tostring(ud),">") | |
374 end | |
375 --@end-debug@ | |
376 | |
377 -- TEXT will automatically be surrounded by brackets | |
378 -- COLOR can be ITEM_QUALITY_* or a formatting string ("|cff...") | |
379 -- FUNC can be "MethodName", "tab_title", or a function | |
380 -- | |
381 -- Returns an obaque token. Calling tostring() on the token will yield a | |
382 -- formatted clickable string that can be displayed in chat. This is | |
383 -- largely an excuse to fool around with Lua data constructs. | |
384 function addon.format_hypertext (text, color, func) | |
385 local ret = _G.newproxy(base) | |
386 local num = #text_map + 1 | |
387 text_map[ret] = hypertext_format_str:format (num, | |
388 type(color)=='number' and ITEM_QUALITY_COLORS[color].hex or color, | |
389 text) | |
390 text_map[num] = ret | |
391 func_map[ret] = func | |
392 return ret | |
393 end | |
394 | |
395 --[[ | |
396 link: OuroLoot:n | |
397 fullstring: |HOuroLoot:n|h|cff.....[foo]|r|h | |
398 mousebutton: "LeftButton", "MiddleButton", "RightButton" | |
399 | |
400 amusingly, print()'ing the fullstring below as a debugging aid yields | |
401 another clickable link, yay data reproducability | |
402 ]] | |
346 local strsplit = _G.strsplit | 403 local strsplit = _G.strsplit |
347 | 404 DEFAULT_CHAT_FRAME:HookScript("OnHyperlinkClick", function(self, link, fullstring, mousebutton) |
348 -- TEXT will automatically be surrounded by brackets | |
349 -- COLOR can be item quality code or a hex string | |
350 function addon.format_hypertext (code, text, color) | |
351 return hypertext_format_str:format (code, | |
352 type(color)=='number' and ITEM_QUALITY_COLORS[color].hex or color, | |
353 text) | |
354 end | |
355 | |
356 DEFAULT_CHAT_FRAME:HookScript("OnHyperlinkClick", function(self, link, string, mousebutton) | |
357 local ltype, arg = strsplit(":",link) | 405 local ltype, arg = strsplit(":",link) |
358 if ltype ~= "OuroRaid" then return end | 406 if ltype ~= "OuroLoot" then return end |
359 -- XXX this is crap, redo this as a dispatch table with code at the call site | 407 local f = func_map[text_map[tonumber(arg)]] |
360 if arg == 'openloot' then | 408 if type(f) == 'function' then |
361 addon:BuildMainDisplay() | 409 f() |
362 elseif arg == 'popupurl' then | 410 elseif type(f) == 'string' then |
363 -- Sadly, this is not generated by the packager, so hardcode it for now. | 411 if type(addon[f]) == 'function' then |
364 -- The 'data' field is handled differently for onshow than for other callbacks. | 412 addon[f](addon) -- method name |
365 StaticPopup_Show("OUROL_URL", --[[text_arg1=]]nil, --[[text_arg2=]]nil, | 413 else |
366 --[[data=]][[http://www.curse.com/addons/wow/ouroloot]]) | 414 addon:BuildMainDisplay(f) -- tab title fragment |
367 elseif arg == 'doping' then | 415 end |
368 addon:DoPing() | |
369 elseif arg == 'help' then | |
370 addon:BuildMainDisplay('help') | |
371 elseif arg == 'bcaston' then | |
372 if not addon.rebroadcast then | |
373 addon:Activate(nil,true) | |
374 end | |
375 addon:broadcast('bcast_responder') | |
376 elseif arg == 'waferthin' then -- mint? it's wafer thin! | |
377 g_wafer_thin = true -- fuck off, I'm full | |
378 addon:broadcast('bcast_denied') -- remove once tested | |
379 elseif arg == 'reload' then | |
380 addon:BuildMainDisplay('opt') | |
381 end | 416 end |
382 end) | 417 end) |
383 | 418 |
384 local old = ItemRefTooltip.SetHyperlink | 419 local old = ItemRefTooltip.SetHyperlink |
385 function ItemRefTooltip:SetHyperlink (link, ...) | 420 function ItemRefTooltip:SetHyperlink (link, ...) |
386 if link:match("^OuroRaid") then return end | 421 if link:match("^OuroLoot") then return end |
387 return old (self, link, ...) | 422 return old (self, link, ...) |
388 end | 423 end |
389 end | 424 end |
390 | 425 |
391 do | 426 do |
414 if typeof == "none" then return name, MAX_RAID_MEMBERS end | 449 if typeof == "none" then return name, MAX_RAID_MEMBERS end |
415 -- diffstr is "5 Player", "10 Player (Heroic)", etc. ugh. | 450 -- diffstr is "5 Player", "10 Player (Heroic)", etc. ugh. |
416 if (GetLFGMode()) and (GetLFGModeType() == 'raid') then | 451 if (GetLFGMode()) and (GetLFGModeType() == 'raid') then |
417 t,r = 'LFR', 25 | 452 t,r = 'LFR', 25 |
418 elseif diffcode == 1 then | 453 elseif diffcode == 1 then |
419 t,r = (GetNumRaidMembers()>0) and "10",10 or "5",5 | 454 if GetNumRaidMembers() > 0 then |
455 t,r = "10",10 | |
456 else | |
457 t,r = "5",5 | |
458 end | |
420 elseif diffcode == 2 then | 459 elseif diffcode == 2 then |
421 t,r = (GetNumRaidMembers()>0) and "25",25 or "5h",5 | 460 if GetNumRaidMembers() > 0 then |
461 t,r = "25",25 | |
462 else | |
463 t,r = "5h",5 | |
464 end | |
422 elseif diffcode == 3 then | 465 elseif diffcode == 3 then |
423 t,r = "10h", 10 | 466 t,r = "10h", 10 |
424 elseif diffcode == 4 then | 467 elseif diffcode == 4 then |
425 t,r = "25h", 25 | 468 t,r = "25h", 25 |
426 end | 469 end |
570 end | 613 end |
571 end | 614 end |
572 local function _test (cache, x) | 615 local function _test (cache, x) |
573 -- FIXME This can return false positives, if called after the onloop | 616 -- FIXME This can return false positives, if called after the onloop |
574 -- fifo has been removed but before the GC has removed the weak entry. | 617 -- fifo has been removed but before the GC has removed the weak entry. |
575 -- What to do, what to do... | 618 -- What to do, what to do... try forcing a GC during alldone. |
576 return cache.hash[x] ~= nil | 619 return cache.hash[x] ~= nil |
577 end | 620 end |
578 | 621 |
579 function create_new_cache (name, ttl, on_alldone) | 622 function create_new_cache (name, ttl, on_alldone) |
580 -- setting OnFinished for cleanup fires at the end of each inner loop, | 623 -- setting OnFinished for cleanup fires at the end of each inner loop, |
581 -- with no 'requested' argument to distinguish cases. thus, on_alldone. | 624 -- with no 'requested' argument to distinguish cases. thus, on_alldone. |
625 -- FWIW, on_alldone is passed this table as its sole argument: | |
582 local c = { | 626 local c = { |
583 ttl = ttl, | 627 ttl = ttl, |
584 name = name, | 628 name = name, |
585 add = _add, | 629 add = _add, |
586 test = _test, | 630 test = _test, |
610 g_restore_p = OuroLootSV ~= nil | 654 g_restore_p = OuroLootSV ~= nil |
611 self.dprint('flow', "oninit sets restore as", g_restore_p) | 655 self.dprint('flow', "oninit sets restore as", g_restore_p) |
612 | 656 |
613 if _G.OuroLootSV_opts == nil then | 657 if _G.OuroLootSV_opts == nil then |
614 _G.OuroLootSV_opts = {} | 658 _G.OuroLootSV_opts = {} |
659 local vclick = self.format_hypertext ([[click here]], ITEM_QUALITY_UNCOMMON, 'help') | |
615 self:ScheduleTimer(function(s) | 660 self:ScheduleTimer(function(s) |
616 s:Print(virgin, s.format_hypertext('help',"click here",ITEM_QUALITY_UNCOMMON)) | 661 s:Print(virgin, tostring(vclick)) |
617 virgin = nil | 662 virgin = nil |
618 end,10,self) | 663 end,10,self) |
619 else | 664 else |
620 virgin = nil | 665 virgin = nil |
621 end | 666 end |
693 loot.unique = loot.id .. ' ' .. loot.when | 738 loot.unique = loot.id .. ' ' .. loot.when |
694 end | 739 end |
695 end | 740 end |
696 end | 741 end |
697 -- format 3 to format 4 was a major revamp of per-player data | 742 -- format 3 to format 4 was a major revamp of per-player data |
698 self:_uplift_history_format(player,rname) | 743 self:_uplift_history_format(player) |
699 end | 744 end |
700 end | 745 end |
701 end | 746 end |
702 self._uplift_history_format = nil | 747 self._uplift_history_format = nil |
703 --OuroLootSV_hist = nil | 748 --OuroLootSV_hist = nil |
801 -- frame-ness, which would slow down all printing and only rarely be useful. | 846 -- frame-ness, which would slow down all printing and only rarely be useful. |
802 -- | 847 -- |
803 -- There is no ITEM_QUALITY_LEGENDARY constant. Sigh. | 848 -- There is no ITEM_QUALITY_LEGENDARY constant. Sigh. |
804 do | 849 do |
805 local AC = LibStub("AceConsole-3.0") | 850 local AC = LibStub("AceConsole-3.0") |
806 local chat_prefix = self.format_hypertext('openloot',"Ouro Loot",--[[legendary]]5) | 851 local chat_prefix = self.format_hypertext ("Ouro Loot", --[[legendary]]5, |
852 --[[empty -> nil -> main tab]]'') | |
853 local chat_prefix_s = tostring(chat_prefix) | |
807 function addon:Print (str, ...) | 854 function addon:Print (str, ...) |
808 if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then | 855 if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then |
809 return AC:Print (chat_prefix, str:format(...)) | 856 return AC:Print (chat_prefix_s, str:format(...)) |
810 else | 857 else |
811 return AC:Print (chat_prefix, str, ...) | 858 return AC:Print (chat_prefix_s, str, ...) |
812 end | 859 end |
813 end | 860 end |
814 function addon:CFPrint (frame, str, ...) | 861 function addon:CFPrint (frame, str, ...) |
815 assert(type(frame)=='table' and frame.AddMessage) | 862 assert(type(frame)=='table' and frame.AddMessage) |
816 if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then | 863 if type(str) == 'string' and str:find("%", nil, --[[plainmatch=]]true) then |
817 return AC:Print (frame, chat_prefix, str:format(...)) | 864 return AC:Print (frame, chat_prefix_s, str:format(...)) |
818 else | 865 else |
819 return AC:Print (frame, chat_prefix, str, ...) | 866 return AC:Print (frame, chat_prefix_s, str, ...) |
820 end | 867 end |
821 end | 868 end |
822 end | 869 end |
823 | 870 |
824 while opts.keybinding do | 871 while opts.keybinding do |
825 if InCombatLockdown() then | 872 if InCombatLockdown() then |
873 local reload = self.format_hypertext ([[the options tab]], | |
874 ITEM_QUALITY_UNCOMMON, 'opt') | |
826 self:Print("Cannot create '%s' as a keybinding while in combat!", | 875 self:Print("Cannot create '%s' as a keybinding while in combat!", |
827 opts.keybinding_text) | 876 opts.keybinding_text) |
828 self:Print("The rest of the addon will continue to work, but you will need to reload out of combat to get the keybinding. Either type /reload or use the button on %s in the lower right.", self.format_hypertext('reload',"the options tab",ITEM_QUALITY_UNCOMMON)) | 877 self:Print("The rest of the addon will continue to work, but you will need to reload out of combat to get the keybinding. Either type /reload or use the button on %s in the lower right.", |
878 tostring(reload)) | |
829 break | 879 break |
830 end | 880 end |
831 | 881 |
832 KeyBindingFrame_LoadUI() | 882 KeyBindingFrame_LoadUI() |
833 local btn = CreateFrame("Button", "OuroLootBindingOpen", nil, "SecureActionButtonTemplate") | 883 local btn = CreateFrame("Button", "OuroLootBindingOpen", nil, "SecureActionButtonTemplate") |
856 | 906 |
857 The SELF variants can be replaced with LOOT_ITEM_PUSHED_SELF[_MULTIPLE] to | 907 The SELF variants can be replaced with LOOT_ITEM_PUSHED_SELF[_MULTIPLE] to |
858 trigger on 'receive item' instead, which would detect extracting stuff | 908 trigger on 'receive item' instead, which would detect extracting stuff |
859 from mail, or s/PUSHED/CREATED/ for things like healthstones and guild | 909 from mail, or s/PUSHED/CREATED/ for things like healthstones and guild |
860 cauldron flasks. | 910 cauldron flasks. |
911 | |
912 ??? do something with LOOT_ITEM_WHILE_PLAYER_INELIGIBLE for locked LFRs? | |
861 ]] | 913 ]] |
862 | 914 |
863 -- LOOT_ITEM = "%s receives loot: %s." --> (.+) receives loot: (.+)%. | 915 -- LOOT_ITEM = "%s receives loot: %s." --> (.+) receives loot: (.+)%. |
864 g_LOOT_ITEM_ss = _G.LOOT_ITEM:gsub('%.$','%%.'):gsub('%%s','(.+)') | 916 g_LOOT_ITEM_ss = _G.LOOT_ITEM:gsub('%.$','%%.'):gsub('%%s','(.+)') |
865 | 917 |
1198 instance=addon.latest_instance, duration=0, | 1250 instance=addon.latest_instance, duration=0, |
1199 raidersnap=ss, maxsize=max | 1251 raidersnap=ss, maxsize=max |
1200 }) | 1252 }) |
1201 end | 1253 end |
1202 | 1254 |
1255 -- Alert other trackers that unique tag EXISTING in subsequent 'casts | |
1256 -- should be replaced by REPLACE instead. If multiple players all saw | |
1257 -- the same loot event, this will cause a flurry of cross-improvs. | |
1258 local function _announce_unique_improvisation (existing, replace) | |
1259 if not g_unique_replace then _setup_unique_replace() end | |
1260 g_unique_replace.new_entry (g_unique_replace.me, existing, replace, 'improv') | |
1261 addon:vbroadcast('improv', g_unique_replace.me, existing, replace) | |
1262 end | |
1263 | |
1203 local random = _G.math.random | 1264 local random = _G.math.random |
1204 local function many_uniques_handle_it (u, check_p) | 1265 local function _many_uniques_handle_it (u, prefix) |
1205 if u and check_p then | 1266 if u then |
1206 -- Check and alert for an existing value. | 1267 -- Check and alert for an existing value. |
1207 u = tostring(u) | 1268 u = tostring(u) |
1208 if g_uniques[u].history ~= g_uniques.NOTFOUND then | 1269 if g_uniques[u].history ~= g_uniques.NOTFOUND then |
1209 return nil, u | 1270 if not g_unique_replace then _setup_unique_replace() end |
1271 local maybe = g_unique_replace.get_previous_replacement (u) | |
1272 if maybe then | |
1273 addon.dprint('loot',"previous replaced tag ("..u | |
1274 ..") with ("..maybe.."), using that instead") | |
1275 return false, u, maybe | |
1276 end | |
1277 local can_replace_p,improv = _many_uniques_handle_it (nil, 'c') | |
1278 if can_replace_p then | |
1279 _announce_unique_improvisation (u, improv) | |
1280 return false, u, improv | |
1281 end | |
1282 return false, u | |
1210 end | 1283 end |
1211 addon.dprint('loot',"verified unique tag ("..u..")") | 1284 addon.dprint('loot',"verified unique tag ("..u..")") |
1212 else | 1285 else |
1213 -- Need to *find* an unused value. For now use a range of | 1286 -- Need to *find* an unused value. For now use a range of |
1214 -- J*10^4 where J is Jenny's Constant. Thank you, xkcd.com/1047. | 1287 -- J*10^4 where J is Jenny's Constant. Thank you, xkcd.com/1047. |
1288 prefix = prefix or 'n' | |
1215 repeat | 1289 repeat |
1216 u = 'n' .. random(8675309) | 1290 u = prefix .. random(8675309) |
1217 until g_uniques:TEST(u).history ~= g_uniques.NOTFOUND | 1291 until g_uniques:TEST(u).history == g_uniques.NOTFOUND |
1218 addon.dprint('loot',"created unique tag", u) | 1292 addon.dprint('loot',"created unique tag ("..u..")") |
1219 end | 1293 end |
1220 return true, u | 1294 return true, u |
1221 end | 1295 end |
1222 | 1296 |
1223 -- Recent loot cache | 1297 -- Recent loot cache |
1224 local candidates = {} | 1298 local candidates = {} |
1225 local sigmap = {} | 1299 local sigmap = {} |
1226 _G.sigmap = sigmap | |
1227 local function preempt_older_signature (oldersig, newersig) | 1300 local function preempt_older_signature (oldersig, newersig) |
1228 local origin = candidates[oldersig] and candidates[oldersig].from | 1301 --pprint("preempt", oldersig, "::", newersig) |
1302 local origin = candidates[oldersig] and candidates[oldersig].bcast_from | |
1303 --pprint("preempt", "candidate", candidates[oldersig], "bcast:", origin) | |
1229 if origin and g_seeing_oldsigs[origin] then | 1304 if origin and g_seeing_oldsigs[origin] then |
1230 -- replace entry from older client with this newer one | 1305 -- replace entry from older client with this newer one |
1231 candidates[oldersig] = nil | 1306 candidates[oldersig] = nil |
1232 addon.dprint('cache', "preempting signature <", oldersig, "> from", origin) | 1307 addon.dprint('loot', "preempting signature <", oldersig, "> from", origin) |
1233 end | 1308 end |
1234 return false | 1309 return false |
1235 end | 1310 end |
1236 | 1311 |
1237 local function prefer_local_loots (cache) | 1312 local function prefer_local_loots (cache) |
1251 if (loot.disposition ~= 'shard') | 1326 if (loot.disposition ~= 'shard') |
1252 and (loot.disposition ~= 'gvault') | 1327 and (loot.disposition ~= 'gvault') |
1253 and (not addon.history_suppress) | 1328 and (not addon.history_suppress) |
1254 then | 1329 then |
1255 addon:_addHistoryEntry(looti) | 1330 addon:_addHistoryEntry(looti) |
1331 elseif #loot.unique > 0 then | |
1332 g_uniques[loot.unique] = -- stub entry | |
1333 { loot = looti, history = g_uniques.NOTFOUND } | |
1256 end | 1334 end |
1257 end | 1335 end |
1258 end | 1336 end |
1259 | 1337 |
1260 if addon.display then | 1338 if addon.display then |
1283 itemid, "C:", count, "frm:", from, "ex:", extratext, "q:", iquality) | 1361 itemid, "C:", count, "frm:", from, "ex:", extratext, "q:", iquality) |
1284 | 1362 |
1285 itemid = tonumber(ilink:match("item:(%d+)") or 0) | 1363 itemid = tonumber(ilink:match("item:(%d+)") or 0) |
1286 | 1364 |
1287 -- This is only a 'while' to make jumping out of it easy. | 1365 -- This is only a 'while' to make jumping out of it easy. |
1288 local i, unique_okay, ret1, ret2 | 1366 local i, unique_okay, replacement, ret1, ret2 |
1289 while local_override | 1367 while local_override |
1290 or ((iquality >= self.threshold) and not opts.itemfilter[itemid]) | 1368 or ((iquality >= self.threshold) and not opts.itemfilter[itemid]) |
1291 do | 1369 do |
1292 unique_okay, unique = many_uniques_handle_it (unique, not local_override) | 1370 unique_okay, unique, replacement = |
1371 _many_uniques_handle_it ((not local_override) and unique) | |
1293 if not unique_okay then | 1372 if not unique_okay then |
1294 i = g_uniques[unique] | 1373 if replacement then |
1295 local err = unique_collision:format (ERROR_CAPS, iname, unique, | 1374 -- collision, but we've generated a placeholder for now |
1296 tostring(from), tostring(i.loot), tostring(i.history)) | 1375 -- and broadcast the fact |
1297 self:Print(err) | 1376 self.dprint('loot', "substituting", unique, "with", replacement) |
1298 _G.PlaySound("igQuestFailed", "master") | 1377 else |
1299 -- Make sure this is logged one way or another | 1378 i = g_uniques[unique] |
1300 ;(self.debug.loot and self.dprint or pprint)('loot', "COLLISION", prefix, err); | 1379 local err = unique_collision:format (ERROR_CAPS, iname, unique, |
1301 ret1, ret2 = nil, err | 1380 tostring(from), tostring(i.loot), tostring(i.history)) |
1302 break | 1381 self:Print(err) |
1382 _G.PlaySoundFile ([[Interface\AddOns\Ouro_Loot\sfrr.ogg]], "master") | |
1383 -- Make sure this is logged one way or another | |
1384 ;(self.debug.loot and self.dprint or pprint)('loot', "COLLISION", prefix, err); | |
1385 ret1, ret2 = nil, err | |
1386 break | |
1387 end | |
1303 end | 1388 end |
1304 | 1389 |
1305 if (self.rebroadcast and (not from)) and not local_override then | 1390 if (self.rebroadcast and (not from)) and not local_override then |
1306 self:vbroadcast('loot', recipient, unique, itemid, count) | 1391 self:vbroadcast('loot', recipient, unique, itemid, count) |
1307 end | 1392 end |
1310 local oldersig = recipient .. iname .. (count or "") | 1395 local oldersig = recipient .. iname .. (count or "") |
1311 local signature, seenit | 1396 local signature, seenit |
1312 if #unique > 0 then | 1397 if #unique > 0 then |
1313 -- newer case | 1398 -- newer case |
1314 signature = unique .. oldersig | 1399 signature = unique .. oldersig |
1400 --pprint("newer", "mapping older <", oldersig, "> to newer <", signature, ">") | |
1315 sigmap[oldersig] = signature | 1401 sigmap[oldersig] = signature |
1316 seenit = from and (recent_loot:test(signature) | 1402 --pprint("newer", "testing recent for", signature, "yields", recent_loot:test(signature)) |
1403 seenit = (from and recent_loot:test(signature)) | |
1317 -- The following clause is what handles older 'casts arriving | 1404 -- The following clause is what handles older 'casts arriving |
1318 -- earlier. All this is tested inside-out to maximize short | 1405 -- earlier. All this is tested inside-out to maximize short |
1319 -- circuit avaluation. | 1406 -- circuit evaluation; the preempt function always returns |
1320 or (g_seeing_oldsigs and preempt_older_signature(oldersig,signature))) | 1407 -- false to force seenit off. |
1408 or (g_seeing_oldsigs and preempt_older_signature(oldersig,signature)) | |
1321 else | 1409 else |
1322 -- older case, only remote | 1410 -- older case, only remote |
1323 assert(from) | 1411 assert(from) |
1324 signature = sigmap[oldersig] or oldersig | 1412 signature = sigmap[oldersig] or oldersig |
1413 --pprint("older", "testing signature will be", signature) | |
1325 seenit = recent_loot:test(signature) | 1414 seenit = recent_loot:test(signature) |
1326 end | 1415 end |
1327 | 1416 |
1328 if seenit then | 1417 if seenit then |
1329 self.dprint('cache', "remote", prefix, "<", signature, | 1418 self.dprint('cache', "remote", prefix, "<", signature, |
1340 quality = iquality, | 1429 quality = iquality, |
1341 itemname = iname, | 1430 itemname = iname, |
1342 id = itemid, | 1431 id = itemid, |
1343 itemlink = ilink, | 1432 itemlink = ilink, |
1344 itexture = itexture, | 1433 itexture = itexture, |
1345 unique = unique, | 1434 unique = replacement or unique, |
1346 count = (count and count ~= "") and count or nil, | 1435 count = (count and count ~= "") and count or nil, |
1347 bcast_from = from, | 1436 bcast_from = from, |
1348 extratext = extratext, | 1437 extratext = extratext, |
1349 variant = self:is_variant_item(ilink), | 1438 variant = self:is_variant_item(ilink), |
1350 } | 1439 } |
1366 if (i.disposition ~= 'shard') | 1455 if (i.disposition ~= 'shard') |
1367 and (i.disposition ~= 'gvault') | 1456 and (i.disposition ~= 'gvault') |
1368 and (not self.history_suppress) | 1457 and (not self.history_suppress) |
1369 then | 1458 then |
1370 self:_addHistoryEntry(looti) | 1459 self:_addHistoryEntry(looti) |
1460 else | |
1461 g_uniques[i.unique] = -- stub entry | |
1462 { loot = looti, history = g_uniques.NOTFOUND } | |
1371 end | 1463 end |
1372 ret1 = looti -- return value mostly for gui's manual entry | 1464 ret1 = looti -- return value mostly for gui's manual entry |
1465 self.dprint('loot', "manual", looti) | |
1373 else | 1466 else |
1374 recent_loot:add(signature) | 1467 recent_loot:add(signature) |
1375 candidates[signature] = i | 1468 candidates[signature] = i |
1376 tinsert (candidates, signature) | 1469 tinsert (candidates, signature) |
1377 self.dprint('cache', prefix, "<", signature, | 1470 self.dprint('cache', prefix, "<", signature, |
1446 ------ Slash command handler | 1539 ------ Slash command handler |
1447 -- Thought about breaking this up into a table-driven dispatcher. But | 1540 -- Thought about breaking this up into a table-driven dispatcher. But |
1448 -- that would result in a pile of teensy functions, most of which would | 1541 -- that would result in a pile of teensy functions, most of which would |
1449 -- never be called. Too much overhead. (2.0: Most of these removed now | 1542 -- never be called. Too much overhead. (2.0: Most of these removed now |
1450 -- that GUI is in place.) | 1543 -- that GUI is in place.) |
1451 function addon:OnSlash (txt) --, editbox) | 1544 do |
1452 txt = strtrim(txt:lower()) | 1545 local green_help_link = addon.format_hypertext ([[Click here]], |
1453 local cmd, arg = "" | 1546 ITEM_QUALITY_UNCOMMON, 'help') |
1454 do | 1547 function addon:OnSlash (txt) --, editbox) |
1455 local s,e = txt:find("^%a+") | 1548 txt = strtrim(txt:lower()) |
1456 if s then | 1549 local cmd, arg = "" |
1457 cmd = txt:sub(s,e) | 1550 do |
1458 s = txt:find("%S", e+2) | 1551 local s,e = txt:find("^%a+") |
1459 if s then arg = txt:sub(s,-1) end | 1552 if s then |
1460 end | 1553 cmd = txt:sub(s,e) |
1461 end | 1554 s = txt:find("%S", e+2) |
1462 | 1555 if s then arg = txt:sub(s,-1) end |
1463 if cmd == "" then | 1556 end |
1464 if InCombatLockdown() then | 1557 end |
1465 return self:Print("Shouldn't display window in combat.") | 1558 |
1559 if cmd == "" then | |
1560 if InCombatLockdown() then | |
1561 return self:Print("Shouldn't display window in combat.") | |
1562 else | |
1563 return self:BuildMainDisplay() | |
1564 end | |
1565 | |
1566 elseif cmd:find("^thre") then | |
1567 self:SetThreshold(arg) | |
1568 | |
1569 elseif cmd == "on" then self:Activate(arg) | |
1570 elseif cmd == "off" then self:Deactivate() | |
1571 elseif cmd == "broadcast" or cmd == "bcast" then self:Activate(nil,true) | |
1572 | |
1573 elseif cmd == "toggle" then | |
1574 if self.display then | |
1575 self.display:Hide() | |
1576 else | |
1577 return self:BuildMainDisplay() | |
1578 end | |
1579 | |
1580 elseif cmd == "fake" then -- maybe comment this out for real users | |
1581 self:_mark_boss_kill (self._addBossEntry{ | |
1582 kind='boss',reason='kill',bossname="Baron Steamroller",duration=0 | |
1583 }) | |
1584 self:CHAT_MSG_LOOT ('manual', my_name, 54797) | |
1585 if self.display then | |
1586 self:redisplay() | |
1587 end | |
1588 self:Print "Baron Steamroller has been slain. Congratulations on your rug." | |
1589 | |
1590 elseif cmd == "debug" then | |
1591 if arg then | |
1592 self.debug[arg] = not self.debug[arg] | |
1593 _G.print(arg,self.debug[arg]) | |
1594 if self.debug[arg] then self.DEBUG_PRINT = true end | |
1595 else | |
1596 self.DEBUG_PRINT = not self.DEBUG_PRINT | |
1597 end | |
1598 | |
1599 elseif cmd == "save" and arg and arg:len() > 0 then | |
1600 self:save_saveas(arg) | |
1601 elseif cmd == "list" then | |
1602 self:save_list() | |
1603 elseif cmd == "restore" and arg and arg:len() > 0 then | |
1604 self:save_restore(tonumber(arg)) | |
1605 elseif cmd == "delete" and arg and arg:len() > 0 then | |
1606 self:save_delete(tonumber(arg)) | |
1607 | |
1608 elseif cmd == "help" then | |
1609 self:BuildMainDisplay('help') | |
1610 elseif cmd == "ping" then | |
1611 self:DoPing() | |
1612 | |
1613 elseif cmd == "fixcache" then | |
1614 self:do_item_cache_fixup() | |
1615 | |
1466 else | 1616 else |
1467 return self:BuildMainDisplay() | 1617 if self:OpenMainDisplayToTab(cmd) then |
1468 end | 1618 return |
1469 | 1619 end |
1470 elseif cmd:find("^thre") then | 1620 self:Print("Unknown command '%s'. %s to see the help window.", |
1471 self:SetThreshold(arg) | 1621 cmd, tostring(green_help_link)) |
1472 | 1622 end |
1473 elseif cmd == "on" then self:Activate(arg) | |
1474 elseif cmd == "off" then self:Deactivate() | |
1475 elseif cmd == "broadcast" or cmd == "bcast" then self:Activate(nil,true) | |
1476 | |
1477 elseif cmd == "toggle" then | |
1478 if self.display then | |
1479 self.display:Hide() | |
1480 else | |
1481 return self:BuildMainDisplay() | |
1482 end | |
1483 | |
1484 elseif cmd == "fake" then -- maybe comment this out for real users | |
1485 self:_mark_boss_kill (self._addBossEntry{ | |
1486 kind='boss',reason='kill',bossname="Baron Steamroller",duration=0 | |
1487 }) | |
1488 self:CHAT_MSG_LOOT ('manual', my_name, 54797) | |
1489 if self.display then | |
1490 self:redisplay() | |
1491 end | |
1492 self:Print "Baron Steamroller has been slain. Congratulations on your rug." | |
1493 | |
1494 elseif cmd == "debug" then | |
1495 if arg then | |
1496 self.debug[arg] = not self.debug[arg] | |
1497 _G.print(arg,self.debug[arg]) | |
1498 if self.debug[arg] then self.DEBUG_PRINT = true end | |
1499 else | |
1500 self.DEBUG_PRINT = not self.DEBUG_PRINT | |
1501 end | |
1502 | |
1503 elseif cmd == "save" and arg and arg:len() > 0 then | |
1504 self:save_saveas(arg) | |
1505 elseif cmd == "list" then | |
1506 self:save_list() | |
1507 elseif cmd == "restore" and arg and arg:len() > 0 then | |
1508 self:save_restore(tonumber(arg)) | |
1509 elseif cmd == "delete" and arg and arg:len() > 0 then | |
1510 self:save_delete(tonumber(arg)) | |
1511 | |
1512 elseif cmd == "help" then | |
1513 self:BuildMainDisplay('help') | |
1514 elseif cmd == "ping" then | |
1515 self:DoPing() | |
1516 | |
1517 elseif cmd == "fixcache" then | |
1518 self:do_item_cache_fixup() | |
1519 | |
1520 else | |
1521 if self:OpenMainDisplayToTab(cmd) then | |
1522 return | |
1523 end | |
1524 self:Print("Unknown command '%s'. %s to see the help window.", | |
1525 cmd, self.format_hypertext('help',"Click here",ITEM_QUALITY_UNCOMMON)) | |
1526 end | 1623 end |
1527 end | 1624 end |
1528 | 1625 |
1529 function addon:SetThreshold (arg, quiet_p) | 1626 function addon:SetThreshold (arg, quiet_p) |
1530 local q = tonumber(arg) | 1627 local q = tonumber(arg) |
1531 if q then | 1628 if q then |
1532 q = math.floor(q+0.001) | 1629 q = math.floor(q+0.1) |
1533 if q<0 or q>6 then | 1630 if q<0 or q>6 then |
1534 return self:Print("Threshold must be 0-6.") | 1631 return self:Print("Threshold must be 0-6.") |
1535 end | 1632 end |
1536 else | 1633 else |
1537 q = qualnames[arg] | 1634 q = qualnames[arg] |
1553 self.popped = true | 1650 self.popped = true |
1554 if GetNumRaidMembers() > 0 then | 1651 if GetNumRaidMembers() > 0 then |
1555 self.dprint('flow', ">:Activate calling RRU") | 1652 self.dprint('flow', ">:Activate calling RRU") |
1556 self:RAID_ROSTER_UPDATE("Activate") | 1653 self:RAID_ROSTER_UPDATE("Activate") |
1557 elseif self.debug.notraid then | 1654 elseif self.debug.notraid then |
1558 self.dprint('flow', ">:Activate registering loot and bossmods") | 1655 self.dprint('flow', ">:(notraid) Activate registering loot and bossmods") |
1559 self:RegisterEvent("CHAT_MSG_LOOT") | 1656 self:RegisterEvent("CHAT_MSG_LOOT") |
1560 _register_bossmod(self) | 1657 _register_bossmod(self) |
1561 elseif g_restore_p then | 1658 elseif g_restore_p then |
1562 g_restore_p = nil | 1659 g_restore_p = nil |
1563 self.popped = nil -- get the reminder if later joining a raid | 1660 self.popped = nil -- get the reminder if later joining a raid |
1624 | 1721 |
1625 | 1722 |
1626 ------ Behind the scenes routines | 1723 ------ Behind the scenes routines |
1627 -- Semi-experimental debugging aid. | 1724 -- Semi-experimental debugging aid. |
1628 do | 1725 do |
1629 -- Putting _log local to here can result in this sequence: | 1726 -- Declaring _log as local to here can result in this sequence: |
1630 -- 1) logging happens, followed by reload or logout/login | 1727 -- 1) logging happens, followed by reload or logout/login |
1631 -- 2) _log points to SV_log | 1728 -- 2) _log points to SV_log |
1632 -- 3) VARIABLES_LOADED replaces SV_log pointer with restored version | 1729 -- 3) VARIABLES_LOADED replaces SV_log pointer with restored version |
1633 -- 4) logging happens to _log table (now with no other references) | 1730 -- 4) logging happens to _log table (now with no other references) |
1634 -- 5) at logout, nothing new has been entered in the table being saved | 1731 -- 5) at logout, nothing new has been entered in the table being saved |
1705 to_text = "normal" | 1802 to_text = "normal" |
1706 end | 1803 end |
1707 to_color = addon.disposition_colors[e.disposition or "normal"].hex | 1804 to_color = addon.disposition_colors[e.disposition or "normal"].hex |
1708 end | 1805 end |
1709 | 1806 |
1807 addon.dprint ('loot', "notifying:", sender, index, | |
1808 e.itemlink, from_color, from_text, to_color, to_text) | |
1710 addon:CFPrint (remote_change_chatframe, remote_chatty, sender, index, | 1809 addon:CFPrint (remote_change_chatframe, remote_chatty, sender, index, |
1711 e.itemlink, from_color, from_text, to_color, to_text) | 1810 e.itemlink, from_color, from_text, to_color, to_text) |
1712 end | 1811 end |
1713 end | 1812 end |
1714 | 1813 |
1753 elseif otherrev < revision_large then | 1852 elseif otherrev < revision_large then |
1754 self.dprint('comm', "ours is newer, notifying") | 1853 self.dprint('comm', "ours is newer, notifying") |
1755 self:broadcast('revcheck',revision_large) | 1854 self:broadcast('revcheck',revision_large) |
1756 | 1855 |
1757 else | 1856 else |
1758 self.dprint('comm', "ours is older, yammering") | 1857 self.dprint('comm', "ours is older, (possibly) yammering") |
1759 if newer_warning then | 1858 if newer_warning then |
1760 self:Print(newer_warning, | 1859 local pop = addon.format_hypertext ([[click here]], ITEM_QUALITY_UNCOMMON, |
1761 self.format_hypertext('popupurl',"click here",ITEM_QUALITY_UNCOMMON), | 1860 function() |
1762 self.format_hypertext('doping',"click here",ITEM_QUALITY_UNCOMMON)) | 1861 -- Sadly, this is not generated by the packager, so hardcode it for now. |
1862 -- The 'data' field is handled differently for onshow than for other callbacks. | |
1863 StaticPopup_Show("OUROL_URL", --[[text_arg1=]]nil, --[[text_arg2=]]nil, | |
1864 --[[data=]][[http://www.curse.com/addons/wow/ouroloot]]) | |
1865 end) | |
1866 local ping = addon.format_hypertext ([[click here]], ITEM_QUALITY_UNCOMMON, 'DoPing') | |
1867 self:Print(newer_warning, tostring(pop), tostring(ping)) | |
1763 newer_warning = nil | 1868 newer_warning = nil |
1764 end | 1869 end |
1765 end | 1870 end |
1766 end | 1871 end |
1767 | 1872 |
2016 end | 2121 end |
2017 | 2122 |
2018 function addon:register_boss_mod (name, registration_func, deregistration_func) | 2123 function addon:register_boss_mod (name, registration_func, deregistration_func) |
2019 assert(type(name)=='string') | 2124 assert(type(name)=='string') |
2020 assert(type(registration_func)=='function') | 2125 assert(type(registration_func)=='function') |
2021 if deregistration_func ~= nil then assert(type(deregistration_func)=='function') end | 2126 if deregistration_func ~= nil then |
2127 assert(type(deregistration_func)=='function') | |
2128 end | |
2022 self.bossmods[#self.bossmods+1] = { | 2129 self.bossmods[#self.bossmods+1] = { |
2023 n = name, | 2130 n = name, |
2024 r = registration_func, | 2131 r = registration_func, |
2025 d = deregistration_func, | 2132 d = deregistration_func, |
2026 } | 2133 } |
2233 e.cache_miss = nil | 2340 e.cache_miss = nil |
2234 if e.unique then | 2341 if e.unique then |
2235 local gu = g_uniques[e.unique] | 2342 local gu = g_uniques[e.unique] |
2236 local player_i, player_h, hist_i = _history_by_loot_id (e.unique, "fixcache") | 2343 local player_i, player_h, hist_i = _history_by_loot_id (e.unique, "fixcache") |
2237 if gu.loot ~= i then -- is this an actual problem? | 2344 if gu.loot ~= i then -- is this an actual problem? |
2238 pprint('loot', ("Unique value '%s' had iterator value %d but g_uniques index %s."):format(e.unique,i,tostring(gu.loot))) | 2345 pprint ('loot', |
2346 ("Unique value '%s' had iterator value %d but g_uniques index %s."): | |
2347 format(e.unique,i,tostring(gu.loot))) | |
2239 end | 2348 end |
2240 if player_i then | 2349 if player_i then |
2241 player_h.id[e.unique] = e.id | 2350 player_h.id[e.unique] = e.id |
2242 msg = [[ Entry %d (and history) patched up with %s.]] | 2351 msg = [[ Entry %d (and history) patched up with %s.]] |
2243 end | 2352 end |
2246 end | 2355 end |
2247 break | 2356 break |
2248 end end | 2357 end end |
2249 | 2358 |
2250 self:Print("...finished. Found %d |4entry:entries; with weird data.", numfound) | 2359 self:Print("...finished. Found %d |4entry:entries; with weird data.", numfound) |
2360 end | |
2361 | |
2362 do | |
2363 local gur | |
2364 | |
2365 -- Strictly speaking, we'd want to handle individual 'exist' entries | |
2366 -- as they expire, rather then waiting for all of them to expire and then | |
2367 -- doing them as a group. But, if there're more than one of these per | |
2368 -- boss, something else is funky anyhow and certainly not a hurry. | |
2369 local function fixup_unique_replacements() | |
2370 --print("replacements fixup happening!") | |
2371 --tabledump(g_unique_replace.replacements) | |
2372 --_G.GRR = g_unique_replace.replacements | |
2373 for exist,info in pairs(gur.replacements) do | |
2374 local winning_index = 1 | |
2375 local winner = info[1] | |
2376 info[1] = nil | |
2377 pprint('improv', "fixup for", exist, "starting with", winner[1], | |
2378 "with", winner[2], "out of", #info, "total entries") | |
2379 -- Lowest player GUID wins. Seniority gotta count for something. | |
2380 for i = 2, #info do | |
2381 if winner[1] <= info[i][1] then | |
2382 pprint('improv', "champ wins against", i, info[i][1]) | |
2383 flib.del(info[i]) | |
2384 else | |
2385 pprint('improv', "challenger wins with", i, info[i][1]) | |
2386 flib.del(winner) | |
2387 winner = info[i] | |
2388 winning_index = i | |
2389 end | |
2390 info[i] = nil | |
2391 end | |
2392 pprint('improv', "final:", winner[1], winner[2]) | |
2393 --[[ | |
2394 A: winner was generated locally | |
2395 >g_loot and history already has the replacement value | |
2396 >winning_index == 1 | |
2397 B: winner was generated remotely | |
2398 >need to scan and replace | |
2399 ]] | |
2400 if winning_index ~= 1 then | |
2401 --XXX still needs to be debugged: | |
2402 local cache = g_uniques[exist] | |
2403 local looti = assert(cache.loot) -- can't possibly be missing... | |
2404 if g_loot[looti].unique ~= exist then | |
2405 pprint('improv', "WTF. entry", looti, | |
2406 "does not match original unique tag! instead", | |
2407 g_loot[looti].unique) | |
2408 else | |
2409 pprint('improv', "found and replaced loot entry", looti) | |
2410 g_loot[looti].unique = winner[2] | |
2411 end | |
2412 local hi,ui = cache.history, cache.history_may | |
2413 if hi ~= g_uniques.NOTFOUND then | |
2414 local hist = addon.history[hi] | |
2415 if ui and hist.unique[ui] == exist then | |
2416 -- ui is valid | |
2417 else | |
2418 ui = nil | |
2419 for i,ui2 in ipairs(hist.unique) do | |
2420 if ui2 == exist then | |
2421 ui = i | |
2422 break | |
2423 end | |
2424 end | |
2425 end | |
2426 if ui then | |
2427 pprint('improv', "found and replacing history entry", hi, | |
2428 ui, hist.name) | |
2429 hist.when[winner[2]] = hist.when[exist] | |
2430 hist.id[winner[2]] = hist.id[exist] | |
2431 hist.count[winner[2]] = hist.count[exist] | |
2432 hist.unique[ui] = winner[2] | |
2433 hist.when[exist] = nil | |
2434 hist.id[exist] = nil | |
2435 hist.count[exist] = nil | |
2436 end | |
2437 end | |
2438 end | |
2439 pprint('improv', "finished with", exist, "into", winner[2]) | |
2440 flib.del(winner) | |
2441 flib.del(info) | |
2442 gur.replacements[exist] = nil | |
2443 end | |
2444 end | |
2445 | |
2446 local function new_entry (id, exist, repl, is_local) | |
2447 pprint('improv', "new_entry", id, exist, repl, is_local) | |
2448 gur.replacements[exist] = gur.replacements[exist] or flib.new() | |
2449 tinsert (gur.replacements[exist], flib.new (tonumber(id), repl)) | |
2450 if is_local then | |
2451 gur.replacements[exist].LOCAL = repl | |
2452 end | |
2453 gur.cache:add (exist) | |
2454 end | |
2455 | |
2456 local function get_previous_replacement (exist) | |
2457 local l = gur.replacements[exist] | |
2458 if l and l.LOCAL then | |
2459 pprint('improv', "check for previous", exist, "returns valid", | |
2460 l.LOCAL) | |
2461 return l.LOCAL | |
2462 end | |
2463 pprint('improv', "check for previous", exist, "returns nil") | |
2464 end | |
2465 | |
2466 function _setup_unique_replace () | |
2467 gur = {} | |
2468 gur.cache = create_new_cache ('improv', 10, fixup_unique_replacements) | |
2469 gur.me = tonumber(_G.UnitGUID('player'):sub(-7),16) | |
2470 gur.replacements = {} | |
2471 gur.new_entry = new_entry | |
2472 gur.get_previous_replacement = get_previous_replacement | |
2473 g_unique_replace = gur | |
2474 _setup_unique_replace = nil | |
2475 end | |
2251 end | 2476 end |
2252 | 2477 |
2253 | 2478 |
2254 ------ Saved texts | 2479 ------ Saved texts |
2255 function addon:check_saved_table(silent_p) | 2480 function addon:check_saved_table(silent_p) |
2343 -- [2] = { ......., [count = "x3"] } -- previous loot | 2568 -- [2] = { ......., [count = "x3"] } -- previous loot |
2344 -- which was much easier to manipulate, but had a ton of memory overhead. | 2569 -- which was much easier to manipulate, but had a ton of memory overhead. |
2345 do | 2570 do |
2346 -- Sorts a player's history from newest to oldest, according to the | 2571 -- Sorts a player's history from newest to oldest, according to the |
2347 -- formatted timestamp. This is expensive, and destructive for P.unique. | 2572 -- formatted timestamp. This is expensive, and destructive for P.unique. |
2573 local function compare_timestamps (L, R) | |
2574 return L > R -- reverse of normal order, newest first | |
2575 end | |
2348 local function sort_player (p) | 2576 local function sort_player (p) |
2349 local new_uniques, uniques_bywhen, when_array = {}, {}, {} | 2577 local new_uniques, uniques_bywhen, when_array = {}, {}, {} |
2350 for u,tstamp in pairs(p.when) do | 2578 for u,tstamp in pairs(p.when) do |
2351 uniques_bywhen[tstamp] = u | 2579 uniques_bywhen[tstamp] = u |
2352 when_array[#when_array+1] = tstamp | 2580 when_array[#when_array+1] = tstamp |
2353 end | 2581 end |
2354 _G.table.sort(when_array) | 2582 _G.table.sort (when_array, compare_timestamps) |
2355 for i,tstamp in ipairs(when_array) do | 2583 for i,tstamp in ipairs(when_array) do |
2356 new_uniques[i] = uniques_bywhen[tstamp] | 2584 new_uniques[i] = uniques_bywhen[tstamp] |
2357 end | 2585 end |
2358 p.unique = new_uniques | 2586 p.unique = new_uniques |
2359 end | 2587 end |
2360 | 2588 |
2361 -- Possibly called during login. Cleared when no longer needed. | 2589 -- Possibly called during login. Cleared when no longer needed. |
2362 -- Rewrites a PLAYER table from format 3 to format 4. | 2590 -- Rewrites a PLAYER table from format 3 to format 4. |
2363 function addon:_uplift_history_format (player, realmname) | 2591 function addon:_uplift_history_format (player) |
2364 local unique, when, id, count = {}, {}, {}, {} | 2592 local unique, when, id, count = {}, {}, {}, {} |
2365 local name = player.name | 2593 local name = player.name |
2366 | 2594 |
2367 for i,h in ipairs(player) do | 2595 for i,h in ipairs(player) do |
2368 local U = h.unique | 2596 local U = h.unique |
2375 wipe(player) | 2603 wipe(player) |
2376 player.name = name | 2604 player.name = name |
2377 player.id, player.when, player.unique, player.count = | 2605 player.id, player.when, player.unique, player.count = |
2378 id, when, unique, count | 2606 id, when, unique, count |
2379 end | 2607 end |
2608 | |
2380 function addon:_cache_history_uniques() | 2609 function addon:_cache_history_uniques() |
2381 UpdateAddOnMemoryUsage() | 2610 UpdateAddOnMemoryUsage() |
2382 local before = GetAddOnMemoryUsage(nametag) | 2611 local before = GetAddOnMemoryUsage(nametag) |
2383 local trouble | 2612 local trouble |
2384 local count = 0 | 2613 local count = 0 |
2396 elseif e.disposition == 'shard' or e.disposition == 'gvault' then | 2625 elseif e.disposition == 'shard' or e.disposition == 'gvault' then |
2397 g_uniques[e.unique] = { loot = i, history = g_uniques.NOTFOUND } | 2626 g_uniques[e.unique] = { loot = i, history = g_uniques.NOTFOUND } |
2398 count = count + 1 | 2627 count = count + 1 |
2399 --print("Active loot", i, "INSERTED with tag", e.unique, "as", e.disposition) | 2628 --print("Active loot", i, "INSERTED with tag", e.unique, "as", e.disposition) |
2400 else | 2629 else |
2401 hmmm = "wonked data ("..i.."/"..tostring(e.unique)..") in precache loop!" | 2630 hmmm = "active data not found in history ("..i.."/"..tostring(e.unique) |
2402 pprint(hmmm) | 2631 ..") in precache loop! trying to fixup for this session" |
2632 pprint(hmmm) -- more? | |
2403 -- try to simply fix up errors as we go | 2633 -- try to simply fix up errors as we go |
2404 g_uniques[e.unique] = { loot = i, history = g_uniques.NOTFOUND } | 2634 g_uniques[e.unique] = { loot = i, history = g_uniques.NOTFOUND } |
2405 trouble = true | 2635 --trouble = true |
2406 end | 2636 end |
2407 else | 2637 else |
2408 trouble = true | 2638 trouble = true |
2409 pprint('loot', "ERROR precache loop found bad unique tag!", | 2639 pprint('loot', "ERROR precache loop found bad unique tag!", |
2410 i, "tag", tostring(e.unique), "from?", tostring(e.bcast_from)) | 2640 i, "tag", tostring(e.unique), "from?", tostring(e.bcast_from)) |
2515 for i,h in ipairs(self.history) do | 2745 for i,h in ipairs(self.history) do |
2516 sort_player(h) | 2746 sort_player(h) |
2517 end | 2747 end |
2518 end | 2748 end |
2519 | 2749 |
2520 -- Clears all but latest entry for each player. | 2750 -- Clears all but the most recent HOWMANY (optional, default 1) entries |
2521 function addon:preen_history (realmname) | 2751 -- for each player on REALMNAME. |
2752 -- This function's name is the legacy of the orignal fsck(8) "-p" option, | |
2753 -- which has a similar feel. | |
2754 function addon:preen_history (realmname, howmany) | |
2522 local r = assert(realmname) | 2755 local r = assert(realmname) |
2756 howmany = tonumber(howmany) or 1 | |
2757 if type(self.history_all[r]) ~= 'table' then | |
2758 return | |
2759 end | |
2523 g_uniques:RESET() | 2760 g_uniques:RESET() |
2524 for i,h in ipairs(self.history) do | 2761 for i,h in ipairs(self.history_all[r]) do |
2525 -- This is going to do horrible things to memory. The subtables | 2762 -- This is going to do horrible things to memory. The subtables |
2526 -- after this step would be large and sparse, with no good way | 2763 -- after this step would be large and sparse, with no good way |
2527 -- of shrinking the allocation... | 2764 -- of shrinking the allocation... |
2528 sort_player(h) | 2765 sort_player(h) |
2529 -- ...so it's better in the long run to discard them. | 2766 -- ...so it's better in the long run to discard them. |
2530 local U = h.unique[1] | 2767 local new_unique, new_id, new_when, new_count = {}, {}, {}, {} |
2531 h.unique = { U } | 2768 for ui = 1, howmany do |
2532 h.id = { [U] = h.id[U] } | 2769 local U = h.unique[ui] |
2533 h.when = { [U] = h.when[U] } | 2770 if not U then break end |
2534 h.count = { [U] = h.count[U] } | 2771 new_unique[ui] = U |
2772 new_id[U] = h.id[U] | |
2773 new_when[U] = h.when[U] | |
2774 new_count[U] = h.count[U] | |
2775 end | |
2776 h.unique, h.id, h.when, h.count = | |
2777 new_unique, new_id, new_when, new_count | |
2535 end | 2778 end |
2536 end | 2779 end |
2537 | 2780 |
2538 -- Given a unique tag OR an entry in a g_loot table, looks up the | 2781 -- Given a unique tag OR an entry in a g_loot table, looks up the |
2539 -- corresponding history entry. Returns the player's index and history | 2782 -- corresponding history entry. Returns the player's index and history |
2742 index = tonumber(cache.loot) | 2985 index = tonumber(cache.loot) |
2743 e = g_loot[index] | 2986 e = g_loot[index] |
2744 end | 2987 end |
2745 | 2988 |
2746 else | 2989 else |
2747 return -- silently ignore newer cases from newer clients | 2990 return -- silently ignore future cases from future clients |
2748 end | 2991 end |
2749 | 2992 |
2750 if self.debug.loot then | 2993 if self.debug.loot then |
2751 local m = ("Re-mark index %d(pre-unique %s) with id %d from '%s' to '%s'."): | 2994 local m = ("Re-mark index %d(pre-unique %s) with id %d from '%s' to '%s'."): |
2752 format(index, unique, id, tostring(olddisp), tostring(newdisp)) | 2995 format(index, unique, id, tostring(olddisp), tostring(newdisp)) |
2753 self.dprint('loot', m) | 2996 self.dprint('loot', m) |
2754 if sender == my_name then | 2997 if sender == my_name then |
2755 self.dprint('loot',"(Returning early from double self-mark.)") | 2998 self.dprint('loot',"(Returning early from debug mode's double self-mark.)") |
2756 return index | 2999 return index |
2757 end | 3000 end |
2758 end | 3001 end |
2759 | 3002 |
2760 if not e then | 3003 if not e then |
2778 | 3021 |
2779 ------ Player communication | 3022 ------ Player communication |
2780 do | 3023 do |
2781 local select, tconcat, strsplit, unpack = select, table.concat, strsplit, unpack | 3024 local select, tconcat, strsplit, unpack = select, table.concat, strsplit, unpack |
2782 --[[ old way: repeated string concatenations, BAD | 3025 --[[ old way: repeated string concatenations, BAD |
2783 new way: new table on every call, BAD | 3026 new way: new table on every broadcast, BAD |
2784 local msg = ... | 3027 local msg = ... |
2785 for i = 2, select('#',...) do | 3028 for i = 2, select('#',...) do |
2786 msg = msg .. '\a' .. (select(i,...) or "") | 3029 msg = msg .. '\a' .. (select(i,...) or "") |
2787 end | 3030 end |
2788 return msg | 3031 return msg |
2842 OCR_funcs.revcheck = function (sender, _, revlarge) | 3085 OCR_funcs.revcheck = function (sender, _, revlarge) |
2843 addon.dprint('comm', "revcheck, sender", sender) | 3086 addon.dprint('comm', "revcheck, sender", sender) |
2844 addon:_check_revision (revlarge) | 3087 addon:_check_revision (revlarge) |
2845 end | 3088 end |
2846 | 3089 |
3090 OCR_funcs['17improv'] = function (sender, _, senderid, existing, replace) | |
3091 addon.dprint('comm', "DOTimprov/17, sender", sender, "id", senderid, | |
3092 "existing", existing, "replace", replace) | |
3093 if not g_unique_replace then _setup_unique_replace() end | |
3094 g_unique_replace.new_entry (senderid, existing, replace) | |
3095 end | |
3096 | |
2847 OCR_funcs['17mark'] = function (sender, _, unique, item, old, new) | 3097 OCR_funcs['17mark'] = function (sender, _, unique, item, old, new) |
2848 addon.dprint('comm', "DOTmark/17, sender", sender, "unique", unique, | 3098 addon.dprint('comm', "DOTmark/17, sender", sender, "unique", unique, |
2849 "item", item, "from old", old, "to new", new) | 3099 "item", item, "from old", old, "to new", new) |
2850 local index = addon:loot_mark_disposition ("remote", sender, unique, item, old, new) | 3100 local index = addon:loot_mark_disposition ("remote", sender, unique, item, old, new) |
2851 --if not addon.enabled then return end -- hmm | 3101 --if not addon.enabled then return end -- hmm |
2888 adduser (sender, nil, true) | 3138 adduser (sender, nil, true) |
2889 addon:on_boss_broadcast (reason, bossname, instancetag, maxsize) | 3139 addon:on_boss_broadcast (reason, bossname, instancetag, maxsize) |
2890 end | 3140 end |
2891 OCR_funcs['17boss'] = OCR_funcs['16boss'] | 3141 OCR_funcs['17boss'] = OCR_funcs['16boss'] |
2892 | 3142 |
3143 local bcast_on = addon.format_hypertext ([[the red pill]], '|cffff4040', | |
3144 function() | |
3145 if not addon.rebroadcast then | |
3146 addon:Activate(nil,true) | |
3147 end | |
3148 addon:broadcast('bcast_responder') | |
3149 end) | |
3150 local waferthin = addon.format_hypertext ([[the blue pill]], '|cff0070dd', | |
3151 function() | |
3152 g_wafer_thin = true -- mint? it's wafer thin! | |
3153 addon:broadcast('bcast_denied') -- fuck off, I'm full | |
3154 end) | |
2893 OCR_funcs.bcast_req = function (sender) | 3155 OCR_funcs.bcast_req = function (sender) |
2894 if addon.debug.comm or ((not g_wafer_thin) and (not addon.rebroadcast)) | 3156 if addon.debug.comm or ((not g_wafer_thin) and (not addon.rebroadcast)) |
2895 then | 3157 then |
2896 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.", | 3158 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.", |
2897 sender, | 3159 sender, |
2898 addon.format_hypertext('bcaston',"the red pill",'|cffff4040'), | 3160 tostring(bcast_on), |
2899 addon.format_hypertext('waferthin',"the blue pill",'|cff0070dd')) | 3161 tostring(waferthin)) |
2900 end | 3162 end |
2901 addon.popped = true | 3163 addon.popped = true |
2902 end | 3164 end |
2903 | 3165 |
2904 OCR_funcs.bcast_responder = function (sender) | 3166 OCR_funcs.bcast_responder = function (sender) |