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)